diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000000..0facceeae0 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,13 @@ +version = 1 + +[[analyzers]] +name = "php" +enabled = true + +[[analyzers]] +name = "test-coverage" +enabled = true + +[[transformers]] +name = "php-cs-fixer" +enabled = true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfc7a30bc3..f55a063197 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,7 @@ jobs: lint: name: "Lint" runs-on: "ubuntu-latest" + if: ${{ false }} strategy: matrix: @@ -52,6 +53,7 @@ jobs: name: "Coding Standard" runs-on: "ubuntu-latest" + if: ${{ false }} strategy: matrix: @@ -84,6 +86,7 @@ jobs: name: "Dependency Analysis" runs-on: "ubuntu-latest" + if: ${{ false }} strategy: matrix: @@ -109,6 +112,7 @@ jobs: tests: name: "Tests" runs-on: ${{ matrix.operating-system }} + if: ${{ false }} strategy: fail-fast: false @@ -149,6 +153,7 @@ jobs: tests-old-phpunit: name: "Tests with old PHPUnit" runs-on: ${{ matrix.operating-system }} + if: ${{ false }} strategy: fail-fast: false @@ -208,19 +213,28 @@ jobs: run: "composer install --no-interaction --no-progress --no-suggest" - name: "Tests" + continue-on-error: true run: | php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude="~vendor~" vendor/bin/phpunit - name: "Coveralls" + if: ${{ false }} env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | composer require twinh/php-coveralls --dev && \ vendor/bin/php-coveralls --verbose --coverage_clover=tests/tmp/clover.xml --json_path=tests/tmp/coveralls-upload.json + - name: "DeepSource" + env: + DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} + run: | + curl https://deepsource.io/cli | sh && \ + ./bin/deepsource report --analyzer test-coverage --key php --value-file tests/tmp/clover.xml static-analysis: name: "PHPStan" runs-on: ${{ matrix.operating-system }} + if: ${{ false }} strategy: fail-fast: false @@ -264,6 +278,7 @@ jobs: static-analysis-with-result-cache: name: "PHPStan with result cache" + if: ${{ false }} runs-on: ubuntu-latest @@ -311,6 +326,7 @@ jobs: name: "Result cache E2E tests" runs-on: ${{ matrix.operating-system }} + if: ${{ false }} strategy: matrix: @@ -339,6 +355,7 @@ jobs: compiler-tests: name: "Compiler Tests" + if: ${{ false }} runs-on: "ubuntu-latest" @@ -369,6 +386,7 @@ jobs: generate-baseline: name: "Generate baseline" + if: ${{ false }} runs-on: "ubuntu-latest" @@ -399,6 +417,7 @@ jobs: e2e-tests: name: "E2E tests" runs-on: "ubuntu-latest" + if: ${{ false }} strategy: matrix: diff --git a/.php_cs.cache b/.php_cs.cache new file mode 100644 index 0000000000..a36754e789 --- /dev/null +++ b/.php_cs.cache @@ -0,0 +1 @@ +{"php":"8.0.5","version":"3.0.0-beta.2:v3.0.0-beta.2#9225f5d06f5c3ee24b89348cd3dff69c9b3c1ede","indent":" ","lineEnding":"\n","rules":{"blank_line_after_opening_tag":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true},"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":{"elements":["const","method","property"]},"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\/PhpDoc\/LazyTypeNodeResolverExtensionRegistryProvider.php":2070709368,"src\/PhpDoc\/TypeNodeResolverExtensionRegistry.php":1010506996,"src\/PhpDoc\/PhpDocInheritanceResolver.php":3834233661,"src\/PhpDoc\/TypeNodeResolverExtensionRegistryProvider.php":1485975865,"src\/PhpDoc\/TypeStringResolver.php":2177960853,"src\/PhpDoc\/StubPhpDocProvider.php":3506980709,"src\/PhpDoc\/TypeNodeResolverAwareExtension.php":2621202234,"src\/PhpDoc\/ConstExprNodeResolver.php":301452670,"src\/PhpDoc\/StubValidator.php":1120984297,"src\/PhpDoc\/PhpDocStringResolver.php":2222392091,"src\/PhpDoc\/PhpDocBlock.php":673480093,"src\/PhpDoc\/StubSourceLocatorFactory.php":2186230182,"src\/PhpDoc\/ResolvedPhpDocBlock.php":3631364061,"src\/PhpDoc\/NameScopedPhpDocString.php":1374998379,"src\/PhpDoc\/TypeNodeResolverExtension.php":1999504161,"src\/PhpDoc\/TypeNodeResolver.php":2867776983,"src\/PhpDoc\/Tag\/ThrowsTag.php":1638073718,"src\/PhpDoc\/Tag\/MethodTag.php":3609384034,"src\/PhpDoc\/Tag\/ExtendsTag.php":463275757,"src\/PhpDoc\/Tag\/TemplateTag.php":3395480153,"src\/PhpDoc\/Tag\/MethodTagParameter.php":4269875777,"src\/PhpDoc\/Tag\/ParamTag.php":2575465248,"src\/PhpDoc\/Tag\/TypedTag.php":2896167611,"src\/PhpDoc\/Tag\/TypeAliasTag.php":2020933633,"src\/PhpDoc\/Tag\/UsesTag.php":2087209465,"src\/PhpDoc\/Tag\/VarTag.php":596777738,"src\/PhpDoc\/Tag\/TypeAliasImportTag.php":2184062141,"src\/PhpDoc\/Tag\/PropertyTag.php":3732814757,"src\/PhpDoc\/Tag\/ReturnTag.php":2788211065,"src\/PhpDoc\/Tag\/ImplementsTag.php":3706834400,"src\/PhpDoc\/Tag\/MixinTag.php":1548573465,"src\/PhpDoc\/Tag\/DeprecatedTag.php":2134565452,"src\/PhpDoc\/PhpDocNodeResolver.php":1436151896,"src\/dumpType.php":2472981247,"src\/Parallel\/ParallelAnalyser.php":1084814084,"src\/Parallel\/Scheduler.php":3467031897,"src\/Parallel\/Schedule.php":137207043,"src\/Parallel\/ProcessPool.php":1582327563,"src\/Parallel\/Process.php":2587952020,"src\/Broker\/BrokerFactory.php":3300264003,"src\/Broker\/ClassAutoloadingException.php":2473012509,"src\/Broker\/Broker.php":431626342,"src\/Broker\/FunctionNotFoundException.php":1284713880,"src\/Broker\/ConstantNotFoundException.php":2181519770,"src\/Broker\/ClassNotFoundException.php":228366300,"src\/Broker\/AnonymousClassNameHelper.php":3262219658,"src\/Cache\/CacheItem.php":4389708,"src\/Cache\/FileCacheStorage.php":4167254588,"src\/Cache\/MemoryCacheStorage.php":4119483419,"src\/Cache\/Cache.php":1140197138,"src\/Cache\/CacheStorage.php":833291489,"src\/NodeVisitor\/StatementOrderVisitor.php":3900050932,"src\/File\/SimpleRelativePathHelper.php":2634014821,"src\/File\/FileWriter.php":3726659167,"src\/File\/FileFinder.php":1587005273,"src\/File\/FileMonitorResult.php":3717862047,"src\/File\/FileExcluder.php":934009438,"src\/File\/FileReader.php":335837198,"src\/File\/ParentDirectoryRelativePathHelper.php":2039304132,"src\/File\/FileExcluderRawFactory.php":3638953858,"src\/File\/FileHelper.php":2411992370,"src\/File\/FileMonitor.php":3811686238,"src\/File\/FileFinderResult.php":2869025621,"src\/File\/FuzzyRelativePathHelper.php":1578604028,"src\/File\/FileExcluderFactory.php":327897184,"src\/File\/CouldNotWriteFileException.php":311590565,"src\/File\/RelativePathHelper.php":2753368078,"src\/File\/CouldNotReadFileException.php":2926799608,"src\/File\/NullRelativePathHelper.php":4184284688,"src\/File\/PathNotFoundException.php":1452931929,"src\/Internal\/BytesHelper.php":2076090908,"src\/Internal\/UnionTypeGetInternalDynamicReturnTypeExtension.php":1133653731,"src\/Internal\/ContainerDynamicReturnTypeExtension.php":401486129,"src\/Internal\/ScopeIsInClassTypeSpecifyingExtension.php":3517383752,"src\/Dependency\/DependencyDumper.php":1492353749,"src\/Dependency\/ExportedNode\/ExportedPhpDocNode.php":2156676070,"src\/Dependency\/ExportedNode\/ExportedMethodNode.php":2681712486,"src\/Dependency\/ExportedNode\/ExportedTraitNode.php":1424100624,"src\/Dependency\/ExportedNode\/ExportedFunctionNode.php":3892056061,"src\/Dependency\/ExportedNode\/ExportedClassConstantNode.php":1505815512,"src\/Dependency\/ExportedNode\/ExportedClassNode.php":2306242423,"src\/Dependency\/ExportedNode\/ExportedParameterNode.php":2174933977,"src\/Dependency\/ExportedNode\/ExportedTraitUseAdaptation.php":584970335,"src\/Dependency\/ExportedNode\/ExportedInterfaceNode.php":3377314923,"src\/Dependency\/ExportedNode\/ExportedPropertyNode.php":687294107,"src\/Dependency\/DependencyResolver.php":3181403285,"src\/Dependency\/ExportedNode.php":296931520,"src\/Dependency\/NodeDependencies.php":152804790,"src\/Dependency\/ExportedNodeFetcher.php":2013941707,"src\/Dependency\/ExportedNodeResolver.php":3795377437,"src\/Dependency\/ExportedNodeVisitor.php":1703929322,"src\/Php\/PhpVersionFactory.php":2365205473,"src\/Php\/PhpVersion.php":3721074192,"src\/Php\/PhpVersionFactoryFactory.php":3550272709,"src\/Parser\/RichParser.php":2875846215,"src\/Parser\/LexerFactory.php":3881166539,"src\/Parser\/NodeList.php":1532565474,"src\/Parser\/Parser.php":3398484298,"src\/Parser\/FunctionCallStatementFinder.php":2046155478,"src\/Parser\/SimpleParser.php":2111493360,"src\/Parser\/PathRoutingParser.php":1710038351,"src\/Parser\/ParserErrorsException.php":2345765408,"src\/Parser\/CachedParser.php":2624970920,"src\/Parser\/PhpParserDecorator.php":1110304136,"src\/Testing\/functions.php":76987345,"src\/Testing\/TypeInferenceTestCase.php":789219583,"src\/Testing\/TestCaseSourceLocatorFactory.php":1324688942,"src\/Testing\/LevelsTestCase.php":2785913216,"src\/Testing\/RuleTestCase.php":1037406792,"src\/Testing\/TestCase.php":3281296871,"src\/Testing\/ErrorFormatterTestCase.php":2315382246,"src\/Type\/StringAlwaysAcceptingObjectWithToStringType.php":1358108502,"src\/Type\/GenericTypeVariableResolver.php":4050954956,"src\/Type\/TypeWithClassName.php":2111962449,"src\/Type\/StrictMixedType.php":205389803,"src\/Type\/DynamicStaticMethodThrowTypeExtension.php":1641553875,"src\/Type\/ConstantScalarType.php":2351526041,"src\/Type\/DynamicFunctionThrowTypeExtension.php":2069053030,"src\/Type\/IntegerRangeType.php":3342479162,"src\/Type\/Type.php":572105259,"src\/Type\/DynamicStaticMethodReturnTypeExtension.php":2934265389,"src\/Type\/ResourceType.php":1383448677,"src\/Type\/ClosureType.php":4004776996,"src\/Type\/Traits\/UndecidedBooleanTypeTrait.php":4098211935,"src\/Type\/Traits\/MaybeIterableTypeTrait.php":2393216765,"src\/Type\/Traits\/UndecidedComparisonTypeTrait.php":3243332973,"src\/Type\/Traits\/NonGenericTypeTrait.php":3925235498,"src\/Type\/Traits\/ConstantNumericComparisonTypeTrait.php":2901006185,"src\/Type\/Traits\/MaybeCallableTypeTrait.php":3243886785,"src\/Type\/Traits\/MaybeOffsetAccessibleTypeTrait.php":677791914,"src\/Type\/Traits\/MaybeObjectTypeTrait.php":744207874,"src\/Type\/Traits\/FalseyBooleanTypeTrait.php":943314466,"src\/Type\/Traits\/ConstantScalarTypeTrait.php":2268219794,"src\/Type\/Traits\/NonOffsetAccessibleTypeTrait.php":2941571604,"src\/Type\/Traits\/TruthyBooleanTypeTrait.php":2372971179,"src\/Type\/Traits\/NonCallableTypeTrait.php":1684719519,"src\/Type\/Traits\/ObjectTypeTrait.php":1060409676,"src\/Type\/Traits\/NonObjectTypeTrait.php":188559172,"src\/Type\/Traits\/UndecidedComparisonCompoundTypeTrait.php":2232366701,"src\/Type\/Traits\/NonIterableTypeTrait.php":4000168834,"src\/Type\/DynamicMethodReturnTypeExtension.php":728209767,"src\/Type\/TypeAlias.php":865851856,"src\/Type\/ErrorType.php":3489461963,"src\/Type\/CompoundTypeHelper.php":1008350523,"src\/Type\/SubtractableType.php":426271441,"src\/Type\/OperatorTypeSpecifyingExtensionRegistry.php":3440125385,"src\/Type\/DynamicFunctionReturnTypeExtension.php":3244756261,"src\/Type\/VerbosityLevel.php":993547775,"src\/Type\/TypeUtils.php":1487650349,"src\/Type\/FunctionTypeSpecifyingExtension.php":2261115093,"src\/Type\/CompoundType.php":2881955285,"src\/Type\/CommentHelper.php":2455277578,"src\/Type\/CircularTypeAliasDefinitionException.php":2773938714,"src\/Type\/RecursionGuard.php":1761459352,"src\/Type\/ConstantType.php":2368482201,"src\/Type\/DynamicReturnTypeExtensionRegistry.php":684786842,"src\/Type\/BooleanType.php":1443091948,"src\/Type\/TypeAliasResolver.php":4154952518,"src\/Type\/NullType.php":861381742,"src\/Type\/TypehintHelper.php":4248859859,"src\/Type\/MethodTypeSpecifyingExtension.php":2007614278,"src\/Type\/ArrayType.php":3939769223,"src\/Type\/Php\/VarExportFunctionDynamicReturnTypeExtension.php":3415768292,"src\/Type\/Php\/ArrayKeyFirstDynamicReturnTypeExtension.php":3676729232,"src\/Type\/Php\/XMLReaderOpenReturnTypeExtension.php":2402847578,"src\/Type\/Php\/JsonThrowOnErrorDynamicReturnTypeExtension.php":2686774492,"src\/Type\/Php\/ArrayCombineFunctionReturnTypeExtension.php":605830265,"src\/Type\/Php\/IsObjectFunctionTypeSpecifyingExtension.php":4227062665,"src\/Type\/Php\/PregSplitDynamicReturnTypeExtension.php":3146301784,"src\/Type\/Php\/ArrayKeyExistsFunctionTypeSpecifyingExtension.php":4155584384,"src\/Type\/Php\/SprintfFunctionDynamicReturnTypeExtension.php":3939845329,"src\/Type\/Php\/ArrayPointerFunctionsDynamicReturnTypeExtension.php":2152603415,"src\/Type\/Php\/DefinedConstantTypeSpecifyingExtension.php":3012089030,"src\/Type\/Php\/PropertyExistsTypeSpecifyingExtension.php":4188202879,"src\/Type\/Php\/IsIterableFunctionTypeSpecifyingExtension.php":4268902196,"src\/Type\/Php\/PathinfoFunctionDynamicReturnTypeExtension.php":1127080130,"src\/Type\/Php\/StatDynamicReturnTypeExtension.php":1918339383,"src\/Type\/Php\/ClosureFromCallableDynamicReturnTypeExtension.php":3572738258,"src\/Type\/Php\/DefineConstantTypeSpecifyingExtension.php":2157689169,"src\/Type\/Php\/ArrayKeysFunctionDynamicReturnTypeExtension.php":250547734,"src\/Type\/Php\/ArgumentBasedFunctionReturnTypeExtension.php":3342949638,"src\/Type\/Php\/CurlInitReturnTypeExtension.php":1646215276,"src\/Type\/Php\/DateTimeConstructorThrowTypeExtension.php":4223582904,"src\/Type\/Php\/IsCountableFunctionTypeSpecifyingExtension.php":3931058186,"src\/Type\/Php\/ArrayShiftFunctionReturnTypeExtension.php":3648142211,"src\/Type\/Php\/ExplodeFunctionDynamicReturnTypeExtension.php":566128766,"src\/Type\/Php\/IsFloatFunctionTypeSpecifyingExtension.php":1669051570,"src\/Type\/Php\/PowFunctionReturnTypeExtension.php":233684306,"src\/Type\/Php\/ArrayFillFunctionReturnTypeExtension.php":4083353295,"src\/Type\/Php\/ArrayMergeFunctionDynamicReturnTypeExtension.php":1320834879,"src\/Type\/Php\/IsScalarFunctionTypeSpecifyingExtension.php":2443646955,"src\/Type\/Php\/GetCalledClassDynamicReturnTypeExtension.php":561126738,"src\/Type\/Php\/ArrayReduceFunctionReturnTypeExtension.php":186831669,"src\/Type\/Php\/ArrayMapFunctionReturnTypeExtension.php":985422215,"src\/Type\/Php\/ArrayValuesFunctionDynamicReturnTypeExtension.php":1796299559,"src\/Type\/Php\/DateTimeDynamicReturnTypeExtension.php":2899311008,"src\/Type\/Php\/RangeFunctionReturnTypeExtension.php":1323873174,"src\/Type\/Php\/FilterVarDynamicReturnTypeExtension.php":2746947645,"src\/Type\/Php\/IsNullFunctionTypeSpecifyingExtension.php":3173464149,"src\/Type\/Php\/ArrayFillKeysFunctionReturnTypeExtension.php":3640561468,"src\/Type\/Php\/ArrayKeyDynamicReturnTypeExtension.php":3751625323,"src\/Type\/Php\/IsBoolFunctionTypeSpecifyingExtension.php":3545044301,"src\/Type\/Php\/MinMaxFunctionReturnTypeExtension.php":4089315361,"src\/Type\/Php\/ClosureBindDynamicReturnTypeExtension.php":2549360393,"src\/Type\/Php\/Base64DecodeDynamicFunctionReturnTypeExtension.php":2726003159,"src\/Type\/Php\/ParseUrlFunctionDynamicReturnTypeExtension.php":806980216,"src\/Type\/Php\/GettimeofdayDynamicFunctionReturnTypeExtension.php":2192519735,"src\/Type\/Php\/IsNumericFunctionTypeSpecifyingExtension.php":2077235381,"src\/Type\/Php\/ClosureBindToDynamicReturnTypeExtension.php":44081760,"src\/Type\/Php\/ReplaceFunctionsDynamicReturnTypeExtension.php":1578750696,"src\/Type\/Php\/ArraySearchFunctionDynamicReturnTypeExtension.php":3454320084,"src\/Type\/Php\/IsCallableFunctionTypeSpecifyingExtension.php":1998505678,"src\/Type\/Php\/StrSplitFunctionReturnTypeExtension.php":3237298097,"src\/Type\/Php\/StrWordCountFunctionDynamicReturnTypeExtension.php":128331850,"src\/Type\/Php\/ArraySumFunctionDynamicReturnTypeExtension.php":4257222864,"src\/Type\/Php\/SimpleXMLElementAsXMLMethodReturnTypeExtension.php":3821125254,"src\/Type\/Php\/HashFunctionsReturnTypeExtension.php":1836630272,"src\/Type\/Php\/MicrotimeFunctionReturnTypeExtension.php":611667267,"src\/Type\/Php\/DioStatDynamicFunctionReturnTypeExtension.php":1346678284,"src\/Type\/Php\/ArrayRandFunctionReturnTypeExtension.php":4161457668,"src\/Type\/Php\/DateFunctionReturnTypeExtension.php":1296153032,"src\/Type\/Php\/GetParentClassDynamicFunctionReturnTypeExtension.php":2523985887,"src\/Type\/Php\/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php":3163861658,"src\/Type\/Php\/SimpleXMLElementClassPropertyReflectionExtension.php":4153626235,"src\/Type\/Php\/StrtotimeFunctionReturnTypeExtension.php":4135781162,"src\/Type\/Php\/ArrayReverseFunctionReturnTypeExtension.php":2856357749,"src\/Type\/Php\/RandomIntFunctionReturnTypeExtension.php":4128326165,"src\/Type\/Php\/VersionCompareFunctionDynamicReturnTypeExtension.php":73838955,"src\/Type\/Php\/JsonThrowTypeExtension.php":551646829,"src\/Type\/Php\/ArrayFilterFunctionReturnTypeReturnTypeExtension.php":4021125895,"src\/Type\/Php\/ArraySliceFunctionReturnTypeExtension.php":3769753048,"src\/Type\/Php\/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php":4215201542,"src\/Type\/Php\/CountFunctionTypeSpecifyingExtension.php":3358370699,"src\/Type\/Php\/ArrayPopFunctionReturnTypeExtension.php":1447556816,"src\/Type\/Php\/IsAFunctionTypeSpecifyingExtension.php":1181197832,"src\/Type\/Php\/MbFunctionsReturnTypeExtension.php":1942701097,"src\/Type\/Php\/IsSubclassOfFunctionTypeSpecifyingExtension.php":4157617685,"src\/Type\/Php\/AssertFunctionTypeSpecifyingExtension.php":3878744878,"src\/Type\/Php\/HashHmacFunctionsReturnTypeExtension.php":2849260539,"src\/Type\/Php\/BcMathStringOrNullReturnTypeExtension.php":2451827458,"src\/Type\/Php\/InArrayFunctionTypeSpecifyingExtension.php":1004054687,"src\/Type\/Php\/SimpleXMLElementXpathMethodReturnTypeExtension.php":947892704,"src\/Type\/Php\/GetClassDynamicReturnTypeExtension.php":341402722,"src\/Type\/Php\/HrtimeFunctionReturnTypeExtension.php":874272290,"src\/Type\/Php\/MethodExistsTypeSpecifyingExtension.php":670276120,"src\/Type\/Php\/MbConvertEncodingFunctionReturnTypeExtension.php":492724859,"src\/Type\/Php\/ArrayKeyLastDynamicReturnTypeExtension.php":3431900798,"src\/Type\/Php\/CompactFunctionReturnTypeExtension.php":2808757466,"src\/Type\/Php\/CountFunctionReturnTypeExtension.php":4084987699,"src\/Type\/Php\/DsMapDynamicReturnTypeExtension.php":956089431,"src\/Type\/Php\/IsIntFunctionTypeSpecifyingExtension.php":1613929457,"src\/Type\/Php\/IsArrayFunctionTypeSpecifyingExtension.php":1463981805,"src\/Type\/Php\/ArrayCurrentDynamicReturnTypeExtension.php":2693450642,"src\/Type\/Php\/ClassExistsFunctionTypeSpecifyingExtension.php":1786646075,"src\/Type\/Php\/IsStringFunctionTypeSpecifyingExtension.php":1697710353,"src\/Type\/Php\/IsResourceFunctionTypeSpecifyingExtension.php":2483275622,"src\/Type\/ObjectType.php":2219832542,"src\/Type\/BenevolentUnionType.php":2669274252,"src\/Type\/FileTypeMapper.php":3461129494,"src\/Type\/CallableType.php":2634410347,"src\/Type\/IntegerType.php":2222695374,"src\/Type\/UnionTypeHelper.php":3188523211,"src\/Type\/NonexistentParentClassType.php":3574841140,"src\/Type\/ParserNodeTypeToPHPStanType.php":3966479122,"src\/Type\/ThisType.php":3956380048,"src\/Type\/Constant\/ConstantBooleanType.php":1313812820,"src\/Type\/Constant\/ConstantArrayTypeAndMethod.php":286730457,"src\/Type\/Constant\/ConstantScalarToBooleanTrait.php":3745247682,"src\/Type\/Constant\/ConstantFloatType.php":3975897986,"src\/Type\/Constant\/ConstantArrayType.php":1031952708,"src\/Type\/Constant\/ConstantStringType.php":2210569601,"src\/Type\/Constant\/ConstantArrayTypeBuilder.php":126872662,"src\/Type\/Constant\/ConstantIntegerType.php":948974060,"src\/Type\/JustNullableTypeTrait.php":3561989052,"src\/Type\/UnionType.php":287456984,"src\/Type\/StaticTypeFactory.php":892143340,"src\/Type\/ClassStringType.php":529194518,"src\/Type\/NeverType.php":3551317678,"src\/Type\/StaticMethodTypeSpecifyingExtension.php":1447569186,"src\/Type\/StaticType.php":4163009299,"src\/Type\/ConstantTypeHelper.php":4094775373,"src\/Type\/FloatType.php":2404908957,"src\/Type\/CallableTypeHelper.php":3135250473,"src\/Type\/OperatorTypeSpecifyingExtension.php":364514014,"src\/Type\/TypeTraverser.php":1357737069,"src\/Type\/Generic\/GenericObjectType.php":430431810,"src\/Type\/Generic\/TemplateTypeFactory.php":19801518,"src\/Type\/Generic\/TemplateTypeStrategy.php":1596511100,"src\/Type\/Generic\/GenericClassStringType.php":3288733303,"src\/Type\/Generic\/TemplateStringType.php":606144235,"src\/Type\/Generic\/TemplateTypeTrait.php":3667889282,"src\/Type\/Generic\/TemplateTypeParameterStrategy.php":3131848968,"src\/Type\/Generic\/TemplateIntegerType.php":2511636499,"src\/Type\/Generic\/TemplateTypeHelper.php":1390669367,"src\/Type\/Generic\/TemplateTypeArgumentStrategy.php":1551094230,"src\/Type\/Generic\/TemplateTypeMap.php":2401157918,"src\/Type\/Generic\/TemplateTypeVariance.php":1168464387,"src\/Type\/Generic\/TemplateObjectWithoutClassType.php":3410797196,"src\/Type\/Generic\/TemplateGenericObjectType.php":863190408,"src\/Type\/Generic\/TemplateUnionType.php":56840910,"src\/Type\/Generic\/TemplateTypeReference.php":536057828,"src\/Type\/Generic\/TemplateMixedType.php":679395491,"src\/Type\/Generic\/TemplateObjectType.php":50069628,"src\/Type\/Generic\/TemplateType.php":2994137285,"src\/Type\/Generic\/TemplateBenevolentUnionType.php":487991199,"src\/Type\/Generic\/TemplateTypeScope.php":2291020133,"src\/Type\/ObjectWithoutClassType.php":1989093239,"src\/Type\/TypeCombinator.php":2857323442,"src\/Type\/VoidType.php":723099077,"src\/Type\/Accessory\/AccessoryNumericStringType.php":2880841868,"src\/Type\/Accessory\/AccessoryType.php":2201983925,"src\/Type\/Accessory\/HasOffsetType.php":3707073419,"src\/Type\/Accessory\/NonEmptyArrayType.php":3347700196,"src\/Type\/Accessory\/HasMethodType.php":396224129,"src\/Type\/Accessory\/HasPropertyType.php":564428357,"src\/Type\/IntersectionType.php":670806599,"src\/Type\/StringType.php":929560341,"src\/Type\/MixedType.php":3608307661,"src\/Type\/IterableType.php":1335170670,"src\/Type\/DynamicMethodThrowTypeExtension.php":922558927,"src\/Analyser\/IgnoredErrorHelperResult.php":871776861,"src\/Analyser\/FileAnalyserResult.php":862943768,"src\/Analyser\/TypeSpecifierAwareExtension.php":1826405697,"src\/Analyser\/NodeScopeResolver.php":1100968528,"src\/Analyser\/ScopeFactory.php":2417410582,"src\/Analyser\/MutatingScope.php":2295456870,"src\/Analyser\/FileAnalyser.php":352585083,"src\/Analyser\/IgnoredErrorHelper.php":1064450419,"src\/Analyser\/TypeSpecifier.php":672134617,"src\/Analyser\/SpecifiedTypes.php":908218924,"src\/Analyser\/OutOfClassScope.php":2840993193,"src\/Analyser\/TypeSpecifierFactory.php":2245912962,"src\/Analyser\/Scope.php":1230997061,"src\/Analyser\/UndefinedVariableException.php":3805167740,"src\/Analyser\/EnsuredNonNullabilityResultExpression.php":382713396,"src\/Analyser\/ExpressionResult.php":1412234892,"src\/Analyser\/StatementExitPoint.php":605457949,"src\/Analyser\/AnalyserResult.php":1787849117,"src\/Analyser\/VariableTypeHolder.php":1755901479,"src\/Analyser\/ScopeContext.php":2447631554,"src\/Analyser\/Error.php":1858818304,"src\/Analyser\/DirectScopeFactory.php":3650426197,"src\/Analyser\/ExpressionContext.php":3734745257,"src\/Analyser\/LazyScopeFactory.php":94995038,"src\/Analyser\/TypeSpecifierContext.php":600472135,"src\/Analyser\/Analyser.php":503930173,"src\/Analyser\/NameScope.php":694080886,"src\/Analyser\/NullsafeOperatorHelper.php":4052618710,"src\/Analyser\/StatementResult.php":2873940742,"src\/Analyser\/ConditionalExpressionHolder.php":1686566387,"src\/Analyser\/IgnoredError.php":103455297,"src\/Analyser\/EnsuredNonNullabilityResult.php":1790271120,"src\/Analyser\/ResultCache\/ResultCache.php":2027767760,"src\/Analyser\/ResultCache\/ResultCacheManagerFactory.php":1675119479,"src\/Analyser\/ResultCache\/ResultCacheManager.php":2919286981,"src\/Analyser\/ResultCache\/ResultCacheClearer.php":275214288,"src\/Analyser\/ResultCache\/ResultCacheProcessResult.php":1034510966,"src\/Analyser\/ThrowPoint.php":2071707441,"src\/Rules\/Generics\/ClassTemplateTypeRule.php":3253896118,"src\/Rules\/Generics\/VarianceCheck.php":2620142457,"src\/Rules\/Generics\/TraitTemplateTypeRule.php":173854832,"src\/Rules\/Generics\/InterfaceTemplateTypeRule.php":2838207541,"src\/Rules\/Generics\/ClassAncestorsRule.php":4164062173,"src\/Rules\/Generics\/MethodSignatureVarianceRule.php":1693224578,"src\/Rules\/Generics\/UsedTraitsRule.php":1512076296,"src\/Rules\/Generics\/InterfaceAncestorsRule.php":624628993,"src\/Rules\/Generics\/GenericAncestorsCheck.php":1069342636,"src\/Rules\/Generics\/FunctionSignatureVarianceRule.php":2397421052,"src\/Rules\/Generics\/FunctionTemplateTypeRule.php":3964365903,"src\/Rules\/Generics\/TemplateTypeCheck.php":1578872029,"src\/Rules\/Generics\/GenericObjectTypeCheck.php":180949860,"src\/Rules\/Generics\/MethodTemplateTypeRule.php":3186875101,"src\/Rules\/FunctionCallParametersCheck.php":3394051715,"src\/Rules\/FileRuleError.php":2049746741,"src\/Rules\/PhpDoc\/InvalidPHPStanDocTagRule.php":2687117515,"src\/Rules\/PhpDoc\/IncompatiblePhpDocTypeRule.php":3688770071,"src\/Rules\/PhpDoc\/InvalidPhpDocVarTagTypeRule.php":1251849335,"src\/Rules\/PhpDoc\/WrongVariableNameInVarTagRule.php":1982849624,"src\/Rules\/PhpDoc\/IncompatiblePropertyPhpDocTypeRule.php":2576852597,"src\/Rules\/PhpDoc\/InvalidThrowsPhpDocValueRule.php":3820368662,"src\/Rules\/PhpDoc\/InvalidPhpDocTagValueRule.php":171688965,"src\/Rules\/RuleErrors\/RuleError3.php":1569957943,"src\/Rules\/RuleErrors\/RuleError13.php":1473769232,"src\/Rules\/RuleErrors\/RuleError11.php":1618784337,"src\/Rules\/RuleErrors\/RuleError39.php":2656448116,"src\/Rules\/RuleErrors\/RuleError1.php":4018893802,"src\/Rules\/RuleErrors\/RuleError5.php":2821654340,"src\/Rules\/RuleErrors\/RuleError15.php":3286676605,"src\/Rules\/RuleErrors\/RuleError29.php":1895852189,"src\/Rules\/RuleErrors\/RuleError17.php":1960361842,"src\/Rules\/RuleErrors\/RuleError7.php":2118116456,"src\/Rules\/RuleErrors\/RuleError71.php":4053921462,"src\/Rules\/RuleErrors\/RuleError65.php":2941192815,"src\/Rules\/RuleErrors\/RuleError59.php":3539973396,"src\/Rules\/RuleErrors\/RuleError123.php":890143106,"src\/Rules\/RuleErrors\/RuleError99.php":1026738723,"src\/Rules\/RuleErrors\/RuleError109.php":1781896733,"src\/Rules\/RuleErrors\/RuleError121.php":204815758,"src\/Rules\/RuleErrors\/RuleError73.php":3380921549,"src\/Rules\/RuleErrors\/RuleError67.php":1743004899,"src\/Rules\/RuleErrors\/RuleError63.php":641294825,"src\/Rules\/RuleErrors\/RuleError77.php":4150813270,"src\/Rules\/RuleErrors\/RuleError119.php":3817657238,"src\/Rules\/RuleErrors\/RuleError125.php":1206879265,"src\/Rules\/RuleErrors\/RuleError89.php":3573946898,"src\/Rules\/RuleErrors\/RuleError127.php":3399747032,"src\/Rules\/RuleErrors\/RuleError61.php":679909310,"src\/Rules\/RuleErrors\/RuleError75.php":1614883839,"src\/Rules\/RuleErrors\/RuleError49.php":2665404451,"src\/Rules\/RuleErrors\/RuleError93.php":3037281089,"src\/Rules\/RuleErrors\/RuleError87.php":2855564284,"src\/Rules\/RuleErrors\/RuleError117.php":2088970122,"src\/Rules\/RuleErrors\/RuleError103.php":266422700,"src\/Rules\/RuleErrors\/RuleError79.php":3424480935,"src\/Rules\/RuleErrors\/RuleError45.php":1899217819,"src\/Rules\/RuleErrors\/RuleError51.php":3817217186,"src\/Rules\/RuleErrors\/RuleError47.php":488657684,"src\/Rules\/RuleErrors\/RuleError53.php":709558118,"src\/Rules\/RuleErrors\/RuleError115.php":3734638407,"src\/Rules\/RuleErrors\/RuleError101.php":3985917897,"src\/Rules\/RuleErrors\/RuleError91.php":3907730385,"src\/Rules\/RuleErrors\/RuleError85.php":3343549550,"src\/Rules\/RuleErrors\/RuleError81.php":1507343420,"src\/Rules\/RuleErrors\/RuleError95.php":788539511,"src\/Rules\/RuleErrors\/RuleError105.php":3937403791,"src\/Rules\/RuleErrors\/RuleError111.php":3757960461,"src\/Rules\/RuleErrors\/RuleError57.php":2429400647,"src\/Rules\/RuleErrors\/RuleError43.php":2533453547,"src\/Rules\/RuleErrors\/RuleError69.php":899517886,"src\/Rules\/RuleErrors\/RuleError55.php":3069272673,"src\/Rules\/RuleErrors\/RuleError41.php":2239826259,"src\/Rules\/RuleErrors\/RuleError107.php":3185350616,"src\/Rules\/RuleErrors\/RuleError113.php":596990215,"src\/Rules\/RuleErrors\/RuleError83.php":1702130432,"src\/Rules\/RuleErrors\/RuleError97.php":3302594426,"src\/Rules\/RuleErrors\/RuleError33.php":2644186134,"src\/Rules\/RuleErrors\/RuleError27.php":3747913926,"src\/Rules\/RuleErrors\/RuleError9.php":639228062,"src\/Rules\/RuleErrors\/RuleError19.php":1849618785,"src\/Rules\/RuleErrors\/RuleError31.php":2866003973,"src\/Rules\/RuleErrors\/RuleError25.php":1906862135,"src\/Rules\/RuleErrors\/RuleError21.php":460579506,"src\/Rules\/RuleErrors\/RuleError35.php":1901989689,"src\/Rules\/RuleErrors\/RuleError23.php":2466032415,"src\/Rules\/RuleErrors\/RuleError37.php":1396135911,"src\/Rules\/ClassCaseSensitivityCheck.php":979917575,"src\/Rules\/NonIgnorableRuleError.php":960553181,"src\/Rules\/Methods\/AbstractMethodInNonAbstractClassRule.php":2404298387,"src\/Rules\/Methods\/CallToConstructorStatementWithoutSideEffectsRule.php":1843088512,"src\/Rules\/Methods\/MissingMethodReturnTypehintRule.php":1515928131,"src\/Rules\/Methods\/MissingMethodImplementationRule.php":2667689254,"src\/Rules\/Methods\/ExistingClassesInTypehintsRule.php":2945740007,"src\/Rules\/Methods\/MethodAttributesRule.php":1732408665,"src\/Rules\/Methods\/NullsafeMethodCallRule.php":1236004386,"src\/Rules\/Methods\/ReturnTypeRule.php":3533050732,"src\/Rules\/Methods\/CallToStaticMethodStamentWithoutSideEffectsRule.php":2170410280,"src\/Rules\/Methods\/MethodSignatureRule.php":396003695,"src\/Rules\/Methods\/CallToMethodStamentWithoutSideEffectsRule.php":1326084035,"src\/Rules\/Methods\/CallStaticMethodsRule.php":2276219989,"src\/Rules\/Methods\/MissingMethodParameterTypehintRule.php":553605059,"src\/Rules\/Methods\/OverridingMethodRule.php":2638924129,"src\/Rules\/Methods\/CallMethodsRule.php":1272236848,"src\/Rules\/Methods\/IncompatibleDefaultParameterTypeRule.php":2732986973,"src\/Rules\/Regexp\/RegularExpressionPatternRule.php":2871498838,"src\/Rules\/RuleLevelHelper.php":1533571429,"src\/Rules\/Operators\/InvalidIncDecOperationRule.php":4260142731,"src\/Rules\/Operators\/InvalidAssignVarRule.php":1135414561,"src\/Rules\/Operators\/InvalidUnaryOperationRule.php":4191250985,"src\/Rules\/Operators\/InvalidComparisonOperationRule.php":1250037162,"src\/Rules\/Operators\/InvalidBinaryOperationRule.php":1497426854,"src\/Rules\/Rule.php":3203451349,"src\/Rules\/FunctionReturnTypeCheck.php":924915875,"src\/Rules\/LineRuleError.php":2020429969,"src\/Rules\/Classes\/ImpossibleInstanceOfRule.php":3438147273,"src\/Rules\/Classes\/LocalTypeAliasesRule.php":585294878,"src\/Rules\/Classes\/ExistingClassInTraitUseRule.php":1376076702,"src\/Rules\/Classes\/ExistingClassInInstanceOfRule.php":3974622888,"src\/Rules\/Classes\/ClassConstantRule.php":2164220224,"src\/Rules\/Classes\/InvalidPromotedPropertiesRule.php":978977959,"src\/Rules\/Classes\/NonClassAttributeClassRule.php":1320548339,"src\/Rules\/Classes\/ExistingClassesInInterfaceExtendsRule.php":1799273694,"src\/Rules\/Classes\/ExistingClassesInClassImplementsRule.php":3063931074,"src\/Rules\/Classes\/TraitAttributeClassRule.php":734159816,"src\/Rules\/Classes\/InstantiationRule.php":2698968425,"src\/Rules\/Classes\/ExistingClassInClassExtendsRule.php":2533499224,"src\/Rules\/Classes\/UnusedConstructorParametersRule.php":3208952881,"src\/Rules\/Classes\/ClassAttributesRule.php":3362137491,"src\/Rules\/Classes\/MixinRule.php":1588047288,"src\/Rules\/Classes\/NewStaticRule.php":3014185040,"src\/Rules\/Classes\/DuplicateDeclarationRule.php":2033744840,"src\/Rules\/Classes\/ClassConstantAttributesRule.php":129410956,"src\/Rules\/Cast\/PrintRule.php":3284613492,"src\/Rules\/Cast\/UnsetCastRule.php":3888928789,"src\/Rules\/Cast\/InvalidPartOfEncapsedStringRule.php":397841507,"src\/Rules\/Cast\/InvalidCastRule.php":235731057,"src\/Rules\/Cast\/EchoRule.php":1554256791,"src\/Rules\/DateTimeInstantiationRule.php":1853050635,"src\/Rules\/RuleError.php":1866650704,"src\/Rules\/MissingTypehintCheck.php":2467764864,"src\/Rules\/Constants\/ConstantRule.php":308898608,"src\/Rules\/Constants\/AlwaysUsedClassConstantsExtension.php":1059896107,"src\/Rules\/Constants\/LazyAlwaysUsedClassConstantsExtensionProvider.php":3146818686,"src\/Rules\/Constants\/AlwaysUsedClassConstantsExtensionProvider.php":1417998549,"src\/Rules\/Registry.php":354601267,"src\/Rules\/NullsafeCheck.php":1611897384,"src\/Rules\/ClassNameNodePair.php":2242358198,"src\/Rules\/TooWideTypehints\/TooWideFunctionReturnTypehintRule.php":3612962037,"src\/Rules\/TooWideTypehints\/TooWideMethodReturnTypehintRule.php":3168893700,"src\/Rules\/TooWideTypehints\/TooWideClosureReturnTypehintRule.php":1987584844,"src\/Rules\/TooWideTypehints\/TooWideArrowFunctionReturnTypehintRule.php":1831708026,"src\/Rules\/Missing\/MissingClosureNativeReturnTypehintRule.php":1176929878,"src\/Rules\/Missing\/MissingReturnRule.php":4262376509,"src\/Rules\/TipRuleError.php":3393196054,"src\/Rules\/Exceptions\/MissingCheckedExceptionInMethodThrowsRule.php":2944466403,"src\/Rules\/Exceptions\/OverwrittenExitPointByFinallyRule.php":92396449,"src\/Rules\/Exceptions\/MissingCheckedExceptionInThrowsCheck.php":2818001403,"src\/Rules\/Exceptions\/TooWideMethodThrowTypeRule.php":2208317117,"src\/Rules\/Exceptions\/TooWideThrowTypeCheck.php":2072857476,"src\/Rules\/Exceptions\/ExceptionTypeResolver.php":122419946,"src\/Rules\/Exceptions\/DeadCatchRule.php":511487616,"src\/Rules\/Exceptions\/CaughtExceptionExistenceRule.php":4038850572,"src\/Rules\/Exceptions\/MissingCheckedExceptionInFunctionThrowsRule.php":242234063,"src\/Rules\/Exceptions\/ThrowExpressionRule.php":77342376,"src\/Rules\/Exceptions\/CatchWithUnthrownExceptionRule.php":338380024,"src\/Rules\/Exceptions\/TooWideFunctionThrowTypeRule.php":1229333908,"src\/Rules\/IdentifierRuleError.php":4142082662,"src\/Rules\/RegistryFactory.php":684346151,"src\/Rules\/Namespaces\/ExistingNamesInUseRule.php":2452899372,"src\/Rules\/Namespaces\/ExistingNamesInGroupUseRule.php":3366078479,"src\/Rules\/IssetCheck.php":3762073937,"src\/Rules\/RuleErrorBuilder.php":2401680118,"src\/Rules\/Properties\/ReadWritePropertiesExtension.php":801780550,"src\/Rules\/Properties\/AccessPropertiesRule.php":2483860618,"src\/Rules\/Properties\/ExistingClassesInPropertiesRule.php":1290986200,"src\/Rules\/Properties\/PropertyDescriptor.php":2248837720,"src\/Rules\/Properties\/LazyReadWritePropertiesExtensionProvider.php":3706356390,"src\/Rules\/Properties\/ReadingWriteOnlyPropertiesRule.php":1313821517,"src\/Rules\/Properties\/TypesAssignedToPropertiesRule.php":646776659,"src\/Rules\/Properties\/DefaultValueTypesAssignedToPropertiesRule.php":3554886982,"src\/Rules\/Properties\/AccessStaticPropertiesRule.php":3556752458,"src\/Rules\/Properties\/AccessPropertiesInAssignRule.php":1040576382,"src\/Rules\/Properties\/WritingToReadOnlyPropertiesRule.php":2144581935,"src\/Rules\/Properties\/MissingPropertyTypehintRule.php":3966278956,"src\/Rules\/Properties\/PropertyReflectionFinder.php":395878335,"src\/Rules\/Properties\/NullsafePropertyFetchRule.php":3936362164,"src\/Rules\/Properties\/ReadWritePropertiesExtensionProvider.php":1660543142,"src\/Rules\/Properties\/AccessStaticPropertiesInAssignRule.php":1310677319,"src\/Rules\/Properties\/UninitializedPropertyRule.php":1130280161,"src\/Rules\/Properties\/PropertyAttributesRule.php":3136046757,"src\/Rules\/Properties\/FoundPropertyReflection.php":642407052,"src\/Rules\/DeadCode\/UnusedPrivateMethodRule.php":3661228154,"src\/Rules\/DeadCode\/UnreachableStatementRule.php":4157831305,"src\/Rules\/DeadCode\/NoopRule.php":3678518597,"src\/Rules\/DeadCode\/UnusedPrivatePropertyRule.php":2189431812,"src\/Rules\/DeadCode\/UnusedPrivateConstantRule.php":3371192742,"src\/Rules\/Variables\/IssetRule.php":3966318530,"src\/Rules\/Variables\/CompactVariablesRule.php":953736369,"src\/Rules\/Variables\/NullCoalesceRule.php":647220628,"src\/Rules\/Variables\/VariableCertaintyInIssetRule.php":2486942727,"src\/Rules\/Variables\/VariableCertaintyNullCoalesceRule.php":1390585556,"src\/Rules\/Variables\/VariableCloningRule.php":2170241519,"src\/Rules\/Variables\/UnsetRule.php":2123036641,"src\/Rules\/Variables\/ThrowTypeRule.php":3089213909,"src\/Rules\/Variables\/DefinedVariableRule.php":2878623316,"src\/Rules\/Whitespace\/FileWhitespaceRule.php":4087604786,"src\/Rules\/Arrays\/AppendedArrayKeyTypeRule.php":189718980,"src\/Rules\/Arrays\/InvalidKeyInArrayItemRule.php":1477346113,"src\/Rules\/Arrays\/EmptyArrayItemRule.php":3292696659,"src\/Rules\/Arrays\/InvalidKeyInArrayDimFetchRule.php":920060318,"src\/Rules\/Arrays\/NonexistentOffsetInArrayDimFetchCheck.php":2344327982,"src\/Rules\/Arrays\/OffsetAccessValueAssignmentRule.php":1159837450,"src\/Rules\/Arrays\/OffsetAccessWithoutDimForReadingRule.php":2845913303,"src\/Rules\/Arrays\/OffsetAccessAssignOpRule.php":62201468,"src\/Rules\/Arrays\/OffsetAccessAssignmentRule.php":2761571660,"src\/Rules\/Arrays\/ArrayDestructuringRule.php":406642529,"src\/Rules\/Arrays\/AppendedArrayItemTypeRule.php":564669145,"src\/Rules\/Arrays\/UnpackIterableInArrayRule.php":2058846810,"src\/Rules\/Arrays\/DuplicateKeysInLiteralArraysRule.php":3381263419,"src\/Rules\/Arrays\/AllowedArrayKeysTypes.php":853250096,"src\/Rules\/Arrays\/NonexistentOffsetInArrayDimFetchRule.php":2182893127,"src\/Rules\/Arrays\/DeadForeachRule.php":1092600136,"src\/Rules\/Arrays\/IterableInForeachRule.php":559495582,"src\/Rules\/Functions\/MissingFunctionReturnTypehintRule.php":938252015,"src\/Rules\/Functions\/CallToFunctionParametersRule.php":2133507652,"src\/Rules\/Functions\/ExistingClassesInArrowFunctionTypehintsRule.php":1571239396,"src\/Rules\/Functions\/InnerFunctionRule.php":1719119452,"src\/Rules\/Functions\/ArrowFunctionAttributesRule.php":1796184740,"src\/Rules\/Functions\/ArrowFunctionReturnTypeRule.php":1319246783,"src\/Rules\/Functions\/ArrowFunctionReturnNullsafeByRefRule.php":2325384122,"src\/Rules\/Functions\/ExistingClassesInTypehintsRule.php":1457579457,"src\/Rules\/Functions\/FunctionAttributesRule.php":550332302,"src\/Rules\/Functions\/UnusedClosureUsesRule.php":3554414265,"src\/Rules\/Functions\/ReturnTypeRule.php":361344564,"src\/Rules\/Functions\/ParamAttributesRule.php":1182845730,"src\/Rules\/Functions\/RandomIntParametersRule.php":275234226,"src\/Rules\/Functions\/CallCallablesRule.php":467794435,"src\/Rules\/Functions\/MissingFunctionParameterTypehintRule.php":2222867992,"src\/Rules\/Functions\/CallToFunctionStamentWithoutSideEffectsRule.php":1455762700,"src\/Rules\/Functions\/ClosureReturnTypeRule.php":3191704549,"src\/Rules\/Functions\/ExistingClassesInClosureTypehintsRule.php":2881894510,"src\/Rules\/Functions\/ClosureUsesThisRule.php":1240347682,"src\/Rules\/Functions\/ClosureAttributesRule.php":205797002,"src\/Rules\/Functions\/PrintfParametersRule.php":122390684,"src\/Rules\/Functions\/CallToNonExistentFunctionRule.php":171709407,"src\/Rules\/Functions\/IncompatibleDefaultParameterTypeRule.php":827538603,"src\/Rules\/Functions\/ReturnNullsafeByRefRule.php":1272882833,"src\/Rules\/FoundTypeResult.php":4204303750,"src\/Rules\/Comparison\/ImpossibleCheckTypeStaticMethodCallRule.php":4208392143,"src\/Rules\/Comparison\/UnreachableIfBranchesRule.php":2060510122,"src\/Rules\/Comparison\/UsageOfVoidMatchExpressionRule.php":887021375,"src\/Rules\/Comparison\/ImpossibleCheckTypeMethodCallRule.php":2790127112,"src\/Rules\/Comparison\/ImpossibleCheckTypeHelper.php":2467166906,"src\/Rules\/Comparison\/NumberComparisonOperatorsConstantConditionRule.php":4106144088,"src\/Rules\/Comparison\/UnreachableTernaryElseBranchRule.php":367452894,"src\/Rules\/Comparison\/StrictComparisonOfDifferentTypesRule.php":3063855881,"src\/Rules\/Comparison\/IfConstantConditionRule.php":3599011055,"src\/Rules\/Comparison\/MatchExpressionRule.php":2514558816,"src\/Rules\/Comparison\/BooleanAndConstantConditionRule.php":405591092,"src\/Rules\/Comparison\/ElseIfConstantConditionRule.php":3414997090,"src\/Rules\/Comparison\/ConstantConditionRuleHelper.php":1895769635,"src\/Rules\/Comparison\/BooleanNotConstantConditionRule.php":3925698351,"src\/Rules\/Comparison\/ImpossibleCheckTypeFunctionCallRule.php":1453156521,"src\/Rules\/Comparison\/BooleanOrConstantConditionRule.php":2115040458,"src\/Rules\/Comparison\/TernaryOperatorConstantConditionRule.php":2618108090,"src\/Rules\/Generators\/YieldTypeRule.php":2639889078,"src\/Rules\/Generators\/YieldFromTypeRule.php":2283442268,"src\/Rules\/Generators\/YieldInGeneratorRule.php":2191400075,"src\/Rules\/AttributesCheck.php":548544306,"src\/Rules\/Keywords\/ContinueBreakInLoopRule.php":1448946253,"src\/Rules\/MetadataRuleError.php":1709077979,"src\/Rules\/FunctionDefinitionCheck.php":319097499,"src\/Rules\/UnusedFunctionParametersCheck.php":708611628,"src\/Rules\/Debug\/DumpTypeRule.php":1591520594,"src\/Rules\/Debug\/FileAssertRule.php":2515565113,"src\/ShouldNotHappenException.php":1644931968,"src\/AnalysedCodeException.php":3863363180,"src\/Command\/ErrorsConsoleStyle.php":3475874946,"src\/Command\/AnalyserRunner.php":816558735,"src\/Command\/IgnoredRegexValidator.php":2420464276,"src\/Command\/FixerWorkerCommand.php":1922368272,"src\/Command\/CommandHelper.php":1426225472,"src\/Command\/IgnoredRegexValidatorResult.php":3291353073,"src\/Command\/OutputStyle.php":1285612652,"src\/Command\/ErrorFormatter\/JunitErrorFormatter.php":1008095842,"src\/Command\/ErrorFormatter\/JsonErrorFormatter.php":4071889306,"src\/Command\/ErrorFormatter\/RawErrorFormatter.php":4224691413,"src\/Command\/ErrorFormatter\/TeamcityErrorFormatter.php":367931781,"src\/Command\/ErrorFormatter\/TableErrorFormatter.php":1174463499,"src\/Command\/ErrorFormatter\/ErrorFormatter.php":3054035329,"src\/Command\/ErrorFormatter\/BaselineNeonErrorFormatter.php":1022296018,"src\/Command\/ErrorFormatter\/CheckstyleErrorFormatter.php":1499327555,"src\/Command\/ErrorFormatter\/GithubErrorFormatter.php":4124635409,"src\/Command\/ErrorFormatter\/GitlabErrorFormatter.php":2120373944,"src\/Command\/AnalysisResult.php":1437039323,"src\/Command\/AnalyseCommand.php":3621263416,"src\/Command\/ClearResultCacheCommand.php":777416051,"src\/Command\/InceptionNotSuccessfulException.php":1632435199,"src\/Command\/DumpDependenciesCommand.php":3074800637,"src\/Command\/Output.php":775584855,"src\/Command\/AnalyseApplication.php":607529951,"src\/Command\/FixerApplication.php":883227624,"src\/Command\/WorkerCommand.php":2814606198,"src\/Command\/Symfony\/SymfonyOutput.php":3556599754,"src\/Command\/Symfony\/SymfonyStyle.php":2884262050,"src\/Command\/InceptionResult.php":1306600096,"src\/Command\/FixerProcessException.php":2767166641,"src\/Node\/MethodReturnStatementsNode.php":3285762124,"src\/Node\/ClassMethodsNode.php":597985499,"src\/Node\/CatchWithUnthrownExceptionNode.php":733169932,"src\/Node\/BooleanAndNode.php":3704996419,"src\/Node\/InClassMethodNode.php":2906204584,"src\/Node\/MatchExpressionNode.php":3444624980,"src\/Node\/MatchExpressionArm.php":2929175818,"src\/Node\/BooleanOrNode.php":2653732279,"src\/Node\/FileNode.php":4261017329,"src\/Node\/InFunctionNode.php":623935855,"src\/Node\/Property\/PropertyWrite.php":1847152408,"src\/Node\/Property\/PropertyRead.php":2114718794,"src\/Node\/ClassPropertiesNode.php":3336647149,"src\/Node\/ClassStatementsGatherer.php":478177825,"src\/Node\/FinallyExitPointsNode.php":3179906966,"src\/Node\/ReturnStatementsNode.php":879449269,"src\/Node\/ClosureReturnStatementsNode.php":4146937581,"src\/Node\/ReturnStatement.php":24799957,"src\/Node\/VirtualNode.php":1768729166,"src\/Node\/InClassNode.php":1260880011,"src\/Node\/LiteralArrayNode.php":3893664181,"src\/Node\/Constant\/ClassConstantFetch.php":467340361,"src\/Node\/LiteralArrayItem.php":2477263933,"src\/Node\/MatchExpressionArmCondition.php":2828235231,"src\/Node\/UnreachableStatementNode.php":3458539931,"src\/Node\/InArrowFunctionNode.php":2835164680,"src\/Node\/ExecutionEndNode.php":3245561848,"src\/Node\/InClosureNode.php":813089105,"src\/Node\/ClassPropertyNode.php":223803625,"src\/Node\/ClassConstantsNode.php":996471326,"src\/Node\/Method\/MethodCall.php":3769881345,"src\/Node\/FunctionReturnStatementsNode.php":109540945,"src\/DependencyInjection\/RulesExtension.php":604678488,"src\/DependencyInjection\/ContainerFactory.php":2214496457,"src\/DependencyInjection\/Container.php":800092587,"src\/DependencyInjection\/ParameterNotFoundException.php":3083540928,"src\/DependencyInjection\/DerivativeContainerFactory.php":1561463053,"src\/DependencyInjection\/LoaderFactory.php":768425581,"src\/DependencyInjection\/NeonLoader.php":920438007,"src\/DependencyInjection\/ParametersSchemaExtension.php":25071580,"src\/DependencyInjection\/Nette\/NetteContainer.php":829669580,"src\/DependencyInjection\/MemoizingContainer.php":104126360,"src\/DependencyInjection\/Type\/LazyDynamicReturnTypeExtensionRegistryProvider.php":2609158996,"src\/DependencyInjection\/Type\/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php":1195012904,"src\/DependencyInjection\/Type\/DirectDynamicReturnTypeExtensionRegistryProvider.php":4204665837,"src\/DependencyInjection\/Type\/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php":3893046619,"src\/DependencyInjection\/Type\/DynamicReturnTypeExtensionRegistryProvider.php":2344991232,"src\/DependencyInjection\/Type\/DynamicThrowTypeExtensionProvider.php":3653226432,"src\/DependencyInjection\/Type\/LazyDynamicThrowTypeExtensionProvider.php":1583940041,"src\/DependencyInjection\/Type\/OperatorTypeSpecifyingExtensionRegistryProvider.php":2273443892,"src\/DependencyInjection\/ConditionalTagsExtension.php":285855112,"src\/DependencyInjection\/NeonAdapter.php":3415059487,"src\/DependencyInjection\/Reflection\/ClassReflectionExtensionRegistryProvider.php":2371651892,"src\/DependencyInjection\/Reflection\/DirectClassReflectionExtensionRegistryProvider.php":1716260327,"src\/DependencyInjection\/Reflection\/LazyClassReflectionExtensionRegistryProvider.php":3782305040,"src\/DependencyInjection\/Configurator.php":3446910303,"src\/TrinaryLogic.php":4127167537,"src\/Process\/CpuCoreCounter.php":3862860165,"src\/Process\/ProcessHelper.php":3339107771,"src\/Process\/ProcessCrashedException.php":1120948520,"src\/Process\/Runnable\/RunnableQueueLogger.php":460167398,"src\/Process\/Runnable\/RunnableQueue.php":1006868405,"src\/Process\/Runnable\/RunnableCanceledException.php":1941454280,"src\/Process\/Runnable\/Runnable.php":1556583114,"src\/Process\/ProcessPromise.php":3407097870,"src\/Process\/ProcessCanceledException.php":94521420,"src\/Reflection\/ReflectionProvider.php":1991619024,"src\/Reflection\/GlobalConstantReflection.php":1198849031,"src\/Reflection\/ParametersAcceptor.php":4244582461,"src\/Reflection\/FunctionVariantWithPhpDocs.php":4061292489,"src\/Reflection\/ParameterReflection.php":2476043037,"src\/Reflection\/ParametersAcceptorSelector.php":272064967,"src\/Reflection\/Dummy\/DummyMethodReflection.php":3550753551,"src\/Reflection\/Dummy\/ChangedTypePropertyReflection.php":1605655901,"src\/Reflection\/Dummy\/DummyConstantReflection.php":947704564,"src\/Reflection\/Dummy\/DummyConstructorReflection.php":3433651587,"src\/Reflection\/Dummy\/ChangedTypeMethodReflection.php":3900603091,"src\/Reflection\/Dummy\/DummyPropertyReflection.php":1576630723,"src\/Reflection\/SignatureMap\/SignatureMapProvider.php":962460378,"src\/Reflection\/SignatureMap\/NativeFunctionReflectionProvider.php":2082040262,"src\/Reflection\/SignatureMap\/SignatureMapProviderFactory.php":2860581168,"src\/Reflection\/SignatureMap\/FunctionSignature.php":2167406884,"src\/Reflection\/SignatureMap\/Php8SignatureMapProvider.php":2323466028,"src\/Reflection\/SignatureMap\/ParameterSignature.php":3449367018,"src\/Reflection\/SignatureMap\/FunctionSignatureMapProvider.php":1954513846,"src\/Reflection\/SignatureMap\/SignatureMapParser.php":4216477567,"src\/Reflection\/ClassReflection.php":1325453721,"src\/Reflection\/BetterReflection\/SourceLocator\/FetchedNodesResult.php":2660087801,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedDirectorySourceLocatorRepository.php":1647910836,"src\/Reflection\/BetterReflection\/SourceLocator\/ComposerJsonAndInstalledJsonSourceLocatorMaker.php":794276999,"src\/Reflection\/BetterReflection\/SourceLocator\/ClassBlacklistSourceLocator.php":419453766,"src\/Reflection\/BetterReflection\/SourceLocator\/FileNodesFetcher.php":1869034650,"src\/Reflection\/BetterReflection\/SourceLocator\/ClassWhitelistSourceLocator.php":1725625448,"src\/Reflection\/BetterReflection\/SourceLocator\/AutoloadSourceLocator.php":97645345,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedPsrAutoloaderLocatorFactory.php":412303112,"src\/Reflection\/BetterReflection\/SourceLocator\/FileReadTrapStreamWrapper.php":3055097212,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedPsrAutoloaderLocator.php":1847434513,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedDirectorySourceLocator.php":2803624858,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedDirectorySourceLocatorFactory.php":2169619968,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedSingleFileSourceLocatorFactory.php":3429496480,"src\/Reflection\/BetterReflection\/SourceLocator\/PhpVersionBlacklistSourceLocator.php":3346637626,"src\/Reflection\/BetterReflection\/SourceLocator\/SkipClassAliasSourceLocator.php":3428829631,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedSingleFileSourceLocator.php":411050195,"src\/Reflection\/BetterReflection\/SourceLocator\/CachingVisitor.php":938960874,"src\/Reflection\/BetterReflection\/SourceLocator\/OptimizedSingleFileSourceLocatorRepository.php":295304408,"src\/Reflection\/BetterReflection\/SourceLocator\/FetchedNode.php":3469458597,"src\/Reflection\/BetterReflection\/BetterReflectionProvider.php":2756976608,"src\/Reflection\/BetterReflection\/BetterReflectionSourceLocatorFactory.php":1735184451,"src\/Reflection\/BetterReflection\/Reflector\/MemoizingClassReflector.php":2579267219,"src\/Reflection\/BetterReflection\/Reflector\/MemoizingConstantReflector.php":2318506271,"src\/Reflection\/BetterReflection\/Reflector\/MemoizingFunctionReflector.php":294655918,"src\/Reflection\/BetterReflection\/SourceStubber\/Php8StubsSourceStubber.php":676850825,"src\/Reflection\/BetterReflection\/BetterReflectionProviderFactory.php":2879848126,"src\/Reflection\/Native\/NativeFunctionReflection.php":801947095,"src\/Reflection\/Native\/NativeParameterWithPhpDocsReflection.php":989297493,"src\/Reflection\/Native\/NativeParameterReflection.php":3745425098,"src\/Reflection\/Native\/NativeMethodReflection.php":572181841,"src\/Reflection\/MissingMethodFromReflectionException.php":3293784779,"src\/Reflection\/ConstantReflection.php":3233101940,"src\/Reflection\/BrokerAwareExtension.php":20304974,"src\/Reflection\/ClassMemberAccessAnswerer.php":260414749,"src\/Reflection\/GenericParametersAcceptorResolver.php":3299962234,"src\/Reflection\/FunctionReflection.php":1885115648,"src\/Reflection\/MethodReflection.php":1769856337,"src\/Reflection\/MethodsClassReflectionExtension.php":1343577040,"src\/Reflection\/Mixin\/MixinMethodReflection.php":2501741192,"src\/Reflection\/Mixin\/MixinPropertiesClassReflectionExtension.php":1362844658,"src\/Reflection\/Mixin\/MixinMethodsClassReflectionExtension.php":2336973805,"src\/Reflection\/Runtime\/RuntimeReflectionProvider.php":4188428205,"src\/Reflection\/ClassReflectionExtensionRegistry.php":971804635,"src\/Reflection\/Php\/ClosureCallMethodReflection.php":52275636,"src\/Reflection\/Php\/ClosureCallUnresolvedMethodPrototypeReflection.php":1068361834,"src\/Reflection\/Php\/PhpFunctionReflection.php":4006286835,"src\/Reflection\/Php\/BuiltinMethodReflection.php":1119259756,"src\/Reflection\/Php\/DummyParameter.php":223495997,"src\/Reflection\/Php\/SimpleXMLElementProperty.php":3412552276,"src\/Reflection\/Php\/PhpParameterReflection.php":3907533061,"src\/Reflection\/Php\/UniversalObjectCrateProperty.php":3046058578,"src\/Reflection\/Php\/PhpMethodReflectionFactory.php":2814456000,"src\/Reflection\/Php\/UniversalObjectCratesClassReflectionExtension.php":2659542194,"src\/Reflection\/Php\/Soap\/SoapClientMethodReflection.php":739926589,"src\/Reflection\/Php\/Soap\/SoapClientMethodsClassReflectionExtension.php":3254861802,"src\/Reflection\/Php\/PhpClassReflectionExtension.php":3963308967,"src\/Reflection\/Php\/PhpPropertyReflection.php":2383399593,"src\/Reflection\/Php\/NativeBuiltinMethodReflection.php":2224303856,"src\/Reflection\/Php\/PhpMethodFromParserNodeReflection.php":1852452065,"src\/Reflection\/Php\/PhpParameterFromParserNodeReflection.php":1055914355,"src\/Reflection\/Php\/PhpMethodReflection.php":1245098589,"src\/Reflection\/Php\/PhpFunctionFromParserNodeReflection.php":1570547397,"src\/Reflection\/Php\/FakeBuiltinMethodReflection.php":1280585532,"src\/Reflection\/MethodPrototypeReflection.php":3533231801,"src\/Reflection\/Annotations\/AnnotationsMethodsClassReflectionExtension.php":3336706549,"src\/Reflection\/Annotations\/AnnotationsMethodParameterReflection.php":536179474,"src\/Reflection\/Annotations\/AnnotationPropertyReflection.php":1962992447,"src\/Reflection\/Annotations\/AnnotationMethodReflection.php":3673815215,"src\/Reflection\/Annotations\/AnnotationsPropertiesClassReflectionExtension.php":4212249369,"src\/Reflection\/ClassConstantReflection.php":3952280368,"src\/Reflection\/ReflectionWithFilename.php":660049215,"src\/Reflection\/Type\/IntersectionTypePropertyReflection.php":3823095484,"src\/Reflection\/Type\/IntersectionTypeMethodReflection.php":2640933141,"src\/Reflection\/Type\/IntersectionTypeUnresolvedPropertyPrototypeReflection.php":2533798630,"src\/Reflection\/Type\/CallbackUnresolvedMethodPrototypeReflection.php":1977336423,"src\/Reflection\/Type\/UnresolvedMethodPrototypeReflection.php":3741409212,"src\/Reflection\/Type\/IntersectionTypeUnresolvedMethodPrototypeReflection.php":895711299,"src\/Reflection\/Type\/UnionTypeUnresolvedPropertyPrototypeReflection.php":2057296031,"src\/Reflection\/Type\/UnresolvedPropertyPrototypeReflection.php":4233654107,"src\/Reflection\/Type\/CalledOnTypeUnresolvedPropertyPrototypeReflection.php":368074370,"src\/Reflection\/Type\/UnionTypeUnresolvedMethodPrototypeReflection.php":655243188,"src\/Reflection\/Type\/UnionTypePropertyReflection.php":2247992770,"src\/Reflection\/Type\/UnionTypeMethodReflection.php":2180595498,"src\/Reflection\/Type\/CalledOnTypeUnresolvedMethodPrototypeReflection.php":2426826560,"src\/Reflection\/Type\/CallbackUnresolvedPropertyPrototypeReflection.php":3428495041,"src\/Reflection\/ParametersAcceptorWithPhpDocs.php":2349023251,"src\/Reflection\/ResolvedFunctionVariant.php":4045782105,"src\/Reflection\/Constant\/RuntimeConstantReflection.php":515871640,"src\/Reflection\/InaccessibleMethod.php":539676144,"src\/Reflection\/TrivialParametersAcceptor.php":3587031431,"src\/Reflection\/ResolvedPropertyReflection.php":3611647978,"src\/Reflection\/ResolvedMethodReflection.php":2136560841,"src\/Reflection\/Generic\/ResolvedFunctionVariant.php":2778642095,"src\/Reflection\/ClassMemberReflection.php":3659122126,"src\/Reflection\/MissingConstantFromReflectionException.php":3919603406,"src\/Reflection\/PassedByReference.php":2575192894,"src\/Reflection\/FunctionReflectionFactory.php":2736317001,"src\/Reflection\/WrapperPropertyReflection.php":2192506387,"src\/Reflection\/ParameterReflectionWithPhpDocs.php":3015755527,"src\/Reflection\/ReflectionProvider\/LazyReflectionProviderProvider.php":2566422961,"src\/Reflection\/ReflectionProvider\/MemoizingReflectionProvider.php":931682107,"src\/Reflection\/ReflectionProvider\/ChainReflectionProvider.php":3148383905,"src\/Reflection\/ReflectionProvider\/ReflectionProviderFactory.php":2732179883,"src\/Reflection\/ReflectionProvider\/ClassBlacklistReflectionProvider.php":3986101625,"src\/Reflection\/ReflectionProvider\/SetterReflectionProviderProvider.php":1625639727,"src\/Reflection\/ReflectionProvider\/DirectReflectionProviderProvider.php":1059953772,"src\/Reflection\/ReflectionProvider\/ReflectionProviderProvider.php":3265504103,"src\/Reflection\/FunctionVariant.php":3492600008,"src\/Reflection\/PropertiesClassReflectionExtension.php":3720559112,"src\/Reflection\/MissingPropertyFromReflectionException.php":3420711392,"src\/Reflection\/PropertyReflection.php":3306025485}} \ No newline at end of file diff --git a/bin/functionMetadata_original.php b/bin/functionMetadata_original.php index b7505a6f6f..71a4fad11e 100644 --- a/bin/functionMetadata_original.php +++ b/bin/functionMetadata_original.php @@ -1,107 +1,109 @@ - ['hasSideEffects' => false], - 'acos' => ['hasSideEffects' => false], - 'acosh' => ['hasSideEffects' => false], - 'addcslashes' => ['hasSideEffects' => false], - 'addslashes' => ['hasSideEffects' => false], - 'array_change_key_case' => ['hasSideEffects' => false], - 'array_chunk' => ['hasSideEffects' => false], - 'array_column' => ['hasSideEffects' => false], - 'array_combine' => ['hasSideEffects' => false], - 'array_count_values' => ['hasSideEffects' => false], - 'array_diff' => ['hasSideEffects' => false], - 'array_diff_assoc' => ['hasSideEffects' => false], - 'array_diff_key' => ['hasSideEffects' => false], - 'array_diff_uassoc' => ['hasSideEffects' => false], - 'array_diff_ukey' => ['hasSideEffects' => false], - 'array_fill' => ['hasSideEffects' => false], - 'array_fill_keys' => ['hasSideEffects' => false], - 'array_flip' => ['hasSideEffects' => false], - 'array_intersect' => ['hasSideEffects' => false], - 'array_intersect_assoc' => ['hasSideEffects' => false], - 'array_intersect_key' => ['hasSideEffects' => false], - 'array_intersect_uassoc' => ['hasSideEffects' => false], - 'array_intersect_ukey' => ['hasSideEffects' => false], - 'array_key_first' => ['hasSideEffects' => false], - 'array_key_last' => ['hasSideEffects' => false], - 'array_key_exists' => ['hasSideEffects' => false], - 'array_keys' => ['hasSideEffects' => false], - 'array_merge' => ['hasSideEffects' => false], - 'array_merge_recursive' => ['hasSideEffects' => false], - 'array_pad' => ['hasSideEffects' => false], - 'array_product' => ['hasSideEffects' => false], - 'array_rand' => ['hasSideEffects' => false], - 'array_replace' => ['hasSideEffects' => false], - 'array_replace_recursive' => ['hasSideEffects' => false], - 'array_reverse' => ['hasSideEffects' => false], - 'array_slice' => ['hasSideEffects' => false], - 'array_sum' => ['hasSideEffects' => false], - 'array_udiff' => ['hasSideEffects' => false], - 'array_udiff_assoc' => ['hasSideEffects' => false], - 'array_udiff_uassoc' => ['hasSideEffects' => false], - 'array_uintersect' => ['hasSideEffects' => false], - 'array_uintersect_assoc' => ['hasSideEffects' => false], - 'array_uintersect_uassoc' => ['hasSideEffects' => false], - 'array_unique' => ['hasSideEffects' => false], - 'array_values' => ['hasSideEffects' => false], - 'asin' => ['hasSideEffects' => false], - 'asinh' => ['hasSideEffects' => false], - 'atan' => ['hasSideEffects' => false], - 'atan2' => ['hasSideEffects' => false], - 'atanh' => ['hasSideEffects' => false], - 'base64_decode' => ['hasSideEffects' => false], - 'base64_encode' => ['hasSideEffects' => false], - 'base_convert' => ['hasSideEffects' => false], - 'basename' => ['hasSideEffects' => false], - 'bcadd' => ['hasSideEffects' => false], - 'bccomp' => ['hasSideEffects' => false], - 'bcdiv' => ['hasSideEffects' => false], - 'bcmod' => ['hasSideEffects' => false], - 'bcmul' => ['hasSideEffects' => false], - // continue functionMap.php, line 424 - 'count' => ['hasSideEffects' => false], - 'sprintf' => ['hasSideEffects' => false], + 'abs' => ['hasSideEffects' => false], + 'acos' => ['hasSideEffects' => false], + 'acosh' => ['hasSideEffects' => false], + 'addcslashes' => ['hasSideEffects' => false], + 'addslashes' => ['hasSideEffects' => false], + 'array_change_key_case' => ['hasSideEffects' => false], + 'array_chunk' => ['hasSideEffects' => false], + 'array_column' => ['hasSideEffects' => false], + 'array_combine' => ['hasSideEffects' => false], + 'array_count_values' => ['hasSideEffects' => false], + 'array_diff' => ['hasSideEffects' => false], + 'array_diff_assoc' => ['hasSideEffects' => false], + 'array_diff_key' => ['hasSideEffects' => false], + 'array_diff_uassoc' => ['hasSideEffects' => false], + 'array_diff_ukey' => ['hasSideEffects' => false], + 'array_fill' => ['hasSideEffects' => false], + 'array_fill_keys' => ['hasSideEffects' => false], + 'array_flip' => ['hasSideEffects' => false], + 'array_intersect' => ['hasSideEffects' => false], + 'array_intersect_assoc' => ['hasSideEffects' => false], + 'array_intersect_key' => ['hasSideEffects' => false], + 'array_intersect_uassoc' => ['hasSideEffects' => false], + 'array_intersect_ukey' => ['hasSideEffects' => false], + 'array_key_first' => ['hasSideEffects' => false], + 'array_key_last' => ['hasSideEffects' => false], + 'array_key_exists' => ['hasSideEffects' => false], + 'array_keys' => ['hasSideEffects' => false], + 'array_merge' => ['hasSideEffects' => false], + 'array_merge_recursive' => ['hasSideEffects' => false], + 'array_pad' => ['hasSideEffects' => false], + 'array_product' => ['hasSideEffects' => false], + 'array_rand' => ['hasSideEffects' => false], + 'array_replace' => ['hasSideEffects' => false], + 'array_replace_recursive' => ['hasSideEffects' => false], + 'array_reverse' => ['hasSideEffects' => false], + 'array_slice' => ['hasSideEffects' => false], + 'array_sum' => ['hasSideEffects' => false], + 'array_udiff' => ['hasSideEffects' => false], + 'array_udiff_assoc' => ['hasSideEffects' => false], + 'array_udiff_uassoc' => ['hasSideEffects' => false], + 'array_uintersect' => ['hasSideEffects' => false], + 'array_uintersect_assoc' => ['hasSideEffects' => false], + 'array_uintersect_uassoc' => ['hasSideEffects' => false], + 'array_unique' => ['hasSideEffects' => false], + 'array_values' => ['hasSideEffects' => false], + 'asin' => ['hasSideEffects' => false], + 'asinh' => ['hasSideEffects' => false], + 'atan' => ['hasSideEffects' => false], + 'atan2' => ['hasSideEffects' => false], + 'atanh' => ['hasSideEffects' => false], + 'base64_decode' => ['hasSideEffects' => false], + 'base64_encode' => ['hasSideEffects' => false], + 'base_convert' => ['hasSideEffects' => false], + 'basename' => ['hasSideEffects' => false], + 'bcadd' => ['hasSideEffects' => false], + 'bccomp' => ['hasSideEffects' => false], + 'bcdiv' => ['hasSideEffects' => false], + 'bcmod' => ['hasSideEffects' => false], + 'bcmul' => ['hasSideEffects' => false], + // continue functionMap.php, line 424 + 'count' => ['hasSideEffects' => false], + 'sprintf' => ['hasSideEffects' => false], - // random functions, do not have side effects but are not deterministic - 'mt_rand' => ['hasSideEffects' => true], - 'rand' => ['hasSideEffects' => true], - 'random_bytes' => ['hasSideEffects' => true], - 'random_int' => ['hasSideEffects' => true], + // random functions, do not have side effects but are not deterministic + 'mt_rand' => ['hasSideEffects' => true], + 'rand' => ['hasSideEffects' => true], + 'random_bytes' => ['hasSideEffects' => true], + 'random_int' => ['hasSideEffects' => true], - // methods - 'DateTime::createFromFormat' => ['hasSideEffects' => false], - 'DateTime::createFromImmutable' => ['hasSideEffects' => false], - 'DateTime::getLastErrors' => ['hasSideEffects' => false], - 'DateTime::add' => ['hasSideEffects' => true], - 'DateTime::modify' => ['hasSideEffects' => true], - 'DateTime::setDate' => ['hasSideEffects' => true], - 'DateTime::setISODate' => ['hasSideEffects' => true], - 'DateTime::setTime' => ['hasSideEffects' => true], - 'DateTime::setTimestamp' => ['hasSideEffects' => true], - 'DateTime::setTimezone' => ['hasSideEffects' => true], - 'DateTime::sub' => ['hasSideEffects' => true], - 'DateTime::diff' => ['hasSideEffects' => false], - 'DateTime::format' => ['hasSideEffects' => false], - 'DateTime::getOffset' => ['hasSideEffects' => false], - 'DateTime::getTimestamp' => ['hasSideEffects' => false], - 'DateTime::getTimezone' => ['hasSideEffects' => false], + // methods + 'DateTime::createFromFormat' => ['hasSideEffects' => false], + 'DateTime::createFromImmutable' => ['hasSideEffects' => false], + 'DateTime::getLastErrors' => ['hasSideEffects' => false], + 'DateTime::add' => ['hasSideEffects' => true], + 'DateTime::modify' => ['hasSideEffects' => true], + 'DateTime::setDate' => ['hasSideEffects' => true], + 'DateTime::setISODate' => ['hasSideEffects' => true], + 'DateTime::setTime' => ['hasSideEffects' => true], + 'DateTime::setTimestamp' => ['hasSideEffects' => true], + 'DateTime::setTimezone' => ['hasSideEffects' => true], + 'DateTime::sub' => ['hasSideEffects' => true], + 'DateTime::diff' => ['hasSideEffects' => false], + 'DateTime::format' => ['hasSideEffects' => false], + 'DateTime::getOffset' => ['hasSideEffects' => false], + 'DateTime::getTimestamp' => ['hasSideEffects' => false], + 'DateTime::getTimezone' => ['hasSideEffects' => false], - 'DateTimeImmutable::createFromFormat' => ['hasSideEffects' => false], - 'DateTimeImmutable::createFromMutable' => ['hasSideEffects' => false], - 'DateTimeImmutable::getLastErrors' => ['hasSideEffects' => false], - 'DateTimeImmutable::add' => ['hasSideEffects' => false], - 'DateTimeImmutable::modify' => ['hasSideEffects' => false], - 'DateTimeImmutable::setDate' => ['hasSideEffects' => false], - 'DateTimeImmutable::setISODate' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTime' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], - 'DateTimeImmutable::sub' => ['hasSideEffects' => false], - 'DateTimeImmutable::diff' => ['hasSideEffects' => false], - 'DateTimeImmutable::format' => ['hasSideEffects' => false], - 'DateTimeImmutable::getOffset' => ['hasSideEffects' => false], - 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], - 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromFormat' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromMutable' => ['hasSideEffects' => false], + 'DateTimeImmutable::getLastErrors' => ['hasSideEffects' => false], + 'DateTimeImmutable::add' => ['hasSideEffects' => false], + 'DateTimeImmutable::modify' => ['hasSideEffects' => false], + 'DateTimeImmutable::setDate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setISODate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTime' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'DateTimeImmutable::diff' => ['hasSideEffects' => false], + 'DateTimeImmutable::format' => ['hasSideEffects' => false], + 'DateTimeImmutable::getOffset' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], ]; diff --git a/bin/generate-changelog.php b/bin/generate-changelog.php index 5119d62564..d050468977 100755 --- a/bin/generate-changelog.php +++ b/bin/generate-changelog.php @@ -7,100 +7,97 @@ use Symfony\Component\Console\Output\OutputInterface; (function () { - require_once __DIR__ . '/../vendor/autoload.php'; - - $command = new class() extends Symfony\Component\Console\Command\Command { - - protected function configure() - { - $this->setName('run'); - $this->addArgument('fromCommit', InputArgument::REQUIRED); - $this->addArgument('toCommit', InputArgument::REQUIRED); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $commitLines = $this->exec(['git', 'log', sprintf('%s..%s', $input->getArgument('fromCommit'), $input->getArgument('toCommit')), '--reverse', '--pretty=%H %s']); - $commits = array_map(function (string $line): array { - [$hash, $message] = explode(' ', $line, 2); - - return [ - 'hash' => $hash, - 'message' => $message - ]; - }, explode("\n", $commitLines)); - - $i = 0; - - foreach ($commits as $commit) { - $searchPullRequestsResponse = Request::get(sprintf('https://api.github.com/search/issues?q=repo:phpstan/phpstan-src+%s', $commit['hash'])) - ->sendsAndExpectsType('application/json') - ->basicAuth('ondrejmirtes', getenv('GITHUB_TOKEN')) - ->send(); - if ($searchPullRequestsResponse->code !== 200) { - $output->writeln(var_export($searchPullRequestsResponse->body, true)); - throw new \InvalidArgumentException((string) $searchPullRequestsResponse->code); - } - $searchPullRequestsResponse = $searchPullRequestsResponse->body; - - $searchIssuesResponse = Request::get(sprintf('https://api.github.com/search/issues?q=repo:phpstan/phpstan+%s', $commit['hash'])) - ->sendsAndExpectsType('application/json') - ->basicAuth('ondrejmirtes', getenv('GITHUB_TOKEN')) - ->send(); - if ($searchIssuesResponse->code !== 200) { - $output->writeln(var_export($searchIssuesResponse->body, true)); - throw new \InvalidArgumentException((string) $searchIssuesResponse->code); - } - $searchIssuesResponse = $searchIssuesResponse->body; - $items = array_merge($searchPullRequestsResponse->items, $searchIssuesResponse->items); - $parenthesis = 'https://github.com/phpstan/phpstan-src/commit/' . $commit['hash']; - $thanks = null; - $issuesToReference = []; - foreach ($items as $responseItem) { - if (isset($responseItem->pull_request)) { - $parenthesis = sprintf('[#%d](%s)', $responseItem->number, 'https://github.com/phpstan/phpstan-src/pull/' . $responseItem->number); - $thanks = $responseItem->user->login; - } else { - $issuesToReference[] = sprintf('#%d', $responseItem->number); - } - } - - $output->writeln(sprintf('* %s (%s)%s%s', $commit['message'], $parenthesis, count($issuesToReference) > 0 ? ', ' . implode(', ', $issuesToReference) : '', $thanks !== null ? sprintf(', thanks @%s!', $thanks) : '')); - - if ($i > 0 && $i % 8 === 0) { - sleep(60); - } - - $i++; - } - - return 0; - } - - /** - * @param string[] $commandParts - * @return string - */ - private function exec(array $commandParts): string - { - $command = implode(' ', array_map(function (string $part): string { - return escapeshellarg($part); - }, $commandParts)); - - exec($command, $outputLines, $statusCode); - $output = implode("\n", $outputLines); - if ($statusCode !== 0) { - throw new \InvalidArgumentException(sprintf('Command %s failed: %s', $command, $output)); - } - - return $output; - } - - }; - - $application = new \Symfony\Component\Console\Application(); - $application->add($command); - $application->setDefaultCommand('run', true); - $application->run(); - + require_once __DIR__ . '/../vendor/autoload.php'; + + $command = new class() extends Symfony\Component\Console\Command\Command { + protected function configure() + { + $this->setName('run'); + $this->addArgument('fromCommit', InputArgument::REQUIRED); + $this->addArgument('toCommit', InputArgument::REQUIRED); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $commitLines = $this->exec(['git', 'log', sprintf('%s..%s', $input->getArgument('fromCommit'), $input->getArgument('toCommit')), '--reverse', '--pretty=%H %s']); + $commits = array_map(function (string $line): array { + [$hash, $message] = explode(' ', $line, 2); + + return [ + 'hash' => $hash, + 'message' => $message + ]; + }, explode("\n", $commitLines)); + + $i = 0; + + foreach ($commits as $commit) { + $searchPullRequestsResponse = Request::get(sprintf('https://api.github.com/search/issues?q=repo:phpstan/phpstan-src+%s', $commit['hash'])) + ->sendsAndExpectsType('application/json') + ->basicAuth('ondrejmirtes', getenv('GITHUB_TOKEN')) + ->send(); + if ($searchPullRequestsResponse->code !== 200) { + $output->writeln(var_export($searchPullRequestsResponse->body, true)); + throw new \InvalidArgumentException((string) $searchPullRequestsResponse->code); + } + $searchPullRequestsResponse = $searchPullRequestsResponse->body; + + $searchIssuesResponse = Request::get(sprintf('https://api.github.com/search/issues?q=repo:phpstan/phpstan+%s', $commit['hash'])) + ->sendsAndExpectsType('application/json') + ->basicAuth('ondrejmirtes', getenv('GITHUB_TOKEN')) + ->send(); + if ($searchIssuesResponse->code !== 200) { + $output->writeln(var_export($searchIssuesResponse->body, true)); + throw new \InvalidArgumentException((string) $searchIssuesResponse->code); + } + $searchIssuesResponse = $searchIssuesResponse->body; + $items = array_merge($searchPullRequestsResponse->items, $searchIssuesResponse->items); + $parenthesis = 'https://github.com/phpstan/phpstan-src/commit/' . $commit['hash']; + $thanks = null; + $issuesToReference = []; + foreach ($items as $responseItem) { + if (isset($responseItem->pull_request)) { + $parenthesis = sprintf('[#%d](%s)', $responseItem->number, 'https://github.com/phpstan/phpstan-src/pull/' . $responseItem->number); + $thanks = $responseItem->user->login; + } else { + $issuesToReference[] = sprintf('#%d', $responseItem->number); + } + } + + $output->writeln(sprintf('* %s (%s)%s%s', $commit['message'], $parenthesis, count($issuesToReference) > 0 ? ', ' . implode(', ', $issuesToReference) : '', $thanks !== null ? sprintf(', thanks @%s!', $thanks) : '')); + + if ($i > 0 && $i % 8 === 0) { + sleep(60); + } + + $i++; + } + + return 0; + } + + /** + * @param string[] $commandParts + * @return string + */ + private function exec(array $commandParts): string + { + $command = implode(' ', array_map(function (string $part): string { + return escapeshellarg($part); + }, $commandParts)); + + exec($command, $outputLines, $statusCode); + $output = implode("\n", $outputLines); + if ($statusCode !== 0) { + throw new \InvalidArgumentException(sprintf('Command %s failed: %s', $command, $output)); + } + + return $output; + } + }; + + $application = new \Symfony\Component\Console\Application(); + $application->add($command); + $application->setDefaultCommand('run', true); + $application->run(); })(); diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index a7e4ebd35a..d1786cdaa9 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -8,103 +8,101 @@ use PhpParser\ParserFactory; (function () { - require_once __DIR__ . '/../vendor/autoload.php'; - - $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); - $finder = new Symfony\Component\Finder\Finder(); - $finder->in(__DIR__ . '/../vendor/jetbrains/phpstorm-stubs')->files()->name('*.php'); - - $visitor = new class() extends \PhpParser\NodeVisitorAbstract { - - /** @var string[] */ - public $functions = []; - - /** @var string[] */ - public $methods = []; - - public function enterNode(Node $node) - { - if ($node instanceof Node\Stmt\Function_) { - foreach ($node->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { - $this->functions[] = $node->namespacedName->toLowerString(); - break; - } - } - } - } - - if ($node instanceof Node\Stmt\ClassMethod) { - $class = $node->getAttribute('parent'); - if (!$class instanceof Node\Stmt\ClassLike) { - throw new \PHPStan\ShouldNotHappenException($node->name->toString()); - } - $className = $class->namespacedName->toString(); - foreach ($node->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { - $this->methods[] = sprintf('%s::%s', $className, $node->name->toString()); - break; - } - } - } - } - - return null; - } - }; - - foreach ($finder as $stubFile) { - $path = $stubFile->getPathname(); - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NameResolver()); - $traverser->addVisitor(new NodeConnectingVisitor()); - $traverser->addVisitor($visitor); - - $traverser->traverse( - $parser->parse(\PHPStan\File\FileReader::read($path)) - ); - } - - $metadata = require __DIR__ . '/functionMetadata_original.php'; - foreach ($visitor->functions as $functionName) { - if (array_key_exists($functionName, $metadata)) { - if ($metadata[$functionName]['hasSideEffects']) { - throw new \PHPStan\ShouldNotHappenException($functionName); - } - } - $metadata[$functionName] = ['hasSideEffects' => false]; - } - - foreach ($visitor->methods as $methodName) { - if (array_key_exists($methodName, $metadata)) { - if ($metadata[$methodName]['hasSideEffects']) { - throw new \PHPStan\ShouldNotHappenException($methodName); - } - } - $metadata[$methodName] = ['hasSideEffects' => false]; - } - - ksort($metadata); - - $template = <<<'php' + require_once __DIR__ . '/../vendor/autoload.php'; + + $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); + $finder = new Symfony\Component\Finder\Finder(); + $finder->in(__DIR__ . '/../vendor/jetbrains/phpstorm-stubs')->files()->name('*.php'); + + $visitor = new class() extends \PhpParser\NodeVisitorAbstract { + /** @var string[] */ + public $functions = []; + + /** @var string[] */ + public $methods = []; + + public function enterNode(Node $node) + { + if ($node instanceof Node\Stmt\Function_) { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { + $this->functions[] = $node->namespacedName->toLowerString(); + break; + } + } + } + } + + if ($node instanceof Node\Stmt\ClassMethod) { + $class = $node->getAttribute('parent'); + if (!$class instanceof Node\Stmt\ClassLike) { + throw new \PHPStan\ShouldNotHappenException($node->name->toString()); + } + $className = $class->namespacedName->toString(); + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { + $this->methods[] = sprintf('%s::%s', $className, $node->name->toString()); + break; + } + } + } + } + + return null; + } + }; + + foreach ($finder as $stubFile) { + $path = $stubFile->getPathname(); + $traverser = new NodeTraverser(); + $traverser->addVisitor(new NameResolver()); + $traverser->addVisitor(new NodeConnectingVisitor()); + $traverser->addVisitor($visitor); + + $traverser->traverse( + $parser->parse(\PHPStan\File\FileReader::read($path)) + ); + } + + $metadata = require __DIR__ . '/functionMetadata_original.php'; + foreach ($visitor->functions as $functionName) { + if (array_key_exists($functionName, $metadata)) { + if ($metadata[$functionName]['hasSideEffects']) { + throw new \PHPStan\ShouldNotHappenException($functionName); + } + } + $metadata[$functionName] = ['hasSideEffects' => false]; + } + + foreach ($visitor->methods as $methodName) { + if (array_key_exists($methodName, $metadata)) { + if ($metadata[$methodName]['hasSideEffects']) { + throw new \PHPStan\ShouldNotHappenException($methodName); + } + } + $metadata[$methodName] = ['hasSideEffects' => false]; + } + + ksort($metadata); + + $template = <<<'php' $meta) { - $content .= sprintf( - "\t%s => [%s => %s],\n", - var_export($name, true), - var_export('hasSideEffects', true), - var_export($meta['hasSideEffects'], true), - ); - } - - \PHPStan\File\FileWriter::write(__DIR__ . '/../resources/functionMetadata.php', sprintf($template, $content)); - + $content = ''; + foreach ($metadata as $name => $meta) { + $content .= sprintf( + "\t%s => [%s => %s],\n", + var_export($name, true), + var_export('hasSideEffects', true), + var_export($meta['hasSideEffects'], true), + ); + } + + \PHPStan\File\FileWriter::write(__DIR__ . '/../resources/functionMetadata.php', sprintf($template, $content)); })(); diff --git a/bin/generate-rule-error-classes.php b/bin/generate-rule-error-classes.php index 116910ee86..1299bebe14 100755 --- a/bin/generate-rule-error-classes.php +++ b/bin/generate-rule-error-classes.php @@ -2,9 +2,9 @@ [$interface, $propertyName, $nativePropertyType, $phpDocPropertyType]) { - if (($typeCombination & $typeNumber) === $typeNumber) { - $interfaces[] = '\\' . $interface; - if ($propertyName !== null && $nativePropertyType !== null && $phpDocPropertyType !== null) { - $properties[] = [$propertyName, $nativePropertyType, $phpDocPropertyType]; - } - } - } - - $phpClass = sprintf( - $template, - $typeCombination, - implode(', ', $interfaces), - implode("\n\n\t", array_map(function (array $property): string { - return sprintf("%spublic %s $%s;", $property[2] !== $property[1] ? sprintf("/** @var %s */\n\t", $property[2]) : '', $property[1], $property[0]); - }, $properties)), - implode("\n\n\t", array_map(function (array $property): string { - return sprintf("%spublic function get%s(): %s\n\t{\n\t\treturn \$this->%s;\n\t}", $property[2] !== $property[1] ? sprintf("/**\n\t * @return %s\n\t */\n\t", $property[2]) : '', ucfirst($property[0]), $property[1], $property[0]); - }, $properties)) - ); - - file_put_contents(__DIR__ . '/../src/Rules/RuleErrors/RuleError' . $typeCombination . '.php', $phpClass); - } + ; + + $ruleErrorTypes = \PHPStan\Rules\RuleErrorBuilder::getRuleErrorTypes(); + $maxTypeNumber = array_sum(array_keys($ruleErrorTypes)); + foreach (range(1, $maxTypeNumber) as $typeCombination) { + if (($typeCombination & 1) !== 1) { + continue; + } + $properties = []; + $interfaces = []; + foreach ($ruleErrorTypes as $typeNumber => [$interface, $propertyName, $nativePropertyType, $phpDocPropertyType]) { + if (($typeCombination & $typeNumber) === $typeNumber) { + $interfaces[] = '\\' . $interface; + if ($propertyName !== null && $nativePropertyType !== null && $phpDocPropertyType !== null) { + $properties[] = [$propertyName, $nativePropertyType, $phpDocPropertyType]; + } + } + } + + $phpClass = sprintf( + $template, + $typeCombination, + implode(', ', $interfaces), + implode("\n\n\t", array_map(function (array $property): string { + return sprintf("%spublic %s $%s;", $property[2] !== $property[1] ? sprintf("/** @var %s */\n\t", $property[2]) : '', $property[1], $property[0]); + }, $properties)), + implode("\n\n\t", array_map(function (array $property): string { + return sprintf("%spublic function get%s(): %s\n\t{\n\t\treturn \$this->%s;\n\t}", $property[2] !== $property[1] ? sprintf("/**\n\t * @return %s\n\t */\n\t", $property[2]) : '', ucfirst($property[0]), $property[1], $property[0]); + }, $properties)) + ); + + file_put_contents(__DIR__ . '/../src/Rules/RuleErrors/RuleError' . $typeCombination . '.php', $phpClass); + } })(); diff --git a/bin/transform-source.php b/bin/transform-source.php index 8ecbefb023..faa5b0cc3e 100755 --- a/bin/transform-source.php +++ b/bin/transform-source.php @@ -19,104 +19,102 @@ class PhpPatcher extends NodeVisitorAbstract { - - public function leaveNode(Node $node) - { - if (!$node instanceof Node\Stmt\Property) { - return null; - } - if ($node->type === null) { - return null; - } - $docComment = $node->getDocComment(); - if ($docComment !== null) { - $node->type = null; - return $node; - } - - $node->setDocComment(new \PhpParser\Comment\Doc(sprintf('/** @var %s */', $this->printType($node->type)))); - $node->type = null; - - return $node; - } - - /** - * @param Identifier|Name|NullableType|UnionType $type - * @return string - */ - private function printType($type): string - { - if ($type instanceof NullableType) { - return $this->printType($type->type) . '|null'; - } - - if ($type instanceof UnionType) { - throw new \Exception('UnionType not yet supported'); - } - - if ($type instanceof Name) { - $name = $type->toString(); - if ($type->isFullyQualified()) { - return '\\' . $name; - } - - return $name; - } - - if ($type instanceof Identifier) { - return $type->name; - } - - throw new \Exception('Unsupported type class'); - } - + public function leaveNode(Node $node) + { + if (!$node instanceof Node\Stmt\Property) { + return null; + } + if ($node->type === null) { + return null; + } + $docComment = $node->getDocComment(); + if ($docComment !== null) { + $node->type = null; + return $node; + } + + $node->setDocComment(new \PhpParser\Comment\Doc(sprintf('/** @var %s */', $this->printType($node->type)))); + $node->type = null; + + return $node; + } + + /** + * @param Identifier|Name|NullableType|UnionType $type + * @return string + */ + private function printType($type): string + { + if ($type instanceof NullableType) { + return $this->printType($type->type) . '|null'; + } + + if ($type instanceof UnionType) { + throw new \Exception('UnionType not yet supported'); + } + + if ($type instanceof Name) { + $name = $type->toString(); + if ($type->isFullyQualified()) { + return '\\' . $name; + } + + return $name; + } + + if ($type instanceof Identifier) { + return $type->name; + } + + throw new \Exception('Unsupported type class'); + } } (function () { - $dir = __DIR__ . '/../src'; - - $lexer = new Lexer\Emulative([ - 'usedAttributes' => [ - 'comments', - 'startLine', 'endLine', - 'startTokenPos', 'endTokenPos', - ], - ]); - $parser = new Parser\Php7($lexer, [ - 'useIdentifierNodes' => true, - 'useConsistentVariableNodes' => true, - 'useExpressionStatements' => true, - 'useNopStatements' => false, - ]); - $nameResolver = new NodeVisitor\NameResolver(null, [ - 'replaceNodes' => false - ]); - - $printer = new PrettyPrinter\Standard(); - - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NodeVisitor\CloningVisitor()); - $traverser->addVisitor($nameResolver); - $traverser->addVisitor(new PhpPatcher($printer)); - - $it = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dir), - RecursiveIteratorIterator::LEAVES_ONLY - ); - foreach ($it as $file) { - $fileName = $file->getPathname(); - if (!preg_match('/\.php$/', $fileName)) { - continue; - } - - $code = \PHPStan\File\FileReader::read($fileName); - $origStmts = $parser->parse($code); - $newCode = $printer->printFormatPreserving( - $traverser->traverse($origStmts), - $origStmts, - $lexer->getTokens() - ); - - \PHPStan\File\FileWriter::write($fileName, $newCode); - } + $dir = __DIR__ . '/../src'; + + $lexer = new Lexer\Emulative([ + 'usedAttributes' => [ + 'comments', + 'startLine', 'endLine', + 'startTokenPos', 'endTokenPos', + ], + ]); + $parser = new Parser\Php7($lexer, [ + 'useIdentifierNodes' => true, + 'useConsistentVariableNodes' => true, + 'useExpressionStatements' => true, + 'useNopStatements' => false, + ]); + $nameResolver = new NodeVisitor\NameResolver(null, [ + 'replaceNodes' => false + ]); + + $printer = new PrettyPrinter\Standard(); + + $traverser = new NodeTraverser(); + $traverser->addVisitor(new NodeVisitor\CloningVisitor()); + $traverser->addVisitor($nameResolver); + $traverser->addVisitor(new PhpPatcher($printer)); + + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), + RecursiveIteratorIterator::LEAVES_ONLY + ); + foreach ($it as $file) { + $fileName = $file->getPathname(); + if (!preg_match('/\.php$/', $fileName)) { + continue; + } + + $code = \PHPStan\File\FileReader::read($fileName); + $origStmts = $parser->parse($code); + $newCode = $printer->printFormatPreserving( + $traverser->traverse($origStmts), + $origStmts, + $lexer->getTokens() + ); + + \PHPStan\File\FileWriter::write($fileName, $newCode); + } })(); diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 8ad0e108c1..da72525e01 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), [ - 'getByType', - 'createInstance', - ], true); - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - if (count($methodCall->args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - $argType = $scope->getType($methodCall->args[0]->value); - if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $type = new ObjectType($argType->getValue()); - if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { - $argType = $scope->getType($methodCall->args[1]->value); - if ($argType instanceof ConstantBooleanType && $argType->getValue()) { - $type = TypeCombinator::addNull($type); - } - } - - return $type; - } - + public function getClass(): string + { + return \Nette\DI\Container::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), [ + 'getByType', + 'createInstance', + ], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if (count($methodCall->args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + $argType = $scope->getType($methodCall->args[0]->value); + if (!$argType instanceof ConstantStringType) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $type = new ObjectType($argType->getValue()); + if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { + $argType = $scope->getType($methodCall->args[1]->value); + if ($argType instanceof ConstantBooleanType && $argType->getValue()) { + $type = TypeCombinator::addNull($type); + } + } + + return $type; + } } diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 44c106ee82..531b1c8a6c 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -1,4 +1,6 @@ -load(__DIR__ . '/baseline-lt-7.3.neon')); + $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-lt-7.3.neon')); } else { - $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-7.3.neon')); + $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-7.3.neon')); } if (PHP_VERSION_ID >= 80000) { - $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-8.0.neon')); + $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-8.0.neon')); } if (PHP_VERSION_ID >= 70400) { - $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/ignore-gte-php7.4-errors.neon')); + $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/ignore-gte-php7.4-errors.neon')); } return $config; diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 5bcee896f3..bab69443cf 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -1,207 +1,209 @@ -files()->name('*.php')->in([ - '../../stubs', - '../../vendor/jetbrains/phpstorm-stubs', - '../../vendor/phpstan/php-8-stubs/stubs', + '../../stubs', + '../../vendor/jetbrains/phpstorm-stubs', + '../../vendor/phpstan/php-8-stubs/stubs', ]) as $file) { - if ($file->getPathName() === '../../vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { - continue; - } - $stubs[] = $file->getPathName(); + if ($file->getPathName() === '../../vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { + continue; + } + $stubs[] = $file->getPathName(); } return [ - 'prefix' => null, - 'finders' => [], - 'files-whitelist' => $stubs, - 'patchers' => [ - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'bin/phpstan') { - return $content; - } - return str_replace('__DIR__ . \'/..', '\'phar://phpstan.phar', $content); - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'vendor/nette/di/src/DI/Compiler.php') { - return $content; - } - return str_replace('|Nette\\\\DI\\\\Statement', sprintf('|\\\\%s\\\\Nette\\\\DI\\\\Statement', $prefix), $content); - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'vendor/nette/di/src/DI/Config/DefinitionSchema.php') { - return $content; - } - $content = str_replace( - sprintf('\'%s\\\\callable', $prefix), - '\'callable', - $content - ); - $content = str_replace( - '|Nette\\\\DI\\\\Definitions\\\\Statement', - sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix), - $content - ); - - return $content; - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'vendor/nette/di/src/DI/Extensions/ExtensionsExtension.php') { - return $content; - } - $content = str_replace( - sprintf('\'%s\\\\string', $prefix), - '\'string', - $content - ); - $content = str_replace( - '|Nette\\\\DI\\\\Definitions\\\\Statement', - sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix), - $content - ); - - return $content; - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'src/Testing/TestCase.php') { - return $content; - } - return str_replace(sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix), '\\PHPUnit\\Framework\\TestCase', $content); - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'src/Testing/LevelsTestCase.php') { - return $content; - } - return str_replace( - [sprintf('\\%s\\PHPUnit\\Framework\\AssertionFailedError', $prefix), sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix)], - ['\\PHPUnit\\Framework\\AssertionFailedError', '\\PHPUnit\\Framework\\TestCase'], - $content - ); - }, - function (string $filePath, string $prefix, string $content): string { - if (strpos($filePath, 'src/') !== 0) { - return $content; - } - - $content = str_replace(sprintf('\'%s\\\\r\\\\n\'', $prefix), '\'\\\\r\\\\n\'', $content); - $content = str_replace(sprintf('\'%s\\\\', $prefix), '\'', $content); - - return $content; - }, - function (string $filePath, string $prefix, string $content): string { - if (strpos($filePath, '.neon') === false) { - return $content; - } - - if ($content === '') { - return $content; - } - - $prefixClass = function (string $class) use ($prefix): string { - if (strpos($class, 'PHPStan\\') === 0) { - return $class; - } - - if (strpos($class, 'PhpParser\\') === 0) { - return $class; - } - - if (strpos($class, 'Hoa\\') === 0) { - return $class; - } - - if (strpos($class, '@') === 0) { - return $class; - } - - return $prefix . '\\' . $class; - }; - - $neon = \Nette\Neon\Neon::decode($content); - $updatedNeon = $neon; - if (array_key_exists('services', $neon)) { - foreach ($neon['services'] as $key => $service) { - if (array_key_exists('class', $service) && is_string($service['class'])) { - $service['class'] = $prefixClass($service['class']); - } - if (array_key_exists('factory', $service) && is_string($service['factory'])) { - $service['factory'] = $prefixClass($service['factory']); - } - - if (array_key_exists('autowired', $service) && is_array($service['autowired'])) { - foreach ($service['autowired'] as $i => $autowiredName) { - $service['autowired'][$i] = $prefixClass($autowiredName); - } - } - - $updatedNeon['services'][$key] = $service; - } - } - - return \Nette\Neon\Neon::encode($updatedNeon, \Nette\Neon\Neon::BLOCK); - }, - function (string $filePath, string $prefix, string $content): string { - if (!in_array($filePath, [ - 'src/Testing/TestCaseSourceLocatorFactory.php', - 'src/Testing/TestCase.php', - ], true)) { - return $content; - } - - return str_replace(sprintf('%s\\Composer\\Autoload\\ClassLoader', $prefix), 'Composer\\Autoload\\ClassLoader', $content); - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { - return $content; - } - - $content = str_replace('\'' . $prefix . '\\\\', '\'', $content); - - return $content; - }, - function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'vendor/phpstan/php-8-stubs/Php8StubsMap.php') { - return $content; - } - - $content = str_replace('\'' . $prefix . '\\\\', '\'', $content); - - return $content; - }, - function (string $filePath, string $prefix, string $content): string { - if (!in_array($filePath, [ - 'src/Type/TypehintHelper.php', - 'vendor/ondrejmirtes/better-reflection/src/Reflection/Adapter/ReflectionUnionType.php', - ], true)) { - return $content; - } - - return str_replace(sprintf('%s\\ReflectionUnionType', $prefix), 'ReflectionUnionType', $content); - }, - function (string $filePath, string $prefix, string $content): string { - if (strpos($filePath, 'src/') !== 0) { - return $content; - } - - return str_replace(sprintf('%s\\Attribute', $prefix), 'Attribute', $content); - } - ], - 'whitelist' => [ - 'PHPStan\*', - 'PhpParser\*', - 'Hoa\*', - ], - 'whitelist-global-functions' => false, - 'whitelist-global-classes' => false, + 'prefix' => null, + 'finders' => [], + 'files-whitelist' => $stubs, + 'patchers' => [ + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'bin/phpstan') { + return $content; + } + return str_replace('__DIR__ . \'/..', '\'phar://phpstan.phar', $content); + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/nette/di/src/DI/Compiler.php') { + return $content; + } + return str_replace('|Nette\\\\DI\\\\Statement', sprintf('|\\\\%s\\\\Nette\\\\DI\\\\Statement', $prefix), $content); + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/nette/di/src/DI/Config/DefinitionSchema.php') { + return $content; + } + $content = str_replace( + sprintf('\'%s\\\\callable', $prefix), + '\'callable', + $content + ); + $content = str_replace( + '|Nette\\\\DI\\\\Definitions\\\\Statement', + sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix), + $content + ); + + return $content; + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/nette/di/src/DI/Extensions/ExtensionsExtension.php') { + return $content; + } + $content = str_replace( + sprintf('\'%s\\\\string', $prefix), + '\'string', + $content + ); + $content = str_replace( + '|Nette\\\\DI\\\\Definitions\\\\Statement', + sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix), + $content + ); + + return $content; + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'src/Testing/TestCase.php') { + return $content; + } + return str_replace(sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix), '\\PHPUnit\\Framework\\TestCase', $content); + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'src/Testing/LevelsTestCase.php') { + return $content; + } + return str_replace( + [sprintf('\\%s\\PHPUnit\\Framework\\AssertionFailedError', $prefix), sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix)], + ['\\PHPUnit\\Framework\\AssertionFailedError', '\\PHPUnit\\Framework\\TestCase'], + $content + ); + }, + function (string $filePath, string $prefix, string $content): string { + if (strpos($filePath, 'src/') !== 0) { + return $content; + } + + $content = str_replace(sprintf('\'%s\\\\r\\\\n\'', $prefix), '\'\\\\r\\\\n\'', $content); + $content = str_replace(sprintf('\'%s\\\\', $prefix), '\'', $content); + + return $content; + }, + function (string $filePath, string $prefix, string $content): string { + if (strpos($filePath, '.neon') === false) { + return $content; + } + + if ($content === '') { + return $content; + } + + $prefixClass = function (string $class) use ($prefix): string { + if (strpos($class, 'PHPStan\\') === 0) { + return $class; + } + + if (strpos($class, 'PhpParser\\') === 0) { + return $class; + } + + if (strpos($class, 'Hoa\\') === 0) { + return $class; + } + + if (strpos($class, '@') === 0) { + return $class; + } + + return $prefix . '\\' . $class; + }; + + $neon = \Nette\Neon\Neon::decode($content); + $updatedNeon = $neon; + if (array_key_exists('services', $neon)) { + foreach ($neon['services'] as $key => $service) { + if (array_key_exists('class', $service) && is_string($service['class'])) { + $service['class'] = $prefixClass($service['class']); + } + if (array_key_exists('factory', $service) && is_string($service['factory'])) { + $service['factory'] = $prefixClass($service['factory']); + } + + if (array_key_exists('autowired', $service) && is_array($service['autowired'])) { + foreach ($service['autowired'] as $i => $autowiredName) { + $service['autowired'][$i] = $prefixClass($autowiredName); + } + } + + $updatedNeon['services'][$key] = $service; + } + } + + return \Nette\Neon\Neon::encode($updatedNeon, \Nette\Neon\Neon::BLOCK); + }, + function (string $filePath, string $prefix, string $content): string { + if (!in_array($filePath, [ + 'src/Testing/TestCaseSourceLocatorFactory.php', + 'src/Testing/TestCase.php', + ], true)) { + return $content; + } + + return str_replace(sprintf('%s\\Composer\\Autoload\\ClassLoader', $prefix), 'Composer\\Autoload\\ClassLoader', $content); + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { + return $content; + } + + $content = str_replace('\'' . $prefix . '\\\\', '\'', $content); + + return $content; + }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/phpstan/php-8-stubs/Php8StubsMap.php') { + return $content; + } + + $content = str_replace('\'' . $prefix . '\\\\', '\'', $content); + + return $content; + }, + function (string $filePath, string $prefix, string $content): string { + if (!in_array($filePath, [ + 'src/Type/TypehintHelper.php', + 'vendor/ondrejmirtes/better-reflection/src/Reflection/Adapter/ReflectionUnionType.php', + ], true)) { + return $content; + } + + return str_replace(sprintf('%s\\ReflectionUnionType', $prefix), 'ReflectionUnionType', $content); + }, + function (string $filePath, string $prefix, string $content): string { + if (strpos($filePath, 'src/') !== 0) { + return $content; + } + + return str_replace(sprintf('%s\\Attribute', $prefix), 'Attribute', $content); + } + ], + 'whitelist' => [ + 'PHPStan\*', + 'PhpParser\*', + 'Hoa\*', + ], + 'whitelist-global-functions' => false, + 'whitelist-global-classes' => false, ]; diff --git a/compiler/src/Console/CompileCommand.php b/compiler/src/Console/CompileCommand.php index 194198f2f2..0b8cabfd0b 100644 --- a/compiler/src/Console/CompileCommand.php +++ b/compiler/src/Console/CompileCommand.php @@ -1,4 +1,6 @@ -filesystem = $filesystem; - $this->processFactory = $processFactory; - $this->dataDir = $dataDir; - $this->buildDir = $buildDir; - } - - protected function configure(): void - { - $this->setName('phpstan:compile') - ->setDescription('Compile PHAR'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $this->processFactory->setOutput($output); - - $this->buildPreloadScript(); - $this->deleteUnnecessaryVendorCode(); - $this->fixComposerJson($this->buildDir); - $this->renamePhpStormStubs(); - $this->patchPhpStormStubs($output); - $this->renamePhp8Stubs(); - $this->transformSource(); - - $this->processFactory->create(['php', 'box.phar', 'compile', '--no-parallel'], $this->dataDir); - - return 0; - } - - private function fixComposerJson(string $buildDir): void - { - $json = json_decode($this->filesystem->read($buildDir . '/composer.json'), true); - - unset($json['replace']); - $json['name'] = 'phpstan/phpstan'; - $json['require']['php'] = '^7.1'; - - // simplify autoload (remove not packed build directory] - $json['autoload']['psr-4']['PHPStan\\'] = 'src/'; - - $encodedJson = json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); - if ($encodedJson === false) { - throw new \Exception('json_encode() was not successful.'); - } - - $this->filesystem->write($buildDir . '/composer.json', $encodedJson); - } - - private function renamePhpStormStubs(): void - { - $directory = $this->buildDir . '/vendor/jetbrains/phpstorm-stubs'; - if (!is_dir($directory)) { - return; - } - - $stubFinder = \Symfony\Component\Finder\Finder::create(); - $stubsMapPath = $directory . '/PhpStormStubsMap.php'; - foreach ($stubFinder->files()->name('*.php')->in($directory) as $stubFile) { - $path = $stubFile->getPathname(); - if ($path === $stubsMapPath) { - continue; - } - - $renameSuccess = rename( - $path, - dirname($path) . '/' . $stubFile->getBasename('.php') . '.stub' - ); - if ($renameSuccess === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not rename %s', $path)); - } - } - - $stubsMapContents = file_get_contents($stubsMapPath); - if ($stubsMapContents === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not read %s', $stubsMapPath)); - } - - $stubsMapContents = str_replace('.php\',', '.stub\',', $stubsMapContents); - - $putSuccess = file_put_contents($stubsMapPath, $stubsMapContents); - if ($putSuccess === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not write %s', $stubsMapPath)); - } - } - - private function renamePhp8Stubs(): void - { - $directory = $this->buildDir . '/vendor/phpstan/php-8-stubs/stubs'; - if (!is_dir($directory)) { - return; - } - - $stubFinder = \Symfony\Component\Finder\Finder::create(); - $stubsMapPath = $directory . '/../Php8StubsMap.php'; - foreach ($stubFinder->files()->name('*.php')->in($directory) as $stubFile) { - $path = $stubFile->getPathname(); - if ($path === $stubsMapPath) { - continue; - } - - $renameSuccess = rename( - $path, - dirname($path) . '/' . $stubFile->getBasename('.php') . '.stub' - ); - if ($renameSuccess === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not rename %s', $path)); - } - } - - $stubsMapContents = file_get_contents($stubsMapPath); - if ($stubsMapContents === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not read %s', $stubsMapPath)); - } - - $stubsMapContents = str_replace('.php\',', '.stub\',', $stubsMapContents); - - $putSuccess = file_put_contents($stubsMapPath, $stubsMapContents); - if ($putSuccess === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not write %s', $stubsMapPath)); - } - } - - private function patchPhpStormStubs(OutputInterface $output): void - { - $stubFinder = \Symfony\Component\Finder\Finder::create(); - $stubsDirectory = __DIR__ . '/../../../vendor/jetbrains/phpstorm-stubs'; - foreach ($stubFinder->files()->name('*.patch')->in(__DIR__ . '/../../patches/stubs') as $patchFile) { - $absolutePatchPath = $patchFile->getPathname(); - $patchPath = $patchFile->getRelativePathname(); - $stubPath = realpath($stubsDirectory . '/' . dirname($patchPath) . '/' . basename($patchPath, '.patch')); - if ($stubPath === false) { - $output->writeln(sprintf('Stub %s not found.', $stubPath)); - continue; - } - $this->patchFile($output, $stubPath, $absolutePatchPath); - } - } - - private function buildPreloadScript(): void - { - $vendorDir = $this->buildDir . '/vendor'; - if (!is_dir($vendorDir . '/nikic/php-parser/lib/PhpParser')) { - return; - } - - $preloadScript = $this->buildDir . '/preload.php'; - $template = <<<'php' + /** @var Filesystem */ + private $filesystem; + + /** @var ProcessFactory */ + private $processFactory; + + /** @var string */ + private $dataDir; + + /** @var string */ + private $buildDir; + + public function __construct( + Filesystem $filesystem, + ProcessFactory $processFactory, + string $dataDir, + string $buildDir + ) { + parent::__construct(); + $this->filesystem = $filesystem; + $this->processFactory = $processFactory; + $this->dataDir = $dataDir; + $this->buildDir = $buildDir; + } + + protected function configure(): void + { + $this->setName('phpstan:compile') + ->setDescription('Compile PHAR'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->processFactory->setOutput($output); + + $this->buildPreloadScript(); + $this->deleteUnnecessaryVendorCode(); + $this->fixComposerJson($this->buildDir); + $this->renamePhpStormStubs(); + $this->patchPhpStormStubs($output); + $this->renamePhp8Stubs(); + $this->transformSource(); + + $this->processFactory->create(['php', 'box.phar', 'compile', '--no-parallel'], $this->dataDir); + + return 0; + } + + private function fixComposerJson(string $buildDir): void + { + $json = json_decode($this->filesystem->read($buildDir . '/composer.json'), true); + + unset($json['replace']); + $json['name'] = 'phpstan/phpstan'; + $json['require']['php'] = '^7.1'; + + // simplify autoload (remove not packed build directory] + $json['autoload']['psr-4']['PHPStan\\'] = 'src/'; + + $encodedJson = json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + if ($encodedJson === false) { + throw new \Exception('json_encode() was not successful.'); + } + + $this->filesystem->write($buildDir . '/composer.json', $encodedJson); + } + + private function renamePhpStormStubs(): void + { + $directory = $this->buildDir . '/vendor/jetbrains/phpstorm-stubs'; + if (!is_dir($directory)) { + return; + } + + $stubFinder = \Symfony\Component\Finder\Finder::create(); + $stubsMapPath = $directory . '/PhpStormStubsMap.php'; + foreach ($stubFinder->files()->name('*.php')->in($directory) as $stubFile) { + $path = $stubFile->getPathname(); + if ($path === $stubsMapPath) { + continue; + } + + $renameSuccess = rename( + $path, + dirname($path) . '/' . $stubFile->getBasename('.php') . '.stub' + ); + if ($renameSuccess === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not rename %s', $path)); + } + } + + $stubsMapContents = file_get_contents($stubsMapPath); + if ($stubsMapContents === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not read %s', $stubsMapPath)); + } + + $stubsMapContents = str_replace('.php\',', '.stub\',', $stubsMapContents); + + $putSuccess = file_put_contents($stubsMapPath, $stubsMapContents); + if ($putSuccess === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not write %s', $stubsMapPath)); + } + } + + private function renamePhp8Stubs(): void + { + $directory = $this->buildDir . '/vendor/phpstan/php-8-stubs/stubs'; + if (!is_dir($directory)) { + return; + } + + $stubFinder = \Symfony\Component\Finder\Finder::create(); + $stubsMapPath = $directory . '/../Php8StubsMap.php'; + foreach ($stubFinder->files()->name('*.php')->in($directory) as $stubFile) { + $path = $stubFile->getPathname(); + if ($path === $stubsMapPath) { + continue; + } + + $renameSuccess = rename( + $path, + dirname($path) . '/' . $stubFile->getBasename('.php') . '.stub' + ); + if ($renameSuccess === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not rename %s', $path)); + } + } + + $stubsMapContents = file_get_contents($stubsMapPath); + if ($stubsMapContents === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not read %s', $stubsMapPath)); + } + + $stubsMapContents = str_replace('.php\',', '.stub\',', $stubsMapContents); + + $putSuccess = file_put_contents($stubsMapPath, $stubsMapContents); + if ($putSuccess === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not write %s', $stubsMapPath)); + } + } + + private function patchPhpStormStubs(OutputInterface $output): void + { + $stubFinder = \Symfony\Component\Finder\Finder::create(); + $stubsDirectory = __DIR__ . '/../../../vendor/jetbrains/phpstorm-stubs'; + foreach ($stubFinder->files()->name('*.patch')->in(__DIR__ . '/../../patches/stubs') as $patchFile) { + $absolutePatchPath = $patchFile->getPathname(); + $patchPath = $patchFile->getRelativePathname(); + $stubPath = realpath($stubsDirectory . '/' . dirname($patchPath) . '/' . basename($patchPath, '.patch')); + if ($stubPath === false) { + $output->writeln(sprintf('Stub %s not found.', $stubPath)); + continue; + } + $this->patchFile($output, $stubPath, $absolutePatchPath); + } + } + + private function buildPreloadScript(): void + { + $vendorDir = $this->buildDir . '/vendor'; + if (!is_dir($vendorDir . '/nikic/php-parser/lib/PhpParser')) { + return; + } + + $preloadScript = $this->buildDir . '/preload.php'; + $template = <<<'php' files()->name('*.php')->in([ - $this->buildDir . '/src', - $vendorDir . '/nikic/php-parser/lib/PhpParser', - $vendorDir . '/phpstan/phpdoc-parser/src', - ])->exclude([ - 'Testing', - ]) as $phpFile) { - $realPath = $phpFile->getRealPath(); - if ($realPath === false) { - return; - } - $path = substr($realPath, strlen($root)); - $output .= 'require_once __DIR__ . ' . var_export($path, true) . ';' . "\n"; - } - - file_put_contents($preloadScript, sprintf($template, $output)); - } - - private function deleteUnnecessaryVendorCode(): void - { - $vendorDir = $this->buildDir . '/vendor'; - if (!is_dir($vendorDir . '/nikic/php-parser')) { - return; - } - - @unlink($vendorDir . '/nikic/php-parser/grammar/rebuildParsers.php'); - @unlink($vendorDir . '/nikic/php-parser/bin/php-parse'); - } - - private function patchFile(OutputInterface $output, string $originalFile, string $patchFile): void - { - exec(sprintf( - 'patch -d %s %s %s', - escapeshellarg($this->buildDir), - escapeshellarg($originalFile), - escapeshellarg($patchFile) - ), $outputLines, $exitCode); - if ($exitCode === 0) { - return; - } - - $output->writeln(sprintf('Patching failed: %s', implode("\n", $outputLines))); - } - - private function transformSource(): void - { - exec(escapeshellarg(__DIR__ . '/../../../bin/transform-source.php'), $outputLines, $exitCode); - if ($exitCode === 0) { - return; - } - - throw new \PHPStan\ShouldNotHappenException(implode("\n", $outputLines)); - } - + $finder = \Symfony\Component\Finder\Finder::create(); + $root = realpath(__DIR__ . '/../../..'); + if ($root === false) { + return; + } + $output = ''; + foreach ($finder->files()->name('*.php')->in([ + $this->buildDir . '/src', + $vendorDir . '/nikic/php-parser/lib/PhpParser', + $vendorDir . '/phpstan/phpdoc-parser/src', + ])->exclude([ + 'Testing', + ]) as $phpFile) { + $realPath = $phpFile->getRealPath(); + if ($realPath === false) { + return; + } + $path = substr($realPath, strlen($root)); + $output .= 'require_once __DIR__ . ' . var_export($path, true) . ';' . "\n"; + } + + file_put_contents($preloadScript, sprintf($template, $output)); + } + + private function deleteUnnecessaryVendorCode(): void + { + $vendorDir = $this->buildDir . '/vendor'; + if (!is_dir($vendorDir . '/nikic/php-parser')) { + return; + } + + @unlink($vendorDir . '/nikic/php-parser/grammar/rebuildParsers.php'); + @unlink($vendorDir . '/nikic/php-parser/bin/php-parse'); + } + + private function patchFile(OutputInterface $output, string $originalFile, string $patchFile): void + { + exec(sprintf( + 'patch -d %s %s %s', + escapeshellarg($this->buildDir), + escapeshellarg($originalFile), + escapeshellarg($patchFile) + ), $outputLines, $exitCode); + if ($exitCode === 0) { + return; + } + + $output->writeln(sprintf('Patching failed: %s', implode("\n", $outputLines))); + } + + private function transformSource(): void + { + exec(escapeshellarg(__DIR__ . '/../../../bin/transform-source.php'), $outputLines, $exitCode); + if ($exitCode === 0) { + return; + } + + throw new \PHPStan\ShouldNotHappenException(implode("\n", $outputLines)); + } } diff --git a/compiler/src/Filesystem/Filesystem.php b/compiler/src/Filesystem/Filesystem.php index 93c24c4b0a..93e0f682dd 100644 --- a/compiler/src/Filesystem/Filesystem.php +++ b/compiler/src/Filesystem/Filesystem.php @@ -1,18 +1,18 @@ -filesystem = $filesystem; - } - - public function exists(string $dir): bool - { - return $this->filesystem->exists($dir); - } - - public function remove(string $dir): void - { - $this->filesystem->remove($dir); - } - - public function mkdir(string $dir): void - { - $this->filesystem->mkdir($dir); - } - - public function read(string $file): string - { - $content = file_get_contents($file); - if ($content === false) { - throw new \RuntimeException(); - } - return $content; - } - - public function write(string $file, string $data): void - { - file_put_contents($file, $data); - } - + /** @var \Symfony\Component\Filesystem\Filesystem */ + private $filesystem; + + public function __construct(\Symfony\Component\Filesystem\Filesystem $filesystem) + { + $this->filesystem = $filesystem; + } + + public function exists(string $dir): bool + { + return $this->filesystem->exists($dir); + } + + public function remove(string $dir): void + { + $this->filesystem->remove($dir); + } + + public function mkdir(string $dir): void + { + $this->filesystem->mkdir($dir); + } + + public function read(string $file): string + { + $content = file_get_contents($file); + if ($content === false) { + throw new \RuntimeException(); + } + return $content; + } + + public function write(string $file, string $data): void + { + file_put_contents($file, $data); + } } diff --git a/compiler/src/Process/DefaultProcessFactory.php b/compiler/src/Process/DefaultProcessFactory.php index 3f4a09a6b1..b0a423a17b 100644 --- a/compiler/src/Process/DefaultProcessFactory.php +++ b/compiler/src/Process/DefaultProcessFactory.php @@ -1,4 +1,6 @@ -output = new NullOutput(); - } - - /** - * @param string[] $command - * @param string $cwd - * @return \PHPStan\Compiler\Process\Process - */ - public function create(array $command, string $cwd): Process - { - return new SymfonyProcess($command, $cwd, $this->output); - } - - public function setOutput(OutputInterface $output): void - { - $this->output = $output; - } - + /** @var OutputInterface */ + private $output; + + public function __construct() + { + $this->output = new NullOutput(); + } + + /** + * @param string[] $command + * @param string $cwd + * @return \PHPStan\Compiler\Process\Process + */ + public function create(array $command, string $cwd): Process + { + return new SymfonyProcess($command, $cwd, $this->output); + } + + public function setOutput(OutputInterface $output): void + { + $this->output = $output; + } } diff --git a/compiler/src/Process/Process.php b/compiler/src/Process/Process.php index 1bf5d1f25d..51dfc4e10f 100644 --- a/compiler/src/Process/Process.php +++ b/compiler/src/Process/Process.php @@ -1,13 +1,13 @@ - - */ - public function getProcess(): \Symfony\Component\Process\Process; - + /** + * @return \Symfony\Component\Process\Process + */ + public function getProcess(): \Symfony\Component\Process\Process; } diff --git a/compiler/src/Process/ProcessFactory.php b/compiler/src/Process/ProcessFactory.php index f892601f73..c5a3b167b1 100644 --- a/compiler/src/Process/ProcessFactory.php +++ b/compiler/src/Process/ProcessFactory.php @@ -1,4 +1,6 @@ - */ + private $process; - /** @var \Symfony\Component\Process\Process */ - private $process; - - /** - * @param string[] $command - * @param string $cwd - * @param \Symfony\Component\Console\Output\OutputInterface $output - */ - public function __construct(array $command, string $cwd, OutputInterface $output) - { - $this->process = (new \Symfony\Component\Process\Process($command, $cwd, null, null, null)) - ->mustRun(static function (string $type, string $buffer) use ($output): void { - $output->write($buffer); - }); - } - - /** - * @return \Symfony\Component\Process\Process - */ - public function getProcess(): \Symfony\Component\Process\Process - { - return $this->process; - } + /** + * @param string[] $command + * @param string $cwd + * @param \Symfony\Component\Console\Output\OutputInterface $output + */ + public function __construct(array $command, string $cwd, OutputInterface $output) + { + $this->process = (new \Symfony\Component\Process\Process($command, $cwd, null, null, null)) + ->mustRun(static function (string $type, string $buffer) use ($output): void { + $output->write($buffer); + }); + } + /** + * @return \Symfony\Component\Process\Process + */ + public function getProcess(): \Symfony\Component\Process\Process + { + return $this->process; + } } diff --git a/compiler/tests/Console/CompileCommandTest.php b/compiler/tests/Console/CompileCommandTest.php index 72e1a5d5dc..379b5fc010 100644 --- a/compiler/tests/Console/CompileCommandTest.php +++ b/compiler/tests/Console/CompileCommandTest.php @@ -1,4 +1,6 @@ -createMock(Filesystem::class); - $filesystem->expects(self::once())->method('read')->with('bar/composer.json')->willReturn('{"name":"phpstan/phpstan-src","replace":{"phpstan/phpstan": "self.version"},"require":{"php":"^7.4"},"require-dev":1,"autoload-dev":2,"autoload":{"psr-4":{"PHPStan\\\\":[3]}}}'); - $filesystem->expects(self::once())->method('write')->with('bar/composer.json', <<createMock(Filesystem::class); + $filesystem->expects(self::once())->method('read')->with('bar/composer.json')->willReturn('{"name":"phpstan/phpstan-src","replace":{"phpstan/phpstan": "self.version"},"require":{"php":"^7.4"},"require-dev":1,"autoload-dev":2,"autoload":{"psr-4":{"PHPStan\\\\":[3]}}}'); + $filesystem->expects(self::once())->method('write')->with( + 'bar/composer.json', + <<createMock(Process::class); + $process = $this->createMock(Process::class); - $processFactory = $this->createMock(ProcessFactory::class); - $processFactory->method('setOutput'); - $processFactory->method('create')->with(['php', 'box.phar', 'compile', '--no-parallel'], 'foo')->willReturn($process); + $processFactory = $this->createMock(ProcessFactory::class); + $processFactory->method('setOutput'); + $processFactory->method('create')->with(['php', 'box.phar', 'compile', '--no-parallel'], 'foo')->willReturn($process); - $application = new Application(); - $application->add(new CompileCommand($filesystem, $processFactory, 'foo', 'bar')); + $application = new Application(); + $application->add(new CompileCommand($filesystem, $processFactory, 'foo', 'bar')); - $command = $application->find('phpstan:compile'); - $commandTester = new CommandTester($command); - $exitCode = $commandTester->execute([ - 'command' => $command->getName(), - ]); - - self::assertSame(0, $exitCode); - } + $command = $application->find('phpstan:compile'); + $commandTester = new CommandTester($command); + $exitCode = $commandTester->execute([ + 'command' => $command->getName(), + ]); + self::assertSame(0, $exitCode); + } } diff --git a/compiler/tests/Filesystem/SymfonyFilesystemTest.php b/compiler/tests/Filesystem/SymfonyFilesystemTest.php index 0d21aff557..b2156afa4b 100644 --- a/compiler/tests/Filesystem/SymfonyFilesystemTest.php +++ b/compiler/tests/Filesystem/SymfonyFilesystemTest.php @@ -1,4 +1,6 @@ -createMock(\Symfony\Component\Filesystem\Filesystem::class); + $inner->expects(self::once())->method('exists')->with('foo')->willReturn(true); - public function testExists(): void - { - $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); - $inner->expects(self::once())->method('exists')->with('foo')->willReturn(true); - - self::assertTrue((new SymfonyFilesystem($inner))->exists('foo')); - } - - public function testRemove(): void - { - $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); - $inner->expects(self::once())->method('remove')->with('foo')->willReturn(true); + self::assertTrue((new SymfonyFilesystem($inner))->exists('foo')); + } - (new SymfonyFilesystem($inner))->remove('foo'); - } + public function testRemove(): void + { + $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); + $inner->expects(self::once())->method('remove')->with('foo')->willReturn(true); - public function testMkdir(): void - { - $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); - $inner->expects(self::once())->method('mkdir')->with('foo')->willReturn(true); + (new SymfonyFilesystem($inner))->remove('foo'); + } - (new SymfonyFilesystem($inner))->mkdir('foo'); - } + public function testMkdir(): void + { + $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); + $inner->expects(self::once())->method('mkdir')->with('foo')->willReturn(true); - public function testRead(): void - { - $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); + (new SymfonyFilesystem($inner))->mkdir('foo'); + } - $content = (new SymfonyFilesystem($inner))->read(__DIR__ . '/data/composer.json'); - self::assertSame("{}\n", $content); - } + public function testRead(): void + { + $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); - public function testWrite(): void - { - $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); + $content = (new SymfonyFilesystem($inner))->read(__DIR__ . '/data/composer.json'); + self::assertSame("{}\n", $content); + } - @unlink(__DIR__ . '/data/test.json'); - (new SymfonyFilesystem($inner))->write(__DIR__ . '/data/test.json', "{}\n"); + public function testWrite(): void + { + $inner = $this->createMock(\Symfony\Component\Filesystem\Filesystem::class); - self::assertFileExists(__DIR__ . '/data/test.json'); - self::assertFileEquals(__DIR__ . '/data/composer.json', __DIR__ . '/data/test.json'); - } + @unlink(__DIR__ . '/data/test.json'); + (new SymfonyFilesystem($inner))->write(__DIR__ . '/data/test.json', "{}\n"); + self::assertFileExists(__DIR__ . '/data/test.json'); + self::assertFileEquals(__DIR__ . '/data/composer.json', __DIR__ . '/data/test.json'); + } } diff --git a/compiler/tests/Process/DefaultProcessFactoryTest.php b/compiler/tests/Process/DefaultProcessFactoryTest.php index 31756e8f18..108902aa95 100644 --- a/compiler/tests/Process/DefaultProcessFactoryTest.php +++ b/compiler/tests/Process/DefaultProcessFactoryTest.php @@ -1,4 +1,6 @@ -createMock(OutputInterface::class); + $output->expects(self::once())->method('write'); - public function testCreate(): void - { - $output = $this->createMock(OutputInterface::class); - $output->expects(self::once())->method('write'); - - $factory = new DefaultProcessFactory(); - $factory->setOutput($output); - - $process = $factory->create(['ls'], __DIR__)->getProcess(); - self::assertSame('\'ls\'', $process->getCommandLine()); - self::assertSame(__DIR__, $process->getWorkingDirectory()); - } + $factory = new DefaultProcessFactory(); + $factory->setOutput($output); + $process = $factory->create(['ls'], __DIR__)->getProcess(); + self::assertSame('\'ls\'', $process->getCommandLine()); + self::assertSame(__DIR__, $process->getWorkingDirectory()); + } } diff --git a/compiler/tests/Process/SymfonyProcessTest.php b/compiler/tests/Process/SymfonyProcessTest.php index 8124ba1c96..a3db4f0daf 100644 --- a/compiler/tests/Process/SymfonyProcessTest.php +++ b/compiler/tests/Process/SymfonyProcessTest.php @@ -1,4 +1,6 @@ -createMock(OutputInterface::class); + $output->expects(self::once())->method('write'); - public function testGetProcess(): void - { - $output = $this->createMock(OutputInterface::class); - $output->expects(self::once())->method('write'); - - $process = (new SymfonyProcess(['ls'], __DIR__, $output))->getProcess(); - self::assertSame('\'ls\'', $process->getCommandLine()); - self::assertSame(__DIR__, $process->getWorkingDirectory()); - } - + $process = (new SymfonyProcess(['ls'], __DIR__, $output))->getProcess(); + self::assertSame('\'ls\'', $process->getCommandLine()); + self::assertSame(__DIR__, $process->getWorkingDirectory()); + } } diff --git a/composer.json b/composer.json index bf0b598849..63250e5fcf 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "clue/ndjson-react": "^1.0", "composer/ca-bundle": "^1.2", "composer/xdebug-handler": "^1.3.0", + "friendsofphp/php-cs-fixer": "^3.0", "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/regex": "1.17.01.13", diff --git a/composer.lock b/composer.lock index c88066076d..176d229d8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3ec3d9726806181d0ba54a663e00cc22", + "content-hash": "e3d372313343142e1a89df91bfcdc309", "packages": [ { "name": "clue/block-react", @@ -276,18 +276,99 @@ ], "time": "2020-08-25T05:50:16+00:00" }, + { + "name": "composer/semver", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-24T12:41:47+00:00" + }, { "name": "composer/xdebug-handler", - "version": "1.4.4", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6e076a124f7ee146f2487554a94b6a19a74887ba" + "reference": "f27e06cd9675801df441b3656569b328e04aa37c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6e076a124f7ee146f2487554a94b6a19a74887ba", - "reference": "6e076a124f7ee146f2487554a94b6a19a74887ba", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c", + "reference": "f27e06cd9675801df441b3656569b328e04aa37c", "shasum": "" }, "require": { @@ -295,7 +376,8 @@ "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "autoload": { @@ -321,7 +403,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/1.4.4" + "source": "https://github.com/composer/xdebug-handler/tree/1.4.6" }, "funding": [ { @@ -337,7 +419,159 @@ "type": "tidelift" } ], - "time": "2020-10-24T12:39:10+00:00" + "time": "2021-03-25T17:01:18+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", + "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.1" + }, + "time": "2021-05-16T18:07:53+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" }, { "name": "evenement/evenement", @@ -386,6 +620,94 @@ }, "time": "2017-07-23T21:35:13+00:00" }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.0.0-beta.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "9225f5d06f5c3ee24b89348cd3dff69c9b3c1ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/9225f5d06f5c3ee24b89348cd3dff69c9b3c1ede", + "reference": "9225f5d06f5c3ee24b89348cd3dff69c9b3c1ede", + "shasum": "" + }, + "require": { + "composer/semver": "^3.2", + "composer/xdebug-handler": "^1.4", + "doctrine/annotations": "^1.12", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.1.3 || ^8.0", + "php-cs-fixer/diff": "^2.0", + "symfony/console": "^4.4.20 || ^5.1.3", + "symfony/event-dispatcher": "^4.4.20 || ^5.0", + "symfony/filesystem": "^4.4.20 || ^5.0", + "symfony/finder": "^4.4.20 || ^5.0", + "symfony/options-resolver": "^4.4.20 || ^5.0", + "symfony/polyfill-php72": "^1.22", + "symfony/process": "^4.4.20 || ^5.0", + "symfony/stopwatch": "^4.4.20 || ^5.0" + }, + "require-dev": { + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^1.4", + "mikey179/vfsstream": "^1.6.8", + "php-coveralls/php-coveralls": "^2.4.3", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy": "^1.10.3", + "phpspec/prophecy-phpunit": "^1.1 || ^2.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5", + "phpunitgoodpractices/polyfill": "^1.5", + "phpunitgoodpractices/traits": "^1.9.1", + "symfony/phpunit-bridge": "^5.2.4", + "symfony/yaml": "^4.4.20 || ^5.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.0.0-beta.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2021-04-06T18:43:43+00:00" + }, { "name": "hoa/compiler", "version": "3.17.08.08", @@ -2142,6 +2464,58 @@ }, "time": "2021-05-04T11:07:19+00:00" }, + { + "name": "php-cs-fixer/diff", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "sebastian/diff v3 backport support for PHP 5.6+", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" + }, + "time": "2020-10-14T08:32:19+00:00" + }, { "name": "phpstan/php-8-stubs", "version": "0.1.16", @@ -2225,26 +2599,31 @@ "time": "2021-04-03T14:46:19+00:00" }, { - "name": "psr/container", - "version": "1.1.1", + "name": "psr/cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=5.3.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2254,18 +2633,62 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Common interface for caching libraries", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], "support": { "issues": "https://github.com/php-fig/container/issues", "source": "https://github.com/php-fig/container/tree/1.1.1" @@ -2327,16 +2750,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -2360,7 +2783,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -2371,9 +2794,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "react/cache", @@ -3121,16 +3544,16 @@ }, { "name": "symfony/console", - "version": "v4.4.21", + "version": "v4.4.25", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23" + "reference": "a62acecdf5b50e314a4f305cd01b5282126f3095" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", + "url": "https://api.github.com/repos/symfony/console/zipball/a62acecdf5b50e314a4f305cd01b5282126f3095", + "reference": "a62acecdf5b50e314a4f305cd01b5282126f3095", "shasum": "" }, "require": { @@ -3190,7 +3613,7 @@ "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/console/tree/v4.4.21" + "source": "https://github.com/symfony/console/tree/v4.4.25" }, "funding": [ { @@ -3206,20 +3629,311 @@ "type": "tidelift" } ], - "time": "2021-03-26T09:23:24+00:00" + "time": "2021-05-26T11:20:16+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.25", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "047773e7016e4fd45102cedf4bd2558ae0d0c32f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/047773e7016e4fd45102cedf4bd2558ae0d0c32f", + "reference": "047773e7016e4fd45102cedf4bd2558ae0d0c32f", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.25" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:39:37+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "348116319d7fb7d1faa781d26a48922428013eb2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/348116319d7fb7d1faa781d26a48922428013eb2", + "reference": "348116319d7fb7d1faa781d26a48922428013eb2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" }, { "name": "symfony/finder", - "version": "v4.4.16", + "version": "v4.4.25", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31" + "reference": "ed33314396d968a8936c95f5bd1b88bd3b3e94a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/26f63b8d4e92f2eecd90f6791a563ebb001abe31", - "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31", + "url": "https://api.github.com/repos/symfony/finder/zipball/ed33314396d968a8936c95f5bd1b88bd3b3e94a3", + "reference": "ed33314396d968a8936c95f5bd1b88bd3b3e94a3", "shasum": "" }, "require": { @@ -3240,18 +3954,246 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v4.4.25" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T11:20:16+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/162e886ca035869866d233a2bfef70cc28f9bbe5", + "reference": "162e886ca035869866d233a2bfef70cc28f9bbe5", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.16" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" }, "funding": [ { @@ -3267,32 +4209,29 @@ "type": "tidelift" } ], - "time": "2020-10-24T11:50:19+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "name": "symfony/polyfill-php72", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "shasum": "" }, "require": { "php": ">=7.1" }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3301,7 +4240,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Php72\\": "" }, "files": [ "bootstrap.php" @@ -3321,17 +4260,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" }, "funding": [ { @@ -3347,20 +4285,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -3369,7 +4307,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3410,7 +4348,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" }, "funding": [ { @@ -3426,20 +4364,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, "require": { @@ -3448,7 +4386,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3493,7 +4431,69 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/process", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "714b47f9196de61a196d86c4bad5f09201b307df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", + "reference": "714b47f9196de61a196d86c4bad5f09201b307df", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.3.2" }, "funding": [ { @@ -3509,7 +4509,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-06-12T10:15:01+00:00" }, { "name": "symfony/service-contracts", @@ -3571,6 +4571,68 @@ "source": "https://github.com/symfony/service-contracts/tree/v1.1.8" }, "time": "2019-10-14T12:27:06+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "313d02f59d6543311865007e5ff4ace05b35ee65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/313d02f59d6543311865007e5ff4ace05b35ee65", + "reference": "313d02f59d6543311865007e5ff4ace05b35ee65", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" } ], "packages-dev": [ @@ -6015,147 +7077,6 @@ ], "time": "2020-11-11T09:19:24+00:00" }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.22.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" - }, - { - "name": "symfony/process", - "version": "v5.2.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v5.2.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.0", diff --git a/phpstan.neon.dist b/phpstan.neon.dist.bk similarity index 100% rename from phpstan.neon.dist rename to phpstan.neon.dist.bk diff --git a/preload.php b/preload.php index 3c6b265174..174d7fd709 100644 --- a/preload.php +++ b/preload.php @@ -1 +1,3 @@ - [ - 'FFI::addr' => ['FFI\CData', '&ptr'=>'FFI\CData'], - 'FFI::alignof' => ['int', '&ptr'=>'mixed'], - 'FFI::arrayType' => ['FFI\CType', 'type'=>'string|FFI\CType', 'dims'=>'array'], - 'FFI::cast' => ['FFI\CData', 'type'=>'string|FFI\CType', '&ptr'=>''], - 'FFI::cdef' => ['FFI', 'code='=>'string', 'lib='=>'?string'], - 'FFI::free' => ['void', '&ptr'=>'FFI\CData'], - 'FFI::load' => ['FFI', 'filename'=>'string'], - 'FFI::memcmp' => ['int', '&ptr1'=>'FFI\CData|string', '&ptr2'=>'FFI\CData|string', 'size'=>'int'], - 'FFI::memcpy' => ['void', '&dst'=>'FFI\CData', '&src'=>'string|FFI\CData', 'size'=>'int'], - 'FFI::memset' => ['void', '&ptr'=>'FFI\CData', 'ch'=>'int', 'size'=>'int'], - 'FFI::new' => ['FFI\CData', 'type'=>'string|FFI\CType', 'owned='=>'bool', 'persistent='=>'bool'], - 'FFI::scope' => ['FFI', 'scope_name'=>'string'], - 'FFI::sizeof' => ['int', '&ptr'=>'FFI\CData|FFI\CType'], - 'FFI::string' => ['string', '&ptr'=>'FFI\CData', 'size='=>'int'], - 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], - 'FFI::type' => ['FFI\CType', 'type'=>'string'], - 'get_mangled_object_vars' => ['array', 'obj'=>'object'], - 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], - 'password_algos' => ['array'], - 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], - 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], - 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], - 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], - 'sapi_windows_set_ctrl_handler' => ['bool', 'callable'=>'callable', 'add='=>'bool'], - 'ReflectionProperty::getType' => ['?ReflectionType'], - 'ReflectionProperty::hasType' => ['bool'], - 'ReflectionProperty::isInitialized' => ['bool', 'object='=>'?object'], - 'ReflectionReference::fromArrayElement' => ['?ReflectionReference', 'array'=>'array', 'key'=>'int|string'], - 'ReflectionReference::getId' => ['string'], - 'SQLite3Stmt::getSQL' => ['string', 'expanded='=>'bool'], - 'strip_tags' => ['string', 'str'=>'string', 'allowable_tags='=>'string|array'], - 'WeakReference::create' => ['WeakReference', 'referent'=>'object'], - 'WeakReference::get' => ['?object'], - ], - 'old' => [ - 'implode\'2' => ['string', 'pieces'=>'array', 'glue'=>'string'], - ], + 'new' => [ + 'FFI::addr' => ['FFI\CData', '&ptr'=>'FFI\CData'], + 'FFI::alignof' => ['int', '&ptr'=>'mixed'], + 'FFI::arrayType' => ['FFI\CType', 'type'=>'string|FFI\CType', 'dims'=>'array'], + 'FFI::cast' => ['FFI\CData', 'type'=>'string|FFI\CType', '&ptr'=>''], + 'FFI::cdef' => ['FFI', 'code='=>'string', 'lib='=>'?string'], + 'FFI::free' => ['void', '&ptr'=>'FFI\CData'], + 'FFI::load' => ['FFI', 'filename'=>'string'], + 'FFI::memcmp' => ['int', '&ptr1'=>'FFI\CData|string', '&ptr2'=>'FFI\CData|string', 'size'=>'int'], + 'FFI::memcpy' => ['void', '&dst'=>'FFI\CData', '&src'=>'string|FFI\CData', 'size'=>'int'], + 'FFI::memset' => ['void', '&ptr'=>'FFI\CData', 'ch'=>'int', 'size'=>'int'], + 'FFI::new' => ['FFI\CData', 'type'=>'string|FFI\CType', 'owned='=>'bool', 'persistent='=>'bool'], + 'FFI::scope' => ['FFI', 'scope_name'=>'string'], + 'FFI::sizeof' => ['int', '&ptr'=>'FFI\CData|FFI\CType'], + 'FFI::string' => ['string', '&ptr'=>'FFI\CData', 'size='=>'int'], + 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], + 'FFI::type' => ['FFI\CType', 'type'=>'string'], + 'get_mangled_object_vars' => ['array', 'obj'=>'object'], + 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'password_algos' => ['array'], + 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], + 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], + 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], + 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], + 'sapi_windows_set_ctrl_handler' => ['bool', 'callable'=>'callable', 'add='=>'bool'], + 'ReflectionProperty::getType' => ['?ReflectionType'], + 'ReflectionProperty::hasType' => ['bool'], + 'ReflectionProperty::isInitialized' => ['bool', 'object='=>'?object'], + 'ReflectionReference::fromArrayElement' => ['?ReflectionReference', 'array'=>'array', 'key'=>'int|string'], + 'ReflectionReference::getId' => ['string'], + 'SQLite3Stmt::getSQL' => ['string', 'expanded='=>'bool'], + 'strip_tags' => ['string', 'str'=>'string', 'allowable_tags='=>'string|array'], + 'WeakReference::create' => ['WeakReference', 'referent'=>'object'], + 'WeakReference::get' => ['?object'], + ], + 'old' => [ + 'implode\'2' => ['string', 'pieces'=>'array', 'glue'=>'string'], + ], ]; diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index e17864ac5c..206482bab9 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -1,4 +1,6 @@ - [ - 'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'], - 'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], - 'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], - 'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], - 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], - 'count_chars' => ['array|string', 'input'=>'string', 'mode='=>'int'], - 'date_add' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], - 'date_date_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], - 'date_diff' => ['DateInterval', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], - 'date_format' => ['string', 'object'=>'DateTimeInterface', 'format'=>'string'], - 'date_isodate_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], - 'date_parse' => ['array', 'date'=>'string'], - 'date_sub' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], - 'date_sun_info' => ['array', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], - 'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], - 'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], - 'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], - 'explode' => ['array', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], - 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], - 'get_debug_type' => ['string', 'var'=>'mixed'], - 'get_resource_id' => ['int', 'res'=>'resource'], - 'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'], - 'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], - 'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], - 'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], - 'imagecreate' => ['false|object', 'x_size'=>'int', 'y_size'=>'int'], - 'imagecreatefrombmp' => ['false|object', 'filename'=>'string'], - 'imagecreatefromgd' => ['false|object', 'filename'=>'string'], - 'imagecreatefromgd2' => ['false|object', 'filename'=>'string'], - 'imagecreatefromgd2part' => ['false|object', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], - 'imagecreatefromgif' => ['false|object', 'filename'=>'string'], - 'imagecreatefromjpeg' => ['false|object', 'filename'=>'string'], - 'imagecreatefrompng' => ['false|object', 'filename'=>'string'], - 'imagecreatefromstring' => ['false|object', 'image'=>'string'], - 'imagecreatefromwbmp' => ['false|object', 'filename'=>'string'], - 'imagecreatefromwebp' => ['false|object', 'filename'=>'string'], - 'imagecreatefromxbm' => ['false|object', 'filename'=>'string'], - 'imagecreatefromxpm' => ['false|object', 'filename'=>'string'], - 'imagecreatetruecolor' => ['false|object', 'x_size'=>'int', 'y_size'=>'int'], - 'imagecrop' => ['false|object', 'im'=>'resource', 'rect'=>'array'], - 'imagecropauto' => ['false|object', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], - 'imagegetclip' => ['array', 'im'=>'resource'], - 'imagegrabscreen' => ['false|object'], - 'imagegrabwindow' => ['false|object', 'window_handle'=>'int', 'client_area='=>'int'], - 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], - 'imagejpeg\'1' => ['string|false', 'im'=>'GdImage', 'filename='=>'null', 'quality='=>'int'], - 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], - 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], - 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], - 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], - 'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], - 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result'=>'array'], - 'password_hash' => ['string', 'password'=>'string', 'algo'=>'string|int|null', 'options='=>'array'], - 'PhpToken::tokenize' => ['list', 'code'=>'string', 'flags='=>'int'], - 'PhpToken::is' => ['bool', 'kind'=>'string|int|string[]|int[]'], - 'PhpToken::isIgnorable' => ['bool'], - 'PhpToken::getTokenName' => ['string'], - 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], - 'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], - 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], - 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], - 'str_split' => ['array', 'str'=>'string', 'split_length='=>'int'], - 'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], - 'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], - 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], - 'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], - 'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], - 'strpos' => ['positive-int|0|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], - 'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string'], - 'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], - 'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], - 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], - 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], - 'xml_parser_create' => ['XMLParser', 'encoding='=>'string'], - 'xml_parser_create_ns' => ['XMLParser', 'encoding='=>'string', 'sep='=>'string'], - 'xml_parser_free' => ['bool', 'parser'=>'XMLParser'], - 'xml_parser_get_option' => ['mixed|false', 'parser'=>'XMLParser', 'option'=>'int'], - 'xml_parser_set_option' => ['bool', 'parser'=>'XMLParser', 'option'=>'int', 'value'=>'mixed'], - 'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'XMLWriter', 'empty='=>'bool'], - 'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_open_memory' => ['XMLWriter'], - 'xmlwriter_open_uri' => ['XMLWriter', 'source'=>'string'], - 'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'XMLWriter', 'flush='=>'bool'], - 'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'XMLWriter', 'indent'=>'bool'], - 'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'XMLWriter', 'indentstring'=>'string'], - 'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], - 'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], - 'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], - 'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'XMLWriter', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], - 'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], - 'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], - 'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], - 'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'isparam'=>'bool'], - 'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], - 'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], - 'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string'], - 'xmlwriter_text' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], - 'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], - 'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], - 'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], - 'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], - 'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], - 'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], - 'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string', 'content'=>'string'], - 'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], - ], - 'old' => [ + 'new' => [ + 'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'], + 'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], + 'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], + 'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], + 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], + 'count_chars' => ['array|string', 'input'=>'string', 'mode='=>'int'], + 'date_add' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], + 'date_date_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], + 'date_diff' => ['DateInterval', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], + 'date_format' => ['string', 'object'=>'DateTimeInterface', 'format'=>'string'], + 'date_isodate_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], + 'date_parse' => ['array', 'date'=>'string'], + 'date_sub' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], + 'date_sun_info' => ['array', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], + 'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], + 'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], + 'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], + 'explode' => ['array', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], + 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], + 'get_debug_type' => ['string', 'var'=>'mixed'], + 'get_resource_id' => ['int', 'res'=>'resource'], + 'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'], + 'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], + 'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], + 'imagecreate' => ['false|object', 'x_size'=>'int', 'y_size'=>'int'], + 'imagecreatefrombmp' => ['false|object', 'filename'=>'string'], + 'imagecreatefromgd' => ['false|object', 'filename'=>'string'], + 'imagecreatefromgd2' => ['false|object', 'filename'=>'string'], + 'imagecreatefromgd2part' => ['false|object', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], + 'imagecreatefromgif' => ['false|object', 'filename'=>'string'], + 'imagecreatefromjpeg' => ['false|object', 'filename'=>'string'], + 'imagecreatefrompng' => ['false|object', 'filename'=>'string'], + 'imagecreatefromstring' => ['false|object', 'image'=>'string'], + 'imagecreatefromwbmp' => ['false|object', 'filename'=>'string'], + 'imagecreatefromwebp' => ['false|object', 'filename'=>'string'], + 'imagecreatefromxbm' => ['false|object', 'filename'=>'string'], + 'imagecreatefromxpm' => ['false|object', 'filename'=>'string'], + 'imagecreatetruecolor' => ['false|object', 'x_size'=>'int', 'y_size'=>'int'], + 'imagecrop' => ['false|object', 'im'=>'resource', 'rect'=>'array'], + 'imagecropauto' => ['false|object', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], + 'imagegetclip' => ['array', 'im'=>'resource'], + 'imagegrabscreen' => ['false|object'], + 'imagegrabwindow' => ['false|object', 'window_handle'=>'int', 'client_area='=>'int'], + 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], + 'imagejpeg\'1' => ['string|false', 'im'=>'GdImage', 'filename='=>'null', 'quality='=>'int'], + 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], + 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], + 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], + 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], + 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result'=>'array'], + 'password_hash' => ['string', 'password'=>'string', 'algo'=>'string|int|null', 'options='=>'array'], + 'PhpToken::tokenize' => ['list', 'code'=>'string', 'flags='=>'int'], + 'PhpToken::is' => ['bool', 'kind'=>'string|int|string[]|int[]'], + 'PhpToken::isIgnorable' => ['bool'], + 'PhpToken::getTokenName' => ['string'], + 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], + 'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], + 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], + 'str_split' => ['array', 'str'=>'string', 'split_length='=>'int'], + 'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], + 'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], + 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], + 'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], + 'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], + 'strpos' => ['positive-int|0|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], + 'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string'], + 'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], + 'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'], + 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], + 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], + 'xml_parser_create' => ['XMLParser', 'encoding='=>'string'], + 'xml_parser_create_ns' => ['XMLParser', 'encoding='=>'string', 'sep='=>'string'], + 'xml_parser_free' => ['bool', 'parser'=>'XMLParser'], + 'xml_parser_get_option' => ['mixed|false', 'parser'=>'XMLParser', 'option'=>'int'], + 'xml_parser_set_option' => ['bool', 'parser'=>'XMLParser', 'option'=>'int', 'value'=>'mixed'], + 'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'XMLWriter', 'empty='=>'bool'], + 'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_open_memory' => ['XMLWriter'], + 'xmlwriter_open_uri' => ['XMLWriter', 'source'=>'string'], + 'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'XMLWriter', 'flush='=>'bool'], + 'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'XMLWriter', 'indent'=>'bool'], + 'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'XMLWriter', 'indentstring'=>'string'], + 'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], + 'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], + 'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'XMLWriter'], + 'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'XMLWriter', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], + 'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], + 'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], + 'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], + 'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'isparam'=>'bool'], + 'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string'], + 'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], + 'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string'], + 'xmlwriter_text' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], + 'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], + 'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], + 'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], + 'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], + 'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], + 'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'XMLWriter', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'XMLWriter', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], + 'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'XMLWriter', 'target'=>'string', 'content'=>'string'], + 'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'XMLWriter', 'content'=>'string'], + ], + 'old' => [ - 'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], - 'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], - 'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], - 'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], - 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'bool'], - 'count_chars' => ['array|false|string', 'input'=>'string', 'mode='=>'int'], - 'create_function' => ['string', 'args'=>'string', 'code'=>'string'], - 'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], - 'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], - 'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], - 'date_format' => ['string|false', 'object'=>'DateTimeInterface', 'format'=>'string'], - 'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], - 'date_parse' => ['array|false', 'date'=>'string'], - 'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], - 'date_sun_info' => ['array|false', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], - 'date_time_set' => ['DateTime|false', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], - 'date_timestamp_set' => ['DateTime|false', 'object'=>'DateTime', 'unixtimestamp'=>'int'], - 'date_timezone_set' => ['DateTime|false', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], - 'each' => ['array{0:int|string,key:int|string,1:mixed,value:mixed}', '&r_arr'=>'array'], - 'gmdate' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], - 'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], - 'gmp_random' => ['GMP', 'limiter='=>'int'], - 'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'], - 'hash_hkdf' => ['string|false', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], - 'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'], - 'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], - 'imagecreate' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], - 'imagecreatefrombmp' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromgd' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromgd2' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromgd2part' => ['resource|false', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], - 'imagecreatefromgif' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromjpeg' => ['resource|false', 'filename'=>'string'], - 'imagecreatefrompng' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromstring' => ['resource|false', 'image'=>'string'], - 'imagecreatefromwbmp' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromwebp' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromxbm' => ['resource|false', 'filename'=>'string'], - 'imagecreatefromxpm' => ['resource|false', 'filename'=>'string'], - 'imagecreatetruecolor' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], - 'imagecrop' => ['resource|false', 'im'=>'resource', 'rect'=>'array'], - 'imagecropauto' => ['resource|false', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], - 'imagegetclip' => ['array|false', 'im'=>'resource'], - 'imagegrabscreen' => ['false|resource'], - 'imagegrabwindow' => ['false|resource', 'window_handle'=>'int', 'client_area='=>'int'], - 'imagejpeg' => ['bool', 'im'=>'resource', 'filename='=>'string|resource|null', 'quality='=>'int'], - 'imagejpeg\'1' => ['string|false', 'im'=>'resource', 'filename='=>'null', 'quality='=>'int'], - 'imagerotate' => ['resource|false', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], - 'imagescale' => ['resource|false', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], - 'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], - 'ldap_sort' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', 'sortfilter'=>'string'], - 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], - 'mktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], - 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'], - 'password_hash' => ['string|false|null', 'password'=>'string', 'algo'=>'?string|?int', 'options='=>'array'], - 'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], - 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], - 'read_exif_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], - 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['?string|?false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], - 'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], - 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], - 'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], - 'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], - 'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], - 'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int'], - 'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], - 'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], - 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], - 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], - 'xml_parser_create' => ['resource', 'encoding='=>'string'], - 'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'], - 'xml_parser_free' => ['bool', 'parser'=>'resource'], - 'xml_parser_get_option' => ['mixed|false', 'parser'=>'resource', 'option'=>'int'], - 'xml_parser_set_option' => ['bool', 'parser'=>'resource', 'option'=>'int', 'value'=>'mixed'], - 'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'resource', 'empty='=>'bool'], - 'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_open_memory' => ['resource'], - 'xmlwriter_open_uri' => ['resource', 'source'=>'string'], - 'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'resource', 'flush='=>'bool'], - 'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'resource', 'indent'=>'bool'], - 'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'resource', 'indentstring'=>'string'], - 'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], - 'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], - 'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'resource'], - 'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'resource', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], - 'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], - 'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], - 'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], - 'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'isparam'=>'bool'], - 'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], - 'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], - 'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string'], - 'xmlwriter_text' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], - 'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], - 'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], - 'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], - 'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], - 'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], - 'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], - 'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], - 'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string', 'content'=>'string'], - 'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], - ] + 'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], + 'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], + 'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], + 'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], + 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'bool'], + 'count_chars' => ['array|false|string', 'input'=>'string', 'mode='=>'int'], + 'create_function' => ['string', 'args'=>'string', 'code'=>'string'], + 'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], + 'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], + 'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], + 'date_format' => ['string|false', 'object'=>'DateTimeInterface', 'format'=>'string'], + 'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], + 'date_parse' => ['array|false', 'date'=>'string'], + 'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], + 'date_sun_info' => ['array|false', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], + 'date_time_set' => ['DateTime|false', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], + 'date_timestamp_set' => ['DateTime|false', 'object'=>'DateTime', 'unixtimestamp'=>'int'], + 'date_timezone_set' => ['DateTime|false', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], + 'each' => ['array{0:int|string,key:int|string,1:mixed,value:mixed}', '&r_arr'=>'array'], + 'gmdate' => ['string|false', 'format'=>'string', 'timestamp='=>'int'], + 'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], + 'gmp_random' => ['GMP', 'limiter='=>'int'], + 'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'], + 'hash_hkdf' => ['string|false', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'], + 'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], + 'imagecreate' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], + 'imagecreatefrombmp' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromgd' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromgd2' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromgd2part' => ['resource|false', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'], + 'imagecreatefromgif' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromjpeg' => ['resource|false', 'filename'=>'string'], + 'imagecreatefrompng' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromstring' => ['resource|false', 'image'=>'string'], + 'imagecreatefromwbmp' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromwebp' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromxbm' => ['resource|false', 'filename'=>'string'], + 'imagecreatefromxpm' => ['resource|false', 'filename'=>'string'], + 'imagecreatetruecolor' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'], + 'imagecrop' => ['resource|false', 'im'=>'resource', 'rect'=>'array'], + 'imagecropauto' => ['resource|false', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], + 'imagegetclip' => ['array|false', 'im'=>'resource'], + 'imagegrabscreen' => ['false|resource'], + 'imagegrabwindow' => ['false|resource', 'window_handle'=>'int', 'client_area='=>'int'], + 'imagejpeg' => ['bool', 'im'=>'resource', 'filename='=>'string|resource|null', 'quality='=>'int'], + 'imagejpeg\'1' => ['string|false', 'im'=>'resource', 'filename='=>'null', 'quality='=>'int'], + 'imagerotate' => ['resource|false', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], + 'imagescale' => ['resource|false', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], + 'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], + 'ldap_sort' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', 'sortfilter'=>'string'], + 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], + 'mktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], + 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'], + 'password_hash' => ['string|false|null', 'password'=>'string', 'algo'=>'?string|?int', 'options='=>'array'], + 'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], + 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], + 'read_exif_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], + 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['?string|?false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], + 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], + 'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], + 'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], + 'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], + 'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int'], + 'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], + 'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], + 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], + 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], + 'xml_parser_create' => ['resource', 'encoding='=>'string'], + 'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'], + 'xml_parser_free' => ['bool', 'parser'=>'resource'], + 'xml_parser_get_option' => ['mixed|false', 'parser'=>'resource', 'option'=>'int'], + 'xml_parser_set_option' => ['bool', 'parser'=>'resource', 'option'=>'int', 'value'=>'mixed'], + 'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_flush' => ['mixed', 'xmlwriter'=>'resource', 'empty='=>'bool'], + 'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_open_memory' => ['resource'], + 'xmlwriter_open_uri' => ['resource', 'source'=>'string'], + 'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'resource', 'flush='=>'bool'], + 'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'resource', 'indent'=>'bool'], + 'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'resource', 'indentstring'=>'string'], + 'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], + 'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], + 'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'resource'], + 'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'resource', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'], + 'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string'], + 'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], + 'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], + 'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'isparam'=>'bool'], + 'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'], + 'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'], + 'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string'], + 'xmlwriter_text' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], + 'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], + 'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], + 'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], + 'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'publicid='=>'string', 'sysid='=>'string', 'subset='=>'string'], + 'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'publicid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'], + 'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'], + 'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'], + 'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string', 'content'=>'string'], + 'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'], + ] ]; diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index f136e361e4..8b362d6025 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -1,1481 +1,1483 @@ - ['hasSideEffects' => false], - 'CURLFile::getMimeType' => ['hasSideEffects' => false], - 'CURLFile::getPostFilename' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\AlreadyExistsException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\AuthenticationException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ConfigurationException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\DivideByZeroException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\DomainException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ExecutionException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\InvalidArgumentException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\InvalidQueryException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\InvalidSyntaxException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\IsBootstrappingException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\LogicException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\OverloadedException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ProtocolException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\RangeException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ReadTimeoutException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\RuntimeException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ServerException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\TimeoutException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\TruncateException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\UnauthorizedException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\UnavailableException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\UnpreparedException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\ValidationException::__construct' => ['hasSideEffects' => false], - 'Cassandra\\Exception\\WriteTimeoutException::__construct' => ['hasSideEffects' => false], - 'Collator::__construct' => ['hasSideEffects' => false], - 'Collator::compare' => ['hasSideEffects' => false], - 'Collator::getAttribute' => ['hasSideEffects' => false], - 'Collator::getErrorCode' => ['hasSideEffects' => false], - 'Collator::getErrorMessage' => ['hasSideEffects' => false], - 'Collator::getLocale' => ['hasSideEffects' => false], - 'Collator::getSortKey' => ['hasSideEffects' => false], - 'Collator::getStrength' => ['hasSideEffects' => false], - 'DateTime::add' => ['hasSideEffects' => true], - 'DateTime::createFromFormat' => ['hasSideEffects' => false], - 'DateTime::createFromImmutable' => ['hasSideEffects' => false], - 'DateTime::diff' => ['hasSideEffects' => false], - 'DateTime::format' => ['hasSideEffects' => false], - 'DateTime::getLastErrors' => ['hasSideEffects' => false], - 'DateTime::getOffset' => ['hasSideEffects' => false], - 'DateTime::getTimestamp' => ['hasSideEffects' => false], - 'DateTime::getTimezone' => ['hasSideEffects' => false], - 'DateTime::modify' => ['hasSideEffects' => true], - 'DateTime::setDate' => ['hasSideEffects' => true], - 'DateTime::setISODate' => ['hasSideEffects' => true], - 'DateTime::setTime' => ['hasSideEffects' => true], - 'DateTime::setTimestamp' => ['hasSideEffects' => true], - 'DateTime::setTimezone' => ['hasSideEffects' => true], - 'DateTime::sub' => ['hasSideEffects' => true], - 'DateTimeImmutable::add' => ['hasSideEffects' => false], - 'DateTimeImmutable::createFromFormat' => ['hasSideEffects' => false], - 'DateTimeImmutable::createFromMutable' => ['hasSideEffects' => false], - 'DateTimeImmutable::diff' => ['hasSideEffects' => false], - 'DateTimeImmutable::format' => ['hasSideEffects' => false], - 'DateTimeImmutable::getLastErrors' => ['hasSideEffects' => false], - 'DateTimeImmutable::getOffset' => ['hasSideEffects' => false], - 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], - 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], - 'DateTimeImmutable::modify' => ['hasSideEffects' => false], - 'DateTimeImmutable::setDate' => ['hasSideEffects' => false], - 'DateTimeImmutable::setISODate' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTime' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], - 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], - 'DateTimeImmutable::sub' => ['hasSideEffects' => false], - 'ErrorException::__construct' => ['hasSideEffects' => false], - 'Event::__construct' => ['hasSideEffects' => false], - 'EventBase::getFeatures' => ['hasSideEffects' => false], - 'EventBase::getMethod' => ['hasSideEffects' => false], - 'EventBase::getTimeOfDayCached' => ['hasSideEffects' => false], - 'EventBase::gotExit' => ['hasSideEffects' => false], - 'EventBase::gotStop' => ['hasSideEffects' => false], - 'EventBuffer::__construct' => ['hasSideEffects' => false], - 'EventBufferEvent::__construct' => ['hasSideEffects' => false], - 'EventBufferEvent::getDnsErrorString' => ['hasSideEffects' => false], - 'EventBufferEvent::getEnabled' => ['hasSideEffects' => false], - 'EventBufferEvent::getInput' => ['hasSideEffects' => false], - 'EventBufferEvent::getOutput' => ['hasSideEffects' => false], - 'EventConfig::__construct' => ['hasSideEffects' => false], - 'EventDnsBase::__construct' => ['hasSideEffects' => false], - 'EventHttpConnection::__construct' => ['hasSideEffects' => false], - 'EventHttpRequest::__construct' => ['hasSideEffects' => false], - 'EventHttpRequest::getCommand' => ['hasSideEffects' => false], - 'EventHttpRequest::getHost' => ['hasSideEffects' => false], - 'EventHttpRequest::getInputBuffer' => ['hasSideEffects' => false], - 'EventHttpRequest::getInputHeaders' => ['hasSideEffects' => false], - 'EventHttpRequest::getOutputBuffer' => ['hasSideEffects' => false], - 'EventHttpRequest::getOutputHeaders' => ['hasSideEffects' => false], - 'EventHttpRequest::getResponseCode' => ['hasSideEffects' => false], - 'EventHttpRequest::getUri' => ['hasSideEffects' => false], - 'EventSslContext::__construct' => ['hasSideEffects' => false], - 'Exception::__construct' => ['hasSideEffects' => false], - 'Exception::getCode' => ['hasSideEffects' => false], - 'Exception::getFile' => ['hasSideEffects' => false], - 'Exception::getLine' => ['hasSideEffects' => false], - 'Exception::getMessage' => ['hasSideEffects' => false], - 'Exception::getPrevious' => ['hasSideEffects' => false], - 'Exception::getTrace' => ['hasSideEffects' => false], - 'Exception::getTraceAsString' => ['hasSideEffects' => false], - 'Gmagick::getcopyright' => ['hasSideEffects' => false], - 'Gmagick::getfilename' => ['hasSideEffects' => false], - 'Gmagick::getimagebackgroundcolor' => ['hasSideEffects' => false], - 'Gmagick::getimageblueprimary' => ['hasSideEffects' => false], - 'Gmagick::getimagebordercolor' => ['hasSideEffects' => false], - 'Gmagick::getimagechanneldepth' => ['hasSideEffects' => false], - 'Gmagick::getimagecolors' => ['hasSideEffects' => false], - 'Gmagick::getimagecolorspace' => ['hasSideEffects' => false], - 'Gmagick::getimagecompose' => ['hasSideEffects' => false], - 'Gmagick::getimagedelay' => ['hasSideEffects' => false], - 'Gmagick::getimagedepth' => ['hasSideEffects' => false], - 'Gmagick::getimagedispose' => ['hasSideEffects' => false], - 'Gmagick::getimageextrema' => ['hasSideEffects' => false], - 'Gmagick::getimagefilename' => ['hasSideEffects' => false], - 'Gmagick::getimageformat' => ['hasSideEffects' => false], - 'Gmagick::getimagegamma' => ['hasSideEffects' => false], - 'Gmagick::getimagegreenprimary' => ['hasSideEffects' => false], - 'Gmagick::getimageheight' => ['hasSideEffects' => false], - 'Gmagick::getimagehistogram' => ['hasSideEffects' => false], - 'Gmagick::getimageindex' => ['hasSideEffects' => false], - 'Gmagick::getimageinterlacescheme' => ['hasSideEffects' => false], - 'Gmagick::getimageiterations' => ['hasSideEffects' => false], - 'Gmagick::getimagematte' => ['hasSideEffects' => false], - 'Gmagick::getimagemattecolor' => ['hasSideEffects' => false], - 'Gmagick::getimageprofile' => ['hasSideEffects' => false], - 'Gmagick::getimageredprimary' => ['hasSideEffects' => false], - 'Gmagick::getimagerenderingintent' => ['hasSideEffects' => false], - 'Gmagick::getimageresolution' => ['hasSideEffects' => false], - 'Gmagick::getimagescene' => ['hasSideEffects' => false], - 'Gmagick::getimagesignature' => ['hasSideEffects' => false], - 'Gmagick::getimagetype' => ['hasSideEffects' => false], - 'Gmagick::getimageunits' => ['hasSideEffects' => false], - 'Gmagick::getimagewhitepoint' => ['hasSideEffects' => false], - 'Gmagick::getimagewidth' => ['hasSideEffects' => false], - 'Gmagick::getpackagename' => ['hasSideEffects' => false], - 'Gmagick::getquantumdepth' => ['hasSideEffects' => false], - 'Gmagick::getreleasedate' => ['hasSideEffects' => false], - 'Gmagick::getsamplingfactors' => ['hasSideEffects' => false], - 'Gmagick::getsize' => ['hasSideEffects' => false], - 'Gmagick::getversion' => ['hasSideEffects' => false], - 'GmagickDraw::getfillcolor' => ['hasSideEffects' => false], - 'GmagickDraw::getfillopacity' => ['hasSideEffects' => false], - 'GmagickDraw::getfont' => ['hasSideEffects' => false], - 'GmagickDraw::getfontsize' => ['hasSideEffects' => false], - 'GmagickDraw::getfontstyle' => ['hasSideEffects' => false], - 'GmagickDraw::getfontweight' => ['hasSideEffects' => false], - 'GmagickDraw::getstrokecolor' => ['hasSideEffects' => false], - 'GmagickDraw::getstrokeopacity' => ['hasSideEffects' => false], - 'GmagickDraw::getstrokewidth' => ['hasSideEffects' => false], - 'GmagickDraw::gettextdecoration' => ['hasSideEffects' => false], - 'GmagickDraw::gettextencoding' => ['hasSideEffects' => false], - 'GmagickPixel::getcolor' => ['hasSideEffects' => false], - 'GmagickPixel::getcolorcount' => ['hasSideEffects' => false], - 'GmagickPixel::getcolorvalue' => ['hasSideEffects' => false], - 'HttpMessage::getBody' => ['hasSideEffects' => false], - 'HttpMessage::getHeader' => ['hasSideEffects' => false], - 'HttpMessage::getHeaders' => ['hasSideEffects' => false], - 'HttpMessage::getHttpVersion' => ['hasSideEffects' => false], - 'HttpMessage::getInfo' => ['hasSideEffects' => false], - 'HttpMessage::getParentMessage' => ['hasSideEffects' => false], - 'HttpMessage::getRequestMethod' => ['hasSideEffects' => false], - 'HttpMessage::getRequestUrl' => ['hasSideEffects' => false], - 'HttpMessage::getResponseCode' => ['hasSideEffects' => false], - 'HttpMessage::getResponseStatus' => ['hasSideEffects' => false], - 'HttpMessage::getType' => ['hasSideEffects' => false], - 'HttpQueryString::get' => ['hasSideEffects' => false], - 'HttpQueryString::getArray' => ['hasSideEffects' => false], - 'HttpQueryString::getBool' => ['hasSideEffects' => false], - 'HttpQueryString::getFloat' => ['hasSideEffects' => false], - 'HttpQueryString::getInt' => ['hasSideEffects' => false], - 'HttpQueryString::getObject' => ['hasSideEffects' => false], - 'HttpQueryString::getString' => ['hasSideEffects' => false], - 'HttpRequest::getBody' => ['hasSideEffects' => false], - 'HttpRequest::getContentType' => ['hasSideEffects' => false], - 'HttpRequest::getCookies' => ['hasSideEffects' => false], - 'HttpRequest::getHeaders' => ['hasSideEffects' => false], - 'HttpRequest::getHistory' => ['hasSideEffects' => false], - 'HttpRequest::getMethod' => ['hasSideEffects' => false], - 'HttpRequest::getOptions' => ['hasSideEffects' => false], - 'HttpRequest::getPostFields' => ['hasSideEffects' => false], - 'HttpRequest::getPostFiles' => ['hasSideEffects' => false], - 'HttpRequest::getPutData' => ['hasSideEffects' => false], - 'HttpRequest::getPutFile' => ['hasSideEffects' => false], - 'HttpRequest::getQueryData' => ['hasSideEffects' => false], - 'HttpRequest::getRawPostData' => ['hasSideEffects' => false], - 'HttpRequest::getRawRequestMessage' => ['hasSideEffects' => false], - 'HttpRequest::getRawResponseMessage' => ['hasSideEffects' => false], - 'HttpRequest::getRequestMessage' => ['hasSideEffects' => false], - 'HttpRequest::getResponseBody' => ['hasSideEffects' => false], - 'HttpRequest::getResponseCode' => ['hasSideEffects' => false], - 'HttpRequest::getResponseCookies' => ['hasSideEffects' => false], - 'HttpRequest::getResponseData' => ['hasSideEffects' => false], - 'HttpRequest::getResponseHeader' => ['hasSideEffects' => false], - 'HttpRequest::getResponseInfo' => ['hasSideEffects' => false], - 'HttpRequest::getResponseMessage' => ['hasSideEffects' => false], - 'HttpRequest::getResponseStatus' => ['hasSideEffects' => false], - 'HttpRequest::getSslOptions' => ['hasSideEffects' => false], - 'HttpRequest::getUrl' => ['hasSideEffects' => false], - 'HttpRequestPool::getAttachedRequests' => ['hasSideEffects' => false], - 'HttpRequestPool::getFinishedRequests' => ['hasSideEffects' => false], - 'Imagick::getColorspace' => ['hasSideEffects' => false], - 'Imagick::getCompression' => ['hasSideEffects' => false], - 'Imagick::getCompressionQuality' => ['hasSideEffects' => false], - 'Imagick::getConfigureOptions' => ['hasSideEffects' => false], - 'Imagick::getFeatures' => ['hasSideEffects' => false], - 'Imagick::getFilename' => ['hasSideEffects' => false], - 'Imagick::getFont' => ['hasSideEffects' => false], - 'Imagick::getFormat' => ['hasSideEffects' => false], - 'Imagick::getGravity' => ['hasSideEffects' => false], - 'Imagick::getHDRIEnabled' => ['hasSideEffects' => false], - 'Imagick::getImage' => ['hasSideEffects' => false], - 'Imagick::getImageAlphaChannel' => ['hasSideEffects' => false], - 'Imagick::getImageArtifact' => ['hasSideEffects' => false], - 'Imagick::getImageAttribute' => ['hasSideEffects' => false], - 'Imagick::getImageBackgroundColor' => ['hasSideEffects' => false], - 'Imagick::getImageBlob' => ['hasSideEffects' => false], - 'Imagick::getImageBluePrimary' => ['hasSideEffects' => false], - 'Imagick::getImageBorderColor' => ['hasSideEffects' => false], - 'Imagick::getImageChannelDepth' => ['hasSideEffects' => false], - 'Imagick::getImageChannelDistortion' => ['hasSideEffects' => false], - 'Imagick::getImageChannelDistortions' => ['hasSideEffects' => false], - 'Imagick::getImageChannelExtrema' => ['hasSideEffects' => false], - 'Imagick::getImageChannelKurtosis' => ['hasSideEffects' => false], - 'Imagick::getImageChannelMean' => ['hasSideEffects' => false], - 'Imagick::getImageChannelRange' => ['hasSideEffects' => false], - 'Imagick::getImageChannelStatistics' => ['hasSideEffects' => false], - 'Imagick::getImageClipMask' => ['hasSideEffects' => false], - 'Imagick::getImageColormapColor' => ['hasSideEffects' => false], - 'Imagick::getImageColors' => ['hasSideEffects' => false], - 'Imagick::getImageColorspace' => ['hasSideEffects' => false], - 'Imagick::getImageCompose' => ['hasSideEffects' => false], - 'Imagick::getImageCompression' => ['hasSideEffects' => false], - 'Imagick::getImageCompressionQuality' => ['hasSideEffects' => false], - 'Imagick::getImageDelay' => ['hasSideEffects' => false], - 'Imagick::getImageDepth' => ['hasSideEffects' => false], - 'Imagick::getImageDispose' => ['hasSideEffects' => false], - 'Imagick::getImageDistortion' => ['hasSideEffects' => false], - 'Imagick::getImageExtrema' => ['hasSideEffects' => false], - 'Imagick::getImageFilename' => ['hasSideEffects' => false], - 'Imagick::getImageFormat' => ['hasSideEffects' => false], - 'Imagick::getImageGamma' => ['hasSideEffects' => false], - 'Imagick::getImageGeometry' => ['hasSideEffects' => false], - 'Imagick::getImageGravity' => ['hasSideEffects' => false], - 'Imagick::getImageGreenPrimary' => ['hasSideEffects' => false], - 'Imagick::getImageHeight' => ['hasSideEffects' => false], - 'Imagick::getImageHistogram' => ['hasSideEffects' => false], - 'Imagick::getImageIndex' => ['hasSideEffects' => false], - 'Imagick::getImageInterlaceScheme' => ['hasSideEffects' => false], - 'Imagick::getImageInterpolateMethod' => ['hasSideEffects' => false], - 'Imagick::getImageIterations' => ['hasSideEffects' => false], - 'Imagick::getImageLength' => ['hasSideEffects' => false], - 'Imagick::getImageMatte' => ['hasSideEffects' => false], - 'Imagick::getImageMatteColor' => ['hasSideEffects' => false], - 'Imagick::getImageMimeType' => ['hasSideEffects' => false], - 'Imagick::getImageOrientation' => ['hasSideEffects' => false], - 'Imagick::getImagePage' => ['hasSideEffects' => false], - 'Imagick::getImagePixelColor' => ['hasSideEffects' => false], - 'Imagick::getImageProfile' => ['hasSideEffects' => false], - 'Imagick::getImageProfiles' => ['hasSideEffects' => false], - 'Imagick::getImageProperties' => ['hasSideEffects' => false], - 'Imagick::getImageProperty' => ['hasSideEffects' => false], - 'Imagick::getImageRedPrimary' => ['hasSideEffects' => false], - 'Imagick::getImageRegion' => ['hasSideEffects' => false], - 'Imagick::getImageRenderingIntent' => ['hasSideEffects' => false], - 'Imagick::getImageResolution' => ['hasSideEffects' => false], - 'Imagick::getImageScene' => ['hasSideEffects' => false], - 'Imagick::getImageSignature' => ['hasSideEffects' => false], - 'Imagick::getImageSize' => ['hasSideEffects' => false], - 'Imagick::getImageTicksPerSecond' => ['hasSideEffects' => false], - 'Imagick::getImageTotalInkDensity' => ['hasSideEffects' => false], - 'Imagick::getImageType' => ['hasSideEffects' => false], - 'Imagick::getImageUnits' => ['hasSideEffects' => false], - 'Imagick::getImageVirtualPixelMethod' => ['hasSideEffects' => false], - 'Imagick::getImageWhitePoint' => ['hasSideEffects' => false], - 'Imagick::getImageWidth' => ['hasSideEffects' => false], - 'Imagick::getImagesBlob' => ['hasSideEffects' => false], - 'Imagick::getInterlaceScheme' => ['hasSideEffects' => false], - 'Imagick::getIteratorIndex' => ['hasSideEffects' => false], - 'Imagick::getNumberImages' => ['hasSideEffects' => false], - 'Imagick::getOption' => ['hasSideEffects' => false], - 'Imagick::getPage' => ['hasSideEffects' => false], - 'Imagick::getPixelIterator' => ['hasSideEffects' => false], - 'Imagick::getPixelRegionIterator' => ['hasSideEffects' => false], - 'Imagick::getPointSize' => ['hasSideEffects' => false], - 'Imagick::getSamplingFactors' => ['hasSideEffects' => false], - 'Imagick::getSize' => ['hasSideEffects' => false], - 'Imagick::getSizeOffset' => ['hasSideEffects' => false], - 'ImagickDraw::getBorderColor' => ['hasSideEffects' => false], - 'ImagickDraw::getClipPath' => ['hasSideEffects' => false], - 'ImagickDraw::getClipRule' => ['hasSideEffects' => false], - 'ImagickDraw::getClipUnits' => ['hasSideEffects' => false], - 'ImagickDraw::getDensity' => ['hasSideEffects' => false], - 'ImagickDraw::getFillColor' => ['hasSideEffects' => false], - 'ImagickDraw::getFillOpacity' => ['hasSideEffects' => false], - 'ImagickDraw::getFillRule' => ['hasSideEffects' => false], - 'ImagickDraw::getFont' => ['hasSideEffects' => false], - 'ImagickDraw::getFontFamily' => ['hasSideEffects' => false], - 'ImagickDraw::getFontResolution' => ['hasSideEffects' => false], - 'ImagickDraw::getFontSize' => ['hasSideEffects' => false], - 'ImagickDraw::getFontStretch' => ['hasSideEffects' => false], - 'ImagickDraw::getFontStyle' => ['hasSideEffects' => false], - 'ImagickDraw::getFontWeight' => ['hasSideEffects' => false], - 'ImagickDraw::getGravity' => ['hasSideEffects' => false], - 'ImagickDraw::getOpacity' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeAntialias' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeColor' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeDashArray' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeDashOffset' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeLineCap' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeLineJoin' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeMiterLimit' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeOpacity' => ['hasSideEffects' => false], - 'ImagickDraw::getStrokeWidth' => ['hasSideEffects' => false], - 'ImagickDraw::getTextAlignment' => ['hasSideEffects' => false], - 'ImagickDraw::getTextAntialias' => ['hasSideEffects' => false], - 'ImagickDraw::getTextDecoration' => ['hasSideEffects' => false], - 'ImagickDraw::getTextDirection' => ['hasSideEffects' => false], - 'ImagickDraw::getTextEncoding' => ['hasSideEffects' => false], - 'ImagickDraw::getTextInterLineSpacing' => ['hasSideEffects' => false], - 'ImagickDraw::getTextInterWordSpacing' => ['hasSideEffects' => false], - 'ImagickDraw::getTextKerning' => ['hasSideEffects' => false], - 'ImagickDraw::getTextUnderColor' => ['hasSideEffects' => false], - 'ImagickDraw::getVectorGraphics' => ['hasSideEffects' => false], - 'ImagickKernel::getMatrix' => ['hasSideEffects' => false], - 'ImagickPixel::getColor' => ['hasSideEffects' => false], - 'ImagickPixel::getColorAsString' => ['hasSideEffects' => false], - 'ImagickPixel::getColorCount' => ['hasSideEffects' => false], - 'ImagickPixel::getColorQuantum' => ['hasSideEffects' => false], - 'ImagickPixel::getColorValue' => ['hasSideEffects' => false], - 'ImagickPixel::getColorValueQuantum' => ['hasSideEffects' => false], - 'ImagickPixel::getHSL' => ['hasSideEffects' => false], - 'ImagickPixel::getIndex' => ['hasSideEffects' => false], - 'ImagickPixelIterator::getCurrentIteratorRow' => ['hasSideEffects' => false], - 'ImagickPixelIterator::getIteratorRow' => ['hasSideEffects' => false], - 'ImagickPixelIterator::getNextIteratorRow' => ['hasSideEffects' => false], - 'ImagickPixelIterator::getPreviousIteratorRow' => ['hasSideEffects' => false], - 'IntlBreakIterator::current' => ['hasSideEffects' => false], - 'IntlBreakIterator::getErrorCode' => ['hasSideEffects' => false], - 'IntlBreakIterator::getErrorMessage' => ['hasSideEffects' => false], - 'IntlBreakIterator::getIterator' => ['hasSideEffects' => false], - 'IntlBreakIterator::getLocale' => ['hasSideEffects' => false], - 'IntlBreakIterator::getPartsIterator' => ['hasSideEffects' => false], - 'IntlBreakIterator::getText' => ['hasSideEffects' => false], - 'IntlBreakIterator::isBoundary' => ['hasSideEffects' => false], - 'IntlCalendar::after' => ['hasSideEffects' => false], - 'IntlCalendar::before' => ['hasSideEffects' => false], - 'IntlCalendar::equals' => ['hasSideEffects' => false], - 'IntlCalendar::fieldDifference' => ['hasSideEffects' => false], - 'IntlCalendar::get' => ['hasSideEffects' => false], - 'IntlCalendar::getActualMaximum' => ['hasSideEffects' => false], - 'IntlCalendar::getActualMinimum' => ['hasSideEffects' => false], - 'IntlCalendar::getDayOfWeekType' => ['hasSideEffects' => false], - 'IntlCalendar::getErrorCode' => ['hasSideEffects' => false], - 'IntlCalendar::getErrorMessage' => ['hasSideEffects' => false], - 'IntlCalendar::getFirstDayOfWeek' => ['hasSideEffects' => false], - 'IntlCalendar::getGreatestMinimum' => ['hasSideEffects' => false], - 'IntlCalendar::getLeastMaximum' => ['hasSideEffects' => false], - 'IntlCalendar::getLocale' => ['hasSideEffects' => false], - 'IntlCalendar::getMaximum' => ['hasSideEffects' => false], - 'IntlCalendar::getMinimalDaysInFirstWeek' => ['hasSideEffects' => false], - 'IntlCalendar::getMinimum' => ['hasSideEffects' => false], - 'IntlCalendar::getRepeatedWallTimeOption' => ['hasSideEffects' => false], - 'IntlCalendar::getSkippedWallTimeOption' => ['hasSideEffects' => false], - 'IntlCalendar::getTime' => ['hasSideEffects' => false], - 'IntlCalendar::getTimeZone' => ['hasSideEffects' => false], - 'IntlCalendar::getType' => ['hasSideEffects' => false], - 'IntlCalendar::getWeekendTransition' => ['hasSideEffects' => false], - 'IntlCalendar::inDaylightTime' => ['hasSideEffects' => false], - 'IntlCalendar::isEquivalentTo' => ['hasSideEffects' => false], - 'IntlCalendar::isLenient' => ['hasSideEffects' => false], - 'IntlCalendar::isWeekend' => ['hasSideEffects' => false], - 'IntlCalendar::toDateTime' => ['hasSideEffects' => false], - 'IntlChar::hasBinaryProperty' => ['hasSideEffects' => false], - 'IntlCodePointBreakIterator::getLastCodePoint' => ['hasSideEffects' => false], - 'IntlDateFormatter::__construct' => ['hasSideEffects' => false], - 'IntlDateFormatter::getCalendar' => ['hasSideEffects' => false], - 'IntlDateFormatter::getCalendarObject' => ['hasSideEffects' => false], - 'IntlDateFormatter::getDateType' => ['hasSideEffects' => false], - 'IntlDateFormatter::getErrorCode' => ['hasSideEffects' => false], - 'IntlDateFormatter::getErrorMessage' => ['hasSideEffects' => false], - 'IntlDateFormatter::getLocale' => ['hasSideEffects' => false], - 'IntlDateFormatter::getPattern' => ['hasSideEffects' => false], - 'IntlDateFormatter::getTimeType' => ['hasSideEffects' => false], - 'IntlDateFormatter::getTimeZone' => ['hasSideEffects' => false], - 'IntlDateFormatter::getTimeZoneId' => ['hasSideEffects' => false], - 'IntlDateFormatter::isLenient' => ['hasSideEffects' => false], - 'IntlGregorianCalendar::getGregorianChange' => ['hasSideEffects' => false], - 'IntlGregorianCalendar::isLeapYear' => ['hasSideEffects' => false], - 'IntlPartsIterator::getBreakIterator' => ['hasSideEffects' => false], - 'IntlRuleBasedBreakIterator::__construct' => ['hasSideEffects' => false], - 'IntlRuleBasedBreakIterator::getBinaryRules' => ['hasSideEffects' => false], - 'IntlRuleBasedBreakIterator::getRuleStatus' => ['hasSideEffects' => false], - 'IntlRuleBasedBreakIterator::getRuleStatusVec' => ['hasSideEffects' => false], - 'IntlRuleBasedBreakIterator::getRules' => ['hasSideEffects' => false], - 'IntlTimeZone::getDSTSavings' => ['hasSideEffects' => false], - 'IntlTimeZone::getDisplayName' => ['hasSideEffects' => false], - 'IntlTimeZone::getErrorCode' => ['hasSideEffects' => false], - 'IntlTimeZone::getErrorMessage' => ['hasSideEffects' => false], - 'IntlTimeZone::getID' => ['hasSideEffects' => false], - 'IntlTimeZone::getRawOffset' => ['hasSideEffects' => false], - 'IntlTimeZone::hasSameRules' => ['hasSideEffects' => false], - 'IntlTimeZone::toDateTimeZone' => ['hasSideEffects' => false], - 'JsonIncrementalParser::__construct' => ['hasSideEffects' => false], - 'JsonIncrementalParser::get' => ['hasSideEffects' => false], - 'JsonIncrementalParser::getError' => ['hasSideEffects' => false], - 'MemcachedException::__construct' => ['hasSideEffects' => false], - 'MessageFormatter::__construct' => ['hasSideEffects' => false], - 'MessageFormatter::format' => ['hasSideEffects' => false], - 'MessageFormatter::getErrorCode' => ['hasSideEffects' => false], - 'MessageFormatter::getErrorMessage' => ['hasSideEffects' => false], - 'MessageFormatter::getLocale' => ['hasSideEffects' => false], - 'MessageFormatter::getPattern' => ['hasSideEffects' => false], - 'MessageFormatter::parse' => ['hasSideEffects' => false], - 'NumberFormatter::__construct' => ['hasSideEffects' => false], - 'NumberFormatter::format' => ['hasSideEffects' => false], - 'NumberFormatter::formatCurrency' => ['hasSideEffects' => false], - 'NumberFormatter::getAttribute' => ['hasSideEffects' => false], - 'NumberFormatter::getErrorCode' => ['hasSideEffects' => false], - 'NumberFormatter::getErrorMessage' => ['hasSideEffects' => false], - 'NumberFormatter::getLocale' => ['hasSideEffects' => false], - 'NumberFormatter::getPattern' => ['hasSideEffects' => false], - 'NumberFormatter::getSymbol' => ['hasSideEffects' => false], - 'NumberFormatter::getTextAttribute' => ['hasSideEffects' => false], - 'ReflectionAttribute::getArguments' => ['hasSideEffects' => false], - 'ReflectionAttribute::getName' => ['hasSideEffects' => false], - 'ReflectionAttribute::getTarget' => ['hasSideEffects' => false], - 'ReflectionAttribute::isRepeated' => ['hasSideEffects' => false], - 'ReflectionClass::getAttributes' => ['hasSideEffects' => false], - 'ReflectionClass::getConstant' => ['hasSideEffects' => false], - 'ReflectionClass::getConstants' => ['hasSideEffects' => false], - 'ReflectionClass::getConstructor' => ['hasSideEffects' => false], - 'ReflectionClass::getDefaultProperties' => ['hasSideEffects' => false], - 'ReflectionClass::getDocComment' => ['hasSideEffects' => false], - 'ReflectionClass::getEndLine' => ['hasSideEffects' => false], - 'ReflectionClass::getExtension' => ['hasSideEffects' => false], - 'ReflectionClass::getExtensionName' => ['hasSideEffects' => false], - 'ReflectionClass::getFileName' => ['hasSideEffects' => false], - 'ReflectionClass::getInterfaceNames' => ['hasSideEffects' => false], - 'ReflectionClass::getInterfaces' => ['hasSideEffects' => false], - 'ReflectionClass::getMethod' => ['hasSideEffects' => false], - 'ReflectionClass::getMethods' => ['hasSideEffects' => false], - 'ReflectionClass::getModifiers' => ['hasSideEffects' => false], - 'ReflectionClass::getName' => ['hasSideEffects' => false], - 'ReflectionClass::getNamespaceName' => ['hasSideEffects' => false], - 'ReflectionClass::getParentClass' => ['hasSideEffects' => false], - 'ReflectionClass::getProperties' => ['hasSideEffects' => false], - 'ReflectionClass::getProperty' => ['hasSideEffects' => false], - 'ReflectionClass::getReflectionConstant' => ['hasSideEffects' => false], - 'ReflectionClass::getReflectionConstants' => ['hasSideEffects' => false], - 'ReflectionClass::getShortName' => ['hasSideEffects' => false], - 'ReflectionClass::getStartLine' => ['hasSideEffects' => false], - 'ReflectionClass::getStaticProperties' => ['hasSideEffects' => false], - 'ReflectionClass::getStaticPropertyValue' => ['hasSideEffects' => false], - 'ReflectionClass::getTraitAliases' => ['hasSideEffects' => false], - 'ReflectionClass::getTraitNames' => ['hasSideEffects' => false], - 'ReflectionClass::getTraits' => ['hasSideEffects' => false], - 'ReflectionClass::isAbstract' => ['hasSideEffects' => false], - 'ReflectionClass::isAnonymous' => ['hasSideEffects' => false], - 'ReflectionClass::isCloneable' => ['hasSideEffects' => false], - 'ReflectionClass::isFinal' => ['hasSideEffects' => false], - 'ReflectionClass::isInstance' => ['hasSideEffects' => false], - 'ReflectionClass::isInstantiable' => ['hasSideEffects' => false], - 'ReflectionClass::isInterface' => ['hasSideEffects' => false], - 'ReflectionClass::isInternal' => ['hasSideEffects' => false], - 'ReflectionClass::isIterable' => ['hasSideEffects' => false], - 'ReflectionClass::isIterateable' => ['hasSideEffects' => false], - 'ReflectionClass::isSubclassOf' => ['hasSideEffects' => false], - 'ReflectionClass::isTrait' => ['hasSideEffects' => false], - 'ReflectionClass::isUserDefined' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getAttributes' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getDeclaringClass' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getDocComment' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getModifiers' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getName' => ['hasSideEffects' => false], - 'ReflectionClassConstant::getValue' => ['hasSideEffects' => false], - 'ReflectionClassConstant::isPrivate' => ['hasSideEffects' => false], - 'ReflectionClassConstant::isProtected' => ['hasSideEffects' => false], - 'ReflectionClassConstant::isPublic' => ['hasSideEffects' => false], - 'ReflectionExtension::getClassNames' => ['hasSideEffects' => false], - 'ReflectionExtension::getClasses' => ['hasSideEffects' => false], - 'ReflectionExtension::getConstants' => ['hasSideEffects' => false], - 'ReflectionExtension::getDependencies' => ['hasSideEffects' => false], - 'ReflectionExtension::getFunctions' => ['hasSideEffects' => false], - 'ReflectionExtension::getINIEntries' => ['hasSideEffects' => false], - 'ReflectionExtension::getName' => ['hasSideEffects' => false], - 'ReflectionExtension::getVersion' => ['hasSideEffects' => false], - 'ReflectionExtension::isPersistent' => ['hasSideEffects' => false], - 'ReflectionExtension::isTemporary' => ['hasSideEffects' => false], - 'ReflectionFunction::getClosure' => ['hasSideEffects' => false], - 'ReflectionFunction::isDisabled' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getAttributes' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getClosureScopeClass' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getClosureThis' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getDocComment' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getEndLine' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getExtension' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getExtensionName' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getFileName' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getName' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getNamespaceName' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getNumberOfParameters' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getNumberOfRequiredParameters' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getParameters' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getReturnType' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getShortName' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getStartLine' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::getStaticVariables' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isClosure' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isDeprecated' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isGenerator' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isInternal' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isUserDefined' => ['hasSideEffects' => false], - 'ReflectionFunctionAbstract::isVariadic' => ['hasSideEffects' => false], - 'ReflectionGenerator::getExecutingFile' => ['hasSideEffects' => false], - 'ReflectionGenerator::getExecutingGenerator' => ['hasSideEffects' => false], - 'ReflectionGenerator::getExecutingLine' => ['hasSideEffects' => false], - 'ReflectionGenerator::getFunction' => ['hasSideEffects' => false], - 'ReflectionGenerator::getThis' => ['hasSideEffects' => false], - 'ReflectionGenerator::getTrace' => ['hasSideEffects' => false], - 'ReflectionMethod::getClosure' => ['hasSideEffects' => false], - 'ReflectionMethod::getDeclaringClass' => ['hasSideEffects' => false], - 'ReflectionMethod::getModifiers' => ['hasSideEffects' => false], - 'ReflectionMethod::getPrototype' => ['hasSideEffects' => false], - 'ReflectionMethod::isAbstract' => ['hasSideEffects' => false], - 'ReflectionMethod::isConstructor' => ['hasSideEffects' => false], - 'ReflectionMethod::isDestructor' => ['hasSideEffects' => false], - 'ReflectionMethod::isFinal' => ['hasSideEffects' => false], - 'ReflectionMethod::isPrivate' => ['hasSideEffects' => false], - 'ReflectionMethod::isProtected' => ['hasSideEffects' => false], - 'ReflectionMethod::isPublic' => ['hasSideEffects' => false], - 'ReflectionMethod::isStatic' => ['hasSideEffects' => false], - 'ReflectionNamedType::getName' => ['hasSideEffects' => false], - 'ReflectionNamedType::isBuiltin' => ['hasSideEffects' => false], - 'ReflectionParameter::getAttributes' => ['hasSideEffects' => false], - 'ReflectionParameter::getClass' => ['hasSideEffects' => false], - 'ReflectionParameter::getDeclaringClass' => ['hasSideEffects' => false], - 'ReflectionParameter::getDeclaringFunction' => ['hasSideEffects' => false], - 'ReflectionParameter::getDefaultValue' => ['hasSideEffects' => false], - 'ReflectionParameter::getDefaultValueConstantName' => ['hasSideEffects' => false], - 'ReflectionParameter::getName' => ['hasSideEffects' => false], - 'ReflectionParameter::getPosition' => ['hasSideEffects' => false], - 'ReflectionParameter::getType' => ['hasSideEffects' => false], - 'ReflectionParameter::isArray' => ['hasSideEffects' => false], - 'ReflectionParameter::isCallable' => ['hasSideEffects' => false], - 'ReflectionParameter::isDefaultValueAvailable' => ['hasSideEffects' => false], - 'ReflectionParameter::isDefaultValueConstant' => ['hasSideEffects' => false], - 'ReflectionParameter::isOptional' => ['hasSideEffects' => false], - 'ReflectionParameter::isPassedByReference' => ['hasSideEffects' => false], - 'ReflectionParameter::isPromoted' => ['hasSideEffects' => false], - 'ReflectionParameter::isVariadic' => ['hasSideEffects' => false], - 'ReflectionProperty::getAttributes' => ['hasSideEffects' => false], - 'ReflectionProperty::getDeclaringClass' => ['hasSideEffects' => false], - 'ReflectionProperty::getDefaultValue' => ['hasSideEffects' => false], - 'ReflectionProperty::getDocComment' => ['hasSideEffects' => false], - 'ReflectionProperty::getModifiers' => ['hasSideEffects' => false], - 'ReflectionProperty::getName' => ['hasSideEffects' => false], - 'ReflectionProperty::getType' => ['hasSideEffects' => false], - 'ReflectionProperty::getValue' => ['hasSideEffects' => false], - 'ReflectionProperty::isDefault' => ['hasSideEffects' => false], - 'ReflectionProperty::isInitialized' => ['hasSideEffects' => false], - 'ReflectionProperty::isPrivate' => ['hasSideEffects' => false], - 'ReflectionProperty::isPromoted' => ['hasSideEffects' => false], - 'ReflectionProperty::isProtected' => ['hasSideEffects' => false], - 'ReflectionProperty::isPublic' => ['hasSideEffects' => false], - 'ReflectionProperty::isStatic' => ['hasSideEffects' => false], - 'ReflectionReference::getId' => ['hasSideEffects' => false], - 'ReflectionType::isBuiltin' => ['hasSideEffects' => false], - 'ReflectionUnionType::getTypes' => ['hasSideEffects' => false], - 'ReflectionZendExtension::getAuthor' => ['hasSideEffects' => false], - 'ReflectionZendExtension::getCopyright' => ['hasSideEffects' => false], - 'ReflectionZendExtension::getName' => ['hasSideEffects' => false], - 'ReflectionZendExtension::getURL' => ['hasSideEffects' => false], - 'ReflectionZendExtension::getVersion' => ['hasSideEffects' => false], - 'ResourceBundle::__construct' => ['hasSideEffects' => false], - 'ResourceBundle::count' => ['hasSideEffects' => false], - 'ResourceBundle::get' => ['hasSideEffects' => false], - 'ResourceBundle::getErrorCode' => ['hasSideEffects' => false], - 'ResourceBundle::getErrorMessage' => ['hasSideEffects' => false], - 'ResourceBundle::getIterator' => ['hasSideEffects' => false], - 'SQLiteException::__construct' => ['hasSideEffects' => false], - 'SimpleXMLElement::__construct' => ['hasSideEffects' => false], - 'SimpleXMLElement::children' => ['hasSideEffects' => false], - 'SimpleXMLElement::count' => ['hasSideEffects' => false], - 'SimpleXMLElement::current' => ['hasSideEffects' => false], - 'SimpleXMLElement::getChildren' => ['hasSideEffects' => false], - 'SimpleXMLElement::getDocNamespaces' => ['hasSideEffects' => false], - 'SimpleXMLElement::getName' => ['hasSideEffects' => false], - 'SimpleXMLElement::getNamespaces' => ['hasSideEffects' => false], - 'SimpleXMLElement::hasChildren' => ['hasSideEffects' => false], - 'SimpleXMLElement::offsetExists' => ['hasSideEffects' => false], - 'SimpleXMLElement::offsetGet' => ['hasSideEffects' => false], - 'SimpleXMLElement::valid' => ['hasSideEffects' => false], - 'SimpleXMLIterator::count' => ['hasSideEffects' => false], - 'SimpleXMLIterator::current' => ['hasSideEffects' => false], - 'SimpleXMLIterator::getChildren' => ['hasSideEffects' => false], - 'SimpleXMLIterator::hasChildren' => ['hasSideEffects' => false], - 'SimpleXMLIterator::valid' => ['hasSideEffects' => false], - 'SoapFault::__construct' => ['hasSideEffects' => false], - 'Spoofchecker::__construct' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::getFQN' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::getTypeNameFromNode' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::hasMutedProblem' => ['hasSideEffects' => false], - 'StubTests\\Model\\StubsContainer::getClass' => ['hasSideEffects' => false], - 'StubTests\\Model\\StubsContainer::getInterface' => ['hasSideEffects' => false], - 'StubTests\\Parsers\\ExpectedFunctionArgumentsInfo::__toString' => ['hasSideEffects' => false], - 'StubTests\\Parsers\\Visitors\\CoreStubASTVisitor::__construct' => ['hasSideEffects' => false], - 'StubTests\\StubsParameterNamesTest::printParameters' => ['hasSideEffects' => false], - 'StubTests\\StubsTest::getParameterRepresentation' => ['hasSideEffects' => false], - 'Transliterator::createInverse' => ['hasSideEffects' => false], - 'Transliterator::getErrorCode' => ['hasSideEffects' => false], - 'Transliterator::getErrorMessage' => ['hasSideEffects' => false], - 'Transliterator::transliterate' => ['hasSideEffects' => false], - 'UConverter::__construct' => ['hasSideEffects' => false], - 'UConverter::convert' => ['hasSideEffects' => false], - 'UConverter::getDestinationEncoding' => ['hasSideEffects' => false], - 'UConverter::getDestinationType' => ['hasSideEffects' => false], - 'UConverter::getErrorCode' => ['hasSideEffects' => false], - 'UConverter::getErrorMessage' => ['hasSideEffects' => false], - 'UConverter::getSourceEncoding' => ['hasSideEffects' => false], - 'UConverter::getSourceType' => ['hasSideEffects' => false], - 'UConverter::getStandards' => ['hasSideEffects' => false], - 'UConverter::getSubstChars' => ['hasSideEffects' => false], - 'UConverter::reasonText' => ['hasSideEffects' => false], - 'Zookeeper::getAcl' => ['hasSideEffects' => false], - 'Zookeeper::getChildren' => ['hasSideEffects' => false], - 'Zookeeper::getClientId' => ['hasSideEffects' => false], - 'Zookeeper::getRecvTimeout' => ['hasSideEffects' => false], - 'Zookeeper::getState' => ['hasSideEffects' => false], - '_' => ['hasSideEffects' => false], - 'abs' => ['hasSideEffects' => false], - 'acos' => ['hasSideEffects' => false], - 'acosh' => ['hasSideEffects' => false], - 'addcslashes' => ['hasSideEffects' => false], - 'addslashes' => ['hasSideEffects' => false], - 'apache_get_modules' => ['hasSideEffects' => false], - 'apache_get_version' => ['hasSideEffects' => false], - 'apache_getenv' => ['hasSideEffects' => false], - 'apache_request_headers' => ['hasSideEffects' => false], - 'array_change_key_case' => ['hasSideEffects' => false], - 'array_chunk' => ['hasSideEffects' => false], - 'array_column' => ['hasSideEffects' => false], - 'array_combine' => ['hasSideEffects' => false], - 'array_count_values' => ['hasSideEffects' => false], - 'array_diff' => ['hasSideEffects' => false], - 'array_diff_assoc' => ['hasSideEffects' => false], - 'array_diff_key' => ['hasSideEffects' => false], - 'array_diff_uassoc' => ['hasSideEffects' => false], - 'array_diff_ukey' => ['hasSideEffects' => false], - 'array_fill' => ['hasSideEffects' => false], - 'array_fill_keys' => ['hasSideEffects' => false], - 'array_flip' => ['hasSideEffects' => false], - 'array_intersect' => ['hasSideEffects' => false], - 'array_intersect_assoc' => ['hasSideEffects' => false], - 'array_intersect_key' => ['hasSideEffects' => false], - 'array_intersect_uassoc' => ['hasSideEffects' => false], - 'array_intersect_ukey' => ['hasSideEffects' => false], - 'array_key_exists' => ['hasSideEffects' => false], - 'array_key_first' => ['hasSideEffects' => false], - 'array_key_last' => ['hasSideEffects' => false], - 'array_keys' => ['hasSideEffects' => false], - 'array_merge' => ['hasSideEffects' => false], - 'array_merge_recursive' => ['hasSideEffects' => false], - 'array_pad' => ['hasSideEffects' => false], - 'array_product' => ['hasSideEffects' => false], - 'array_rand' => ['hasSideEffects' => false], - 'array_replace' => ['hasSideEffects' => false], - 'array_replace_recursive' => ['hasSideEffects' => false], - 'array_reverse' => ['hasSideEffects' => false], - 'array_search' => ['hasSideEffects' => false], - 'array_slice' => ['hasSideEffects' => false], - 'array_sum' => ['hasSideEffects' => false], - 'array_udiff' => ['hasSideEffects' => false], - 'array_udiff_assoc' => ['hasSideEffects' => false], - 'array_udiff_uassoc' => ['hasSideEffects' => false], - 'array_uintersect' => ['hasSideEffects' => false], - 'array_uintersect_assoc' => ['hasSideEffects' => false], - 'array_uintersect_uassoc' => ['hasSideEffects' => false], - 'array_unique' => ['hasSideEffects' => false], - 'array_values' => ['hasSideEffects' => false], - 'asin' => ['hasSideEffects' => false], - 'asinh' => ['hasSideEffects' => false], - 'atan' => ['hasSideEffects' => false], - 'atan2' => ['hasSideEffects' => false], - 'atanh' => ['hasSideEffects' => false], - 'base64_decode' => ['hasSideEffects' => false], - 'base64_encode' => ['hasSideEffects' => false], - 'base_convert' => ['hasSideEffects' => false], - 'basename' => ['hasSideEffects' => false], - 'bcadd' => ['hasSideEffects' => false], - 'bccomp' => ['hasSideEffects' => false], - 'bcdiv' => ['hasSideEffects' => false], - 'bcmod' => ['hasSideEffects' => false], - 'bcmul' => ['hasSideEffects' => false], - 'bcpow' => ['hasSideEffects' => false], - 'bcpowmod' => ['hasSideEffects' => false], - 'bcsqrt' => ['hasSideEffects' => false], - 'bcsub' => ['hasSideEffects' => false], - 'bin2hex' => ['hasSideEffects' => false], - 'bindec' => ['hasSideEffects' => false], - 'boolval' => ['hasSideEffects' => false], - 'bzcompress' => ['hasSideEffects' => false], - 'bzdecompress' => ['hasSideEffects' => false], - 'bzerrno' => ['hasSideEffects' => false], - 'bzerror' => ['hasSideEffects' => false], - 'bzerrstr' => ['hasSideEffects' => false], - 'bzopen' => ['hasSideEffects' => false], - 'ceil' => ['hasSideEffects' => false], - 'checkdate' => ['hasSideEffects' => false], - 'checkdnsrr' => ['hasSideEffects' => false], - 'chop' => ['hasSideEffects' => false], - 'chr' => ['hasSideEffects' => false], - 'chunk_split' => ['hasSideEffects' => false], - 'class_implements' => ['hasSideEffects' => false], - 'class_parents' => ['hasSideEffects' => false], - 'cli_get_process_title' => ['hasSideEffects' => false], - 'collator_compare' => ['hasSideEffects' => false], - 'collator_create' => ['hasSideEffects' => false], - 'collator_get_attribute' => ['hasSideEffects' => false], - 'collator_get_error_code' => ['hasSideEffects' => false], - 'collator_get_error_message' => ['hasSideEffects' => false], - 'collator_get_locale' => ['hasSideEffects' => false], - 'collator_get_sort_key' => ['hasSideEffects' => false], - 'collator_get_strength' => ['hasSideEffects' => false], - 'compact' => ['hasSideEffects' => false], - 'connection_aborted' => ['hasSideEffects' => false], - 'connection_status' => ['hasSideEffects' => false], - 'constant' => ['hasSideEffects' => false], - 'convert_cyr_string' => ['hasSideEffects' => false], - 'convert_uudecode' => ['hasSideEffects' => false], - 'convert_uuencode' => ['hasSideEffects' => false], - 'cos' => ['hasSideEffects' => false], - 'cosh' => ['hasSideEffects' => false], - 'count' => ['hasSideEffects' => false], - 'count_chars' => ['hasSideEffects' => false], - 'crc32' => ['hasSideEffects' => false], - 'crypt' => ['hasSideEffects' => false], - 'ctype_alnum' => ['hasSideEffects' => false], - 'ctype_alpha' => ['hasSideEffects' => false], - 'ctype_cntrl' => ['hasSideEffects' => false], - 'ctype_digit' => ['hasSideEffects' => false], - 'ctype_graph' => ['hasSideEffects' => false], - 'ctype_lower' => ['hasSideEffects' => false], - 'ctype_print' => ['hasSideEffects' => false], - 'ctype_punct' => ['hasSideEffects' => false], - 'ctype_space' => ['hasSideEffects' => false], - 'ctype_upper' => ['hasSideEffects' => false], - 'ctype_xdigit' => ['hasSideEffects' => false], - 'curl_copy_handle' => ['hasSideEffects' => false], - 'curl_errno' => ['hasSideEffects' => false], - 'curl_error' => ['hasSideEffects' => false], - 'curl_escape' => ['hasSideEffects' => false], - 'curl_file_create' => ['hasSideEffects' => false], - 'curl_getinfo' => ['hasSideEffects' => false], - 'curl_multi_errno' => ['hasSideEffects' => false], - 'curl_multi_getcontent' => ['hasSideEffects' => false], - 'curl_multi_info_read' => ['hasSideEffects' => false], - 'curl_share_errno' => ['hasSideEffects' => false], - 'curl_share_strerror' => ['hasSideEffects' => false], - 'curl_strerror' => ['hasSideEffects' => false], - 'curl_unescape' => ['hasSideEffects' => false], - 'curl_version' => ['hasSideEffects' => false], - 'current' => ['hasSideEffects' => false], - 'date' => ['hasSideEffects' => false], - 'date_create' => ['hasSideEffects' => false], - 'date_create_from_format' => ['hasSideEffects' => false], - 'date_create_immutable' => ['hasSideEffects' => false], - 'date_create_immutable_from_format' => ['hasSideEffects' => false], - 'date_default_timezone_get' => ['hasSideEffects' => false], - 'date_diff' => ['hasSideEffects' => false], - 'date_format' => ['hasSideEffects' => false], - 'date_get_last_errors' => ['hasSideEffects' => false], - 'date_interval_create_from_date_string' => ['hasSideEffects' => false], - 'date_interval_format' => ['hasSideEffects' => false], - 'date_offset_get' => ['hasSideEffects' => false], - 'date_parse' => ['hasSideEffects' => false], - 'date_parse_from_format' => ['hasSideEffects' => false], - 'date_sun_info' => ['hasSideEffects' => false], - 'date_sunrise' => ['hasSideEffects' => false], - 'date_sunset' => ['hasSideEffects' => false], - 'date_timestamp_get' => ['hasSideEffects' => false], - 'date_timezone_get' => ['hasSideEffects' => false], - 'datefmt_create' => ['hasSideEffects' => false], - 'datefmt_format' => ['hasSideEffects' => false], - 'datefmt_format_object' => ['hasSideEffects' => false], - 'datefmt_get_calendar' => ['hasSideEffects' => false], - 'datefmt_get_calendar_object' => ['hasSideEffects' => false], - 'datefmt_get_datetype' => ['hasSideEffects' => false], - 'datefmt_get_error_code' => ['hasSideEffects' => false], - 'datefmt_get_error_message' => ['hasSideEffects' => false], - 'datefmt_get_locale' => ['hasSideEffects' => false], - 'datefmt_get_pattern' => ['hasSideEffects' => false], - 'datefmt_get_timetype' => ['hasSideEffects' => false], - 'datefmt_get_timezone' => ['hasSideEffects' => false], - 'datefmt_get_timezone_id' => ['hasSideEffects' => false], - 'datefmt_is_lenient' => ['hasSideEffects' => false], - 'dcngettext' => ['hasSideEffects' => false], - 'decbin' => ['hasSideEffects' => false], - 'dechex' => ['hasSideEffects' => false], - 'decoct' => ['hasSideEffects' => false], - 'defined' => ['hasSideEffects' => false], - 'deflate_init' => ['hasSideEffects' => false], - 'deg2rad' => ['hasSideEffects' => false], - 'dirname' => ['hasSideEffects' => false], - 'disk_free_space' => ['hasSideEffects' => false], - 'diskfreespace' => ['hasSideEffects' => false], - 'dngettext' => ['hasSideEffects' => false], - 'doubleval' => ['hasSideEffects' => false], - 'error_get_last' => ['hasSideEffects' => false], - 'escapeshellarg' => ['hasSideEffects' => false], - 'escapeshellcmd' => ['hasSideEffects' => false], - 'exp' => ['hasSideEffects' => false], - 'explode' => ['hasSideEffects' => false], - 'expm1' => ['hasSideEffects' => false], - 'extension_loaded' => ['hasSideEffects' => false], - 'fdiv' => ['hasSideEffects' => false], - 'file_exists' => ['hasSideEffects' => false], - 'fileatime' => ['hasSideEffects' => false], - 'filectime' => ['hasSideEffects' => false], - 'filegroup' => ['hasSideEffects' => false], - 'fileinode' => ['hasSideEffects' => false], - 'filemtime' => ['hasSideEffects' => false], - 'fileowner' => ['hasSideEffects' => false], - 'fileperms' => ['hasSideEffects' => false], - 'filesize' => ['hasSideEffects' => false], - 'filetype' => ['hasSideEffects' => false], - 'filter_has_var' => ['hasSideEffects' => false], - 'filter_id' => ['hasSideEffects' => false], - 'filter_input' => ['hasSideEffects' => false], - 'filter_input_array' => ['hasSideEffects' => false], - 'filter_list' => ['hasSideEffects' => false], - 'filter_var' => ['hasSideEffects' => false], - 'filter_var_array' => ['hasSideEffects' => false], - 'finfo::buffer' => ['hasSideEffects' => false], - 'finfo::file' => ['hasSideEffects' => false], - 'floatval' => ['hasSideEffects' => false], - 'floor' => ['hasSideEffects' => false], - 'fmod' => ['hasSideEffects' => false], - 'ftok' => ['hasSideEffects' => false], - 'func_get_arg' => ['hasSideEffects' => false], - 'func_get_args' => ['hasSideEffects' => false], - 'func_num_args' => ['hasSideEffects' => false], - 'function_exists' => ['hasSideEffects' => false], - 'gc_enabled' => ['hasSideEffects' => false], - 'gc_status' => ['hasSideEffects' => false], - 'gd_info' => ['hasSideEffects' => false], - 'geoip_continent_code_by_name' => ['hasSideEffects' => false], - 'geoip_country_code3_by_name' => ['hasSideEffects' => false], - 'geoip_country_code_by_name' => ['hasSideEffects' => false], - 'geoip_country_name_by_name' => ['hasSideEffects' => false], - 'geoip_database_info' => ['hasSideEffects' => false], - 'geoip_db_avail' => ['hasSideEffects' => false], - 'geoip_db_filename' => ['hasSideEffects' => false], - 'geoip_db_get_all_info' => ['hasSideEffects' => false], - 'geoip_id_by_name' => ['hasSideEffects' => false], - 'geoip_isp_by_name' => ['hasSideEffects' => false], - 'geoip_org_by_name' => ['hasSideEffects' => false], - 'geoip_record_by_name' => ['hasSideEffects' => false], - 'geoip_region_by_name' => ['hasSideEffects' => false], - 'geoip_region_name_by_code' => ['hasSideEffects' => false], - 'geoip_time_zone_by_country_and_region' => ['hasSideEffects' => false], - 'get_browser' => ['hasSideEffects' => false], - 'get_called_class' => ['hasSideEffects' => false], - 'get_cfg_var' => ['hasSideEffects' => false], - 'get_class' => ['hasSideEffects' => false], - 'get_class_methods' => ['hasSideEffects' => false], - 'get_class_vars' => ['hasSideEffects' => false], - 'get_current_user' => ['hasSideEffects' => false], - 'get_debug_type' => ['hasSideEffects' => false], - 'get_declared_classes' => ['hasSideEffects' => false], - 'get_declared_interfaces' => ['hasSideEffects' => false], - 'get_declared_traits' => ['hasSideEffects' => false], - 'get_defined_constants' => ['hasSideEffects' => false], - 'get_defined_functions' => ['hasSideEffects' => false], - 'get_defined_vars' => ['hasSideEffects' => false], - 'get_extension_funcs' => ['hasSideEffects' => false], - 'get_headers' => ['hasSideEffects' => false], - 'get_html_translation_table' => ['hasSideEffects' => false], - 'get_include_path' => ['hasSideEffects' => false], - 'get_included_files' => ['hasSideEffects' => false], - 'get_loaded_extensions' => ['hasSideEffects' => false], - 'get_meta_tags' => ['hasSideEffects' => false], - 'get_object_vars' => ['hasSideEffects' => false], - 'get_parent_class' => ['hasSideEffects' => false], - 'get_required_files' => ['hasSideEffects' => false], - 'get_resource_id' => ['hasSideEffects' => false], - 'get_resources' => ['hasSideEffects' => false], - 'getallheaders' => ['hasSideEffects' => false], - 'getcwd' => ['hasSideEffects' => false], - 'getdate' => ['hasSideEffects' => false], - 'getenv' => ['hasSideEffects' => false], - 'gethostbyaddr' => ['hasSideEffects' => false], - 'gethostbyname' => ['hasSideEffects' => false], - 'gethostbynamel' => ['hasSideEffects' => false], - 'gethostname' => ['hasSideEffects' => false], - 'getlastmod' => ['hasSideEffects' => false], - 'getmygid' => ['hasSideEffects' => false], - 'getmyinode' => ['hasSideEffects' => false], - 'getmypid' => ['hasSideEffects' => false], - 'getmyuid' => ['hasSideEffects' => false], - 'getprotobyname' => ['hasSideEffects' => false], - 'getprotobynumber' => ['hasSideEffects' => false], - 'getrandmax' => ['hasSideEffects' => false], - 'getrusage' => ['hasSideEffects' => false], - 'getservbyname' => ['hasSideEffects' => false], - 'getservbyport' => ['hasSideEffects' => false], - 'gettext' => ['hasSideEffects' => false], - 'gettimeofday' => ['hasSideEffects' => false], - 'gettype' => ['hasSideEffects' => false], - 'glob' => ['hasSideEffects' => false], - 'gmdate' => ['hasSideEffects' => false], - 'gmmktime' => ['hasSideEffects' => false], - 'gmp_abs' => ['hasSideEffects' => false], - 'gmp_add' => ['hasSideEffects' => false], - 'gmp_and' => ['hasSideEffects' => false], - 'gmp_binomial' => ['hasSideEffects' => false], - 'gmp_cmp' => ['hasSideEffects' => false], - 'gmp_com' => ['hasSideEffects' => false], - 'gmp_div' => ['hasSideEffects' => false], - 'gmp_div_q' => ['hasSideEffects' => false], - 'gmp_div_qr' => ['hasSideEffects' => false], - 'gmp_div_r' => ['hasSideEffects' => false], - 'gmp_divexact' => ['hasSideEffects' => false], - 'gmp_export' => ['hasSideEffects' => false], - 'gmp_fact' => ['hasSideEffects' => false], - 'gmp_gcd' => ['hasSideEffects' => false], - 'gmp_gcdext' => ['hasSideEffects' => false], - 'gmp_hamdist' => ['hasSideEffects' => false], - 'gmp_import' => ['hasSideEffects' => false], - 'gmp_init' => ['hasSideEffects' => false], - 'gmp_intval' => ['hasSideEffects' => false], - 'gmp_invert' => ['hasSideEffects' => false], - 'gmp_jacobi' => ['hasSideEffects' => false], - 'gmp_kronecker' => ['hasSideEffects' => false], - 'gmp_lcm' => ['hasSideEffects' => false], - 'gmp_legendre' => ['hasSideEffects' => false], - 'gmp_mod' => ['hasSideEffects' => false], - 'gmp_mul' => ['hasSideEffects' => false], - 'gmp_neg' => ['hasSideEffects' => false], - 'gmp_nextprime' => ['hasSideEffects' => false], - 'gmp_or' => ['hasSideEffects' => false], - 'gmp_perfect_power' => ['hasSideEffects' => false], - 'gmp_perfect_square' => ['hasSideEffects' => false], - 'gmp_popcount' => ['hasSideEffects' => false], - 'gmp_pow' => ['hasSideEffects' => false], - 'gmp_powm' => ['hasSideEffects' => false], - 'gmp_prob_prime' => ['hasSideEffects' => false], - 'gmp_random' => ['hasSideEffects' => false], - 'gmp_random_bits' => ['hasSideEffects' => false], - 'gmp_random_range' => ['hasSideEffects' => false], - 'gmp_root' => ['hasSideEffects' => false], - 'gmp_rootrem' => ['hasSideEffects' => false], - 'gmp_scan0' => ['hasSideEffects' => false], - 'gmp_scan1' => ['hasSideEffects' => false], - 'gmp_sign' => ['hasSideEffects' => false], - 'gmp_sqrt' => ['hasSideEffects' => false], - 'gmp_sqrtrem' => ['hasSideEffects' => false], - 'gmp_strval' => ['hasSideEffects' => false], - 'gmp_sub' => ['hasSideEffects' => false], - 'gmp_testbit' => ['hasSideEffects' => false], - 'gmp_xor' => ['hasSideEffects' => false], - 'grapheme_stripos' => ['hasSideEffects' => false], - 'grapheme_stristr' => ['hasSideEffects' => false], - 'grapheme_strlen' => ['hasSideEffects' => false], - 'grapheme_strpos' => ['hasSideEffects' => false], - 'grapheme_strripos' => ['hasSideEffects' => false], - 'grapheme_strrpos' => ['hasSideEffects' => false], - 'grapheme_strstr' => ['hasSideEffects' => false], - 'grapheme_substr' => ['hasSideEffects' => false], - 'gzcompress' => ['hasSideEffects' => false], - 'gzdecode' => ['hasSideEffects' => false], - 'gzdeflate' => ['hasSideEffects' => false], - 'gzencode' => ['hasSideEffects' => false], - 'gzinflate' => ['hasSideEffects' => false], - 'gzuncompress' => ['hasSideEffects' => false], - 'hash' => ['hasSideEffects' => false], - 'hash_algos' => ['hasSideEffects' => false], - 'hash_copy' => ['hasSideEffects' => false], - 'hash_equals' => ['hasSideEffects' => false], - 'hash_file' => ['hasSideEffects' => false], - 'hash_hkdf' => ['hasSideEffects' => false], - 'hash_hmac' => ['hasSideEffects' => false], - 'hash_hmac_algos' => ['hasSideEffects' => false], - 'hash_hmac_file' => ['hasSideEffects' => false], - 'hash_init' => ['hasSideEffects' => false], - 'hash_pbkdf2' => ['hasSideEffects' => false], - 'headers_list' => ['hasSideEffects' => false], - 'hebrev' => ['hasSideEffects' => false], - 'hexdec' => ['hasSideEffects' => false], - 'hrtime' => ['hasSideEffects' => false], - 'html_entity_decode' => ['hasSideEffects' => false], - 'htmlentities' => ['hasSideEffects' => false], - 'htmlspecialchars' => ['hasSideEffects' => false], - 'htmlspecialchars_decode' => ['hasSideEffects' => false], - 'http_build_cookie' => ['hasSideEffects' => false], - 'http_build_query' => ['hasSideEffects' => false], - 'http_build_str' => ['hasSideEffects' => false], - 'http_cache_etag' => ['hasSideEffects' => false], - 'http_cache_last_modified' => ['hasSideEffects' => false], - 'http_chunked_decode' => ['hasSideEffects' => false], - 'http_date' => ['hasSideEffects' => false], - 'http_deflate' => ['hasSideEffects' => false], - 'http_get_request_body' => ['hasSideEffects' => false], - 'http_get_request_body_stream' => ['hasSideEffects' => false], - 'http_get_request_headers' => ['hasSideEffects' => false], - 'http_inflate' => ['hasSideEffects' => false], - 'http_match_etag' => ['hasSideEffects' => false], - 'http_match_modified' => ['hasSideEffects' => false], - 'http_match_request_header' => ['hasSideEffects' => false], - 'http_parse_cookie' => ['hasSideEffects' => false], - 'http_parse_headers' => ['hasSideEffects' => false], - 'http_parse_message' => ['hasSideEffects' => false], - 'http_parse_params' => ['hasSideEffects' => false], - 'http_request_body_encode' => ['hasSideEffects' => false], - 'http_request_method_exists' => ['hasSideEffects' => false], - 'http_request_method_name' => ['hasSideEffects' => false], - 'http_support' => ['hasSideEffects' => false], - 'hypot' => ['hasSideEffects' => false], - 'iconv' => ['hasSideEffects' => false], - 'iconv_get_encoding' => ['hasSideEffects' => false], - 'iconv_mime_decode' => ['hasSideEffects' => false], - 'iconv_mime_decode_headers' => ['hasSideEffects' => false], - 'iconv_mime_encode' => ['hasSideEffects' => false], - 'iconv_strlen' => ['hasSideEffects' => false], - 'iconv_strpos' => ['hasSideEffects' => false], - 'iconv_strrpos' => ['hasSideEffects' => false], - 'iconv_substr' => ['hasSideEffects' => false], - 'idate' => ['hasSideEffects' => false], - 'image_type_to_extension' => ['hasSideEffects' => false], - 'image_type_to_mime_type' => ['hasSideEffects' => false], - 'imagecolorat' => ['hasSideEffects' => false], - 'imagecolorclosest' => ['hasSideEffects' => false], - 'imagecolorclosestalpha' => ['hasSideEffects' => false], - 'imagecolorclosesthwb' => ['hasSideEffects' => false], - 'imagecolorexact' => ['hasSideEffects' => false], - 'imagecolorexactalpha' => ['hasSideEffects' => false], - 'imagecolorresolve' => ['hasSideEffects' => false], - 'imagecolorresolvealpha' => ['hasSideEffects' => false], - 'imagecolorsforindex' => ['hasSideEffects' => false], - 'imagecolorstotal' => ['hasSideEffects' => false], - 'imagecreate' => ['hasSideEffects' => false], - 'imagecreatefromstring' => ['hasSideEffects' => false], - 'imagecreatetruecolor' => ['hasSideEffects' => false], - 'imagefontheight' => ['hasSideEffects' => false], - 'imagefontwidth' => ['hasSideEffects' => false], - 'imageftbbox' => ['hasSideEffects' => false], - 'imagegetinterpolation' => ['hasSideEffects' => false], - 'imagegrabscreen' => ['hasSideEffects' => false], - 'imagegrabwindow' => ['hasSideEffects' => false], - 'imageistruecolor' => ['hasSideEffects' => false], - 'imagesx' => ['hasSideEffects' => false], - 'imagesy' => ['hasSideEffects' => false], - 'imagettfbbox' => ['hasSideEffects' => false], - 'imagetypes' => ['hasSideEffects' => false], - 'implode' => ['hasSideEffects' => false], - 'in_array' => ['hasSideEffects' => false], - 'inet_ntop' => ['hasSideEffects' => false], - 'inet_pton' => ['hasSideEffects' => false], - 'inflate_get_read_len' => ['hasSideEffects' => false], - 'inflate_get_status' => ['hasSideEffects' => false], - 'inflate_init' => ['hasSideEffects' => false], - 'ini_get' => ['hasSideEffects' => false], - 'ini_get_all' => ['hasSideEffects' => false], - 'intcal_get_maximum' => ['hasSideEffects' => false], - 'intdiv' => ['hasSideEffects' => false], - 'intl_error_name' => ['hasSideEffects' => false], - 'intl_get' => ['hasSideEffects' => false], - 'intl_get_error_code' => ['hasSideEffects' => false], - 'intl_get_error_message' => ['hasSideEffects' => false], - 'intl_is_failure' => ['hasSideEffects' => false], - 'intlcal_after' => ['hasSideEffects' => false], - 'intlcal_before' => ['hasSideEffects' => false], - 'intlcal_create_instance' => ['hasSideEffects' => false], - 'intlcal_equals' => ['hasSideEffects' => false], - 'intlcal_field_difference' => ['hasSideEffects' => false], - 'intlcal_from_date_time' => ['hasSideEffects' => false], - 'intlcal_get' => ['hasSideEffects' => false], - 'intlcal_get_actual_maximum' => ['hasSideEffects' => false], - 'intlcal_get_actual_minimum' => ['hasSideEffects' => false], - 'intlcal_get_available_locales' => ['hasSideEffects' => false], - 'intlcal_get_day_of_week_type' => ['hasSideEffects' => false], - 'intlcal_get_error_code' => ['hasSideEffects' => false], - 'intlcal_get_error_message' => ['hasSideEffects' => false], - 'intlcal_get_first_day_of_week' => ['hasSideEffects' => false], - 'intlcal_get_greatest_minimum' => ['hasSideEffects' => false], - 'intlcal_get_keyword_values_for_locale' => ['hasSideEffects' => false], - 'intlcal_get_least_maximum' => ['hasSideEffects' => false], - 'intlcal_get_locale' => ['hasSideEffects' => false], - 'intlcal_get_maximum' => ['hasSideEffects' => false], - 'intlcal_get_minimal_days_in_first_week' => ['hasSideEffects' => false], - 'intlcal_get_minimum' => ['hasSideEffects' => false], - 'intlcal_get_now' => ['hasSideEffects' => false], - 'intlcal_get_repeated_wall_time_option' => ['hasSideEffects' => false], - 'intlcal_get_skipped_wall_time_option' => ['hasSideEffects' => false], - 'intlcal_get_time' => ['hasSideEffects' => false], - 'intlcal_get_time_zone' => ['hasSideEffects' => false], - 'intlcal_get_type' => ['hasSideEffects' => false], - 'intlcal_get_weekend_transition' => ['hasSideEffects' => false], - 'intlcal_greates_minimum' => ['hasSideEffects' => false], - 'intlcal_in_daylight_time' => ['hasSideEffects' => false], - 'intlcal_is_equivalent_to' => ['hasSideEffects' => false], - 'intlcal_is_lenient' => ['hasSideEffects' => false], - 'intlcal_is_set' => ['hasSideEffects' => false], - 'intlcal_is_weekend' => ['hasSideEffects' => false], - 'intlcal_to_date_time' => ['hasSideEffects' => false], - 'intlgregcal_create_instance' => ['hasSideEffects' => false], - 'intlgregcal_get_gregorian_change' => ['hasSideEffects' => false], - 'intlgregcal_is_leap_year' => ['hasSideEffects' => false], - 'intltz_count_equivalent_ids' => ['hasSideEffects' => false], - 'intltz_create_default' => ['hasSideEffects' => false], - 'intltz_create_enumeration' => ['hasSideEffects' => false], - 'intltz_create_time_zone' => ['hasSideEffects' => false], - 'intltz_create_time_zone_id_enumeration' => ['hasSideEffects' => false], - 'intltz_from_date_time_zone' => ['hasSideEffects' => false], - 'intltz_get_canonical_id' => ['hasSideEffects' => false], - 'intltz_get_display_name' => ['hasSideEffects' => false], - 'intltz_get_dst_savings' => ['hasSideEffects' => false], - 'intltz_get_equivalent_id' => ['hasSideEffects' => false], - 'intltz_get_error_code' => ['hasSideEffects' => false], - 'intltz_get_error_message' => ['hasSideEffects' => false], - 'intltz_get_gmt' => ['hasSideEffects' => false], - 'intltz_get_id' => ['hasSideEffects' => false], - 'intltz_get_offset' => ['hasSideEffects' => false], - 'intltz_get_raw_offset' => ['hasSideEffects' => false], - 'intltz_get_region' => ['hasSideEffects' => false], - 'intltz_get_tz_data_version' => ['hasSideEffects' => false], - 'intltz_get_unknown' => ['hasSideEffects' => false], - 'intltz_getgmt' => ['hasSideEffects' => false], - 'intltz_has_same_rules' => ['hasSideEffects' => false], - 'intltz_to_date_time_zone' => ['hasSideEffects' => false], - 'intltz_use_daylight_time' => ['hasSideEffects' => false], - 'intlz_create_default' => ['hasSideEffects' => false], - 'intval' => ['hasSideEffects' => false], - 'ip2long' => ['hasSideEffects' => false], - 'iptcparse' => ['hasSideEffects' => false], - 'is_a' => ['hasSideEffects' => false], - 'is_array' => ['hasSideEffects' => false], - 'is_bool' => ['hasSideEffects' => false], - 'is_countable' => ['hasSideEffects' => false], - 'is_dir' => ['hasSideEffects' => false], - 'is_double' => ['hasSideEffects' => false], - 'is_executable' => ['hasSideEffects' => false], - 'is_file' => ['hasSideEffects' => false], - 'is_finite' => ['hasSideEffects' => false], - 'is_float' => ['hasSideEffects' => false], - 'is_infinite' => ['hasSideEffects' => false], - 'is_int' => ['hasSideEffects' => false], - 'is_integer' => ['hasSideEffects' => false], - 'is_iterable' => ['hasSideEffects' => false], - 'is_link' => ['hasSideEffects' => false], - 'is_long' => ['hasSideEffects' => false], - 'is_nan' => ['hasSideEffects' => false], - 'is_null' => ['hasSideEffects' => false], - 'is_numeric' => ['hasSideEffects' => false], - 'is_object' => ['hasSideEffects' => false], - 'is_readable' => ['hasSideEffects' => false], - 'is_real' => ['hasSideEffects' => false], - 'is_resource' => ['hasSideEffects' => false], - 'is_scalar' => ['hasSideEffects' => false], - 'is_string' => ['hasSideEffects' => false], - 'is_subclass_of' => ['hasSideEffects' => false], - 'is_uploaded_file' => ['hasSideEffects' => false], - 'is_writable' => ['hasSideEffects' => false], - 'is_writeable' => ['hasSideEffects' => false], - 'iterator_count' => ['hasSideEffects' => false], - 'join' => ['hasSideEffects' => false], - 'json_last_error' => ['hasSideEffects' => false], - 'json_last_error_msg' => ['hasSideEffects' => false], - 'key' => ['hasSideEffects' => false], - 'key_exists' => ['hasSideEffects' => false], - 'lcfirst' => ['hasSideEffects' => false], - 'libxml_get_errors' => ['hasSideEffects' => false], - 'libxml_get_last_error' => ['hasSideEffects' => false], - 'linkinfo' => ['hasSideEffects' => false], - 'locale_accept_from_http' => ['hasSideEffects' => false], - 'locale_canonicalize' => ['hasSideEffects' => false], - 'locale_compose' => ['hasSideEffects' => false], - 'locale_filter_matches' => ['hasSideEffects' => false], - 'locale_get_all_variants' => ['hasSideEffects' => false], - 'locale_get_default' => ['hasSideEffects' => false], - 'locale_get_display_language' => ['hasSideEffects' => false], - 'locale_get_display_name' => ['hasSideEffects' => false], - 'locale_get_display_region' => ['hasSideEffects' => false], - 'locale_get_display_script' => ['hasSideEffects' => false], - 'locale_get_display_variant' => ['hasSideEffects' => false], - 'locale_get_keywords' => ['hasSideEffects' => false], - 'locale_get_primary_language' => ['hasSideEffects' => false], - 'locale_get_region' => ['hasSideEffects' => false], - 'locale_get_script' => ['hasSideEffects' => false], - 'locale_lookup' => ['hasSideEffects' => false], - 'locale_parse' => ['hasSideEffects' => false], - 'localeconv' => ['hasSideEffects' => false], - 'localtime' => ['hasSideEffects' => false], - 'log' => ['hasSideEffects' => false], - 'log10' => ['hasSideEffects' => false], - 'log1p' => ['hasSideEffects' => false], - 'long2ip' => ['hasSideEffects' => false], - 'lstat' => ['hasSideEffects' => false], - 'ltrim' => ['hasSideEffects' => false], - 'max' => ['hasSideEffects' => false], - 'mb_check_encoding' => ['hasSideEffects' => false], - 'mb_chr' => ['hasSideEffects' => false], - 'mb_convert_case' => ['hasSideEffects' => false], - 'mb_convert_encoding' => ['hasSideEffects' => false], - 'mb_convert_kana' => ['hasSideEffects' => false], - 'mb_decode_mimeheader' => ['hasSideEffects' => false], - 'mb_decode_numericentity' => ['hasSideEffects' => false], - 'mb_detect_encoding' => ['hasSideEffects' => false], - 'mb_encode_mimeheader' => ['hasSideEffects' => false], - 'mb_encode_numericentity' => ['hasSideEffects' => false], - 'mb_encoding_aliases' => ['hasSideEffects' => false], - 'mb_ereg_match' => ['hasSideEffects' => false], - 'mb_ereg_replace' => ['hasSideEffects' => false], - 'mb_ereg_search' => ['hasSideEffects' => false], - 'mb_ereg_search_getpos' => ['hasSideEffects' => false], - 'mb_ereg_search_getregs' => ['hasSideEffects' => false], - 'mb_ereg_search_pos' => ['hasSideEffects' => false], - 'mb_ereg_search_regs' => ['hasSideEffects' => false], - 'mb_ereg_search_setpos' => ['hasSideEffects' => false], - 'mb_eregi_replace' => ['hasSideEffects' => false], - 'mb_get_info' => ['hasSideEffects' => false], - 'mb_http_input' => ['hasSideEffects' => false], - 'mb_list_encodings' => ['hasSideEffects' => false], - 'mb_ord' => ['hasSideEffects' => false], - 'mb_output_handler' => ['hasSideEffects' => false], - 'mb_preferred_mime_name' => ['hasSideEffects' => false], - 'mb_scrub' => ['hasSideEffects' => false], - 'mb_split' => ['hasSideEffects' => false], - 'mb_str_split' => ['hasSideEffects' => false], - 'mb_strcut' => ['hasSideEffects' => false], - 'mb_strimwidth' => ['hasSideEffects' => false], - 'mb_stripos' => ['hasSideEffects' => false], - 'mb_stristr' => ['hasSideEffects' => false], - 'mb_strlen' => ['hasSideEffects' => false], - 'mb_strpos' => ['hasSideEffects' => false], - 'mb_strrchr' => ['hasSideEffects' => false], - 'mb_strrichr' => ['hasSideEffects' => false], - 'mb_strripos' => ['hasSideEffects' => false], - 'mb_strrpos' => ['hasSideEffects' => false], - 'mb_strstr' => ['hasSideEffects' => false], - 'mb_strtolower' => ['hasSideEffects' => false], - 'mb_strtoupper' => ['hasSideEffects' => false], - 'mb_strwidth' => ['hasSideEffects' => false], - 'mb_substr' => ['hasSideEffects' => false], - 'mb_substr_count' => ['hasSideEffects' => false], - 'mbereg_search_setpos' => ['hasSideEffects' => false], - 'md5' => ['hasSideEffects' => false], - 'md5_file' => ['hasSideEffects' => false], - 'memory_get_peak_usage' => ['hasSideEffects' => false], - 'memory_get_usage' => ['hasSideEffects' => false], - 'metaphone' => ['hasSideEffects' => false], - 'method_exists' => ['hasSideEffects' => false], - 'mhash' => ['hasSideEffects' => false], - 'mhash_count' => ['hasSideEffects' => false], - 'mhash_get_block_size' => ['hasSideEffects' => false], - 'mhash_get_hash_name' => ['hasSideEffects' => false], - 'mhash_keygen_s2k' => ['hasSideEffects' => false], - 'microtime' => ['hasSideEffects' => false], - 'min' => ['hasSideEffects' => false], - 'mktime' => ['hasSideEffects' => false], - 'msgfmt_create' => ['hasSideEffects' => false], - 'msgfmt_format' => ['hasSideEffects' => false], - 'msgfmt_format_message' => ['hasSideEffects' => false], - 'msgfmt_get_error_code' => ['hasSideEffects' => false], - 'msgfmt_get_error_message' => ['hasSideEffects' => false], - 'msgfmt_get_locale' => ['hasSideEffects' => false], - 'msgfmt_get_pattern' => ['hasSideEffects' => false], - 'msgfmt_parse' => ['hasSideEffects' => false], - 'msgfmt_parse_message' => ['hasSideEffects' => false], - 'mt_getrandmax' => ['hasSideEffects' => false], - 'mt_rand' => ['hasSideEffects' => true], - 'net_get_interfaces' => ['hasSideEffects' => false], - 'ngettext' => ['hasSideEffects' => false], - 'nl2br' => ['hasSideEffects' => false], - 'nl_langinfo' => ['hasSideEffects' => false], - 'normalizer_get_raw_decomposition' => ['hasSideEffects' => false], - 'normalizer_is_normalized' => ['hasSideEffects' => false], - 'normalizer_normalize' => ['hasSideEffects' => false], - 'number_format' => ['hasSideEffects' => false], - 'numfmt_create' => ['hasSideEffects' => false], - 'numfmt_format' => ['hasSideEffects' => false], - 'numfmt_format_currency' => ['hasSideEffects' => false], - 'numfmt_get_attribute' => ['hasSideEffects' => false], - 'numfmt_get_error_code' => ['hasSideEffects' => false], - 'numfmt_get_error_message' => ['hasSideEffects' => false], - 'numfmt_get_locale' => ['hasSideEffects' => false], - 'numfmt_get_pattern' => ['hasSideEffects' => false], - 'numfmt_get_symbol' => ['hasSideEffects' => false], - 'numfmt_get_text_attribute' => ['hasSideEffects' => false], - 'numfmt_parse' => ['hasSideEffects' => false], - 'ob_etaghandler' => ['hasSideEffects' => false], - 'ob_iconv_handler' => ['hasSideEffects' => false], - 'octdec' => ['hasSideEffects' => false], - 'ord' => ['hasSideEffects' => false], - 'pack' => ['hasSideEffects' => false], - 'parse_ini_string' => ['hasSideEffects' => false], - 'parse_url' => ['hasSideEffects' => false], - 'pathinfo' => ['hasSideEffects' => false], - 'pcntl_errno' => ['hasSideEffects' => false], - 'pcntl_get_last_error' => ['hasSideEffects' => false], - 'pcntl_getpriority' => ['hasSideEffects' => false], - 'pcntl_strerror' => ['hasSideEffects' => false], - 'pcntl_wexitstatus' => ['hasSideEffects' => false], - 'pcntl_wifcontinued' => ['hasSideEffects' => false], - 'pcntl_wifexited' => ['hasSideEffects' => false], - 'pcntl_wifsignaled' => ['hasSideEffects' => false], - 'pcntl_wifstopped' => ['hasSideEffects' => false], - 'pcntl_wstopsig' => ['hasSideEffects' => false], - 'pcntl_wtermsig' => ['hasSideEffects' => false], - 'pdo_drivers' => ['hasSideEffects' => false], - 'php_ini_loaded_file' => ['hasSideEffects' => false], - 'php_ini_scanned_files' => ['hasSideEffects' => false], - 'php_logo_guid' => ['hasSideEffects' => false], - 'php_sapi_name' => ['hasSideEffects' => false], - 'php_strip_whitespace' => ['hasSideEffects' => false], - 'php_uname' => ['hasSideEffects' => false], - 'phpversion' => ['hasSideEffects' => false], - 'pi' => ['hasSideEffects' => false], - 'pos' => ['hasSideEffects' => false], - 'posix_ctermid' => ['hasSideEffects' => false], - 'posix_errno' => ['hasSideEffects' => false], - 'posix_get_last_error' => ['hasSideEffects' => false], - 'posix_getcwd' => ['hasSideEffects' => false], - 'posix_getegid' => ['hasSideEffects' => false], - 'posix_geteuid' => ['hasSideEffects' => false], - 'posix_getgid' => ['hasSideEffects' => false], - 'posix_getgrgid' => ['hasSideEffects' => false], - 'posix_getgrnam' => ['hasSideEffects' => false], - 'posix_getgroups' => ['hasSideEffects' => false], - 'posix_getlogin' => ['hasSideEffects' => false], - 'posix_getpgid' => ['hasSideEffects' => false], - 'posix_getpgrp' => ['hasSideEffects' => false], - 'posix_getpid' => ['hasSideEffects' => false], - 'posix_getppid' => ['hasSideEffects' => false], - 'posix_getpwnam' => ['hasSideEffects' => false], - 'posix_getpwuid' => ['hasSideEffects' => false], - 'posix_getrlimit' => ['hasSideEffects' => false], - 'posix_getsid' => ['hasSideEffects' => false], - 'posix_getuid' => ['hasSideEffects' => false], - 'posix_initgroups' => ['hasSideEffects' => false], - 'posix_isatty' => ['hasSideEffects' => false], - 'posix_strerror' => ['hasSideEffects' => false], - 'posix_times' => ['hasSideEffects' => false], - 'posix_ttyname' => ['hasSideEffects' => false], - 'posix_uname' => ['hasSideEffects' => false], - 'pow' => ['hasSideEffects' => false], - 'preg_grep' => ['hasSideEffects' => false], - 'preg_last_error' => ['hasSideEffects' => false], - 'preg_last_error_msg' => ['hasSideEffects' => false], - 'preg_quote' => ['hasSideEffects' => false], - 'preg_split' => ['hasSideEffects' => false], - 'property_exists' => ['hasSideEffects' => false], - 'quoted_printable_decode' => ['hasSideEffects' => false], - 'quoted_printable_encode' => ['hasSideEffects' => false], - 'quotemeta' => ['hasSideEffects' => false], - 'rad2deg' => ['hasSideEffects' => false], - 'rand' => ['hasSideEffects' => true], - 'random_bytes' => ['hasSideEffects' => true], - 'random_int' => ['hasSideEffects' => true], - 'range' => ['hasSideEffects' => false], - 'rawurldecode' => ['hasSideEffects' => false], - 'rawurlencode' => ['hasSideEffects' => false], - 'realpath' => ['hasSideEffects' => false], - 'realpath_cache_get' => ['hasSideEffects' => false], - 'realpath_cache_size' => ['hasSideEffects' => false], - 'resourcebundle_count' => ['hasSideEffects' => false], - 'resourcebundle_create' => ['hasSideEffects' => false], - 'resourcebundle_get' => ['hasSideEffects' => false], - 'resourcebundle_get_error_code' => ['hasSideEffects' => false], - 'resourcebundle_get_error_message' => ['hasSideEffects' => false], - 'resourcebundle_locales' => ['hasSideEffects' => false], - 'round' => ['hasSideEffects' => false], - 'rtrim' => ['hasSideEffects' => false], - 'sha1' => ['hasSideEffects' => false], - 'sha1_file' => ['hasSideEffects' => false], - 'sin' => ['hasSideEffects' => false], - 'sinh' => ['hasSideEffects' => false], - 'sizeof' => ['hasSideEffects' => false], - 'soundex' => ['hasSideEffects' => false], - 'spl_classes' => ['hasSideEffects' => false], - 'spl_object_hash' => ['hasSideEffects' => false], - 'sprintf' => ['hasSideEffects' => false], - 'sqrt' => ['hasSideEffects' => false], - 'stat' => ['hasSideEffects' => false], - 'str_contains' => ['hasSideEffects' => false], - 'str_ends_with' => ['hasSideEffects' => false], - 'str_getcsv' => ['hasSideEffects' => false], - 'str_pad' => ['hasSideEffects' => false], - 'str_repeat' => ['hasSideEffects' => false], - 'str_rot13' => ['hasSideEffects' => false], - 'str_shuffle' => ['hasSideEffects' => false], - 'str_split' => ['hasSideEffects' => false], - 'str_starts_with' => ['hasSideEffects' => false], - 'str_word_count' => ['hasSideEffects' => false], - 'strcasecmp' => ['hasSideEffects' => false], - 'strchr' => ['hasSideEffects' => false], - 'strcmp' => ['hasSideEffects' => false], - 'strcoll' => ['hasSideEffects' => false], - 'strcspn' => ['hasSideEffects' => false], - 'stream_get_filters' => ['hasSideEffects' => false], - 'stream_get_transports' => ['hasSideEffects' => false], - 'stream_get_wrappers' => ['hasSideEffects' => false], - 'stream_is_local' => ['hasSideEffects' => false], - 'stream_isatty' => ['hasSideEffects' => false], - 'strip_tags' => ['hasSideEffects' => false], - 'stripcslashes' => ['hasSideEffects' => false], - 'stripos' => ['hasSideEffects' => false], - 'stripslashes' => ['hasSideEffects' => false], - 'stristr' => ['hasSideEffects' => false], - 'strlen' => ['hasSideEffects' => false], - 'strnatcasecmp' => ['hasSideEffects' => false], - 'strnatcmp' => ['hasSideEffects' => false], - 'strncasecmp' => ['hasSideEffects' => false], - 'strncmp' => ['hasSideEffects' => false], - 'strpbrk' => ['hasSideEffects' => false], - 'strpos' => ['hasSideEffects' => false], - 'strptime' => ['hasSideEffects' => false], - 'strrchr' => ['hasSideEffects' => false], - 'strrev' => ['hasSideEffects' => false], - 'strripos' => ['hasSideEffects' => false], - 'strrpos' => ['hasSideEffects' => false], - 'strspn' => ['hasSideEffects' => false], - 'strstr' => ['hasSideEffects' => false], - 'strtolower' => ['hasSideEffects' => false], - 'strtotime' => ['hasSideEffects' => false], - 'strtoupper' => ['hasSideEffects' => false], - 'strtr' => ['hasSideEffects' => false], - 'strval' => ['hasSideEffects' => false], - 'substr' => ['hasSideEffects' => false], - 'substr_compare' => ['hasSideEffects' => false], - 'substr_count' => ['hasSideEffects' => false], - 'substr_replace' => ['hasSideEffects' => false], - 'sys_getloadavg' => ['hasSideEffects' => false], - 'tan' => ['hasSideEffects' => false], - 'tanh' => ['hasSideEffects' => false], - 'timezone_abbreviations_list' => ['hasSideEffects' => false], - 'timezone_identifiers_list' => ['hasSideEffects' => false], - 'timezone_location_get' => ['hasSideEffects' => false], - 'timezone_name_from_abbr' => ['hasSideEffects' => false], - 'timezone_name_get' => ['hasSideEffects' => false], - 'timezone_offset_get' => ['hasSideEffects' => false], - 'timezone_open' => ['hasSideEffects' => false], - 'timezone_transitions_get' => ['hasSideEffects' => false], - 'timezone_version_get' => ['hasSideEffects' => false], - 'token_get_all' => ['hasSideEffects' => false], - 'token_name' => ['hasSideEffects' => false], - 'transliterator_create' => ['hasSideEffects' => false], - 'transliterator_create_from_rules' => ['hasSideEffects' => false], - 'transliterator_create_inverse' => ['hasSideEffects' => false], - 'transliterator_get_error_code' => ['hasSideEffects' => false], - 'transliterator_get_error_message' => ['hasSideEffects' => false], - 'transliterator_list_ids' => ['hasSideEffects' => false], - 'transliterator_transliterate' => ['hasSideEffects' => false], - 'trim' => ['hasSideEffects' => false], - 'ucfirst' => ['hasSideEffects' => false], - 'ucwords' => ['hasSideEffects' => false], - 'uniqid' => ['hasSideEffects' => false], - 'unpack' => ['hasSideEffects' => false], - 'urldecode' => ['hasSideEffects' => false], - 'urlencode' => ['hasSideEffects' => false], - 'utf8_decode' => ['hasSideEffects' => false], - 'utf8_encode' => ['hasSideEffects' => false], - 'vsprintf' => ['hasSideEffects' => false], - 'wordwrap' => ['hasSideEffects' => false], - 'xml_error_string' => ['hasSideEffects' => false], - 'xml_get_current_byte_index' => ['hasSideEffects' => false], - 'xml_get_current_column_number' => ['hasSideEffects' => false], - 'xml_get_current_line_number' => ['hasSideEffects' => false], - 'xml_get_error_code' => ['hasSideEffects' => false], - 'xml_parser_create' => ['hasSideEffects' => false], - 'xml_parser_create_ns' => ['hasSideEffects' => false], - 'xml_parser_get_option' => ['hasSideEffects' => false], - 'zend_version' => ['hasSideEffects' => false], - 'zlib_decode' => ['hasSideEffects' => false], - 'zlib_encode' => ['hasSideEffects' => false], - 'zlib_get_coding_type' => ['hasSideEffects' => false], + 'CURLFile::getFilename' => ['hasSideEffects' => false], + 'CURLFile::getMimeType' => ['hasSideEffects' => false], + 'CURLFile::getPostFilename' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\AlreadyExistsException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\AuthenticationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ConfigurationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DivideByZeroException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DomainException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ExecutionException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidArgumentException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidQueryException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidSyntaxException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\IsBootstrappingException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\LogicException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\OverloadedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ProtocolException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RangeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ReadTimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RuntimeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ServerException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TruncateException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnauthorizedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnavailableException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnpreparedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ValidationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\WriteTimeoutException::__construct' => ['hasSideEffects' => false], + 'Collator::__construct' => ['hasSideEffects' => false], + 'Collator::compare' => ['hasSideEffects' => false], + 'Collator::getAttribute' => ['hasSideEffects' => false], + 'Collator::getErrorCode' => ['hasSideEffects' => false], + 'Collator::getErrorMessage' => ['hasSideEffects' => false], + 'Collator::getLocale' => ['hasSideEffects' => false], + 'Collator::getSortKey' => ['hasSideEffects' => false], + 'Collator::getStrength' => ['hasSideEffects' => false], + 'DateTime::add' => ['hasSideEffects' => true], + 'DateTime::createFromFormat' => ['hasSideEffects' => false], + 'DateTime::createFromImmutable' => ['hasSideEffects' => false], + 'DateTime::diff' => ['hasSideEffects' => false], + 'DateTime::format' => ['hasSideEffects' => false], + 'DateTime::getLastErrors' => ['hasSideEffects' => false], + 'DateTime::getOffset' => ['hasSideEffects' => false], + 'DateTime::getTimestamp' => ['hasSideEffects' => false], + 'DateTime::getTimezone' => ['hasSideEffects' => false], + 'DateTime::modify' => ['hasSideEffects' => true], + 'DateTime::setDate' => ['hasSideEffects' => true], + 'DateTime::setISODate' => ['hasSideEffects' => true], + 'DateTime::setTime' => ['hasSideEffects' => true], + 'DateTime::setTimestamp' => ['hasSideEffects' => true], + 'DateTime::setTimezone' => ['hasSideEffects' => true], + 'DateTime::sub' => ['hasSideEffects' => true], + 'DateTimeImmutable::add' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromFormat' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromMutable' => ['hasSideEffects' => false], + 'DateTimeImmutable::diff' => ['hasSideEffects' => false], + 'DateTimeImmutable::format' => ['hasSideEffects' => false], + 'DateTimeImmutable::getLastErrors' => ['hasSideEffects' => false], + 'DateTimeImmutable::getOffset' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::modify' => ['hasSideEffects' => false], + 'DateTimeImmutable::setDate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setISODate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTime' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'ErrorException::__construct' => ['hasSideEffects' => false], + 'Event::__construct' => ['hasSideEffects' => false], + 'EventBase::getFeatures' => ['hasSideEffects' => false], + 'EventBase::getMethod' => ['hasSideEffects' => false], + 'EventBase::getTimeOfDayCached' => ['hasSideEffects' => false], + 'EventBase::gotExit' => ['hasSideEffects' => false], + 'EventBase::gotStop' => ['hasSideEffects' => false], + 'EventBuffer::__construct' => ['hasSideEffects' => false], + 'EventBufferEvent::__construct' => ['hasSideEffects' => false], + 'EventBufferEvent::getDnsErrorString' => ['hasSideEffects' => false], + 'EventBufferEvent::getEnabled' => ['hasSideEffects' => false], + 'EventBufferEvent::getInput' => ['hasSideEffects' => false], + 'EventBufferEvent::getOutput' => ['hasSideEffects' => false], + 'EventConfig::__construct' => ['hasSideEffects' => false], + 'EventDnsBase::__construct' => ['hasSideEffects' => false], + 'EventHttpConnection::__construct' => ['hasSideEffects' => false], + 'EventHttpRequest::__construct' => ['hasSideEffects' => false], + 'EventHttpRequest::getCommand' => ['hasSideEffects' => false], + 'EventHttpRequest::getHost' => ['hasSideEffects' => false], + 'EventHttpRequest::getInputBuffer' => ['hasSideEffects' => false], + 'EventHttpRequest::getInputHeaders' => ['hasSideEffects' => false], + 'EventHttpRequest::getOutputBuffer' => ['hasSideEffects' => false], + 'EventHttpRequest::getOutputHeaders' => ['hasSideEffects' => false], + 'EventHttpRequest::getResponseCode' => ['hasSideEffects' => false], + 'EventHttpRequest::getUri' => ['hasSideEffects' => false], + 'EventSslContext::__construct' => ['hasSideEffects' => false], + 'Exception::__construct' => ['hasSideEffects' => false], + 'Exception::getCode' => ['hasSideEffects' => false], + 'Exception::getFile' => ['hasSideEffects' => false], + 'Exception::getLine' => ['hasSideEffects' => false], + 'Exception::getMessage' => ['hasSideEffects' => false], + 'Exception::getPrevious' => ['hasSideEffects' => false], + 'Exception::getTrace' => ['hasSideEffects' => false], + 'Exception::getTraceAsString' => ['hasSideEffects' => false], + 'Gmagick::getcopyright' => ['hasSideEffects' => false], + 'Gmagick::getfilename' => ['hasSideEffects' => false], + 'Gmagick::getimagebackgroundcolor' => ['hasSideEffects' => false], + 'Gmagick::getimageblueprimary' => ['hasSideEffects' => false], + 'Gmagick::getimagebordercolor' => ['hasSideEffects' => false], + 'Gmagick::getimagechanneldepth' => ['hasSideEffects' => false], + 'Gmagick::getimagecolors' => ['hasSideEffects' => false], + 'Gmagick::getimagecolorspace' => ['hasSideEffects' => false], + 'Gmagick::getimagecompose' => ['hasSideEffects' => false], + 'Gmagick::getimagedelay' => ['hasSideEffects' => false], + 'Gmagick::getimagedepth' => ['hasSideEffects' => false], + 'Gmagick::getimagedispose' => ['hasSideEffects' => false], + 'Gmagick::getimageextrema' => ['hasSideEffects' => false], + 'Gmagick::getimagefilename' => ['hasSideEffects' => false], + 'Gmagick::getimageformat' => ['hasSideEffects' => false], + 'Gmagick::getimagegamma' => ['hasSideEffects' => false], + 'Gmagick::getimagegreenprimary' => ['hasSideEffects' => false], + 'Gmagick::getimageheight' => ['hasSideEffects' => false], + 'Gmagick::getimagehistogram' => ['hasSideEffects' => false], + 'Gmagick::getimageindex' => ['hasSideEffects' => false], + 'Gmagick::getimageinterlacescheme' => ['hasSideEffects' => false], + 'Gmagick::getimageiterations' => ['hasSideEffects' => false], + 'Gmagick::getimagematte' => ['hasSideEffects' => false], + 'Gmagick::getimagemattecolor' => ['hasSideEffects' => false], + 'Gmagick::getimageprofile' => ['hasSideEffects' => false], + 'Gmagick::getimageredprimary' => ['hasSideEffects' => false], + 'Gmagick::getimagerenderingintent' => ['hasSideEffects' => false], + 'Gmagick::getimageresolution' => ['hasSideEffects' => false], + 'Gmagick::getimagescene' => ['hasSideEffects' => false], + 'Gmagick::getimagesignature' => ['hasSideEffects' => false], + 'Gmagick::getimagetype' => ['hasSideEffects' => false], + 'Gmagick::getimageunits' => ['hasSideEffects' => false], + 'Gmagick::getimagewhitepoint' => ['hasSideEffects' => false], + 'Gmagick::getimagewidth' => ['hasSideEffects' => false], + 'Gmagick::getpackagename' => ['hasSideEffects' => false], + 'Gmagick::getquantumdepth' => ['hasSideEffects' => false], + 'Gmagick::getreleasedate' => ['hasSideEffects' => false], + 'Gmagick::getsamplingfactors' => ['hasSideEffects' => false], + 'Gmagick::getsize' => ['hasSideEffects' => false], + 'Gmagick::getversion' => ['hasSideEffects' => false], + 'GmagickDraw::getfillcolor' => ['hasSideEffects' => false], + 'GmagickDraw::getfillopacity' => ['hasSideEffects' => false], + 'GmagickDraw::getfont' => ['hasSideEffects' => false], + 'GmagickDraw::getfontsize' => ['hasSideEffects' => false], + 'GmagickDraw::getfontstyle' => ['hasSideEffects' => false], + 'GmagickDraw::getfontweight' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokecolor' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokeopacity' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokewidth' => ['hasSideEffects' => false], + 'GmagickDraw::gettextdecoration' => ['hasSideEffects' => false], + 'GmagickDraw::gettextencoding' => ['hasSideEffects' => false], + 'GmagickPixel::getcolor' => ['hasSideEffects' => false], + 'GmagickPixel::getcolorcount' => ['hasSideEffects' => false], + 'GmagickPixel::getcolorvalue' => ['hasSideEffects' => false], + 'HttpMessage::getBody' => ['hasSideEffects' => false], + 'HttpMessage::getHeader' => ['hasSideEffects' => false], + 'HttpMessage::getHeaders' => ['hasSideEffects' => false], + 'HttpMessage::getHttpVersion' => ['hasSideEffects' => false], + 'HttpMessage::getInfo' => ['hasSideEffects' => false], + 'HttpMessage::getParentMessage' => ['hasSideEffects' => false], + 'HttpMessage::getRequestMethod' => ['hasSideEffects' => false], + 'HttpMessage::getRequestUrl' => ['hasSideEffects' => false], + 'HttpMessage::getResponseCode' => ['hasSideEffects' => false], + 'HttpMessage::getResponseStatus' => ['hasSideEffects' => false], + 'HttpMessage::getType' => ['hasSideEffects' => false], + 'HttpQueryString::get' => ['hasSideEffects' => false], + 'HttpQueryString::getArray' => ['hasSideEffects' => false], + 'HttpQueryString::getBool' => ['hasSideEffects' => false], + 'HttpQueryString::getFloat' => ['hasSideEffects' => false], + 'HttpQueryString::getInt' => ['hasSideEffects' => false], + 'HttpQueryString::getObject' => ['hasSideEffects' => false], + 'HttpQueryString::getString' => ['hasSideEffects' => false], + 'HttpRequest::getBody' => ['hasSideEffects' => false], + 'HttpRequest::getContentType' => ['hasSideEffects' => false], + 'HttpRequest::getCookies' => ['hasSideEffects' => false], + 'HttpRequest::getHeaders' => ['hasSideEffects' => false], + 'HttpRequest::getHistory' => ['hasSideEffects' => false], + 'HttpRequest::getMethod' => ['hasSideEffects' => false], + 'HttpRequest::getOptions' => ['hasSideEffects' => false], + 'HttpRequest::getPostFields' => ['hasSideEffects' => false], + 'HttpRequest::getPostFiles' => ['hasSideEffects' => false], + 'HttpRequest::getPutData' => ['hasSideEffects' => false], + 'HttpRequest::getPutFile' => ['hasSideEffects' => false], + 'HttpRequest::getQueryData' => ['hasSideEffects' => false], + 'HttpRequest::getRawPostData' => ['hasSideEffects' => false], + 'HttpRequest::getRawRequestMessage' => ['hasSideEffects' => false], + 'HttpRequest::getRawResponseMessage' => ['hasSideEffects' => false], + 'HttpRequest::getRequestMessage' => ['hasSideEffects' => false], + 'HttpRequest::getResponseBody' => ['hasSideEffects' => false], + 'HttpRequest::getResponseCode' => ['hasSideEffects' => false], + 'HttpRequest::getResponseCookies' => ['hasSideEffects' => false], + 'HttpRequest::getResponseData' => ['hasSideEffects' => false], + 'HttpRequest::getResponseHeader' => ['hasSideEffects' => false], + 'HttpRequest::getResponseInfo' => ['hasSideEffects' => false], + 'HttpRequest::getResponseMessage' => ['hasSideEffects' => false], + 'HttpRequest::getResponseStatus' => ['hasSideEffects' => false], + 'HttpRequest::getSslOptions' => ['hasSideEffects' => false], + 'HttpRequest::getUrl' => ['hasSideEffects' => false], + 'HttpRequestPool::getAttachedRequests' => ['hasSideEffects' => false], + 'HttpRequestPool::getFinishedRequests' => ['hasSideEffects' => false], + 'Imagick::getColorspace' => ['hasSideEffects' => false], + 'Imagick::getCompression' => ['hasSideEffects' => false], + 'Imagick::getCompressionQuality' => ['hasSideEffects' => false], + 'Imagick::getConfigureOptions' => ['hasSideEffects' => false], + 'Imagick::getFeatures' => ['hasSideEffects' => false], + 'Imagick::getFilename' => ['hasSideEffects' => false], + 'Imagick::getFont' => ['hasSideEffects' => false], + 'Imagick::getFormat' => ['hasSideEffects' => false], + 'Imagick::getGravity' => ['hasSideEffects' => false], + 'Imagick::getHDRIEnabled' => ['hasSideEffects' => false], + 'Imagick::getImage' => ['hasSideEffects' => false], + 'Imagick::getImageAlphaChannel' => ['hasSideEffects' => false], + 'Imagick::getImageArtifact' => ['hasSideEffects' => false], + 'Imagick::getImageAttribute' => ['hasSideEffects' => false], + 'Imagick::getImageBackgroundColor' => ['hasSideEffects' => false], + 'Imagick::getImageBlob' => ['hasSideEffects' => false], + 'Imagick::getImageBluePrimary' => ['hasSideEffects' => false], + 'Imagick::getImageBorderColor' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDepth' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDistortion' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDistortions' => ['hasSideEffects' => false], + 'Imagick::getImageChannelExtrema' => ['hasSideEffects' => false], + 'Imagick::getImageChannelKurtosis' => ['hasSideEffects' => false], + 'Imagick::getImageChannelMean' => ['hasSideEffects' => false], + 'Imagick::getImageChannelRange' => ['hasSideEffects' => false], + 'Imagick::getImageChannelStatistics' => ['hasSideEffects' => false], + 'Imagick::getImageClipMask' => ['hasSideEffects' => false], + 'Imagick::getImageColormapColor' => ['hasSideEffects' => false], + 'Imagick::getImageColors' => ['hasSideEffects' => false], + 'Imagick::getImageColorspace' => ['hasSideEffects' => false], + 'Imagick::getImageCompose' => ['hasSideEffects' => false], + 'Imagick::getImageCompression' => ['hasSideEffects' => false], + 'Imagick::getImageCompressionQuality' => ['hasSideEffects' => false], + 'Imagick::getImageDelay' => ['hasSideEffects' => false], + 'Imagick::getImageDepth' => ['hasSideEffects' => false], + 'Imagick::getImageDispose' => ['hasSideEffects' => false], + 'Imagick::getImageDistortion' => ['hasSideEffects' => false], + 'Imagick::getImageExtrema' => ['hasSideEffects' => false], + 'Imagick::getImageFilename' => ['hasSideEffects' => false], + 'Imagick::getImageFormat' => ['hasSideEffects' => false], + 'Imagick::getImageGamma' => ['hasSideEffects' => false], + 'Imagick::getImageGeometry' => ['hasSideEffects' => false], + 'Imagick::getImageGravity' => ['hasSideEffects' => false], + 'Imagick::getImageGreenPrimary' => ['hasSideEffects' => false], + 'Imagick::getImageHeight' => ['hasSideEffects' => false], + 'Imagick::getImageHistogram' => ['hasSideEffects' => false], + 'Imagick::getImageIndex' => ['hasSideEffects' => false], + 'Imagick::getImageInterlaceScheme' => ['hasSideEffects' => false], + 'Imagick::getImageInterpolateMethod' => ['hasSideEffects' => false], + 'Imagick::getImageIterations' => ['hasSideEffects' => false], + 'Imagick::getImageLength' => ['hasSideEffects' => false], + 'Imagick::getImageMatte' => ['hasSideEffects' => false], + 'Imagick::getImageMatteColor' => ['hasSideEffects' => false], + 'Imagick::getImageMimeType' => ['hasSideEffects' => false], + 'Imagick::getImageOrientation' => ['hasSideEffects' => false], + 'Imagick::getImagePage' => ['hasSideEffects' => false], + 'Imagick::getImagePixelColor' => ['hasSideEffects' => false], + 'Imagick::getImageProfile' => ['hasSideEffects' => false], + 'Imagick::getImageProfiles' => ['hasSideEffects' => false], + 'Imagick::getImageProperties' => ['hasSideEffects' => false], + 'Imagick::getImageProperty' => ['hasSideEffects' => false], + 'Imagick::getImageRedPrimary' => ['hasSideEffects' => false], + 'Imagick::getImageRegion' => ['hasSideEffects' => false], + 'Imagick::getImageRenderingIntent' => ['hasSideEffects' => false], + 'Imagick::getImageResolution' => ['hasSideEffects' => false], + 'Imagick::getImageScene' => ['hasSideEffects' => false], + 'Imagick::getImageSignature' => ['hasSideEffects' => false], + 'Imagick::getImageSize' => ['hasSideEffects' => false], + 'Imagick::getImageTicksPerSecond' => ['hasSideEffects' => false], + 'Imagick::getImageTotalInkDensity' => ['hasSideEffects' => false], + 'Imagick::getImageType' => ['hasSideEffects' => false], + 'Imagick::getImageUnits' => ['hasSideEffects' => false], + 'Imagick::getImageVirtualPixelMethod' => ['hasSideEffects' => false], + 'Imagick::getImageWhitePoint' => ['hasSideEffects' => false], + 'Imagick::getImageWidth' => ['hasSideEffects' => false], + 'Imagick::getImagesBlob' => ['hasSideEffects' => false], + 'Imagick::getInterlaceScheme' => ['hasSideEffects' => false], + 'Imagick::getIteratorIndex' => ['hasSideEffects' => false], + 'Imagick::getNumberImages' => ['hasSideEffects' => false], + 'Imagick::getOption' => ['hasSideEffects' => false], + 'Imagick::getPage' => ['hasSideEffects' => false], + 'Imagick::getPixelIterator' => ['hasSideEffects' => false], + 'Imagick::getPixelRegionIterator' => ['hasSideEffects' => false], + 'Imagick::getPointSize' => ['hasSideEffects' => false], + 'Imagick::getSamplingFactors' => ['hasSideEffects' => false], + 'Imagick::getSize' => ['hasSideEffects' => false], + 'Imagick::getSizeOffset' => ['hasSideEffects' => false], + 'ImagickDraw::getBorderColor' => ['hasSideEffects' => false], + 'ImagickDraw::getClipPath' => ['hasSideEffects' => false], + 'ImagickDraw::getClipRule' => ['hasSideEffects' => false], + 'ImagickDraw::getClipUnits' => ['hasSideEffects' => false], + 'ImagickDraw::getDensity' => ['hasSideEffects' => false], + 'ImagickDraw::getFillColor' => ['hasSideEffects' => false], + 'ImagickDraw::getFillOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getFillRule' => ['hasSideEffects' => false], + 'ImagickDraw::getFont' => ['hasSideEffects' => false], + 'ImagickDraw::getFontFamily' => ['hasSideEffects' => false], + 'ImagickDraw::getFontResolution' => ['hasSideEffects' => false], + 'ImagickDraw::getFontSize' => ['hasSideEffects' => false], + 'ImagickDraw::getFontStretch' => ['hasSideEffects' => false], + 'ImagickDraw::getFontStyle' => ['hasSideEffects' => false], + 'ImagickDraw::getFontWeight' => ['hasSideEffects' => false], + 'ImagickDraw::getGravity' => ['hasSideEffects' => false], + 'ImagickDraw::getOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeAntialias' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeColor' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeDashArray' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeDashOffset' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeLineCap' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeLineJoin' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeMiterLimit' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeWidth' => ['hasSideEffects' => false], + 'ImagickDraw::getTextAlignment' => ['hasSideEffects' => false], + 'ImagickDraw::getTextAntialias' => ['hasSideEffects' => false], + 'ImagickDraw::getTextDecoration' => ['hasSideEffects' => false], + 'ImagickDraw::getTextDirection' => ['hasSideEffects' => false], + 'ImagickDraw::getTextEncoding' => ['hasSideEffects' => false], + 'ImagickDraw::getTextInterLineSpacing' => ['hasSideEffects' => false], + 'ImagickDraw::getTextInterWordSpacing' => ['hasSideEffects' => false], + 'ImagickDraw::getTextKerning' => ['hasSideEffects' => false], + 'ImagickDraw::getTextUnderColor' => ['hasSideEffects' => false], + 'ImagickDraw::getVectorGraphics' => ['hasSideEffects' => false], + 'ImagickKernel::getMatrix' => ['hasSideEffects' => false], + 'ImagickPixel::getColor' => ['hasSideEffects' => false], + 'ImagickPixel::getColorAsString' => ['hasSideEffects' => false], + 'ImagickPixel::getColorCount' => ['hasSideEffects' => false], + 'ImagickPixel::getColorQuantum' => ['hasSideEffects' => false], + 'ImagickPixel::getColorValue' => ['hasSideEffects' => false], + 'ImagickPixel::getColorValueQuantum' => ['hasSideEffects' => false], + 'ImagickPixel::getHSL' => ['hasSideEffects' => false], + 'ImagickPixel::getIndex' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getCurrentIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getNextIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getPreviousIteratorRow' => ['hasSideEffects' => false], + 'IntlBreakIterator::current' => ['hasSideEffects' => false], + 'IntlBreakIterator::getErrorCode' => ['hasSideEffects' => false], + 'IntlBreakIterator::getErrorMessage' => ['hasSideEffects' => false], + 'IntlBreakIterator::getIterator' => ['hasSideEffects' => false], + 'IntlBreakIterator::getLocale' => ['hasSideEffects' => false], + 'IntlBreakIterator::getPartsIterator' => ['hasSideEffects' => false], + 'IntlBreakIterator::getText' => ['hasSideEffects' => false], + 'IntlBreakIterator::isBoundary' => ['hasSideEffects' => false], + 'IntlCalendar::after' => ['hasSideEffects' => false], + 'IntlCalendar::before' => ['hasSideEffects' => false], + 'IntlCalendar::equals' => ['hasSideEffects' => false], + 'IntlCalendar::fieldDifference' => ['hasSideEffects' => false], + 'IntlCalendar::get' => ['hasSideEffects' => false], + 'IntlCalendar::getActualMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getActualMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getDayOfWeekType' => ['hasSideEffects' => false], + 'IntlCalendar::getErrorCode' => ['hasSideEffects' => false], + 'IntlCalendar::getErrorMessage' => ['hasSideEffects' => false], + 'IntlCalendar::getFirstDayOfWeek' => ['hasSideEffects' => false], + 'IntlCalendar::getGreatestMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getLeastMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getLocale' => ['hasSideEffects' => false], + 'IntlCalendar::getMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getMinimalDaysInFirstWeek' => ['hasSideEffects' => false], + 'IntlCalendar::getMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getRepeatedWallTimeOption' => ['hasSideEffects' => false], + 'IntlCalendar::getSkippedWallTimeOption' => ['hasSideEffects' => false], + 'IntlCalendar::getTime' => ['hasSideEffects' => false], + 'IntlCalendar::getTimeZone' => ['hasSideEffects' => false], + 'IntlCalendar::getType' => ['hasSideEffects' => false], + 'IntlCalendar::getWeekendTransition' => ['hasSideEffects' => false], + 'IntlCalendar::inDaylightTime' => ['hasSideEffects' => false], + 'IntlCalendar::isEquivalentTo' => ['hasSideEffects' => false], + 'IntlCalendar::isLenient' => ['hasSideEffects' => false], + 'IntlCalendar::isWeekend' => ['hasSideEffects' => false], + 'IntlCalendar::toDateTime' => ['hasSideEffects' => false], + 'IntlChar::hasBinaryProperty' => ['hasSideEffects' => false], + 'IntlCodePointBreakIterator::getLastCodePoint' => ['hasSideEffects' => false], + 'IntlDateFormatter::__construct' => ['hasSideEffects' => false], + 'IntlDateFormatter::getCalendar' => ['hasSideEffects' => false], + 'IntlDateFormatter::getCalendarObject' => ['hasSideEffects' => false], + 'IntlDateFormatter::getDateType' => ['hasSideEffects' => false], + 'IntlDateFormatter::getErrorCode' => ['hasSideEffects' => false], + 'IntlDateFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'IntlDateFormatter::getLocale' => ['hasSideEffects' => false], + 'IntlDateFormatter::getPattern' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeType' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeZone' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeZoneId' => ['hasSideEffects' => false], + 'IntlDateFormatter::isLenient' => ['hasSideEffects' => false], + 'IntlGregorianCalendar::getGregorianChange' => ['hasSideEffects' => false], + 'IntlGregorianCalendar::isLeapYear' => ['hasSideEffects' => false], + 'IntlPartsIterator::getBreakIterator' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::__construct' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getBinaryRules' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRuleStatus' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRuleStatusVec' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRules' => ['hasSideEffects' => false], + 'IntlTimeZone::getDSTSavings' => ['hasSideEffects' => false], + 'IntlTimeZone::getDisplayName' => ['hasSideEffects' => false], + 'IntlTimeZone::getErrorCode' => ['hasSideEffects' => false], + 'IntlTimeZone::getErrorMessage' => ['hasSideEffects' => false], + 'IntlTimeZone::getID' => ['hasSideEffects' => false], + 'IntlTimeZone::getRawOffset' => ['hasSideEffects' => false], + 'IntlTimeZone::hasSameRules' => ['hasSideEffects' => false], + 'IntlTimeZone::toDateTimeZone' => ['hasSideEffects' => false], + 'JsonIncrementalParser::__construct' => ['hasSideEffects' => false], + 'JsonIncrementalParser::get' => ['hasSideEffects' => false], + 'JsonIncrementalParser::getError' => ['hasSideEffects' => false], + 'MemcachedException::__construct' => ['hasSideEffects' => false], + 'MessageFormatter::__construct' => ['hasSideEffects' => false], + 'MessageFormatter::format' => ['hasSideEffects' => false], + 'MessageFormatter::getErrorCode' => ['hasSideEffects' => false], + 'MessageFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'MessageFormatter::getLocale' => ['hasSideEffects' => false], + 'MessageFormatter::getPattern' => ['hasSideEffects' => false], + 'MessageFormatter::parse' => ['hasSideEffects' => false], + 'NumberFormatter::__construct' => ['hasSideEffects' => false], + 'NumberFormatter::format' => ['hasSideEffects' => false], + 'NumberFormatter::formatCurrency' => ['hasSideEffects' => false], + 'NumberFormatter::getAttribute' => ['hasSideEffects' => false], + 'NumberFormatter::getErrorCode' => ['hasSideEffects' => false], + 'NumberFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'NumberFormatter::getLocale' => ['hasSideEffects' => false], + 'NumberFormatter::getPattern' => ['hasSideEffects' => false], + 'NumberFormatter::getSymbol' => ['hasSideEffects' => false], + 'NumberFormatter::getTextAttribute' => ['hasSideEffects' => false], + 'ReflectionAttribute::getArguments' => ['hasSideEffects' => false], + 'ReflectionAttribute::getName' => ['hasSideEffects' => false], + 'ReflectionAttribute::getTarget' => ['hasSideEffects' => false], + 'ReflectionAttribute::isRepeated' => ['hasSideEffects' => false], + 'ReflectionClass::getAttributes' => ['hasSideEffects' => false], + 'ReflectionClass::getConstant' => ['hasSideEffects' => false], + 'ReflectionClass::getConstants' => ['hasSideEffects' => false], + 'ReflectionClass::getConstructor' => ['hasSideEffects' => false], + 'ReflectionClass::getDefaultProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getDocComment' => ['hasSideEffects' => false], + 'ReflectionClass::getEndLine' => ['hasSideEffects' => false], + 'ReflectionClass::getExtension' => ['hasSideEffects' => false], + 'ReflectionClass::getExtensionName' => ['hasSideEffects' => false], + 'ReflectionClass::getFileName' => ['hasSideEffects' => false], + 'ReflectionClass::getInterfaceNames' => ['hasSideEffects' => false], + 'ReflectionClass::getInterfaces' => ['hasSideEffects' => false], + 'ReflectionClass::getMethod' => ['hasSideEffects' => false], + 'ReflectionClass::getMethods' => ['hasSideEffects' => false], + 'ReflectionClass::getModifiers' => ['hasSideEffects' => false], + 'ReflectionClass::getName' => ['hasSideEffects' => false], + 'ReflectionClass::getNamespaceName' => ['hasSideEffects' => false], + 'ReflectionClass::getParentClass' => ['hasSideEffects' => false], + 'ReflectionClass::getProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getProperty' => ['hasSideEffects' => false], + 'ReflectionClass::getReflectionConstant' => ['hasSideEffects' => false], + 'ReflectionClass::getReflectionConstants' => ['hasSideEffects' => false], + 'ReflectionClass::getShortName' => ['hasSideEffects' => false], + 'ReflectionClass::getStartLine' => ['hasSideEffects' => false], + 'ReflectionClass::getStaticProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getStaticPropertyValue' => ['hasSideEffects' => false], + 'ReflectionClass::getTraitAliases' => ['hasSideEffects' => false], + 'ReflectionClass::getTraitNames' => ['hasSideEffects' => false], + 'ReflectionClass::getTraits' => ['hasSideEffects' => false], + 'ReflectionClass::isAbstract' => ['hasSideEffects' => false], + 'ReflectionClass::isAnonymous' => ['hasSideEffects' => false], + 'ReflectionClass::isCloneable' => ['hasSideEffects' => false], + 'ReflectionClass::isFinal' => ['hasSideEffects' => false], + 'ReflectionClass::isInstance' => ['hasSideEffects' => false], + 'ReflectionClass::isInstantiable' => ['hasSideEffects' => false], + 'ReflectionClass::isInterface' => ['hasSideEffects' => false], + 'ReflectionClass::isInternal' => ['hasSideEffects' => false], + 'ReflectionClass::isIterable' => ['hasSideEffects' => false], + 'ReflectionClass::isIterateable' => ['hasSideEffects' => false], + 'ReflectionClass::isSubclassOf' => ['hasSideEffects' => false], + 'ReflectionClass::isTrait' => ['hasSideEffects' => false], + 'ReflectionClass::isUserDefined' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getAttributes' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getDocComment' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getModifiers' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getName' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getValue' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isPrivate' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isProtected' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isPublic' => ['hasSideEffects' => false], + 'ReflectionExtension::getClassNames' => ['hasSideEffects' => false], + 'ReflectionExtension::getClasses' => ['hasSideEffects' => false], + 'ReflectionExtension::getConstants' => ['hasSideEffects' => false], + 'ReflectionExtension::getDependencies' => ['hasSideEffects' => false], + 'ReflectionExtension::getFunctions' => ['hasSideEffects' => false], + 'ReflectionExtension::getINIEntries' => ['hasSideEffects' => false], + 'ReflectionExtension::getName' => ['hasSideEffects' => false], + 'ReflectionExtension::getVersion' => ['hasSideEffects' => false], + 'ReflectionExtension::isPersistent' => ['hasSideEffects' => false], + 'ReflectionExtension::isTemporary' => ['hasSideEffects' => false], + 'ReflectionFunction::getClosure' => ['hasSideEffects' => false], + 'ReflectionFunction::isDisabled' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getAttributes' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureScopeClass' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureThis' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getDocComment' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getEndLine' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getExtension' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getExtensionName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getFileName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNamespaceName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNumberOfParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNumberOfRequiredParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getReturnType' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getShortName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getStartLine' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getStaticVariables' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isClosure' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isDeprecated' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isGenerator' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isInternal' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isUserDefined' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isVariadic' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingFile' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingGenerator' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingLine' => ['hasSideEffects' => false], + 'ReflectionGenerator::getFunction' => ['hasSideEffects' => false], + 'ReflectionGenerator::getThis' => ['hasSideEffects' => false], + 'ReflectionGenerator::getTrace' => ['hasSideEffects' => false], + 'ReflectionMethod::getClosure' => ['hasSideEffects' => false], + 'ReflectionMethod::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionMethod::getModifiers' => ['hasSideEffects' => false], + 'ReflectionMethod::getPrototype' => ['hasSideEffects' => false], + 'ReflectionMethod::isAbstract' => ['hasSideEffects' => false], + 'ReflectionMethod::isConstructor' => ['hasSideEffects' => false], + 'ReflectionMethod::isDestructor' => ['hasSideEffects' => false], + 'ReflectionMethod::isFinal' => ['hasSideEffects' => false], + 'ReflectionMethod::isPrivate' => ['hasSideEffects' => false], + 'ReflectionMethod::isProtected' => ['hasSideEffects' => false], + 'ReflectionMethod::isPublic' => ['hasSideEffects' => false], + 'ReflectionMethod::isStatic' => ['hasSideEffects' => false], + 'ReflectionNamedType::getName' => ['hasSideEffects' => false], + 'ReflectionNamedType::isBuiltin' => ['hasSideEffects' => false], + 'ReflectionParameter::getAttributes' => ['hasSideEffects' => false], + 'ReflectionParameter::getClass' => ['hasSideEffects' => false], + 'ReflectionParameter::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionParameter::getDeclaringFunction' => ['hasSideEffects' => false], + 'ReflectionParameter::getDefaultValue' => ['hasSideEffects' => false], + 'ReflectionParameter::getDefaultValueConstantName' => ['hasSideEffects' => false], + 'ReflectionParameter::getName' => ['hasSideEffects' => false], + 'ReflectionParameter::getPosition' => ['hasSideEffects' => false], + 'ReflectionParameter::getType' => ['hasSideEffects' => false], + 'ReflectionParameter::isArray' => ['hasSideEffects' => false], + 'ReflectionParameter::isCallable' => ['hasSideEffects' => false], + 'ReflectionParameter::isDefaultValueAvailable' => ['hasSideEffects' => false], + 'ReflectionParameter::isDefaultValueConstant' => ['hasSideEffects' => false], + 'ReflectionParameter::isOptional' => ['hasSideEffects' => false], + 'ReflectionParameter::isPassedByReference' => ['hasSideEffects' => false], + 'ReflectionParameter::isPromoted' => ['hasSideEffects' => false], + 'ReflectionParameter::isVariadic' => ['hasSideEffects' => false], + 'ReflectionProperty::getAttributes' => ['hasSideEffects' => false], + 'ReflectionProperty::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionProperty::getDefaultValue' => ['hasSideEffects' => false], + 'ReflectionProperty::getDocComment' => ['hasSideEffects' => false], + 'ReflectionProperty::getModifiers' => ['hasSideEffects' => false], + 'ReflectionProperty::getName' => ['hasSideEffects' => false], + 'ReflectionProperty::getType' => ['hasSideEffects' => false], + 'ReflectionProperty::getValue' => ['hasSideEffects' => false], + 'ReflectionProperty::isDefault' => ['hasSideEffects' => false], + 'ReflectionProperty::isInitialized' => ['hasSideEffects' => false], + 'ReflectionProperty::isPrivate' => ['hasSideEffects' => false], + 'ReflectionProperty::isPromoted' => ['hasSideEffects' => false], + 'ReflectionProperty::isProtected' => ['hasSideEffects' => false], + 'ReflectionProperty::isPublic' => ['hasSideEffects' => false], + 'ReflectionProperty::isStatic' => ['hasSideEffects' => false], + 'ReflectionReference::getId' => ['hasSideEffects' => false], + 'ReflectionType::isBuiltin' => ['hasSideEffects' => false], + 'ReflectionUnionType::getTypes' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getAuthor' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getCopyright' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getName' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getURL' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getVersion' => ['hasSideEffects' => false], + 'ResourceBundle::__construct' => ['hasSideEffects' => false], + 'ResourceBundle::count' => ['hasSideEffects' => false], + 'ResourceBundle::get' => ['hasSideEffects' => false], + 'ResourceBundle::getErrorCode' => ['hasSideEffects' => false], + 'ResourceBundle::getErrorMessage' => ['hasSideEffects' => false], + 'ResourceBundle::getIterator' => ['hasSideEffects' => false], + 'SQLiteException::__construct' => ['hasSideEffects' => false], + 'SimpleXMLElement::__construct' => ['hasSideEffects' => false], + 'SimpleXMLElement::children' => ['hasSideEffects' => false], + 'SimpleXMLElement::count' => ['hasSideEffects' => false], + 'SimpleXMLElement::current' => ['hasSideEffects' => false], + 'SimpleXMLElement::getChildren' => ['hasSideEffects' => false], + 'SimpleXMLElement::getDocNamespaces' => ['hasSideEffects' => false], + 'SimpleXMLElement::getName' => ['hasSideEffects' => false], + 'SimpleXMLElement::getNamespaces' => ['hasSideEffects' => false], + 'SimpleXMLElement::hasChildren' => ['hasSideEffects' => false], + 'SimpleXMLElement::offsetExists' => ['hasSideEffects' => false], + 'SimpleXMLElement::offsetGet' => ['hasSideEffects' => false], + 'SimpleXMLElement::valid' => ['hasSideEffects' => false], + 'SimpleXMLIterator::count' => ['hasSideEffects' => false], + 'SimpleXMLIterator::current' => ['hasSideEffects' => false], + 'SimpleXMLIterator::getChildren' => ['hasSideEffects' => false], + 'SimpleXMLIterator::hasChildren' => ['hasSideEffects' => false], + 'SimpleXMLIterator::valid' => ['hasSideEffects' => false], + 'SoapFault::__construct' => ['hasSideEffects' => false], + 'Spoofchecker::__construct' => ['hasSideEffects' => false], + 'StubTests\\Model\\BasePHPElement::getFQN' => ['hasSideEffects' => false], + 'StubTests\\Model\\BasePHPElement::getTypeNameFromNode' => ['hasSideEffects' => false], + 'StubTests\\Model\\BasePHPElement::hasMutedProblem' => ['hasSideEffects' => false], + 'StubTests\\Model\\StubsContainer::getClass' => ['hasSideEffects' => false], + 'StubTests\\Model\\StubsContainer::getInterface' => ['hasSideEffects' => false], + 'StubTests\\Parsers\\ExpectedFunctionArgumentsInfo::__toString' => ['hasSideEffects' => false], + 'StubTests\\Parsers\\Visitors\\CoreStubASTVisitor::__construct' => ['hasSideEffects' => false], + 'StubTests\\StubsParameterNamesTest::printParameters' => ['hasSideEffects' => false], + 'StubTests\\StubsTest::getParameterRepresentation' => ['hasSideEffects' => false], + 'Transliterator::createInverse' => ['hasSideEffects' => false], + 'Transliterator::getErrorCode' => ['hasSideEffects' => false], + 'Transliterator::getErrorMessage' => ['hasSideEffects' => false], + 'Transliterator::transliterate' => ['hasSideEffects' => false], + 'UConverter::__construct' => ['hasSideEffects' => false], + 'UConverter::convert' => ['hasSideEffects' => false], + 'UConverter::getDestinationEncoding' => ['hasSideEffects' => false], + 'UConverter::getDestinationType' => ['hasSideEffects' => false], + 'UConverter::getErrorCode' => ['hasSideEffects' => false], + 'UConverter::getErrorMessage' => ['hasSideEffects' => false], + 'UConverter::getSourceEncoding' => ['hasSideEffects' => false], + 'UConverter::getSourceType' => ['hasSideEffects' => false], + 'UConverter::getStandards' => ['hasSideEffects' => false], + 'UConverter::getSubstChars' => ['hasSideEffects' => false], + 'UConverter::reasonText' => ['hasSideEffects' => false], + 'Zookeeper::getAcl' => ['hasSideEffects' => false], + 'Zookeeper::getChildren' => ['hasSideEffects' => false], + 'Zookeeper::getClientId' => ['hasSideEffects' => false], + 'Zookeeper::getRecvTimeout' => ['hasSideEffects' => false], + 'Zookeeper::getState' => ['hasSideEffects' => false], + '_' => ['hasSideEffects' => false], + 'abs' => ['hasSideEffects' => false], + 'acos' => ['hasSideEffects' => false], + 'acosh' => ['hasSideEffects' => false], + 'addcslashes' => ['hasSideEffects' => false], + 'addslashes' => ['hasSideEffects' => false], + 'apache_get_modules' => ['hasSideEffects' => false], + 'apache_get_version' => ['hasSideEffects' => false], + 'apache_getenv' => ['hasSideEffects' => false], + 'apache_request_headers' => ['hasSideEffects' => false], + 'array_change_key_case' => ['hasSideEffects' => false], + 'array_chunk' => ['hasSideEffects' => false], + 'array_column' => ['hasSideEffects' => false], + 'array_combine' => ['hasSideEffects' => false], + 'array_count_values' => ['hasSideEffects' => false], + 'array_diff' => ['hasSideEffects' => false], + 'array_diff_assoc' => ['hasSideEffects' => false], + 'array_diff_key' => ['hasSideEffects' => false], + 'array_diff_uassoc' => ['hasSideEffects' => false], + 'array_diff_ukey' => ['hasSideEffects' => false], + 'array_fill' => ['hasSideEffects' => false], + 'array_fill_keys' => ['hasSideEffects' => false], + 'array_flip' => ['hasSideEffects' => false], + 'array_intersect' => ['hasSideEffects' => false], + 'array_intersect_assoc' => ['hasSideEffects' => false], + 'array_intersect_key' => ['hasSideEffects' => false], + 'array_intersect_uassoc' => ['hasSideEffects' => false], + 'array_intersect_ukey' => ['hasSideEffects' => false], + 'array_key_exists' => ['hasSideEffects' => false], + 'array_key_first' => ['hasSideEffects' => false], + 'array_key_last' => ['hasSideEffects' => false], + 'array_keys' => ['hasSideEffects' => false], + 'array_merge' => ['hasSideEffects' => false], + 'array_merge_recursive' => ['hasSideEffects' => false], + 'array_pad' => ['hasSideEffects' => false], + 'array_product' => ['hasSideEffects' => false], + 'array_rand' => ['hasSideEffects' => false], + 'array_replace' => ['hasSideEffects' => false], + 'array_replace_recursive' => ['hasSideEffects' => false], + 'array_reverse' => ['hasSideEffects' => false], + 'array_search' => ['hasSideEffects' => false], + 'array_slice' => ['hasSideEffects' => false], + 'array_sum' => ['hasSideEffects' => false], + 'array_udiff' => ['hasSideEffects' => false], + 'array_udiff_assoc' => ['hasSideEffects' => false], + 'array_udiff_uassoc' => ['hasSideEffects' => false], + 'array_uintersect' => ['hasSideEffects' => false], + 'array_uintersect_assoc' => ['hasSideEffects' => false], + 'array_uintersect_uassoc' => ['hasSideEffects' => false], + 'array_unique' => ['hasSideEffects' => false], + 'array_values' => ['hasSideEffects' => false], + 'asin' => ['hasSideEffects' => false], + 'asinh' => ['hasSideEffects' => false], + 'atan' => ['hasSideEffects' => false], + 'atan2' => ['hasSideEffects' => false], + 'atanh' => ['hasSideEffects' => false], + 'base64_decode' => ['hasSideEffects' => false], + 'base64_encode' => ['hasSideEffects' => false], + 'base_convert' => ['hasSideEffects' => false], + 'basename' => ['hasSideEffects' => false], + 'bcadd' => ['hasSideEffects' => false], + 'bccomp' => ['hasSideEffects' => false], + 'bcdiv' => ['hasSideEffects' => false], + 'bcmod' => ['hasSideEffects' => false], + 'bcmul' => ['hasSideEffects' => false], + 'bcpow' => ['hasSideEffects' => false], + 'bcpowmod' => ['hasSideEffects' => false], + 'bcsqrt' => ['hasSideEffects' => false], + 'bcsub' => ['hasSideEffects' => false], + 'bin2hex' => ['hasSideEffects' => false], + 'bindec' => ['hasSideEffects' => false], + 'boolval' => ['hasSideEffects' => false], + 'bzcompress' => ['hasSideEffects' => false], + 'bzdecompress' => ['hasSideEffects' => false], + 'bzerrno' => ['hasSideEffects' => false], + 'bzerror' => ['hasSideEffects' => false], + 'bzerrstr' => ['hasSideEffects' => false], + 'bzopen' => ['hasSideEffects' => false], + 'ceil' => ['hasSideEffects' => false], + 'checkdate' => ['hasSideEffects' => false], + 'checkdnsrr' => ['hasSideEffects' => false], + 'chop' => ['hasSideEffects' => false], + 'chr' => ['hasSideEffects' => false], + 'chunk_split' => ['hasSideEffects' => false], + 'class_implements' => ['hasSideEffects' => false], + 'class_parents' => ['hasSideEffects' => false], + 'cli_get_process_title' => ['hasSideEffects' => false], + 'collator_compare' => ['hasSideEffects' => false], + 'collator_create' => ['hasSideEffects' => false], + 'collator_get_attribute' => ['hasSideEffects' => false], + 'collator_get_error_code' => ['hasSideEffects' => false], + 'collator_get_error_message' => ['hasSideEffects' => false], + 'collator_get_locale' => ['hasSideEffects' => false], + 'collator_get_sort_key' => ['hasSideEffects' => false], + 'collator_get_strength' => ['hasSideEffects' => false], + 'compact' => ['hasSideEffects' => false], + 'connection_aborted' => ['hasSideEffects' => false], + 'connection_status' => ['hasSideEffects' => false], + 'constant' => ['hasSideEffects' => false], + 'convert_cyr_string' => ['hasSideEffects' => false], + 'convert_uudecode' => ['hasSideEffects' => false], + 'convert_uuencode' => ['hasSideEffects' => false], + 'cos' => ['hasSideEffects' => false], + 'cosh' => ['hasSideEffects' => false], + 'count' => ['hasSideEffects' => false], + 'count_chars' => ['hasSideEffects' => false], + 'crc32' => ['hasSideEffects' => false], + 'crypt' => ['hasSideEffects' => false], + 'ctype_alnum' => ['hasSideEffects' => false], + 'ctype_alpha' => ['hasSideEffects' => false], + 'ctype_cntrl' => ['hasSideEffects' => false], + 'ctype_digit' => ['hasSideEffects' => false], + 'ctype_graph' => ['hasSideEffects' => false], + 'ctype_lower' => ['hasSideEffects' => false], + 'ctype_print' => ['hasSideEffects' => false], + 'ctype_punct' => ['hasSideEffects' => false], + 'ctype_space' => ['hasSideEffects' => false], + 'ctype_upper' => ['hasSideEffects' => false], + 'ctype_xdigit' => ['hasSideEffects' => false], + 'curl_copy_handle' => ['hasSideEffects' => false], + 'curl_errno' => ['hasSideEffects' => false], + 'curl_error' => ['hasSideEffects' => false], + 'curl_escape' => ['hasSideEffects' => false], + 'curl_file_create' => ['hasSideEffects' => false], + 'curl_getinfo' => ['hasSideEffects' => false], + 'curl_multi_errno' => ['hasSideEffects' => false], + 'curl_multi_getcontent' => ['hasSideEffects' => false], + 'curl_multi_info_read' => ['hasSideEffects' => false], + 'curl_share_errno' => ['hasSideEffects' => false], + 'curl_share_strerror' => ['hasSideEffects' => false], + 'curl_strerror' => ['hasSideEffects' => false], + 'curl_unescape' => ['hasSideEffects' => false], + 'curl_version' => ['hasSideEffects' => false], + 'current' => ['hasSideEffects' => false], + 'date' => ['hasSideEffects' => false], + 'date_create' => ['hasSideEffects' => false], + 'date_create_from_format' => ['hasSideEffects' => false], + 'date_create_immutable' => ['hasSideEffects' => false], + 'date_create_immutable_from_format' => ['hasSideEffects' => false], + 'date_default_timezone_get' => ['hasSideEffects' => false], + 'date_diff' => ['hasSideEffects' => false], + 'date_format' => ['hasSideEffects' => false], + 'date_get_last_errors' => ['hasSideEffects' => false], + 'date_interval_create_from_date_string' => ['hasSideEffects' => false], + 'date_interval_format' => ['hasSideEffects' => false], + 'date_offset_get' => ['hasSideEffects' => false], + 'date_parse' => ['hasSideEffects' => false], + 'date_parse_from_format' => ['hasSideEffects' => false], + 'date_sun_info' => ['hasSideEffects' => false], + 'date_sunrise' => ['hasSideEffects' => false], + 'date_sunset' => ['hasSideEffects' => false], + 'date_timestamp_get' => ['hasSideEffects' => false], + 'date_timezone_get' => ['hasSideEffects' => false], + 'datefmt_create' => ['hasSideEffects' => false], + 'datefmt_format' => ['hasSideEffects' => false], + 'datefmt_format_object' => ['hasSideEffects' => false], + 'datefmt_get_calendar' => ['hasSideEffects' => false], + 'datefmt_get_calendar_object' => ['hasSideEffects' => false], + 'datefmt_get_datetype' => ['hasSideEffects' => false], + 'datefmt_get_error_code' => ['hasSideEffects' => false], + 'datefmt_get_error_message' => ['hasSideEffects' => false], + 'datefmt_get_locale' => ['hasSideEffects' => false], + 'datefmt_get_pattern' => ['hasSideEffects' => false], + 'datefmt_get_timetype' => ['hasSideEffects' => false], + 'datefmt_get_timezone' => ['hasSideEffects' => false], + 'datefmt_get_timezone_id' => ['hasSideEffects' => false], + 'datefmt_is_lenient' => ['hasSideEffects' => false], + 'dcngettext' => ['hasSideEffects' => false], + 'decbin' => ['hasSideEffects' => false], + 'dechex' => ['hasSideEffects' => false], + 'decoct' => ['hasSideEffects' => false], + 'defined' => ['hasSideEffects' => false], + 'deflate_init' => ['hasSideEffects' => false], + 'deg2rad' => ['hasSideEffects' => false], + 'dirname' => ['hasSideEffects' => false], + 'disk_free_space' => ['hasSideEffects' => false], + 'diskfreespace' => ['hasSideEffects' => false], + 'dngettext' => ['hasSideEffects' => false], + 'doubleval' => ['hasSideEffects' => false], + 'error_get_last' => ['hasSideEffects' => false], + 'escapeshellarg' => ['hasSideEffects' => false], + 'escapeshellcmd' => ['hasSideEffects' => false], + 'exp' => ['hasSideEffects' => false], + 'explode' => ['hasSideEffects' => false], + 'expm1' => ['hasSideEffects' => false], + 'extension_loaded' => ['hasSideEffects' => false], + 'fdiv' => ['hasSideEffects' => false], + 'file_exists' => ['hasSideEffects' => false], + 'fileatime' => ['hasSideEffects' => false], + 'filectime' => ['hasSideEffects' => false], + 'filegroup' => ['hasSideEffects' => false], + 'fileinode' => ['hasSideEffects' => false], + 'filemtime' => ['hasSideEffects' => false], + 'fileowner' => ['hasSideEffects' => false], + 'fileperms' => ['hasSideEffects' => false], + 'filesize' => ['hasSideEffects' => false], + 'filetype' => ['hasSideEffects' => false], + 'filter_has_var' => ['hasSideEffects' => false], + 'filter_id' => ['hasSideEffects' => false], + 'filter_input' => ['hasSideEffects' => false], + 'filter_input_array' => ['hasSideEffects' => false], + 'filter_list' => ['hasSideEffects' => false], + 'filter_var' => ['hasSideEffects' => false], + 'filter_var_array' => ['hasSideEffects' => false], + 'finfo::buffer' => ['hasSideEffects' => false], + 'finfo::file' => ['hasSideEffects' => false], + 'floatval' => ['hasSideEffects' => false], + 'floor' => ['hasSideEffects' => false], + 'fmod' => ['hasSideEffects' => false], + 'ftok' => ['hasSideEffects' => false], + 'func_get_arg' => ['hasSideEffects' => false], + 'func_get_args' => ['hasSideEffects' => false], + 'func_num_args' => ['hasSideEffects' => false], + 'function_exists' => ['hasSideEffects' => false], + 'gc_enabled' => ['hasSideEffects' => false], + 'gc_status' => ['hasSideEffects' => false], + 'gd_info' => ['hasSideEffects' => false], + 'geoip_continent_code_by_name' => ['hasSideEffects' => false], + 'geoip_country_code3_by_name' => ['hasSideEffects' => false], + 'geoip_country_code_by_name' => ['hasSideEffects' => false], + 'geoip_country_name_by_name' => ['hasSideEffects' => false], + 'geoip_database_info' => ['hasSideEffects' => false], + 'geoip_db_avail' => ['hasSideEffects' => false], + 'geoip_db_filename' => ['hasSideEffects' => false], + 'geoip_db_get_all_info' => ['hasSideEffects' => false], + 'geoip_id_by_name' => ['hasSideEffects' => false], + 'geoip_isp_by_name' => ['hasSideEffects' => false], + 'geoip_org_by_name' => ['hasSideEffects' => false], + 'geoip_record_by_name' => ['hasSideEffects' => false], + 'geoip_region_by_name' => ['hasSideEffects' => false], + 'geoip_region_name_by_code' => ['hasSideEffects' => false], + 'geoip_time_zone_by_country_and_region' => ['hasSideEffects' => false], + 'get_browser' => ['hasSideEffects' => false], + 'get_called_class' => ['hasSideEffects' => false], + 'get_cfg_var' => ['hasSideEffects' => false], + 'get_class' => ['hasSideEffects' => false], + 'get_class_methods' => ['hasSideEffects' => false], + 'get_class_vars' => ['hasSideEffects' => false], + 'get_current_user' => ['hasSideEffects' => false], + 'get_debug_type' => ['hasSideEffects' => false], + 'get_declared_classes' => ['hasSideEffects' => false], + 'get_declared_interfaces' => ['hasSideEffects' => false], + 'get_declared_traits' => ['hasSideEffects' => false], + 'get_defined_constants' => ['hasSideEffects' => false], + 'get_defined_functions' => ['hasSideEffects' => false], + 'get_defined_vars' => ['hasSideEffects' => false], + 'get_extension_funcs' => ['hasSideEffects' => false], + 'get_headers' => ['hasSideEffects' => false], + 'get_html_translation_table' => ['hasSideEffects' => false], + 'get_include_path' => ['hasSideEffects' => false], + 'get_included_files' => ['hasSideEffects' => false], + 'get_loaded_extensions' => ['hasSideEffects' => false], + 'get_meta_tags' => ['hasSideEffects' => false], + 'get_object_vars' => ['hasSideEffects' => false], + 'get_parent_class' => ['hasSideEffects' => false], + 'get_required_files' => ['hasSideEffects' => false], + 'get_resource_id' => ['hasSideEffects' => false], + 'get_resources' => ['hasSideEffects' => false], + 'getallheaders' => ['hasSideEffects' => false], + 'getcwd' => ['hasSideEffects' => false], + 'getdate' => ['hasSideEffects' => false], + 'getenv' => ['hasSideEffects' => false], + 'gethostbyaddr' => ['hasSideEffects' => false], + 'gethostbyname' => ['hasSideEffects' => false], + 'gethostbynamel' => ['hasSideEffects' => false], + 'gethostname' => ['hasSideEffects' => false], + 'getlastmod' => ['hasSideEffects' => false], + 'getmygid' => ['hasSideEffects' => false], + 'getmyinode' => ['hasSideEffects' => false], + 'getmypid' => ['hasSideEffects' => false], + 'getmyuid' => ['hasSideEffects' => false], + 'getprotobyname' => ['hasSideEffects' => false], + 'getprotobynumber' => ['hasSideEffects' => false], + 'getrandmax' => ['hasSideEffects' => false], + 'getrusage' => ['hasSideEffects' => false], + 'getservbyname' => ['hasSideEffects' => false], + 'getservbyport' => ['hasSideEffects' => false], + 'gettext' => ['hasSideEffects' => false], + 'gettimeofday' => ['hasSideEffects' => false], + 'gettype' => ['hasSideEffects' => false], + 'glob' => ['hasSideEffects' => false], + 'gmdate' => ['hasSideEffects' => false], + 'gmmktime' => ['hasSideEffects' => false], + 'gmp_abs' => ['hasSideEffects' => false], + 'gmp_add' => ['hasSideEffects' => false], + 'gmp_and' => ['hasSideEffects' => false], + 'gmp_binomial' => ['hasSideEffects' => false], + 'gmp_cmp' => ['hasSideEffects' => false], + 'gmp_com' => ['hasSideEffects' => false], + 'gmp_div' => ['hasSideEffects' => false], + 'gmp_div_q' => ['hasSideEffects' => false], + 'gmp_div_qr' => ['hasSideEffects' => false], + 'gmp_div_r' => ['hasSideEffects' => false], + 'gmp_divexact' => ['hasSideEffects' => false], + 'gmp_export' => ['hasSideEffects' => false], + 'gmp_fact' => ['hasSideEffects' => false], + 'gmp_gcd' => ['hasSideEffects' => false], + 'gmp_gcdext' => ['hasSideEffects' => false], + 'gmp_hamdist' => ['hasSideEffects' => false], + 'gmp_import' => ['hasSideEffects' => false], + 'gmp_init' => ['hasSideEffects' => false], + 'gmp_intval' => ['hasSideEffects' => false], + 'gmp_invert' => ['hasSideEffects' => false], + 'gmp_jacobi' => ['hasSideEffects' => false], + 'gmp_kronecker' => ['hasSideEffects' => false], + 'gmp_lcm' => ['hasSideEffects' => false], + 'gmp_legendre' => ['hasSideEffects' => false], + 'gmp_mod' => ['hasSideEffects' => false], + 'gmp_mul' => ['hasSideEffects' => false], + 'gmp_neg' => ['hasSideEffects' => false], + 'gmp_nextprime' => ['hasSideEffects' => false], + 'gmp_or' => ['hasSideEffects' => false], + 'gmp_perfect_power' => ['hasSideEffects' => false], + 'gmp_perfect_square' => ['hasSideEffects' => false], + 'gmp_popcount' => ['hasSideEffects' => false], + 'gmp_pow' => ['hasSideEffects' => false], + 'gmp_powm' => ['hasSideEffects' => false], + 'gmp_prob_prime' => ['hasSideEffects' => false], + 'gmp_random' => ['hasSideEffects' => false], + 'gmp_random_bits' => ['hasSideEffects' => false], + 'gmp_random_range' => ['hasSideEffects' => false], + 'gmp_root' => ['hasSideEffects' => false], + 'gmp_rootrem' => ['hasSideEffects' => false], + 'gmp_scan0' => ['hasSideEffects' => false], + 'gmp_scan1' => ['hasSideEffects' => false], + 'gmp_sign' => ['hasSideEffects' => false], + 'gmp_sqrt' => ['hasSideEffects' => false], + 'gmp_sqrtrem' => ['hasSideEffects' => false], + 'gmp_strval' => ['hasSideEffects' => false], + 'gmp_sub' => ['hasSideEffects' => false], + 'gmp_testbit' => ['hasSideEffects' => false], + 'gmp_xor' => ['hasSideEffects' => false], + 'grapheme_stripos' => ['hasSideEffects' => false], + 'grapheme_stristr' => ['hasSideEffects' => false], + 'grapheme_strlen' => ['hasSideEffects' => false], + 'grapheme_strpos' => ['hasSideEffects' => false], + 'grapheme_strripos' => ['hasSideEffects' => false], + 'grapheme_strrpos' => ['hasSideEffects' => false], + 'grapheme_strstr' => ['hasSideEffects' => false], + 'grapheme_substr' => ['hasSideEffects' => false], + 'gzcompress' => ['hasSideEffects' => false], + 'gzdecode' => ['hasSideEffects' => false], + 'gzdeflate' => ['hasSideEffects' => false], + 'gzencode' => ['hasSideEffects' => false], + 'gzinflate' => ['hasSideEffects' => false], + 'gzuncompress' => ['hasSideEffects' => false], + 'hash' => ['hasSideEffects' => false], + 'hash_algos' => ['hasSideEffects' => false], + 'hash_copy' => ['hasSideEffects' => false], + 'hash_equals' => ['hasSideEffects' => false], + 'hash_file' => ['hasSideEffects' => false], + 'hash_hkdf' => ['hasSideEffects' => false], + 'hash_hmac' => ['hasSideEffects' => false], + 'hash_hmac_algos' => ['hasSideEffects' => false], + 'hash_hmac_file' => ['hasSideEffects' => false], + 'hash_init' => ['hasSideEffects' => false], + 'hash_pbkdf2' => ['hasSideEffects' => false], + 'headers_list' => ['hasSideEffects' => false], + 'hebrev' => ['hasSideEffects' => false], + 'hexdec' => ['hasSideEffects' => false], + 'hrtime' => ['hasSideEffects' => false], + 'html_entity_decode' => ['hasSideEffects' => false], + 'htmlentities' => ['hasSideEffects' => false], + 'htmlspecialchars' => ['hasSideEffects' => false], + 'htmlspecialchars_decode' => ['hasSideEffects' => false], + 'http_build_cookie' => ['hasSideEffects' => false], + 'http_build_query' => ['hasSideEffects' => false], + 'http_build_str' => ['hasSideEffects' => false], + 'http_cache_etag' => ['hasSideEffects' => false], + 'http_cache_last_modified' => ['hasSideEffects' => false], + 'http_chunked_decode' => ['hasSideEffects' => false], + 'http_date' => ['hasSideEffects' => false], + 'http_deflate' => ['hasSideEffects' => false], + 'http_get_request_body' => ['hasSideEffects' => false], + 'http_get_request_body_stream' => ['hasSideEffects' => false], + 'http_get_request_headers' => ['hasSideEffects' => false], + 'http_inflate' => ['hasSideEffects' => false], + 'http_match_etag' => ['hasSideEffects' => false], + 'http_match_modified' => ['hasSideEffects' => false], + 'http_match_request_header' => ['hasSideEffects' => false], + 'http_parse_cookie' => ['hasSideEffects' => false], + 'http_parse_headers' => ['hasSideEffects' => false], + 'http_parse_message' => ['hasSideEffects' => false], + 'http_parse_params' => ['hasSideEffects' => false], + 'http_request_body_encode' => ['hasSideEffects' => false], + 'http_request_method_exists' => ['hasSideEffects' => false], + 'http_request_method_name' => ['hasSideEffects' => false], + 'http_support' => ['hasSideEffects' => false], + 'hypot' => ['hasSideEffects' => false], + 'iconv' => ['hasSideEffects' => false], + 'iconv_get_encoding' => ['hasSideEffects' => false], + 'iconv_mime_decode' => ['hasSideEffects' => false], + 'iconv_mime_decode_headers' => ['hasSideEffects' => false], + 'iconv_mime_encode' => ['hasSideEffects' => false], + 'iconv_strlen' => ['hasSideEffects' => false], + 'iconv_strpos' => ['hasSideEffects' => false], + 'iconv_strrpos' => ['hasSideEffects' => false], + 'iconv_substr' => ['hasSideEffects' => false], + 'idate' => ['hasSideEffects' => false], + 'image_type_to_extension' => ['hasSideEffects' => false], + 'image_type_to_mime_type' => ['hasSideEffects' => false], + 'imagecolorat' => ['hasSideEffects' => false], + 'imagecolorclosest' => ['hasSideEffects' => false], + 'imagecolorclosestalpha' => ['hasSideEffects' => false], + 'imagecolorclosesthwb' => ['hasSideEffects' => false], + 'imagecolorexact' => ['hasSideEffects' => false], + 'imagecolorexactalpha' => ['hasSideEffects' => false], + 'imagecolorresolve' => ['hasSideEffects' => false], + 'imagecolorresolvealpha' => ['hasSideEffects' => false], + 'imagecolorsforindex' => ['hasSideEffects' => false], + 'imagecolorstotal' => ['hasSideEffects' => false], + 'imagecreate' => ['hasSideEffects' => false], + 'imagecreatefromstring' => ['hasSideEffects' => false], + 'imagecreatetruecolor' => ['hasSideEffects' => false], + 'imagefontheight' => ['hasSideEffects' => false], + 'imagefontwidth' => ['hasSideEffects' => false], + 'imageftbbox' => ['hasSideEffects' => false], + 'imagegetinterpolation' => ['hasSideEffects' => false], + 'imagegrabscreen' => ['hasSideEffects' => false], + 'imagegrabwindow' => ['hasSideEffects' => false], + 'imageistruecolor' => ['hasSideEffects' => false], + 'imagesx' => ['hasSideEffects' => false], + 'imagesy' => ['hasSideEffects' => false], + 'imagettfbbox' => ['hasSideEffects' => false], + 'imagetypes' => ['hasSideEffects' => false], + 'implode' => ['hasSideEffects' => false], + 'in_array' => ['hasSideEffects' => false], + 'inet_ntop' => ['hasSideEffects' => false], + 'inet_pton' => ['hasSideEffects' => false], + 'inflate_get_read_len' => ['hasSideEffects' => false], + 'inflate_get_status' => ['hasSideEffects' => false], + 'inflate_init' => ['hasSideEffects' => false], + 'ini_get' => ['hasSideEffects' => false], + 'ini_get_all' => ['hasSideEffects' => false], + 'intcal_get_maximum' => ['hasSideEffects' => false], + 'intdiv' => ['hasSideEffects' => false], + 'intl_error_name' => ['hasSideEffects' => false], + 'intl_get' => ['hasSideEffects' => false], + 'intl_get_error_code' => ['hasSideEffects' => false], + 'intl_get_error_message' => ['hasSideEffects' => false], + 'intl_is_failure' => ['hasSideEffects' => false], + 'intlcal_after' => ['hasSideEffects' => false], + 'intlcal_before' => ['hasSideEffects' => false], + 'intlcal_create_instance' => ['hasSideEffects' => false], + 'intlcal_equals' => ['hasSideEffects' => false], + 'intlcal_field_difference' => ['hasSideEffects' => false], + 'intlcal_from_date_time' => ['hasSideEffects' => false], + 'intlcal_get' => ['hasSideEffects' => false], + 'intlcal_get_actual_maximum' => ['hasSideEffects' => false], + 'intlcal_get_actual_minimum' => ['hasSideEffects' => false], + 'intlcal_get_available_locales' => ['hasSideEffects' => false], + 'intlcal_get_day_of_week_type' => ['hasSideEffects' => false], + 'intlcal_get_error_code' => ['hasSideEffects' => false], + 'intlcal_get_error_message' => ['hasSideEffects' => false], + 'intlcal_get_first_day_of_week' => ['hasSideEffects' => false], + 'intlcal_get_greatest_minimum' => ['hasSideEffects' => false], + 'intlcal_get_keyword_values_for_locale' => ['hasSideEffects' => false], + 'intlcal_get_least_maximum' => ['hasSideEffects' => false], + 'intlcal_get_locale' => ['hasSideEffects' => false], + 'intlcal_get_maximum' => ['hasSideEffects' => false], + 'intlcal_get_minimal_days_in_first_week' => ['hasSideEffects' => false], + 'intlcal_get_minimum' => ['hasSideEffects' => false], + 'intlcal_get_now' => ['hasSideEffects' => false], + 'intlcal_get_repeated_wall_time_option' => ['hasSideEffects' => false], + 'intlcal_get_skipped_wall_time_option' => ['hasSideEffects' => false], + 'intlcal_get_time' => ['hasSideEffects' => false], + 'intlcal_get_time_zone' => ['hasSideEffects' => false], + 'intlcal_get_type' => ['hasSideEffects' => false], + 'intlcal_get_weekend_transition' => ['hasSideEffects' => false], + 'intlcal_greates_minimum' => ['hasSideEffects' => false], + 'intlcal_in_daylight_time' => ['hasSideEffects' => false], + 'intlcal_is_equivalent_to' => ['hasSideEffects' => false], + 'intlcal_is_lenient' => ['hasSideEffects' => false], + 'intlcal_is_set' => ['hasSideEffects' => false], + 'intlcal_is_weekend' => ['hasSideEffects' => false], + 'intlcal_to_date_time' => ['hasSideEffects' => false], + 'intlgregcal_create_instance' => ['hasSideEffects' => false], + 'intlgregcal_get_gregorian_change' => ['hasSideEffects' => false], + 'intlgregcal_is_leap_year' => ['hasSideEffects' => false], + 'intltz_count_equivalent_ids' => ['hasSideEffects' => false], + 'intltz_create_default' => ['hasSideEffects' => false], + 'intltz_create_enumeration' => ['hasSideEffects' => false], + 'intltz_create_time_zone' => ['hasSideEffects' => false], + 'intltz_create_time_zone_id_enumeration' => ['hasSideEffects' => false], + 'intltz_from_date_time_zone' => ['hasSideEffects' => false], + 'intltz_get_canonical_id' => ['hasSideEffects' => false], + 'intltz_get_display_name' => ['hasSideEffects' => false], + 'intltz_get_dst_savings' => ['hasSideEffects' => false], + 'intltz_get_equivalent_id' => ['hasSideEffects' => false], + 'intltz_get_error_code' => ['hasSideEffects' => false], + 'intltz_get_error_message' => ['hasSideEffects' => false], + 'intltz_get_gmt' => ['hasSideEffects' => false], + 'intltz_get_id' => ['hasSideEffects' => false], + 'intltz_get_offset' => ['hasSideEffects' => false], + 'intltz_get_raw_offset' => ['hasSideEffects' => false], + 'intltz_get_region' => ['hasSideEffects' => false], + 'intltz_get_tz_data_version' => ['hasSideEffects' => false], + 'intltz_get_unknown' => ['hasSideEffects' => false], + 'intltz_getgmt' => ['hasSideEffects' => false], + 'intltz_has_same_rules' => ['hasSideEffects' => false], + 'intltz_to_date_time_zone' => ['hasSideEffects' => false], + 'intltz_use_daylight_time' => ['hasSideEffects' => false], + 'intlz_create_default' => ['hasSideEffects' => false], + 'intval' => ['hasSideEffects' => false], + 'ip2long' => ['hasSideEffects' => false], + 'iptcparse' => ['hasSideEffects' => false], + 'is_a' => ['hasSideEffects' => false], + 'is_array' => ['hasSideEffects' => false], + 'is_bool' => ['hasSideEffects' => false], + 'is_countable' => ['hasSideEffects' => false], + 'is_dir' => ['hasSideEffects' => false], + 'is_double' => ['hasSideEffects' => false], + 'is_executable' => ['hasSideEffects' => false], + 'is_file' => ['hasSideEffects' => false], + 'is_finite' => ['hasSideEffects' => false], + 'is_float' => ['hasSideEffects' => false], + 'is_infinite' => ['hasSideEffects' => false], + 'is_int' => ['hasSideEffects' => false], + 'is_integer' => ['hasSideEffects' => false], + 'is_iterable' => ['hasSideEffects' => false], + 'is_link' => ['hasSideEffects' => false], + 'is_long' => ['hasSideEffects' => false], + 'is_nan' => ['hasSideEffects' => false], + 'is_null' => ['hasSideEffects' => false], + 'is_numeric' => ['hasSideEffects' => false], + 'is_object' => ['hasSideEffects' => false], + 'is_readable' => ['hasSideEffects' => false], + 'is_real' => ['hasSideEffects' => false], + 'is_resource' => ['hasSideEffects' => false], + 'is_scalar' => ['hasSideEffects' => false], + 'is_string' => ['hasSideEffects' => false], + 'is_subclass_of' => ['hasSideEffects' => false], + 'is_uploaded_file' => ['hasSideEffects' => false], + 'is_writable' => ['hasSideEffects' => false], + 'is_writeable' => ['hasSideEffects' => false], + 'iterator_count' => ['hasSideEffects' => false], + 'join' => ['hasSideEffects' => false], + 'json_last_error' => ['hasSideEffects' => false], + 'json_last_error_msg' => ['hasSideEffects' => false], + 'key' => ['hasSideEffects' => false], + 'key_exists' => ['hasSideEffects' => false], + 'lcfirst' => ['hasSideEffects' => false], + 'libxml_get_errors' => ['hasSideEffects' => false], + 'libxml_get_last_error' => ['hasSideEffects' => false], + 'linkinfo' => ['hasSideEffects' => false], + 'locale_accept_from_http' => ['hasSideEffects' => false], + 'locale_canonicalize' => ['hasSideEffects' => false], + 'locale_compose' => ['hasSideEffects' => false], + 'locale_filter_matches' => ['hasSideEffects' => false], + 'locale_get_all_variants' => ['hasSideEffects' => false], + 'locale_get_default' => ['hasSideEffects' => false], + 'locale_get_display_language' => ['hasSideEffects' => false], + 'locale_get_display_name' => ['hasSideEffects' => false], + 'locale_get_display_region' => ['hasSideEffects' => false], + 'locale_get_display_script' => ['hasSideEffects' => false], + 'locale_get_display_variant' => ['hasSideEffects' => false], + 'locale_get_keywords' => ['hasSideEffects' => false], + 'locale_get_primary_language' => ['hasSideEffects' => false], + 'locale_get_region' => ['hasSideEffects' => false], + 'locale_get_script' => ['hasSideEffects' => false], + 'locale_lookup' => ['hasSideEffects' => false], + 'locale_parse' => ['hasSideEffects' => false], + 'localeconv' => ['hasSideEffects' => false], + 'localtime' => ['hasSideEffects' => false], + 'log' => ['hasSideEffects' => false], + 'log10' => ['hasSideEffects' => false], + 'log1p' => ['hasSideEffects' => false], + 'long2ip' => ['hasSideEffects' => false], + 'lstat' => ['hasSideEffects' => false], + 'ltrim' => ['hasSideEffects' => false], + 'max' => ['hasSideEffects' => false], + 'mb_check_encoding' => ['hasSideEffects' => false], + 'mb_chr' => ['hasSideEffects' => false], + 'mb_convert_case' => ['hasSideEffects' => false], + 'mb_convert_encoding' => ['hasSideEffects' => false], + 'mb_convert_kana' => ['hasSideEffects' => false], + 'mb_decode_mimeheader' => ['hasSideEffects' => false], + 'mb_decode_numericentity' => ['hasSideEffects' => false], + 'mb_detect_encoding' => ['hasSideEffects' => false], + 'mb_encode_mimeheader' => ['hasSideEffects' => false], + 'mb_encode_numericentity' => ['hasSideEffects' => false], + 'mb_encoding_aliases' => ['hasSideEffects' => false], + 'mb_ereg_match' => ['hasSideEffects' => false], + 'mb_ereg_replace' => ['hasSideEffects' => false], + 'mb_ereg_search' => ['hasSideEffects' => false], + 'mb_ereg_search_getpos' => ['hasSideEffects' => false], + 'mb_ereg_search_getregs' => ['hasSideEffects' => false], + 'mb_ereg_search_pos' => ['hasSideEffects' => false], + 'mb_ereg_search_regs' => ['hasSideEffects' => false], + 'mb_ereg_search_setpos' => ['hasSideEffects' => false], + 'mb_eregi_replace' => ['hasSideEffects' => false], + 'mb_get_info' => ['hasSideEffects' => false], + 'mb_http_input' => ['hasSideEffects' => false], + 'mb_list_encodings' => ['hasSideEffects' => false], + 'mb_ord' => ['hasSideEffects' => false], + 'mb_output_handler' => ['hasSideEffects' => false], + 'mb_preferred_mime_name' => ['hasSideEffects' => false], + 'mb_scrub' => ['hasSideEffects' => false], + 'mb_split' => ['hasSideEffects' => false], + 'mb_str_split' => ['hasSideEffects' => false], + 'mb_strcut' => ['hasSideEffects' => false], + 'mb_strimwidth' => ['hasSideEffects' => false], + 'mb_stripos' => ['hasSideEffects' => false], + 'mb_stristr' => ['hasSideEffects' => false], + 'mb_strlen' => ['hasSideEffects' => false], + 'mb_strpos' => ['hasSideEffects' => false], + 'mb_strrchr' => ['hasSideEffects' => false], + 'mb_strrichr' => ['hasSideEffects' => false], + 'mb_strripos' => ['hasSideEffects' => false], + 'mb_strrpos' => ['hasSideEffects' => false], + 'mb_strstr' => ['hasSideEffects' => false], + 'mb_strtolower' => ['hasSideEffects' => false], + 'mb_strtoupper' => ['hasSideEffects' => false], + 'mb_strwidth' => ['hasSideEffects' => false], + 'mb_substr' => ['hasSideEffects' => false], + 'mb_substr_count' => ['hasSideEffects' => false], + 'mbereg_search_setpos' => ['hasSideEffects' => false], + 'md5' => ['hasSideEffects' => false], + 'md5_file' => ['hasSideEffects' => false], + 'memory_get_peak_usage' => ['hasSideEffects' => false], + 'memory_get_usage' => ['hasSideEffects' => false], + 'metaphone' => ['hasSideEffects' => false], + 'method_exists' => ['hasSideEffects' => false], + 'mhash' => ['hasSideEffects' => false], + 'mhash_count' => ['hasSideEffects' => false], + 'mhash_get_block_size' => ['hasSideEffects' => false], + 'mhash_get_hash_name' => ['hasSideEffects' => false], + 'mhash_keygen_s2k' => ['hasSideEffects' => false], + 'microtime' => ['hasSideEffects' => false], + 'min' => ['hasSideEffects' => false], + 'mktime' => ['hasSideEffects' => false], + 'msgfmt_create' => ['hasSideEffects' => false], + 'msgfmt_format' => ['hasSideEffects' => false], + 'msgfmt_format_message' => ['hasSideEffects' => false], + 'msgfmt_get_error_code' => ['hasSideEffects' => false], + 'msgfmt_get_error_message' => ['hasSideEffects' => false], + 'msgfmt_get_locale' => ['hasSideEffects' => false], + 'msgfmt_get_pattern' => ['hasSideEffects' => false], + 'msgfmt_parse' => ['hasSideEffects' => false], + 'msgfmt_parse_message' => ['hasSideEffects' => false], + 'mt_getrandmax' => ['hasSideEffects' => false], + 'mt_rand' => ['hasSideEffects' => true], + 'net_get_interfaces' => ['hasSideEffects' => false], + 'ngettext' => ['hasSideEffects' => false], + 'nl2br' => ['hasSideEffects' => false], + 'nl_langinfo' => ['hasSideEffects' => false], + 'normalizer_get_raw_decomposition' => ['hasSideEffects' => false], + 'normalizer_is_normalized' => ['hasSideEffects' => false], + 'normalizer_normalize' => ['hasSideEffects' => false], + 'number_format' => ['hasSideEffects' => false], + 'numfmt_create' => ['hasSideEffects' => false], + 'numfmt_format' => ['hasSideEffects' => false], + 'numfmt_format_currency' => ['hasSideEffects' => false], + 'numfmt_get_attribute' => ['hasSideEffects' => false], + 'numfmt_get_error_code' => ['hasSideEffects' => false], + 'numfmt_get_error_message' => ['hasSideEffects' => false], + 'numfmt_get_locale' => ['hasSideEffects' => false], + 'numfmt_get_pattern' => ['hasSideEffects' => false], + 'numfmt_get_symbol' => ['hasSideEffects' => false], + 'numfmt_get_text_attribute' => ['hasSideEffects' => false], + 'numfmt_parse' => ['hasSideEffects' => false], + 'ob_etaghandler' => ['hasSideEffects' => false], + 'ob_iconv_handler' => ['hasSideEffects' => false], + 'octdec' => ['hasSideEffects' => false], + 'ord' => ['hasSideEffects' => false], + 'pack' => ['hasSideEffects' => false], + 'parse_ini_string' => ['hasSideEffects' => false], + 'parse_url' => ['hasSideEffects' => false], + 'pathinfo' => ['hasSideEffects' => false], + 'pcntl_errno' => ['hasSideEffects' => false], + 'pcntl_get_last_error' => ['hasSideEffects' => false], + 'pcntl_getpriority' => ['hasSideEffects' => false], + 'pcntl_strerror' => ['hasSideEffects' => false], + 'pcntl_wexitstatus' => ['hasSideEffects' => false], + 'pcntl_wifcontinued' => ['hasSideEffects' => false], + 'pcntl_wifexited' => ['hasSideEffects' => false], + 'pcntl_wifsignaled' => ['hasSideEffects' => false], + 'pcntl_wifstopped' => ['hasSideEffects' => false], + 'pcntl_wstopsig' => ['hasSideEffects' => false], + 'pcntl_wtermsig' => ['hasSideEffects' => false], + 'pdo_drivers' => ['hasSideEffects' => false], + 'php_ini_loaded_file' => ['hasSideEffects' => false], + 'php_ini_scanned_files' => ['hasSideEffects' => false], + 'php_logo_guid' => ['hasSideEffects' => false], + 'php_sapi_name' => ['hasSideEffects' => false], + 'php_strip_whitespace' => ['hasSideEffects' => false], + 'php_uname' => ['hasSideEffects' => false], + 'phpversion' => ['hasSideEffects' => false], + 'pi' => ['hasSideEffects' => false], + 'pos' => ['hasSideEffects' => false], + 'posix_ctermid' => ['hasSideEffects' => false], + 'posix_errno' => ['hasSideEffects' => false], + 'posix_get_last_error' => ['hasSideEffects' => false], + 'posix_getcwd' => ['hasSideEffects' => false], + 'posix_getegid' => ['hasSideEffects' => false], + 'posix_geteuid' => ['hasSideEffects' => false], + 'posix_getgid' => ['hasSideEffects' => false], + 'posix_getgrgid' => ['hasSideEffects' => false], + 'posix_getgrnam' => ['hasSideEffects' => false], + 'posix_getgroups' => ['hasSideEffects' => false], + 'posix_getlogin' => ['hasSideEffects' => false], + 'posix_getpgid' => ['hasSideEffects' => false], + 'posix_getpgrp' => ['hasSideEffects' => false], + 'posix_getpid' => ['hasSideEffects' => false], + 'posix_getppid' => ['hasSideEffects' => false], + 'posix_getpwnam' => ['hasSideEffects' => false], + 'posix_getpwuid' => ['hasSideEffects' => false], + 'posix_getrlimit' => ['hasSideEffects' => false], + 'posix_getsid' => ['hasSideEffects' => false], + 'posix_getuid' => ['hasSideEffects' => false], + 'posix_initgroups' => ['hasSideEffects' => false], + 'posix_isatty' => ['hasSideEffects' => false], + 'posix_strerror' => ['hasSideEffects' => false], + 'posix_times' => ['hasSideEffects' => false], + 'posix_ttyname' => ['hasSideEffects' => false], + 'posix_uname' => ['hasSideEffects' => false], + 'pow' => ['hasSideEffects' => false], + 'preg_grep' => ['hasSideEffects' => false], + 'preg_last_error' => ['hasSideEffects' => false], + 'preg_last_error_msg' => ['hasSideEffects' => false], + 'preg_quote' => ['hasSideEffects' => false], + 'preg_split' => ['hasSideEffects' => false], + 'property_exists' => ['hasSideEffects' => false], + 'quoted_printable_decode' => ['hasSideEffects' => false], + 'quoted_printable_encode' => ['hasSideEffects' => false], + 'quotemeta' => ['hasSideEffects' => false], + 'rad2deg' => ['hasSideEffects' => false], + 'rand' => ['hasSideEffects' => true], + 'random_bytes' => ['hasSideEffects' => true], + 'random_int' => ['hasSideEffects' => true], + 'range' => ['hasSideEffects' => false], + 'rawurldecode' => ['hasSideEffects' => false], + 'rawurlencode' => ['hasSideEffects' => false], + 'realpath' => ['hasSideEffects' => false], + 'realpath_cache_get' => ['hasSideEffects' => false], + 'realpath_cache_size' => ['hasSideEffects' => false], + 'resourcebundle_count' => ['hasSideEffects' => false], + 'resourcebundle_create' => ['hasSideEffects' => false], + 'resourcebundle_get' => ['hasSideEffects' => false], + 'resourcebundle_get_error_code' => ['hasSideEffects' => false], + 'resourcebundle_get_error_message' => ['hasSideEffects' => false], + 'resourcebundle_locales' => ['hasSideEffects' => false], + 'round' => ['hasSideEffects' => false], + 'rtrim' => ['hasSideEffects' => false], + 'sha1' => ['hasSideEffects' => false], + 'sha1_file' => ['hasSideEffects' => false], + 'sin' => ['hasSideEffects' => false], + 'sinh' => ['hasSideEffects' => false], + 'sizeof' => ['hasSideEffects' => false], + 'soundex' => ['hasSideEffects' => false], + 'spl_classes' => ['hasSideEffects' => false], + 'spl_object_hash' => ['hasSideEffects' => false], + 'sprintf' => ['hasSideEffects' => false], + 'sqrt' => ['hasSideEffects' => false], + 'stat' => ['hasSideEffects' => false], + 'str_contains' => ['hasSideEffects' => false], + 'str_ends_with' => ['hasSideEffects' => false], + 'str_getcsv' => ['hasSideEffects' => false], + 'str_pad' => ['hasSideEffects' => false], + 'str_repeat' => ['hasSideEffects' => false], + 'str_rot13' => ['hasSideEffects' => false], + 'str_shuffle' => ['hasSideEffects' => false], + 'str_split' => ['hasSideEffects' => false], + 'str_starts_with' => ['hasSideEffects' => false], + 'str_word_count' => ['hasSideEffects' => false], + 'strcasecmp' => ['hasSideEffects' => false], + 'strchr' => ['hasSideEffects' => false], + 'strcmp' => ['hasSideEffects' => false], + 'strcoll' => ['hasSideEffects' => false], + 'strcspn' => ['hasSideEffects' => false], + 'stream_get_filters' => ['hasSideEffects' => false], + 'stream_get_transports' => ['hasSideEffects' => false], + 'stream_get_wrappers' => ['hasSideEffects' => false], + 'stream_is_local' => ['hasSideEffects' => false], + 'stream_isatty' => ['hasSideEffects' => false], + 'strip_tags' => ['hasSideEffects' => false], + 'stripcslashes' => ['hasSideEffects' => false], + 'stripos' => ['hasSideEffects' => false], + 'stripslashes' => ['hasSideEffects' => false], + 'stristr' => ['hasSideEffects' => false], + 'strlen' => ['hasSideEffects' => false], + 'strnatcasecmp' => ['hasSideEffects' => false], + 'strnatcmp' => ['hasSideEffects' => false], + 'strncasecmp' => ['hasSideEffects' => false], + 'strncmp' => ['hasSideEffects' => false], + 'strpbrk' => ['hasSideEffects' => false], + 'strpos' => ['hasSideEffects' => false], + 'strptime' => ['hasSideEffects' => false], + 'strrchr' => ['hasSideEffects' => false], + 'strrev' => ['hasSideEffects' => false], + 'strripos' => ['hasSideEffects' => false], + 'strrpos' => ['hasSideEffects' => false], + 'strspn' => ['hasSideEffects' => false], + 'strstr' => ['hasSideEffects' => false], + 'strtolower' => ['hasSideEffects' => false], + 'strtotime' => ['hasSideEffects' => false], + 'strtoupper' => ['hasSideEffects' => false], + 'strtr' => ['hasSideEffects' => false], + 'strval' => ['hasSideEffects' => false], + 'substr' => ['hasSideEffects' => false], + 'substr_compare' => ['hasSideEffects' => false], + 'substr_count' => ['hasSideEffects' => false], + 'substr_replace' => ['hasSideEffects' => false], + 'sys_getloadavg' => ['hasSideEffects' => false], + 'tan' => ['hasSideEffects' => false], + 'tanh' => ['hasSideEffects' => false], + 'timezone_abbreviations_list' => ['hasSideEffects' => false], + 'timezone_identifiers_list' => ['hasSideEffects' => false], + 'timezone_location_get' => ['hasSideEffects' => false], + 'timezone_name_from_abbr' => ['hasSideEffects' => false], + 'timezone_name_get' => ['hasSideEffects' => false], + 'timezone_offset_get' => ['hasSideEffects' => false], + 'timezone_open' => ['hasSideEffects' => false], + 'timezone_transitions_get' => ['hasSideEffects' => false], + 'timezone_version_get' => ['hasSideEffects' => false], + 'token_get_all' => ['hasSideEffects' => false], + 'token_name' => ['hasSideEffects' => false], + 'transliterator_create' => ['hasSideEffects' => false], + 'transliterator_create_from_rules' => ['hasSideEffects' => false], + 'transliterator_create_inverse' => ['hasSideEffects' => false], + 'transliterator_get_error_code' => ['hasSideEffects' => false], + 'transliterator_get_error_message' => ['hasSideEffects' => false], + 'transliterator_list_ids' => ['hasSideEffects' => false], + 'transliterator_transliterate' => ['hasSideEffects' => false], + 'trim' => ['hasSideEffects' => false], + 'ucfirst' => ['hasSideEffects' => false], + 'ucwords' => ['hasSideEffects' => false], + 'uniqid' => ['hasSideEffects' => false], + 'unpack' => ['hasSideEffects' => false], + 'urldecode' => ['hasSideEffects' => false], + 'urlencode' => ['hasSideEffects' => false], + 'utf8_decode' => ['hasSideEffects' => false], + 'utf8_encode' => ['hasSideEffects' => false], + 'vsprintf' => ['hasSideEffects' => false], + 'wordwrap' => ['hasSideEffects' => false], + 'xml_error_string' => ['hasSideEffects' => false], + 'xml_get_current_byte_index' => ['hasSideEffects' => false], + 'xml_get_current_column_number' => ['hasSideEffects' => false], + 'xml_get_current_line_number' => ['hasSideEffects' => false], + 'xml_get_error_code' => ['hasSideEffects' => false], + 'xml_parser_create' => ['hasSideEffects' => false], + 'xml_parser_create_ns' => ['hasSideEffects' => false], + 'xml_parser_get_option' => ['hasSideEffects' => false], + 'zend_version' => ['hasSideEffects' => false], + 'zlib_decode' => ['hasSideEffects' => false], + 'zlib_encode' => ['hasSideEffects' => false], + 'zlib_get_coding_type' => ['hasSideEffects' => false], -]; \ No newline at end of file +]; diff --git a/src/AnalysedCodeException.php b/src/AnalysedCodeException.php index d4e7558622..dec1aceb5a 100644 --- a/src/AnalysedCodeException.php +++ b/src/AnalysedCodeException.php @@ -1,10 +1,10 @@ -fileAnalyser = $fileAnalyser; - $this->registry = $registry; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->internalErrorsCountLimit = $internalErrorsCountLimit; - } - - /** - * @param string[] $files - * @param \Closure(string $file): void|null $preFileCallback - * @param \Closure(int): void|null $postFileCallback - * @param bool $debug - * @param string[]|null $allAnalysedFiles - * @return AnalyserResult - */ - public function analyse( - array $files, - ?\Closure $preFileCallback = null, - ?\Closure $postFileCallback = null, - bool $debug = false, - ?array $allAnalysedFiles = null - ): AnalyserResult - { - if ($allAnalysedFiles === null) { - $allAnalysedFiles = $files; - } - - $this->nodeScopeResolver->setAnalysedFiles($allAnalysedFiles); - $allAnalysedFiles = array_fill_keys($allAnalysedFiles, true); - - $this->collectErrors($files); - - $errors = []; - $internalErrorsCount = 0; - $reachedInternalErrorsCountLimit = false; - $dependencies = []; - $exportedNodes = []; - foreach ($files as $file) { - if ($preFileCallback !== null) { - $preFileCallback($file); - } - - try { - $fileAnalyserResult = $this->fileAnalyser->analyseFile( - $file, - $allAnalysedFiles, - $this->registry, - null - ); - $errors = array_merge($errors, $fileAnalyserResult->getErrors()); - $dependencies[$file] = $fileAnalyserResult->getDependencies(); - - $fileExportedNodes = $fileAnalyserResult->getExportedNodes(); - if (count($fileExportedNodes) > 0) { - $exportedNodes[$file] = $fileExportedNodes; - } - } catch (\Throwable $t) { - if ($debug) { - throw $t; - } - $internalErrorsCount++; - $internalErrorMessage = sprintf('Internal error: %s', $t->getMessage()); - $internalErrorMessage .= sprintf( - '%sRun PHPStan with --debug option and post the stack trace to:%s%s', - "\n", - "\n", - 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.md' - ); - $errors[] = new Error($internalErrorMessage, $file, null, $t); - if ($internalErrorsCount >= $this->internalErrorsCountLimit) { - $reachedInternalErrorsCountLimit = true; - break; - } - } - - if ($postFileCallback === null) { - continue; - } - - $postFileCallback(1); - } - - $this->restoreCollectErrorsHandler(); - - $errors = array_merge($errors, $this->collectedErrors); - - return new AnalyserResult( - $errors, - [], - $internalErrorsCount === 0 ? $dependencies : null, - $exportedNodes, - $reachedInternalErrorsCountLimit - ); - } - - /** - * @param string[] $analysedFiles - */ - private function collectErrors(array $analysedFiles): void - { - $this->collectedErrors = []; - set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($analysedFiles): bool { - if (error_reporting() === 0) { - // silence @ operator - return true; - } - - if (!in_array($errfile, $analysedFiles, true)) { - return true; - } - - $this->collectedErrors[] = new Error($errstr, $errfile, $errline, true); - - return true; - }); - } - - private function restoreCollectErrorsHandler(): void - { - restore_error_handler(); - } - + private \PHPStan\Analyser\FileAnalyser $fileAnalyser; + + private Registry $registry; + + private \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver; + + private int $internalErrorsCountLimit; + + /** @var \PHPStan\Analyser\Error[] */ + private array $collectedErrors = []; + + public function __construct( + FileAnalyser $fileAnalyser, + Registry $registry, + NodeScopeResolver $nodeScopeResolver, + int $internalErrorsCountLimit + ) { + $this->fileAnalyser = $fileAnalyser; + $this->registry = $registry; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->internalErrorsCountLimit = $internalErrorsCountLimit; + } + + /** + * @param string[] $files + * @param \Closure(string $file): void|null $preFileCallback + * @param \Closure(int): void|null $postFileCallback + * @param bool $debug + * @param string[]|null $allAnalysedFiles + * @return AnalyserResult + */ + public function analyse( + array $files, + ?\Closure $preFileCallback = null, + ?\Closure $postFileCallback = null, + bool $debug = false, + ?array $allAnalysedFiles = null + ): AnalyserResult { + if ($allAnalysedFiles === null) { + $allAnalysedFiles = $files; + } + + $this->nodeScopeResolver->setAnalysedFiles($allAnalysedFiles); + $allAnalysedFiles = array_fill_keys($allAnalysedFiles, true); + + $this->collectErrors($files); + + $errors = []; + $internalErrorsCount = 0; + $reachedInternalErrorsCountLimit = false; + $dependencies = []; + $exportedNodes = []; + foreach ($files as $file) { + if ($preFileCallback !== null) { + $preFileCallback($file); + } + + try { + $fileAnalyserResult = $this->fileAnalyser->analyseFile( + $file, + $allAnalysedFiles, + $this->registry, + null + ); + $errors = array_merge($errors, $fileAnalyserResult->getErrors()); + $dependencies[$file] = $fileAnalyserResult->getDependencies(); + + $fileExportedNodes = $fileAnalyserResult->getExportedNodes(); + if (count($fileExportedNodes) > 0) { + $exportedNodes[$file] = $fileExportedNodes; + } + } catch (\Throwable $t) { + if ($debug) { + throw $t; + } + $internalErrorsCount++; + $internalErrorMessage = sprintf('Internal error: %s', $t->getMessage()); + $internalErrorMessage .= sprintf( + '%sRun PHPStan with --debug option and post the stack trace to:%s%s', + "\n", + "\n", + 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.md' + ); + $errors[] = new Error($internalErrorMessage, $file, null, $t); + if ($internalErrorsCount >= $this->internalErrorsCountLimit) { + $reachedInternalErrorsCountLimit = true; + break; + } + } + + if ($postFileCallback === null) { + continue; + } + + $postFileCallback(1); + } + + $this->restoreCollectErrorsHandler(); + + $errors = array_merge($errors, $this->collectedErrors); + + return new AnalyserResult( + $errors, + [], + $internalErrorsCount === 0 ? $dependencies : null, + $exportedNodes, + $reachedInternalErrorsCountLimit + ); + } + + /** + * @param string[] $analysedFiles + */ + private function collectErrors(array $analysedFiles): void + { + $this->collectedErrors = []; + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($analysedFiles): bool { + if (error_reporting() === 0) { + // silence @ operator + return true; + } + + if (!in_array($errfile, $analysedFiles, true)) { + return true; + } + + $this->collectedErrors[] = new Error($errstr, $errfile, $errline, true); + + return true; + }); + } + + private function restoreCollectErrorsHandler(): void + { + restore_error_handler(); + } } diff --git a/src/Analyser/AnalyserResult.php b/src/Analyser/AnalyserResult.php index d045f84749..844126f125 100644 --- a/src/Analyser/AnalyserResult.php +++ b/src/Analyser/AnalyserResult.php @@ -1,4 +1,6 @@ ->|null */ - private ?array $dependencies; - - /** @var array> */ - private array $exportedNodes; - - private bool $reachedInternalErrorsCountLimit; - - /** - * @param \PHPStan\Analyser\Error[] $errors - * @param string[] $internalErrors - * @param array>|null $dependencies - * @param array> $exportedNodes - * @param bool $reachedInternalErrorsCountLimit - */ - public function __construct( - array $errors, - array $internalErrors, - ?array $dependencies, - array $exportedNodes, - bool $reachedInternalErrorsCountLimit - ) - { - $this->unorderedErrors = $errors; - - usort( - $errors, - static function (Error $a, Error $b): int { - return [ - $a->getFile(), - $a->getLine(), - $a->getMessage(), - ] <=> [ - $b->getFile(), - $b->getLine(), - $b->getMessage(), - ]; - } - ); - - $this->errors = $errors; - $this->internalErrors = $internalErrors; - $this->dependencies = $dependencies; - $this->exportedNodes = $exportedNodes; - $this->reachedInternalErrorsCountLimit = $reachedInternalErrorsCountLimit; - } - - /** - * @return \PHPStan\Analyser\Error[] - */ - public function getUnorderedErrors(): array - { - return $this->unorderedErrors; - } - - /** - * @return \PHPStan\Analyser\Error[] - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return string[] - */ - public function getInternalErrors(): array - { - return $this->internalErrors; - } - - /** - * @return array>|null - */ - public function getDependencies(): ?array - { - return $this->dependencies; - } - - /** - * @return array> - */ - public function getExportedNodes(): array - { - return $this->exportedNodes; - } - - public function hasReachedInternalErrorsCountLimit(): bool - { - return $this->reachedInternalErrorsCountLimit; - } - + /** @var \PHPStan\Analyser\Error[] */ + private array $unorderedErrors; + + /** @var \PHPStan\Analyser\Error[] */ + private array $errors; + + /** @var string[] */ + private array $internalErrors; + + /** @var array>|null */ + private ?array $dependencies; + + /** @var array> */ + private array $exportedNodes; + + private bool $reachedInternalErrorsCountLimit; + + /** + * @param \PHPStan\Analyser\Error[] $errors + * @param string[] $internalErrors + * @param array>|null $dependencies + * @param array> $exportedNodes + * @param bool $reachedInternalErrorsCountLimit + */ + public function __construct( + array $errors, + array $internalErrors, + ?array $dependencies, + array $exportedNodes, + bool $reachedInternalErrorsCountLimit + ) { + $this->unorderedErrors = $errors; + + usort( + $errors, + static function (Error $a, Error $b): int { + return [ + $a->getFile(), + $a->getLine(), + $a->getMessage(), + ] <=> [ + $b->getFile(), + $b->getLine(), + $b->getMessage(), + ]; + } + ); + + $this->errors = $errors; + $this->internalErrors = $internalErrors; + $this->dependencies = $dependencies; + $this->exportedNodes = $exportedNodes; + $this->reachedInternalErrorsCountLimit = $reachedInternalErrorsCountLimit; + } + + /** + * @return \PHPStan\Analyser\Error[] + */ + public function getUnorderedErrors(): array + { + return $this->unorderedErrors; + } + + /** + * @return \PHPStan\Analyser\Error[] + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return string[] + */ + public function getInternalErrors(): array + { + return $this->internalErrors; + } + + /** + * @return array>|null + */ + public function getDependencies(): ?array + { + return $this->dependencies; + } + + /** + * @return array> + */ + public function getExportedNodes(): array + { + return $this->exportedNodes; + } + + public function hasReachedInternalErrorsCountLimit(): bool + { + return $this->reachedInternalErrorsCountLimit; + } } diff --git a/src/Analyser/ConditionalExpressionHolder.php b/src/Analyser/ConditionalExpressionHolder.php index 834dd49d03..617e78a2c0 100644 --- a/src/Analyser/ConditionalExpressionHolder.php +++ b/src/Analyser/ConditionalExpressionHolder.php @@ -1,4 +1,6 @@ - */ - private array $conditionExpressionTypes; - - private VariableTypeHolder $typeHolder; - - /** - * @param array $conditionExpressionTypes - * @param VariableTypeHolder $typeHolder - */ - public function __construct( - array $conditionExpressionTypes, - VariableTypeHolder $typeHolder - ) - { - if (count($conditionExpressionTypes) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - $this->conditionExpressionTypes = $conditionExpressionTypes; - $this->typeHolder = $typeHolder; - } - - /** - * @return array - */ - public function getConditionExpressionTypes(): array - { - return $this->conditionExpressionTypes; - } - - public function getTypeHolder(): VariableTypeHolder - { - return $this->typeHolder; - } - - public function getKey(): string - { - $parts = []; - foreach ($this->conditionExpressionTypes as $exprString => $type) { - $parts[] = $exprString . '=' . $type->describe(VerbosityLevel::precise()); - } - - return sprintf( - '%s => %s (%s)', - implode(' && ', $parts), - $this->typeHolder->getType()->describe(VerbosityLevel::precise()), - $this->typeHolder->getCertainty()->describe() - ); - } - + /** @var array */ + private array $conditionExpressionTypes; + + private VariableTypeHolder $typeHolder; + + /** + * @param array $conditionExpressionTypes + * @param VariableTypeHolder $typeHolder + */ + public function __construct( + array $conditionExpressionTypes, + VariableTypeHolder $typeHolder + ) { + if (count($conditionExpressionTypes) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + $this->conditionExpressionTypes = $conditionExpressionTypes; + $this->typeHolder = $typeHolder; + } + + /** + * @return array + */ + public function getConditionExpressionTypes(): array + { + return $this->conditionExpressionTypes; + } + + public function getTypeHolder(): VariableTypeHolder + { + return $this->typeHolder; + } + + public function getKey(): string + { + $parts = []; + foreach ($this->conditionExpressionTypes as $exprString => $type) { + $parts[] = $exprString . '=' . $type->describe(VerbosityLevel::precise()); + } + + return sprintf( + '%s => %s (%s)', + implode(' && ', $parts), + $this->typeHolder->getType()->describe(VerbosityLevel::precise()), + $this->typeHolder->getCertainty()->describe() + ); + } } diff --git a/src/Analyser/DirectScopeFactory.php b/src/Analyser/DirectScopeFactory.php index f8e7e43553..3f0e741f7c 100644 --- a/src/Analyser/DirectScopeFactory.php +++ b/src/Analyser/DirectScopeFactory.php @@ -1,4 +1,6 @@ -scopeClass = $scopeClass; - $this->reflectionProvider = $reflectionProvider; - $this->dynamicReturnTypeExtensionRegistryProvider = $dynamicReturnTypeExtensionRegistryProvider; - $this->operatorTypeSpecifyingExtensionRegistryProvider = $operatorTypeSpecifyingExtensionRegistryProvider; - $this->printer = $printer; - $this->typeSpecifier = $typeSpecifier; - $this->propertyReflectionFinder = $propertyReflectionFinder; - $this->parser = $parser; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->objectFromNewClass = $objectFromNewClass; - $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); - } - - /** - * @param \PHPStan\Analyser\ScopeContext $context - * @param bool $declareStrictTypes - * @param array $constantTypes - * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function - * @param string|null $namespace - * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes - * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes - * @param array $conditionalExpressions - * @param string|null $inClosureBindScopeClass - * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection - * @param bool $inFirstLevelStatement - * @param array $currentlyAssignedExpressions - * @param array $nativeExpressionTypes - * @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack - * @param bool $afterExtractCall - * @param Scope|null $parentScope - * - * @return MutatingScope - */ - public function create( - ScopeContext $context, - bool $declareStrictTypes = false, - array $constantTypes = [], - $function = null, - ?string $namespace = null, - array $variablesTypes = [], - array $moreSpecificTypes = [], - array $conditionalExpressions = [], - ?string $inClosureBindScopeClass = null, - ?ParametersAcceptor $anonymousFunctionReflection = null, - bool $inFirstLevelStatement = true, - array $currentlyAssignedExpressions = [], - array $nativeExpressionTypes = [], - array $inFunctionCallsStack = [], - bool $afterExtractCall = false, - ?Scope $parentScope = null - ): MutatingScope - { - $scopeClass = $this->scopeClass; - if (!is_a($scopeClass, MutatingScope::class, true)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return new $scopeClass( - $this, - $this->reflectionProvider, - $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry(), - $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry(), - $this->printer, - $this->typeSpecifier, - $this->propertyReflectionFinder, - $this->parser, - $this->nodeScopeResolver, - $context, - $declareStrictTypes, - $constantTypes, - $function, - $namespace, - $variablesTypes, - $moreSpecificTypes, - $conditionalExpressions, - $inClosureBindScopeClass, - $anonymousFunctionReflection, - $inFirstLevelStatement, - $currentlyAssignedExpressions, - $nativeExpressionTypes, - $inFunctionCallsStack, - $this->dynamicConstantNames, - $this->treatPhpDocTypesAsCertain, - $this->objectFromNewClass, - $afterExtractCall, - $parentScope - ); - } - + private string $scopeClass; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider; + + private OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider; + + private \PhpParser\PrettyPrinter\Standard $printer; + + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + private \PHPStan\Parser\Parser $parser; + + private NodeScopeResolver $nodeScopeResolver; + + private bool $treatPhpDocTypesAsCertain; + + private bool $objectFromNewClass; + + /** @var string[] */ + private array $dynamicConstantNames; + + public function __construct( + string $scopeClass, + ReflectionProvider $reflectionProvider, + DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, + OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider, + \PhpParser\PrettyPrinter\Standard $printer, + TypeSpecifier $typeSpecifier, + PropertyReflectionFinder $propertyReflectionFinder, + \PHPStan\Parser\Parser $parser, + NodeScopeResolver $nodeScopeResolver, + bool $treatPhpDocTypesAsCertain, + bool $objectFromNewClass, + Container $container + ) { + $this->scopeClass = $scopeClass; + $this->reflectionProvider = $reflectionProvider; + $this->dynamicReturnTypeExtensionRegistryProvider = $dynamicReturnTypeExtensionRegistryProvider; + $this->operatorTypeSpecifyingExtensionRegistryProvider = $operatorTypeSpecifyingExtensionRegistryProvider; + $this->printer = $printer; + $this->typeSpecifier = $typeSpecifier; + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->parser = $parser; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->objectFromNewClass = $objectFromNewClass; + $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); + } + + /** + * @param \PHPStan\Analyser\ScopeContext $context + * @param bool $declareStrictTypes + * @param array $constantTypes + * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function + * @param string|null $namespace + * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes + * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes + * @param array $conditionalExpressions + * @param string|null $inClosureBindScopeClass + * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection + * @param bool $inFirstLevelStatement + * @param array $currentlyAssignedExpressions + * @param array $nativeExpressionTypes + * @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack + * @param bool $afterExtractCall + * @param Scope|null $parentScope + * + * @return MutatingScope + */ + public function create( + ScopeContext $context, + bool $declareStrictTypes = false, + array $constantTypes = [], + $function = null, + ?string $namespace = null, + array $variablesTypes = [], + array $moreSpecificTypes = [], + array $conditionalExpressions = [], + ?string $inClosureBindScopeClass = null, + ?ParametersAcceptor $anonymousFunctionReflection = null, + bool $inFirstLevelStatement = true, + array $currentlyAssignedExpressions = [], + array $nativeExpressionTypes = [], + array $inFunctionCallsStack = [], + bool $afterExtractCall = false, + ?Scope $parentScope = null + ): MutatingScope { + $scopeClass = $this->scopeClass; + if (!is_a($scopeClass, MutatingScope::class, true)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return new $scopeClass( + $this, + $this->reflectionProvider, + $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry(), + $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry(), + $this->printer, + $this->typeSpecifier, + $this->propertyReflectionFinder, + $this->parser, + $this->nodeScopeResolver, + $context, + $declareStrictTypes, + $constantTypes, + $function, + $namespace, + $variablesTypes, + $moreSpecificTypes, + $conditionalExpressions, + $inClosureBindScopeClass, + $anonymousFunctionReflection, + $inFirstLevelStatement, + $currentlyAssignedExpressions, + $nativeExpressionTypes, + $inFunctionCallsStack, + $this->dynamicConstantNames, + $this->treatPhpDocTypesAsCertain, + $this->objectFromNewClass, + $afterExtractCall, + $parentScope + ); + } } diff --git a/src/Analyser/EnsuredNonNullabilityResult.php b/src/Analyser/EnsuredNonNullabilityResult.php index a949f46976..dddcb13b34 100644 --- a/src/Analyser/EnsuredNonNullabilityResult.php +++ b/src/Analyser/EnsuredNonNullabilityResult.php @@ -1,36 +1,36 @@ -scope = $scope; - $this->specifiedExpressions = $specifiedExpressions; - } - - public function getScope(): MutatingScope - { - return $this->scope; - } - - /** - * @return EnsuredNonNullabilityResultExpression[] - */ - public function getSpecifiedExpressions(): array - { - return $this->specifiedExpressions; - } - + private MutatingScope $scope; + + /** @var EnsuredNonNullabilityResultExpression[] */ + private array $specifiedExpressions; + + /** + * @param MutatingScope $scope + * @param EnsuredNonNullabilityResultExpression[] $specifiedExpressions + */ + public function __construct(MutatingScope $scope, array $specifiedExpressions) + { + $this->scope = $scope; + $this->specifiedExpressions = $specifiedExpressions; + } + + public function getScope(): MutatingScope + { + return $this->scope; + } + + /** + * @return EnsuredNonNullabilityResultExpression[] + */ + public function getSpecifiedExpressions(): array + { + return $this->specifiedExpressions; + } } diff --git a/src/Analyser/EnsuredNonNullabilityResultExpression.php b/src/Analyser/EnsuredNonNullabilityResultExpression.php index adc3ebfb22..d64176eb31 100644 --- a/src/Analyser/EnsuredNonNullabilityResultExpression.php +++ b/src/Analyser/EnsuredNonNullabilityResultExpression.php @@ -1,4 +1,6 @@ -expression = $expression; - $this->originalType = $originalType; - $this->originalNativeType = $originalNativeType; - } - - public function getExpression(): Expr - { - return $this->expression; - } - - public function getOriginalType(): Type - { - return $this->originalType; - } - - public function getOriginalNativeType(): Type - { - return $this->originalNativeType; - } - + private Expr $expression; + + private Type $originalType; + + private Type $originalNativeType; + + public function __construct( + Expr $expression, + Type $originalType, + Type $originalNativeType + ) { + $this->expression = $expression; + $this->originalType = $originalType; + $this->originalNativeType = $originalNativeType; + } + + public function getExpression(): Expr + { + return $this->expression; + } + + public function getOriginalType(): Type + { + return $this->originalType; + } + + public function getOriginalNativeType(): Type + { + return $this->originalNativeType; + } } diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index 7a80edbf8b..d5f4f8d3c3 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -1,264 +1,263 @@ -|null */ - private ?string $nodeType; - - private ?string $identifier; - - /** @var mixed[] */ - private array $metadata; - - /** - * Error constructor. - * - * @param string $message - * @param string $file - * @param int|null $line - * @param bool|\Throwable $canBeIgnored - * @param string|null $filePath - * @param string|null $traitFilePath - * @param string|null $tip - * @param int|null $nodeLine - * @param class-string<\PhpParser\Node>|null $nodeType - * @param string|null $identifier - * @param mixed[] $metadata - */ - public function __construct( - string $message, - string $file, - ?int $line = null, - $canBeIgnored = true, - ?string $filePath = null, - ?string $traitFilePath = null, - ?string $tip = null, - ?int $nodeLine = null, - ?string $nodeType = null, - ?string $identifier = null, - array $metadata = [] - ) - { - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->canBeIgnored = $canBeIgnored; - $this->filePath = $filePath; - $this->traitFilePath = $traitFilePath; - $this->tip = $tip; - $this->nodeLine = $nodeLine; - $this->nodeType = $nodeType; - $this->identifier = $identifier; - $this->metadata = $metadata; - } - - public function getMessage(): string - { - return $this->message; - } - - public function getFile(): string - { - return $this->file; - } - - public function getFilePath(): string - { - if ($this->filePath === null) { - return $this->file; - } - - return $this->filePath; - } - - public function changeFilePath(string $newFilePath): self - { - if ($this->traitFilePath !== null) { - throw new \PHPStan\ShouldNotHappenException('Errors in traits not yet supported'); - } - - return new self( - $this->message, - $newFilePath, - $this->line, - $this->canBeIgnored, - $newFilePath, - null, - $this->tip, - $this->nodeLine, - $this->nodeType, - $this->identifier, - $this->metadata - ); - } - - public function changeTraitFilePath(string $newFilePath): self - { - return new self( - $this->message, - $this->file, - $this->line, - $this->canBeIgnored, - $this->filePath, - $newFilePath, - $this->tip, - $this->nodeLine, - $this->nodeType, - $this->identifier, - $this->metadata - ); - } - - public function getTraitFilePath(): ?string - { - return $this->traitFilePath; - } - - public function getLine(): ?int - { - return $this->line; - } - - public function canBeIgnored(): bool - { - return $this->canBeIgnored === true; - } - - public function hasNonIgnorableException(): bool - { - return $this->canBeIgnored instanceof \Throwable; - } - - public function getTip(): ?string - { - return $this->tip; - } - - public function withoutTip(): self - { - if ($this->tip === null) { - return $this; - } - - return new self( - $this->message, - $this->file, - $this->line, - $this->canBeIgnored, - $this->filePath, - $this->traitFilePath, - null, - $this->nodeLine, - $this->nodeType - ); - } - - public function getNodeLine(): ?int - { - return $this->nodeLine; - } - - /** - * @return class-string<\PhpParser\Node>|null - */ - public function getNodeType(): ?string - { - return $this->nodeType; - } - - public function getIdentifier(): ?string - { - return $this->identifier; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'message' => $this->message, - 'file' => $this->file, - 'line' => $this->line, - 'canBeIgnored' => is_bool($this->canBeIgnored) ? $this->canBeIgnored : 'exception', - 'filePath' => $this->filePath, - 'traitFilePath' => $this->traitFilePath, - 'tip' => $this->tip, - 'nodeLine' => $this->nodeLine, - 'nodeType' => $this->nodeType, - 'identifier' => $this->identifier, - 'metadata' => $this->metadata, - ]; - } - - /** - * @param mixed[] $json - * @return self - */ - public static function decode(array $json): self - { - return new self( - $json['message'], - $json['file'], - $json['line'], - $json['canBeIgnored'] === 'exception' ? new \Exception() : $json['canBeIgnored'], - $json['filePath'], - $json['traitFilePath'], - $json['tip'], - $json['nodeLine'] ?? null, - $json['nodeType'] ?? null, - $json['identifier'] ?? null, - $json['metadata'] ?? [] - ); - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['message'], - $properties['file'], - $properties['line'], - $properties['canBeIgnored'], - $properties['filePath'], - $properties['traitFilePath'], - $properties['tip'], - $properties['nodeLine'] ?? null, - $properties['nodeType'] ?? null, - $properties['identifier'] ?? null, - $properties['metadata'] ?? [] - ); - } - + private string $message; + + private string $file; + + private ?int $line; + + /** @var bool|\Throwable */ + private $canBeIgnored; + + private ?string $filePath; + + private ?string $traitFilePath; + + private ?string $tip; + + private ?int $nodeLine; + + /** @phpstan-var class-string<\PhpParser\Node>|null */ + private ?string $nodeType; + + private ?string $identifier; + + /** @var mixed[] */ + private array $metadata; + + /** + * Error constructor. + * + * @param string $message + * @param string $file + * @param int|null $line + * @param bool|\Throwable $canBeIgnored + * @param string|null $filePath + * @param string|null $traitFilePath + * @param string|null $tip + * @param int|null $nodeLine + * @param class-string<\PhpParser\Node>|null $nodeType + * @param string|null $identifier + * @param mixed[] $metadata + */ + public function __construct( + string $message, + string $file, + ?int $line = null, + $canBeIgnored = true, + ?string $filePath = null, + ?string $traitFilePath = null, + ?string $tip = null, + ?int $nodeLine = null, + ?string $nodeType = null, + ?string $identifier = null, + array $metadata = [] + ) { + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->canBeIgnored = $canBeIgnored; + $this->filePath = $filePath; + $this->traitFilePath = $traitFilePath; + $this->tip = $tip; + $this->nodeLine = $nodeLine; + $this->nodeType = $nodeType; + $this->identifier = $identifier; + $this->metadata = $metadata; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFilePath(): string + { + if ($this->filePath === null) { + return $this->file; + } + + return $this->filePath; + } + + public function changeFilePath(string $newFilePath): self + { + if ($this->traitFilePath !== null) { + throw new \PHPStan\ShouldNotHappenException('Errors in traits not yet supported'); + } + + return new self( + $this->message, + $newFilePath, + $this->line, + $this->canBeIgnored, + $newFilePath, + null, + $this->tip, + $this->nodeLine, + $this->nodeType, + $this->identifier, + $this->metadata + ); + } + + public function changeTraitFilePath(string $newFilePath): self + { + return new self( + $this->message, + $this->file, + $this->line, + $this->canBeIgnored, + $this->filePath, + $newFilePath, + $this->tip, + $this->nodeLine, + $this->nodeType, + $this->identifier, + $this->metadata + ); + } + + public function getTraitFilePath(): ?string + { + return $this->traitFilePath; + } + + public function getLine(): ?int + { + return $this->line; + } + + public function canBeIgnored(): bool + { + return $this->canBeIgnored === true; + } + + public function hasNonIgnorableException(): bool + { + return $this->canBeIgnored instanceof \Throwable; + } + + public function getTip(): ?string + { + return $this->tip; + } + + public function withoutTip(): self + { + if ($this->tip === null) { + return $this; + } + + return new self( + $this->message, + $this->file, + $this->line, + $this->canBeIgnored, + $this->filePath, + $this->traitFilePath, + null, + $this->nodeLine, + $this->nodeType + ); + } + + public function getNodeLine(): ?int + { + return $this->nodeLine; + } + + /** + * @return class-string<\PhpParser\Node>|null + */ + public function getNodeType(): ?string + { + return $this->nodeType; + } + + public function getIdentifier(): ?string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'message' => $this->message, + 'file' => $this->file, + 'line' => $this->line, + 'canBeIgnored' => is_bool($this->canBeIgnored) ? $this->canBeIgnored : 'exception', + 'filePath' => $this->filePath, + 'traitFilePath' => $this->traitFilePath, + 'tip' => $this->tip, + 'nodeLine' => $this->nodeLine, + 'nodeType' => $this->nodeType, + 'identifier' => $this->identifier, + 'metadata' => $this->metadata, + ]; + } + + /** + * @param mixed[] $json + * @return self + */ + public static function decode(array $json): self + { + return new self( + $json['message'], + $json['file'], + $json['line'], + $json['canBeIgnored'] === 'exception' ? new \Exception() : $json['canBeIgnored'], + $json['filePath'], + $json['traitFilePath'], + $json['tip'], + $json['nodeLine'] ?? null, + $json['nodeType'] ?? null, + $json['identifier'] ?? null, + $json['metadata'] ?? [] + ); + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['message'], + $properties['file'], + $properties['line'], + $properties['canBeIgnored'], + $properties['filePath'], + $properties['traitFilePath'], + $properties['tip'], + $properties['nodeLine'] ?? null, + $properties['nodeType'] ?? null, + $properties['identifier'] ?? null, + $properties['metadata'] ?? [] + ); + } } diff --git a/src/Analyser/ExpressionContext.php b/src/Analyser/ExpressionContext.php index 373ea50d0a..bfcf81c3a3 100644 --- a/src/Analyser/ExpressionContext.php +++ b/src/Analyser/ExpressionContext.php @@ -1,4 +1,6 @@ -isDeep = $isDeep; - $this->inAssignRightSideVariableName = $inAssignRightSideVariableName; - $this->inAssignRightSideType = $inAssignRightSideType; - } - - public static function createTopLevel(): self - { - return new self(false, null, null); - } - - public static function createDeep(): self - { - return new self(true, null, null); - } - - public function enterDeep(): self - { - if ($this->isDeep) { - return $this; - } - - return new self(true, $this->inAssignRightSideVariableName, $this->inAssignRightSideType); - } - - public function isDeep(): bool - { - return $this->isDeep; - } - - public function enterRightSideAssign(string $variableName, Type $type): self - { - return new self($this->isDeep, $variableName, $type); - } - - public function getInAssignRightSideVariableName(): ?string - { - return $this->inAssignRightSideVariableName; - } - - public function getInAssignRightSideType(): ?Type - { - return $this->inAssignRightSideType; - } - + private bool $isDeep; + + private ?string $inAssignRightSideVariableName; + + private ?Type $inAssignRightSideType; + + private function __construct( + bool $isDeep, + ?string $inAssignRightSideVariableName, + ?Type $inAssignRightSideType + ) { + $this->isDeep = $isDeep; + $this->inAssignRightSideVariableName = $inAssignRightSideVariableName; + $this->inAssignRightSideType = $inAssignRightSideType; + } + + public static function createTopLevel(): self + { + return new self(false, null, null); + } + + public static function createDeep(): self + { + return new self(true, null, null); + } + + public function enterDeep(): self + { + if ($this->isDeep) { + return $this; + } + + return new self(true, $this->inAssignRightSideVariableName, $this->inAssignRightSideType); + } + + public function isDeep(): bool + { + return $this->isDeep; + } + + public function enterRightSideAssign(string $variableName, Type $type): self + { + return new self($this->isDeep, $variableName, $type); + } + + public function getInAssignRightSideVariableName(): ?string + { + return $this->inAssignRightSideVariableName; + } + + public function getInAssignRightSideType(): ?Type + { + return $this->inAssignRightSideType; + } } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 4cec309c24..9e769cca12 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -1,95 +1,94 @@ -scope = $scope; - $this->hasYield = $hasYield; - $this->throwPoints = $throwPoints; - $this->truthyScopeCallback = $truthyScopeCallback; - $this->falseyScopeCallback = $falseyScopeCallback; - } - - public function getScope(): MutatingScope - { - return $this->scope; - } - - public function hasYield(): bool - { - return $this->hasYield; - } - - /** - * @return ThrowPoint[] - */ - public function getThrowPoints(): array - { - return $this->throwPoints; - } - - public function getTruthyScope(): MutatingScope - { - if ($this->truthyScopeCallback === null) { - return $this->scope; - } - - if ($this->truthyScope !== null) { - return $this->truthyScope; - } - - $callback = $this->truthyScopeCallback; - $this->truthyScope = $callback(); - return $this->truthyScope; - } - - public function getFalseyScope(): MutatingScope - { - if ($this->falseyScopeCallback === null) { - return $this->scope; - } - - if ($this->falseyScope !== null) { - return $this->falseyScope; - } - - $callback = $this->falseyScopeCallback; - $this->falseyScope = $callback(); - return $this->falseyScope; - } - + private MutatingScope $scope; + + private bool $hasYield; + + /** @var ThrowPoint[] $throwPoints */ + private array $throwPoints; + + /** @var (callable(): MutatingScope)|null */ + private $truthyScopeCallback; + + private ?MutatingScope $truthyScope = null; + + /** @var (callable(): MutatingScope)|null */ + private $falseyScopeCallback; + + private ?MutatingScope $falseyScope = null; + + /** + * @param MutatingScope $scope + * @param bool $hasYield + * @param ThrowPoint[] $throwPoints + * @param (callable(): MutatingScope)|null $truthyScopeCallback + * @param (callable(): MutatingScope)|null $falseyScopeCallback + */ + public function __construct( + MutatingScope $scope, + bool $hasYield, + array $throwPoints, + ?callable $truthyScopeCallback = null, + ?callable $falseyScopeCallback = null + ) { + $this->scope = $scope; + $this->hasYield = $hasYield; + $this->throwPoints = $throwPoints; + $this->truthyScopeCallback = $truthyScopeCallback; + $this->falseyScopeCallback = $falseyScopeCallback; + } + + public function getScope(): MutatingScope + { + return $this->scope; + } + + public function hasYield(): bool + { + return $this->hasYield; + } + + /** + * @return ThrowPoint[] + */ + public function getThrowPoints(): array + { + return $this->throwPoints; + } + + public function getTruthyScope(): MutatingScope + { + if ($this->truthyScopeCallback === null) { + return $this->scope; + } + + if ($this->truthyScope !== null) { + return $this->truthyScope; + } + + $callback = $this->truthyScopeCallback; + $this->truthyScope = $callback(); + return $this->truthyScope; + } + + public function getFalseyScope(): MutatingScope + { + if ($this->falseyScopeCallback === null) { + return $this->scope; + } + + if ($this->falseyScope !== null) { + return $this->falseyScope; + } + + $callback = $this->falseyScopeCallback; + $this->falseyScope = $callback(); + return $this->falseyScope; + } } diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index af21ba3f71..0347803d78 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -1,4 +1,6 @@ -scopeFactory = $scopeFactory; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->parser = $parser; + $this->dependencyResolver = $dependencyResolver; + $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; + } + + /** + * @param string $file + * @param array $analysedFiles + * @param Registry $registry + * @param callable(\PhpParser\Node $node, Scope $scope): void|null $outerNodeCallback + * @return FileAnalyserResult + */ + public function analyseFile( + string $file, + array $analysedFiles, + Registry $registry, + ?callable $outerNodeCallback + ): FileAnalyserResult { + $fileErrors = []; + $fileDependencies = []; + $exportedNodes = []; + if (is_file($file)) { + try { + $parserNodes = $this->parser->parseFile($file); + $linesToIgnore = []; + $temporaryFileErrors = []; + $nodeCallback = function (\PhpParser\Node $node, Scope $scope) use (&$fileErrors, &$fileDependencies, &$exportedNodes, $file, $registry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$temporaryFileErrors): void { + if ($outerNodeCallback !== null) { + $outerNodeCallback($node, $scope); + } + $uniquedAnalysedCodeExceptionMessages = []; + $nodeType = get_class($node); + foreach ($registry->getRules($nodeType) as $rule) { + // We need to identify unique rules to assign an issue code + $ruleName = get_class($rule); + + try { + $ruleErrors = $rule->processNode($node, $scope); + } catch (\PHPStan\AnalysedCodeException $e) { + if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { + continue; + } + + $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; + $fileErrors[] = new Error($e->getMessage(), $file, $node->getLine(), $e, null, null, $e->getTip()); + continue; + } catch (IdentifierNotFound $e) { + $fileErrors[] = new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'); + continue; + } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { + $fileErrors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getLine(), $e); + continue; + } + + foreach ($ruleErrors as $ruleError) { + $nodeLine = $node->getLine(); + $line = $nodeLine; + $canBeIgnored = true; + $fileName = $scope->getFileDescription(); + $filePath = $scope->getFile(); + $traitFilePath = null; + $tip = null; + $identifier = null; + $metadata = []; + if ($scope->isInTrait()) { + $traitReflection = $scope->getTraitReflection(); + if ($traitReflection->getFileName() !== false) { + $traitFilePath = $traitReflection->getFileName(); + } + } + if (is_string($ruleError)) { + $message = $ruleError; + } else { + $message = $ruleError->getMessage(); + if ( + $ruleError instanceof LineRuleError + && $ruleError->getLine() !== -1 + ) { + $line = $ruleError->getLine(); + } + if ( + $ruleError instanceof FileRuleError + && $ruleError->getFile() !== '' + ) { + $fileName = $ruleError->getFile(); + $filePath = $ruleError->getFile(); + $traitFilePath = null; + } + + if ($ruleError instanceof TipRuleError) { + $tip = $ruleError->getTip(); + } + + if ($ruleError instanceof IdentifierRuleError) { + $identifier = $ruleError->getIdentifier(); + } + + if ($ruleError instanceof MetadataRuleError) { + $metadata = $ruleError->getMetadata(); + } + + if ($ruleError instanceof NonIgnorableRuleError) { + $canBeIgnored = false; + } + } + + $message = "{$ruleName}:::{$message}"; + + $temporaryFileErrors[] = new Error( + $message, + $fileName, + $line, + $canBeIgnored, + $filePath, + $traitFilePath, + $tip, + $nodeLine, + $nodeType, + $identifier, + $metadata + ); + } + } + + foreach ($this->getLinesToIgnore($node) as $lineToIgnore) { + $linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true; + } + + try { + $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); + foreach ($dependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) { + $fileDependencies[] = $dependentFile; + } + if ($dependencies->getExportedNode() !== null) { + $exportedNodes[] = $dependencies->getExportedNode(); + } + } catch (\PHPStan\AnalysedCodeException $e) { + // pass + } catch (IdentifierNotFound $e) { + // pass + } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { + // pass + } + }; + + $scope = $this->scopeFactory->create(ScopeContext::create($file)); + $nodeCallback(new FileNode($parserNodes), $scope); + $this->nodeScopeResolver->processNodes( + $parserNodes, + $scope, + $nodeCallback + ); + $unmatchedLineIgnores = $linesToIgnore; + foreach ($temporaryFileErrors as $tmpFileError) { + $line = $tmpFileError->getLine(); + if ( + $line !== null + && $tmpFileError->canBeIgnored() + && array_key_exists($tmpFileError->getFile(), $linesToIgnore) + && array_key_exists($line, $linesToIgnore[$tmpFileError->getFile()]) + ) { + unset($unmatchedLineIgnores[$tmpFileError->getFile()][$line]); + continue; + } + + $fileErrors[] = $tmpFileError; + } + + if ($this->reportUnmatchedIgnoredErrors) { + foreach ($unmatchedLineIgnores as $ignoredFile => $lines) { + if ($ignoredFile !== $file) { + continue; + } + + foreach (array_keys($lines) as $line) { + $fileErrors[] = new Error( + sprintf('No error to ignore is reported on line %d.', $line), + $scope->getFileDescription(), + $line, + false, + $scope->getFile(), + null, + null, + null, + null, + 'ignoredError.unmatchedOnLine' + ); + } + } + } + } catch (\PhpParser\Error $e) { + $fileErrors[] = new Error($e->getMessage(), $file, $e->getStartLine() !== -1 ? $e->getStartLine() : null, $e); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + foreach ($e->getErrors() as $error) { + $fileErrors[] = new Error($error->getMessage(), $e->getParsedFile() ?? $file, $error->getStartLine() !== -1 ? $error->getStartLine() : null, $e); + } + } catch (\PHPStan\AnalysedCodeException $e) { + $fileErrors[] = new Error($e->getMessage(), $file, null, $e, null, null, $e->getTip()); + } catch (IdentifierNotFound $e) { + $fileErrors[] = new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, null, $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'); + } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { + $fileErrors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, null, $e); + } + } elseif (is_dir($file)) { + $fileErrors[] = new Error(sprintf('File %s is a directory.', $file), $file, null, false); + } else { + $fileErrors[] = new Error(sprintf('File %s does not exist.', $file), $file, null, false); + } + + return new FileAnalyserResult($fileErrors, array_values(array_unique($fileDependencies)), $exportedNodes); + } + + /** + * @param Node $node + * @return int[] + */ + private function getLinesToIgnore(Node $node): array + { + $lines = []; + if ($node->getDocComment() !== null) { + $line = $this->findLineToIgnoreComment($node->getDocComment()); + if ($line !== null) { + $lines[] = $line; + } + } + + foreach ($node->getComments() as $comment) { + $line = $this->findLineToIgnoreComment($comment); + if ($line === null) { + continue; + } + + $lines[] = $line; + } + + return $lines; + } + + private function findLineToIgnoreComment(Comment $comment): ?int + { + $text = $comment->getText(); + if ($comment instanceof Comment\Doc) { + $line = $comment->getEndLine(); + } else { + if (strpos($text, "\n") === false || strpos($text, '//') === 0) { + $line = $comment->getStartLine(); + } else { + $line = $comment->getEndLine(); + } + } + if (strpos($text, '@phpstan-ignore-next-line') !== false) { + return $line + 1; + } - private \PHPStan\Analyser\ScopeFactory $scopeFactory; - - private \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver; - - private \PHPStan\Parser\Parser $parser; - - private DependencyResolver $dependencyResolver; - - private bool $reportUnmatchedIgnoredErrors; - - public function __construct( - ScopeFactory $scopeFactory, - NodeScopeResolver $nodeScopeResolver, - Parser $parser, - DependencyResolver $dependencyResolver, - bool $reportUnmatchedIgnoredErrors - ) - { - $this->scopeFactory = $scopeFactory; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->parser = $parser; - $this->dependencyResolver = $dependencyResolver; - $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; - } - - /** - * @param string $file - * @param array $analysedFiles - * @param Registry $registry - * @param callable(\PhpParser\Node $node, Scope $scope): void|null $outerNodeCallback - * @return FileAnalyserResult - */ - public function analyseFile( - string $file, - array $analysedFiles, - Registry $registry, - ?callable $outerNodeCallback - ): FileAnalyserResult - { - $fileErrors = []; - $fileDependencies = []; - $exportedNodes = []; - if (is_file($file)) { - try { - $parserNodes = $this->parser->parseFile($file); - $linesToIgnore = []; - $temporaryFileErrors = []; - $nodeCallback = function (\PhpParser\Node $node, Scope $scope) use (&$fileErrors, &$fileDependencies, &$exportedNodes, $file, $registry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$temporaryFileErrors): void { - if ($outerNodeCallback !== null) { - $outerNodeCallback($node, $scope); - } - $uniquedAnalysedCodeExceptionMessages = []; - $nodeType = get_class($node); - foreach ($registry->getRules($nodeType) as $rule) { - try { - $ruleErrors = $rule->processNode($node, $scope); - } catch (\PHPStan\AnalysedCodeException $e) { - if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { - continue; - } - - $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; - $fileErrors[] = new Error($e->getMessage(), $file, $node->getLine(), $e, null, null, $e->getTip()); - continue; - } catch (IdentifierNotFound $e) { - $fileErrors[] = new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'); - continue; - } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { - $fileErrors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getLine(), $e); - continue; - } - - foreach ($ruleErrors as $ruleError) { - $nodeLine = $node->getLine(); - $line = $nodeLine; - $canBeIgnored = true; - $fileName = $scope->getFileDescription(); - $filePath = $scope->getFile(); - $traitFilePath = null; - $tip = null; - $identifier = null; - $metadata = []; - if ($scope->isInTrait()) { - $traitReflection = $scope->getTraitReflection(); - if ($traitReflection->getFileName() !== false) { - $traitFilePath = $traitReflection->getFileName(); - } - } - if (is_string($ruleError)) { - $message = $ruleError; - } else { - $message = $ruleError->getMessage(); - if ( - $ruleError instanceof LineRuleError - && $ruleError->getLine() !== -1 - ) { - $line = $ruleError->getLine(); - } - if ( - $ruleError instanceof FileRuleError - && $ruleError->getFile() !== '' - ) { - $fileName = $ruleError->getFile(); - $filePath = $ruleError->getFile(); - $traitFilePath = null; - } - - if ($ruleError instanceof TipRuleError) { - $tip = $ruleError->getTip(); - } - - if ($ruleError instanceof IdentifierRuleError) { - $identifier = $ruleError->getIdentifier(); - } - - if ($ruleError instanceof MetadataRuleError) { - $metadata = $ruleError->getMetadata(); - } - - if ($ruleError instanceof NonIgnorableRuleError) { - $canBeIgnored = false; - } - } - $temporaryFileErrors[] = new Error( - $message, - $fileName, - $line, - $canBeIgnored, - $filePath, - $traitFilePath, - $tip, - $nodeLine, - $nodeType, - $identifier, - $metadata - ); - } - } - - foreach ($this->getLinesToIgnore($node) as $lineToIgnore) { - $linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true; - } - - try { - $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); - foreach ($dependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) { - $fileDependencies[] = $dependentFile; - } - if ($dependencies->getExportedNode() !== null) { - $exportedNodes[] = $dependencies->getExportedNode(); - } - } catch (\PHPStan\AnalysedCodeException $e) { - // pass - } catch (IdentifierNotFound $e) { - // pass - } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { - // pass - } - }; - - $scope = $this->scopeFactory->create(ScopeContext::create($file)); - $nodeCallback(new FileNode($parserNodes), $scope); - $this->nodeScopeResolver->processNodes( - $parserNodes, - $scope, - $nodeCallback - ); - $unmatchedLineIgnores = $linesToIgnore; - foreach ($temporaryFileErrors as $tmpFileError) { - $line = $tmpFileError->getLine(); - if ( - $line !== null - && $tmpFileError->canBeIgnored() - && array_key_exists($tmpFileError->getFile(), $linesToIgnore) - && array_key_exists($line, $linesToIgnore[$tmpFileError->getFile()]) - ) { - unset($unmatchedLineIgnores[$tmpFileError->getFile()][$line]); - continue; - } - - $fileErrors[] = $tmpFileError; - } - - if ($this->reportUnmatchedIgnoredErrors) { - foreach ($unmatchedLineIgnores as $ignoredFile => $lines) { - if ($ignoredFile !== $file) { - continue; - } - - foreach (array_keys($lines) as $line) { - $fileErrors[] = new Error( - sprintf('No error to ignore is reported on line %d.', $line), - $scope->getFileDescription(), - $line, - false, - $scope->getFile(), - null, - null, - null, - null, - 'ignoredError.unmatchedOnLine' - ); - } - } - } - } catch (\PhpParser\Error $e) { - $fileErrors[] = new Error($e->getMessage(), $file, $e->getStartLine() !== -1 ? $e->getStartLine() : null, $e); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - foreach ($e->getErrors() as $error) { - $fileErrors[] = new Error($error->getMessage(), $e->getParsedFile() ?? $file, $error->getStartLine() !== -1 ? $error->getStartLine() : null, $e); - } - } catch (\PHPStan\AnalysedCodeException $e) { - $fileErrors[] = new Error($e->getMessage(), $file, null, $e, null, null, $e->getTip()); - } catch (IdentifierNotFound $e) { - $fileErrors[] = new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, null, $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'); - } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { - $fileErrors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, null, $e); - } - } elseif (is_dir($file)) { - $fileErrors[] = new Error(sprintf('File %s is a directory.', $file), $file, null, false); - } else { - $fileErrors[] = new Error(sprintf('File %s does not exist.', $file), $file, null, false); - } - - return new FileAnalyserResult($fileErrors, array_values(array_unique($fileDependencies)), $exportedNodes); - } - - /** - * @param Node $node - * @return int[] - */ - private function getLinesToIgnore(Node $node): array - { - $lines = []; - if ($node->getDocComment() !== null) { - $line = $this->findLineToIgnoreComment($node->getDocComment()); - if ($line !== null) { - $lines[] = $line; - } - } - - foreach ($node->getComments() as $comment) { - $line = $this->findLineToIgnoreComment($comment); - if ($line === null) { - continue; - } - - $lines[] = $line; - } - - return $lines; - } - - private function findLineToIgnoreComment(Comment $comment): ?int - { - $text = $comment->getText(); - if ($comment instanceof Comment\Doc) { - $line = $comment->getEndLine(); - } else { - if (strpos($text, "\n") === false || strpos($text, '//') === 0) { - $line = $comment->getStartLine(); - } else { - $line = $comment->getEndLine(); - } - } - if (strpos($text, '@phpstan-ignore-next-line') !== false) { - return $line + 1; - } - - if (strpos($text, '@phpstan-ignore-line') !== false) { - return $line; - } - - return null; - } + if (strpos($text, '@phpstan-ignore-line') !== false) { + return $line; + } + return null; + } } diff --git a/src/Analyser/FileAnalyserResult.php b/src/Analyser/FileAnalyserResult.php index 024a953026..03f92831fc 100644 --- a/src/Analyser/FileAnalyserResult.php +++ b/src/Analyser/FileAnalyserResult.php @@ -1,4 +1,6 @@ - */ - private array $dependencies; - - /** @var array */ - private array $exportedNodes; - - /** - * @param Error[] $errors - * @param array $dependencies - * @param array $exportedNodes - */ - public function __construct(array $errors, array $dependencies, array $exportedNodes) - { - $this->errors = $errors; - $this->dependencies = $dependencies; - $this->exportedNodes = $exportedNodes; - } - - /** - * @return Error[] - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return array - */ - public function getDependencies(): array - { - return $this->dependencies; - } - - /** - * @return array - */ - public function getExportedNodes(): array - { - return $this->exportedNodes; - } - + /** @var Error[] */ + private array $errors; + + /** @var array */ + private array $dependencies; + + /** @var array */ + private array $exportedNodes; + + /** + * @param Error[] $errors + * @param array $dependencies + * @param array $exportedNodes + */ + public function __construct(array $errors, array $dependencies, array $exportedNodes) + { + $this->errors = $errors; + $this->dependencies = $dependencies; + $this->exportedNodes = $exportedNodes; + } + + /** + * @return Error[] + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return array + */ + public function getDependencies(): array + { + return $this->dependencies; + } + + /** + * @return array + */ + public function getExportedNodes(): array + { + return $this->exportedNodes; + } } diff --git a/src/Analyser/IgnoredError.php b/src/Analyser/IgnoredError.php index f683a95ee1..a7ca0486b3 100644 --- a/src/Analyser/IgnoredError.php +++ b/src/Analyser/IgnoredError.php @@ -1,4 +1,6 @@ -getMessage(); - $errorMessage = str_replace(['\r\n', '\r'], '\n', $errorMessage); - $ignoredErrorPattern = str_replace([preg_quote('\r\n'), preg_quote('\r')], preg_quote('\n'), $ignoredErrorPattern); + return $ignoredError['message']; + } - if ($path !== null) { - $fileExcluder = new FileExcluder($fileHelper, [$path], []); + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param FileHelper $fileHelper + * @param Error $error + * @param string $ignoredErrorPattern + * @param string|null $path + * @return bool To ignore or not to ignore? + */ + public static function shouldIgnore( + FileHelper $fileHelper, + Error $error, + string $ignoredErrorPattern, + ?string $path + ): bool { + // normalize newlines to allow working with ignore-patterns independent of used OS newline-format + $errorMessage = $error->getMessage(); + $errorMessage = str_replace(['\r\n', '\r'], '\n', $errorMessage); + $ignoredErrorPattern = str_replace([preg_quote('\r\n'), preg_quote('\r')], preg_quote('\n'), $ignoredErrorPattern); - if (\Nette\Utils\Strings::match($errorMessage, $ignoredErrorPattern) === null) { - return false; - } + if ($path !== null) { + $fileExcluder = new FileExcluder($fileHelper, [$path], []); - $isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath()); - if (!$isExcluded && $error->getTraitFilePath() !== null) { - return $fileExcluder->isExcludedFromAnalysing($error->getTraitFilePath()); - } + if (\Nette\Utils\Strings::match($errorMessage, $ignoredErrorPattern) === null) { + return false; + } - return $isExcluded; - } + $isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath()); + if (!$isExcluded && $error->getTraitFilePath() !== null) { + return $fileExcluder->isExcludedFromAnalysing($error->getTraitFilePath()); + } - return \Nette\Utils\Strings::match($errorMessage, $ignoredErrorPattern) !== null; - } + return $isExcluded; + } + return \Nette\Utils\Strings::match($errorMessage, $ignoredErrorPattern) !== null; + } } diff --git a/src/Analyser/IgnoredErrorHelper.php b/src/Analyser/IgnoredErrorHelper.php index b38467f3a8..9ddad5b570 100644 --- a/src/Analyser/IgnoredErrorHelper.php +++ b/src/Analyser/IgnoredErrorHelper.php @@ -1,4 +1,6 @@ -ignoredRegexValidator = $ignoredRegexValidator; - $this->fileHelper = $fileHelper; - $this->ignoreErrors = $ignoreErrors; - $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; - } - - public function initialize(): IgnoredErrorHelperResult - { - $otherIgnoreErrors = []; - $ignoreErrorsByFile = []; - $warnings = []; - $errors = []; - foreach ($this->ignoreErrors as $i => $ignoreError) { - try { - if (is_array($ignoreError)) { - if (!isset($ignoreError['message'])) { - $errors[] = sprintf( - 'Ignored error %s is missing a message.', - Json::encode($ignoreError) - ); - continue; - } - if (!isset($ignoreError['path'])) { - if (!isset($ignoreError['paths'])) { - $errors[] = sprintf( - 'Ignored error %s is missing a path.', - Json::encode($ignoreError) - ); - } - - $otherIgnoreErrors[] = [ - 'index' => $i, - 'ignoreError' => $ignoreError, - ]; - } elseif (is_file($ignoreError['path'])) { - $normalizedPath = $this->fileHelper->normalizePath($ignoreError['path']); - $ignoreError['path'] = $normalizedPath; - $ignoreErrorsByFile[$normalizedPath][] = [ - 'index' => $i, - 'ignoreError' => $ignoreError, - ]; - $ignoreError['realPath'] = $normalizedPath; - $this->ignoreErrors[$i] = $ignoreError; - } else { - $otherIgnoreErrors[] = [ - 'index' => $i, - 'ignoreError' => $ignoreError, - ]; - } - - $ignoreMessage = $ignoreError['message']; - \Nette\Utils\Strings::match('', $ignoreMessage); - if (isset($ignoreError['count'])) { - continue; // ignoreError coming from baseline will be correct - } - $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); - $ignoredTypes = $validationResult->getIgnoredTypes(); - if (count($ignoredTypes) > 0) { - $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); - } - - if ($validationResult->hasAnchorsInTheMiddle()) { - $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); - } - - if ($validationResult->areAllErrorsIgnored()) { - $errors[] = sprintf("Ignored error %s has an unescaped '%s' which leads to ignoring all errors. Use '%s' instead.", $ignoreMessage, $validationResult->getWrongSequence(), $validationResult->getEscapedWrongSequence()); - } - } else { - $otherIgnoreErrors[] = [ - 'index' => $i, - 'ignoreError' => $ignoreError, - ]; - $ignoreMessage = $ignoreError; - \Nette\Utils\Strings::match('', $ignoreMessage); - $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); - $ignoredTypes = $validationResult->getIgnoredTypes(); - if (count($ignoredTypes) > 0) { - $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); - } - - if ($validationResult->hasAnchorsInTheMiddle()) { - $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); - } - - if ($validationResult->areAllErrorsIgnored()) { - $errors[] = sprintf("Ignored error %s has an unescaped '%s' which leads to ignoring all errors. Use '%s' instead.", $ignoreMessage, $validationResult->getWrongSequence(), $validationResult->getEscapedWrongSequence()); - } - } - } catch (\Nette\Utils\RegexpException $e) { - $errors[] = $e->getMessage(); - } catch (\Nette\Utils\JsonException $e) { - $errors[] = $e->getMessage(); - } - } - - return new IgnoredErrorHelperResult($this->fileHelper, $errors, $warnings, $otherIgnoreErrors, $ignoreErrorsByFile, $this->ignoreErrors, $this->reportUnmatchedIgnoredErrors); - } - - /** - * @param string $regex - * @param array $ignoredTypes - * @return string - */ - private function createIgnoredTypesWarning(string $regex, array $ignoredTypes): string - { - return sprintf( - "Ignored error %s has an unescaped '|' which leads to ignoring more errors than intended. Use '\\|' instead.\n%s", - $regex, - sprintf( - "It ignores all errors containing the following types:\n%s", - implode("\n", array_map(static function (string $typeDescription): string { - return sprintf('* %s', $typeDescription); - }, array_keys($ignoredTypes))) - ) - ); - } - - private function createAnchorInTheMiddleWarning(string $regex): string - { - return sprintf("Ignored error %s has an unescaped anchor '$' in the middle. This leads to unintended behavior. Use '\\$' instead.", $regex); - } - + private IgnoredRegexValidator $ignoredRegexValidator; + + private \PHPStan\File\FileHelper $fileHelper; + + /** @var (string|mixed[])[] */ + private array $ignoreErrors; + + private bool $reportUnmatchedIgnoredErrors; + + /** + * @param IgnoredRegexValidator $ignoredRegexValidator + * @param FileHelper $fileHelper + * @param (string|mixed[])[] $ignoreErrors + * @param bool $reportUnmatchedIgnoredErrors + */ + public function __construct( + IgnoredRegexValidator $ignoredRegexValidator, + FileHelper $fileHelper, + array $ignoreErrors, + bool $reportUnmatchedIgnoredErrors + ) { + $this->ignoredRegexValidator = $ignoredRegexValidator; + $this->fileHelper = $fileHelper; + $this->ignoreErrors = $ignoreErrors; + $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; + } + + public function initialize(): IgnoredErrorHelperResult + { + $otherIgnoreErrors = []; + $ignoreErrorsByFile = []; + $warnings = []; + $errors = []; + foreach ($this->ignoreErrors as $i => $ignoreError) { + try { + if (is_array($ignoreError)) { + if (!isset($ignoreError['message'])) { + $errors[] = sprintf( + 'Ignored error %s is missing a message.', + Json::encode($ignoreError) + ); + continue; + } + if (!isset($ignoreError['path'])) { + if (!isset($ignoreError['paths'])) { + $errors[] = sprintf( + 'Ignored error %s is missing a path.', + Json::encode($ignoreError) + ); + } + + $otherIgnoreErrors[] = [ + 'index' => $i, + 'ignoreError' => $ignoreError, + ]; + } elseif (is_file($ignoreError['path'])) { + $normalizedPath = $this->fileHelper->normalizePath($ignoreError['path']); + $ignoreError['path'] = $normalizedPath; + $ignoreErrorsByFile[$normalizedPath][] = [ + 'index' => $i, + 'ignoreError' => $ignoreError, + ]; + $ignoreError['realPath'] = $normalizedPath; + $this->ignoreErrors[$i] = $ignoreError; + } else { + $otherIgnoreErrors[] = [ + 'index' => $i, + 'ignoreError' => $ignoreError, + ]; + } + + $ignoreMessage = $ignoreError['message']; + \Nette\Utils\Strings::match('', $ignoreMessage); + if (isset($ignoreError['count'])) { + continue; // ignoreError coming from baseline will be correct + } + $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); + $ignoredTypes = $validationResult->getIgnoredTypes(); + if (count($ignoredTypes) > 0) { + $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); + } + + if ($validationResult->hasAnchorsInTheMiddle()) { + $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); + } + + if ($validationResult->areAllErrorsIgnored()) { + $errors[] = sprintf("Ignored error %s has an unescaped '%s' which leads to ignoring all errors. Use '%s' instead.", $ignoreMessage, $validationResult->getWrongSequence(), $validationResult->getEscapedWrongSequence()); + } + } else { + $otherIgnoreErrors[] = [ + 'index' => $i, + 'ignoreError' => $ignoreError, + ]; + $ignoreMessage = $ignoreError; + \Nette\Utils\Strings::match('', $ignoreMessage); + $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); + $ignoredTypes = $validationResult->getIgnoredTypes(); + if (count($ignoredTypes) > 0) { + $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); + } + + if ($validationResult->hasAnchorsInTheMiddle()) { + $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); + } + + if ($validationResult->areAllErrorsIgnored()) { + $errors[] = sprintf("Ignored error %s has an unescaped '%s' which leads to ignoring all errors. Use '%s' instead.", $ignoreMessage, $validationResult->getWrongSequence(), $validationResult->getEscapedWrongSequence()); + } + } + } catch (\Nette\Utils\RegexpException $e) { + $errors[] = $e->getMessage(); + } catch (\Nette\Utils\JsonException $e) { + $errors[] = $e->getMessage(); + } + } + + return new IgnoredErrorHelperResult($this->fileHelper, $errors, $warnings, $otherIgnoreErrors, $ignoreErrorsByFile, $this->ignoreErrors, $this->reportUnmatchedIgnoredErrors); + } + + /** + * @param string $regex + * @param array $ignoredTypes + * @return string + */ + private function createIgnoredTypesWarning(string $regex, array $ignoredTypes): string + { + return sprintf( + "Ignored error %s has an unescaped '|' which leads to ignoring more errors than intended. Use '\\|' instead.\n%s", + $regex, + sprintf( + "It ignores all errors containing the following types:\n%s", + implode("\n", array_map(static function (string $typeDescription): string { + return sprintf('* %s', $typeDescription); + }, array_keys($ignoredTypes))) + ) + ); + } + + private function createAnchorInTheMiddleWarning(string $regex): string + { + return sprintf("Ignored error %s has an unescaped anchor '$' in the middle. This leads to unintended behavior. Use '\\$' instead.", $regex); + } } diff --git a/src/Analyser/IgnoredErrorHelperResult.php b/src/Analyser/IgnoredErrorHelperResult.php index 4aa7b42bf1..ee2c63df9a 100644 --- a/src/Analyser/IgnoredErrorHelperResult.php +++ b/src/Analyser/IgnoredErrorHelperResult.php @@ -1,4 +1,6 @@ -> */ - private array $otherIgnoreErrors; - - /** @var array>> */ - private array $ignoreErrorsByFile; - - /** @var (string|mixed[])[] */ - private array $ignoreErrors; - - private bool $reportUnmatchedIgnoredErrors; - - /** - * @param FileHelper $fileHelper - * @param string[] $errors - * @param string[] $warnings - * @param array> $otherIgnoreErrors - * @param array>> $ignoreErrorsByFile - * @param (string|mixed[])[] $ignoreErrors - * @param bool $reportUnmatchedIgnoredErrors - */ - public function __construct( - FileHelper $fileHelper, - array $errors, - array $warnings, - array $otherIgnoreErrors, - array $ignoreErrorsByFile, - array $ignoreErrors, - bool $reportUnmatchedIgnoredErrors - ) - { - $this->fileHelper = $fileHelper; - $this->errors = $errors; - $this->warnings = $warnings; - $this->otherIgnoreErrors = $otherIgnoreErrors; - $this->ignoreErrorsByFile = $ignoreErrorsByFile; - $this->ignoreErrors = $ignoreErrors; - $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; - } - - /** - * @return string[] - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return string[] - */ - public function getWarnings(): array - { - return $this->warnings; - } - - /** - * @param Error[] $errors - * @param string[] $analysedFiles - * @return string[]|Error[] - */ - public function process( - array $errors, - bool $onlyFiles, - array $analysedFiles, - bool $hasInternalErrors - ): array - { - $unmatchedIgnoredErrors = $this->ignoreErrors; - $addErrors = []; - - $processIgnoreError = function (Error $error, int $i, $ignore) use (&$unmatchedIgnoredErrors, &$addErrors): bool { - $shouldBeIgnored = false; - if (is_string($ignore)) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore, null); - if ($shouldBeIgnored) { - unset($unmatchedIgnoredErrors[$i]); - } - } else { - if (isset($ignore['path'])) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'], $ignore['path']); - if ($shouldBeIgnored) { - if (isset($ignore['count'])) { - $realCount = $unmatchedIgnoredErrors[$i]['realCount'] ?? 0; - $realCount++; - $unmatchedIgnoredErrors[$i]['realCount'] = $realCount; - - if (!isset($unmatchedIgnoredErrors[$i]['file'])) { - $unmatchedIgnoredErrors[$i]['file'] = $error->getFile(); - $unmatchedIgnoredErrors[$i]['line'] = $error->getLine(); - } - - if ($realCount > $ignore['count']) { - $shouldBeIgnored = false; - } - } else { - unset($unmatchedIgnoredErrors[$i]); - } - } - } elseif (isset($ignore['paths'])) { - foreach ($ignore['paths'] as $j => $ignorePath) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'], $ignorePath); - if (!$shouldBeIgnored) { - continue; - } - - if (isset($unmatchedIgnoredErrors[$i])) { - if (!is_array($unmatchedIgnoredErrors[$i])) { - throw new \PHPStan\ShouldNotHappenException(); - } - unset($unmatchedIgnoredErrors[$i]['paths'][$j]); - if (isset($unmatchedIgnoredErrors[$i]['paths']) && count($unmatchedIgnoredErrors[$i]['paths']) === 0) { - unset($unmatchedIgnoredErrors[$i]); - } - } - break; - } - } else { - throw new \PHPStan\ShouldNotHappenException(); - } - } - - if ($shouldBeIgnored) { - if (!$error->canBeIgnored()) { - $addErrors[] = sprintf( - 'Error message "%s" cannot be ignored, use excludePaths instead.', - $error->getMessage() - ); - return true; - } - return false; - } - - return true; - }; - - $errors = array_values(array_filter($errors, function (Error $error) use ($processIgnoreError): bool { - $filePath = $this->fileHelper->normalizePath($error->getFilePath()); - if (isset($this->ignoreErrorsByFile[$filePath])) { - foreach ($this->ignoreErrorsByFile[$filePath] as $ignoreError) { - $i = $ignoreError['index']; - $ignore = $ignoreError['ignoreError']; - $result = $processIgnoreError($error, $i, $ignore); - if (!$result) { - return false; - } - } - } - - $traitFilePath = $error->getTraitFilePath(); - if ($traitFilePath !== null) { - $normalizedTraitFilePath = $this->fileHelper->normalizePath($traitFilePath); - if (isset($this->ignoreErrorsByFile[$normalizedTraitFilePath])) { - foreach ($this->ignoreErrorsByFile[$normalizedTraitFilePath] as $ignoreError) { - $i = $ignoreError['index']; - $ignore = $ignoreError['ignoreError']; - $result = $processIgnoreError($error, $i, $ignore); - if (!$result) { - return false; - } - } - } - } - - foreach ($this->otherIgnoreErrors as $ignoreError) { - $i = $ignoreError['index']; - $ignore = $ignoreError['ignoreError']; - - $result = $processIgnoreError($error, $i, $ignore); - if (!$result) { - return false; - } - } - - return true; - })); - - foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) { - if (!isset($unmatchedIgnoredError['count']) || !isset($unmatchedIgnoredError['realCount'])) { - continue; - } - - if ($unmatchedIgnoredError['realCount'] <= $unmatchedIgnoredError['count']) { - continue; - } - - $addErrors[] = new Error(sprintf( - 'Ignored error pattern %s is expected to occur %d %s, but occurred %d %s.', - IgnoredError::stringifyPattern($unmatchedIgnoredError), - $unmatchedIgnoredError['count'], - $unmatchedIgnoredError['count'] === 1 ? 'time' : 'times', - $unmatchedIgnoredError['realCount'], - $unmatchedIgnoredError['realCount'] === 1 ? 'time' : 'times' - ), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false); - } - - $errors = array_merge($errors, $addErrors); - - $analysedFilesKeys = array_fill_keys($analysedFiles, true); - - if ($this->reportUnmatchedIgnoredErrors && !$hasInternalErrors) { - foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) { - if ( - isset($unmatchedIgnoredError['count']) - && isset($unmatchedIgnoredError['realCount']) - && (isset($unmatchedIgnoredError['realPath']) || !$onlyFiles) - ) { - if ($unmatchedIgnoredError['realCount'] < $unmatchedIgnoredError['count']) { - $errors[] = new Error(sprintf( - 'Ignored error pattern %s is expected to occur %d %s, but occurred only %d %s.', - IgnoredError::stringifyPattern($unmatchedIgnoredError), - $unmatchedIgnoredError['count'], - $unmatchedIgnoredError['count'] === 1 ? 'time' : 'times', - $unmatchedIgnoredError['realCount'], - $unmatchedIgnoredError['realCount'] === 1 ? 'time' : 'times' - ), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false); - } - } elseif (isset($unmatchedIgnoredError['realPath'])) { - if (!array_key_exists($unmatchedIgnoredError['realPath'], $analysedFilesKeys)) { - continue; - } - - $errors[] = new Error( - sprintf( - 'Ignored error pattern %s was not matched in reported errors.', - IgnoredError::stringifyPattern($unmatchedIgnoredError) - ), - $unmatchedIgnoredError['realPath'], - null, - false - ); - } elseif (!$onlyFiles) { - $errors[] = sprintf( - 'Ignored error pattern %s was not matched in reported errors.', - IgnoredError::stringifyPattern($unmatchedIgnoredError) - ); - } - } - } - - return $errors; - } - + private FileHelper $fileHelper; + + /** @var string[] */ + private array $errors; + + /** @var string[] */ + private array $warnings; + + /** @var array> */ + private array $otherIgnoreErrors; + + /** @var array>> */ + private array $ignoreErrorsByFile; + + /** @var (string|mixed[])[] */ + private array $ignoreErrors; + + private bool $reportUnmatchedIgnoredErrors; + + /** + * @param FileHelper $fileHelper + * @param string[] $errors + * @param string[] $warnings + * @param array> $otherIgnoreErrors + * @param array>> $ignoreErrorsByFile + * @param (string|mixed[])[] $ignoreErrors + * @param bool $reportUnmatchedIgnoredErrors + */ + public function __construct( + FileHelper $fileHelper, + array $errors, + array $warnings, + array $otherIgnoreErrors, + array $ignoreErrorsByFile, + array $ignoreErrors, + bool $reportUnmatchedIgnoredErrors + ) { + $this->fileHelper = $fileHelper; + $this->errors = $errors; + $this->warnings = $warnings; + $this->otherIgnoreErrors = $otherIgnoreErrors; + $this->ignoreErrorsByFile = $ignoreErrorsByFile; + $this->ignoreErrors = $ignoreErrors; + $this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors; + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return string[] + */ + public function getWarnings(): array + { + return $this->warnings; + } + + /** + * @param Error[] $errors + * @param string[] $analysedFiles + * @return string[]|Error[] + */ + public function process( + array $errors, + bool $onlyFiles, + array $analysedFiles, + bool $hasInternalErrors + ): array { + $unmatchedIgnoredErrors = $this->ignoreErrors; + $addErrors = []; + + $processIgnoreError = function (Error $error, int $i, $ignore) use (&$unmatchedIgnoredErrors, &$addErrors): bool { + $shouldBeIgnored = false; + if (is_string($ignore)) { + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore, null); + if ($shouldBeIgnored) { + unset($unmatchedIgnoredErrors[$i]); + } + } else { + if (isset($ignore['path'])) { + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'], $ignore['path']); + if ($shouldBeIgnored) { + if (isset($ignore['count'])) { + $realCount = $unmatchedIgnoredErrors[$i]['realCount'] ?? 0; + $realCount++; + $unmatchedIgnoredErrors[$i]['realCount'] = $realCount; + + if (!isset($unmatchedIgnoredErrors[$i]['file'])) { + $unmatchedIgnoredErrors[$i]['file'] = $error->getFile(); + $unmatchedIgnoredErrors[$i]['line'] = $error->getLine(); + } + + if ($realCount > $ignore['count']) { + $shouldBeIgnored = false; + } + } else { + unset($unmatchedIgnoredErrors[$i]); + } + } + } elseif (isset($ignore['paths'])) { + foreach ($ignore['paths'] as $j => $ignorePath) { + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'], $ignorePath); + if (!$shouldBeIgnored) { + continue; + } + + if (isset($unmatchedIgnoredErrors[$i])) { + if (!is_array($unmatchedIgnoredErrors[$i])) { + throw new \PHPStan\ShouldNotHappenException(); + } + unset($unmatchedIgnoredErrors[$i]['paths'][$j]); + if (isset($unmatchedIgnoredErrors[$i]['paths']) && count($unmatchedIgnoredErrors[$i]['paths']) === 0) { + unset($unmatchedIgnoredErrors[$i]); + } + } + break; + } + } else { + throw new \PHPStan\ShouldNotHappenException(); + } + } + + if ($shouldBeIgnored) { + if (!$error->canBeIgnored()) { + $addErrors[] = sprintf( + 'Error message "%s" cannot be ignored, use excludePaths instead.', + $error->getMessage() + ); + return true; + } + return false; + } + + return true; + }; + + $errors = array_values(array_filter($errors, function (Error $error) use ($processIgnoreError): bool { + $filePath = $this->fileHelper->normalizePath($error->getFilePath()); + if (isset($this->ignoreErrorsByFile[$filePath])) { + foreach ($this->ignoreErrorsByFile[$filePath] as $ignoreError) { + $i = $ignoreError['index']; + $ignore = $ignoreError['ignoreError']; + $result = $processIgnoreError($error, $i, $ignore); + if (!$result) { + return false; + } + } + } + + $traitFilePath = $error->getTraitFilePath(); + if ($traitFilePath !== null) { + $normalizedTraitFilePath = $this->fileHelper->normalizePath($traitFilePath); + if (isset($this->ignoreErrorsByFile[$normalizedTraitFilePath])) { + foreach ($this->ignoreErrorsByFile[$normalizedTraitFilePath] as $ignoreError) { + $i = $ignoreError['index']; + $ignore = $ignoreError['ignoreError']; + $result = $processIgnoreError($error, $i, $ignore); + if (!$result) { + return false; + } + } + } + } + + foreach ($this->otherIgnoreErrors as $ignoreError) { + $i = $ignoreError['index']; + $ignore = $ignoreError['ignoreError']; + + $result = $processIgnoreError($error, $i, $ignore); + if (!$result) { + return false; + } + } + + return true; + })); + + foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) { + if (!isset($unmatchedIgnoredError['count']) || !isset($unmatchedIgnoredError['realCount'])) { + continue; + } + + if ($unmatchedIgnoredError['realCount'] <= $unmatchedIgnoredError['count']) { + continue; + } + + $addErrors[] = new Error(sprintf( + 'Ignored error pattern %s is expected to occur %d %s, but occurred %d %s.', + IgnoredError::stringifyPattern($unmatchedIgnoredError), + $unmatchedIgnoredError['count'], + $unmatchedIgnoredError['count'] === 1 ? 'time' : 'times', + $unmatchedIgnoredError['realCount'], + $unmatchedIgnoredError['realCount'] === 1 ? 'time' : 'times' + ), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false); + } + + $errors = array_merge($errors, $addErrors); + + $analysedFilesKeys = array_fill_keys($analysedFiles, true); + + if ($this->reportUnmatchedIgnoredErrors && !$hasInternalErrors) { + foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) { + if ( + isset($unmatchedIgnoredError['count']) + && isset($unmatchedIgnoredError['realCount']) + && (isset($unmatchedIgnoredError['realPath']) || !$onlyFiles) + ) { + if ($unmatchedIgnoredError['realCount'] < $unmatchedIgnoredError['count']) { + $errors[] = new Error(sprintf( + 'Ignored error pattern %s is expected to occur %d %s, but occurred only %d %s.', + IgnoredError::stringifyPattern($unmatchedIgnoredError), + $unmatchedIgnoredError['count'], + $unmatchedIgnoredError['count'] === 1 ? 'time' : 'times', + $unmatchedIgnoredError['realCount'], + $unmatchedIgnoredError['realCount'] === 1 ? 'time' : 'times' + ), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false); + } + } elseif (isset($unmatchedIgnoredError['realPath'])) { + if (!array_key_exists($unmatchedIgnoredError['realPath'], $analysedFilesKeys)) { + continue; + } + + $errors[] = new Error( + sprintf( + 'Ignored error pattern %s was not matched in reported errors.', + IgnoredError::stringifyPattern($unmatchedIgnoredError) + ), + $unmatchedIgnoredError['realPath'], + null, + false + ); + } elseif (!$onlyFiles) { + $errors[] = sprintf( + 'Ignored error pattern %s was not matched in reported errors.', + IgnoredError::stringifyPattern($unmatchedIgnoredError) + ); + } + } + } + + return $errors; + } } diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index d9ac4ca85b..402bf06910 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -1,4 +1,6 @@ -scopeClass = $scopeClass; - $this->container = $container; - $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); - $this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain'); - $this->objectFromNewClass = $container->getParameter('featureToggles')['objectFromNewClass']; - } + private bool $objectFromNewClass; - /** - * @param \PHPStan\Analyser\ScopeContext $context - * @param bool $declareStrictTypes - * @param array $constantTypes - * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function - * @param string|null $namespace - * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes - * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes - * @param array $conditionalExpressions - * @param string|null $inClosureBindScopeClass - * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection - * @param bool $inFirstLevelStatement - * @param array $currentlyAssignedExpressions - * @param array $nativeExpressionTypes - * @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack - * @param bool $afterExtractCall - * @param Scope|null $parentScope - * - * @return MutatingScope - */ - public function create( - ScopeContext $context, - bool $declareStrictTypes = false, - array $constantTypes = [], - $function = null, - ?string $namespace = null, - array $variablesTypes = [], - array $moreSpecificTypes = [], - array $conditionalExpressions = [], - ?string $inClosureBindScopeClass = null, - ?ParametersAcceptor $anonymousFunctionReflection = null, - bool $inFirstLevelStatement = true, - array $currentlyAssignedExpressions = [], - array $nativeExpressionTypes = [], - array $inFunctionCallsStack = [], - bool $afterExtractCall = false, - ?Scope $parentScope = null - ): MutatingScope - { - $scopeClass = $this->scopeClass; - if (!is_a($scopeClass, MutatingScope::class, true)) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function __construct( + string $scopeClass, + Container $container + ) { + $this->scopeClass = $scopeClass; + $this->container = $container; + $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); + $this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain'); + $this->objectFromNewClass = $container->getParameter('featureToggles')['objectFromNewClass']; + } - return new $scopeClass( - $this, - $this->container->getByType(ReflectionProvider::class), - $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class)->getRegistry(), - $this->container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class)->getRegistry(), - $this->container->getByType(Standard::class), - $this->container->getByType(TypeSpecifier::class), - $this->container->getByType(PropertyReflectionFinder::class), - $this->container->getByType(\PHPStan\Parser\Parser::class), - $this->container->getByType(NodeScopeResolver::class), - $context, - $declareStrictTypes, - $constantTypes, - $function, - $namespace, - $variablesTypes, - $moreSpecificTypes, - $conditionalExpressions, - $inClosureBindScopeClass, - $anonymousFunctionReflection, - $inFirstLevelStatement, - $currentlyAssignedExpressions, - $nativeExpressionTypes, - $inFunctionCallsStack, - $this->dynamicConstantNames, - $this->treatPhpDocTypesAsCertain, - $this->objectFromNewClass, - $afterExtractCall, - $parentScope - ); - } + /** + * @param \PHPStan\Analyser\ScopeContext $context + * @param bool $declareStrictTypes + * @param array $constantTypes + * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function + * @param string|null $namespace + * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes + * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes + * @param array $conditionalExpressions + * @param string|null $inClosureBindScopeClass + * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection + * @param bool $inFirstLevelStatement + * @param array $currentlyAssignedExpressions + * @param array $nativeExpressionTypes + * @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack + * @param bool $afterExtractCall + * @param Scope|null $parentScope + * + * @return MutatingScope + */ + public function create( + ScopeContext $context, + bool $declareStrictTypes = false, + array $constantTypes = [], + $function = null, + ?string $namespace = null, + array $variablesTypes = [], + array $moreSpecificTypes = [], + array $conditionalExpressions = [], + ?string $inClosureBindScopeClass = null, + ?ParametersAcceptor $anonymousFunctionReflection = null, + bool $inFirstLevelStatement = true, + array $currentlyAssignedExpressions = [], + array $nativeExpressionTypes = [], + array $inFunctionCallsStack = [], + bool $afterExtractCall = false, + ?Scope $parentScope = null + ): MutatingScope { + $scopeClass = $this->scopeClass; + if (!is_a($scopeClass, MutatingScope::class, true)) { + throw new \PHPStan\ShouldNotHappenException(); + } + return new $scopeClass( + $this, + $this->container->getByType(ReflectionProvider::class), + $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class)->getRegistry(), + $this->container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class)->getRegistry(), + $this->container->getByType(Standard::class), + $this->container->getByType(TypeSpecifier::class), + $this->container->getByType(PropertyReflectionFinder::class), + $this->container->getByType(\PHPStan\Parser\Parser::class), + $this->container->getByType(NodeScopeResolver::class), + $context, + $declareStrictTypes, + $constantTypes, + $function, + $namespace, + $variablesTypes, + $moreSpecificTypes, + $conditionalExpressions, + $inClosureBindScopeClass, + $anonymousFunctionReflection, + $inFirstLevelStatement, + $currentlyAssignedExpressions, + $nativeExpressionTypes, + $inFunctionCallsStack, + $this->dynamicConstantNames, + $this->treatPhpDocTypesAsCertain, + $this->objectFromNewClass, + $afterExtractCall, + $parentScope + ); + } } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index ad98ea2616..c7ef77c799 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1,4 +1,6 @@ - '+', + Node\Expr\AssignOp\Minus::class => '-', + Node\Expr\AssignOp\Mul::class => '*', + Node\Expr\AssignOp\Pow::class => '^', + Node\Expr\AssignOp\Div::class => '/', + ]; - private const OPERATOR_SIGIL_MAP = [ - Node\Expr\AssignOp\Plus::class => '+', - Node\Expr\AssignOp\Minus::class => '-', - Node\Expr\AssignOp\Mul::class => '*', - Node\Expr\AssignOp\Pow::class => '^', - Node\Expr\AssignOp\Div::class => '/', - ]; - - private \PHPStan\Analyser\ScopeFactory $scopeFactory; - - private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; - - private \PHPStan\Type\DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry; - - private OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry; + private \PHPStan\Analyser\ScopeFactory $scopeFactory; - private \PhpParser\PrettyPrinter\Standard $printer; + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; - private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + private \PHPStan\Type\DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry; - private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + private OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry; - private Parser $parser; + private \PhpParser\PrettyPrinter\Standard $printer; - private NodeScopeResolver $nodeScopeResolver; + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; - private \PHPStan\Analyser\ScopeContext $context; - - /** @var \PHPStan\Type\Type[] */ - private array $resolvedTypes = []; - - private bool $declareStrictTypes; - - /** @var array */ - private array $constantTypes; - - /** @var \PHPStan\Reflection\FunctionReflection|MethodReflection|null */ - private $function; - - private ?string $namespace; - - /** @var \PHPStan\Analyser\VariableTypeHolder[] */ - private array $variableTypes; - - /** @var \PHPStan\Analyser\VariableTypeHolder[] */ - private array $moreSpecificTypes; - - /** @var array */ - private array $conditionalExpressions; - - private ?string $inClosureBindScopeClass; - - private ?ParametersAcceptor $anonymousFunctionReflection; - - private bool $inFirstLevelStatement; - - /** @var array */ - private array $currentlyAssignedExpressions; - - /** @var array */ - private array $inFunctionCallsStack; - - /** @var array */ - private array $nativeExpressionTypes; - - /** @var string[] */ - private array $dynamicConstantNames; - - private bool $treatPhpDocTypesAsCertain; - - private bool $objectFromNewClass; - - private bool $afterExtractCall; - - private ?Scope $parentScope; - - /** - * @param \PHPStan\Analyser\ScopeFactory $scopeFactory - * @param ReflectionProvider $reflectionProvider - * @param \PHPStan\Type\DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry - * @param \PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry - * @param \PhpParser\PrettyPrinter\Standard $printer - * @param \PHPStan\Analyser\TypeSpecifier $typeSpecifier - * @param \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder - * @param Parser $parser - * @param NodeScopeResolver $nodeScopeResolver - * @param \PHPStan\Analyser\ScopeContext $context - * @param bool $declareStrictTypes - * @param array $constantTypes - * @param \PHPStan\Reflection\FunctionReflection|MethodReflection|null $function - * @param string|null $namespace - * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes - * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes - * @param array $conditionalExpressions - * @param string|null $inClosureBindScopeClass - * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection - * @param bool $inFirstLevelStatement - * @param array $currentlyAssignedExpressions - * @param array $nativeExpressionTypes - * @param array $inFunctionCallsStack - * @param string[] $dynamicConstantNames - * @param bool $treatPhpDocTypesAsCertain - * @param bool $objectFromNewClass - * @param bool $afterExtractCall - * @param Scope|null $parentScope - */ - public function __construct( - ScopeFactory $scopeFactory, - ReflectionProvider $reflectionProvider, - DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry, - OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry, - \PhpParser\PrettyPrinter\Standard $printer, - TypeSpecifier $typeSpecifier, - PropertyReflectionFinder $propertyReflectionFinder, - Parser $parser, - NodeScopeResolver $nodeScopeResolver, - ScopeContext $context, - bool $declareStrictTypes = false, - array $constantTypes = [], - $function = null, - ?string $namespace = null, - array $variablesTypes = [], - array $moreSpecificTypes = [], - array $conditionalExpressions = [], - ?string $inClosureBindScopeClass = null, - ?ParametersAcceptor $anonymousFunctionReflection = null, - bool $inFirstLevelStatement = true, - array $currentlyAssignedExpressions = [], - array $nativeExpressionTypes = [], - array $inFunctionCallsStack = [], - array $dynamicConstantNames = [], - bool $treatPhpDocTypesAsCertain = true, - bool $objectFromNewClass = false, - bool $afterExtractCall = false, - ?Scope $parentScope = null - ) - { - if ($namespace === '') { - $namespace = null; - } - - $this->scopeFactory = $scopeFactory; - $this->reflectionProvider = $reflectionProvider; - $this->dynamicReturnTypeExtensionRegistry = $dynamicReturnTypeExtensionRegistry; - $this->operatorTypeSpecifyingExtensionRegistry = $operatorTypeSpecifyingExtensionRegistry; - $this->printer = $printer; - $this->typeSpecifier = $typeSpecifier; - $this->propertyReflectionFinder = $propertyReflectionFinder; - $this->parser = $parser; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->context = $context; - $this->declareStrictTypes = $declareStrictTypes; - $this->constantTypes = $constantTypes; - $this->function = $function; - $this->namespace = $namespace; - $this->variableTypes = $variablesTypes; - $this->moreSpecificTypes = $moreSpecificTypes; - $this->conditionalExpressions = $conditionalExpressions; - $this->inClosureBindScopeClass = $inClosureBindScopeClass; - $this->anonymousFunctionReflection = $anonymousFunctionReflection; - $this->inFirstLevelStatement = $inFirstLevelStatement; - $this->currentlyAssignedExpressions = $currentlyAssignedExpressions; - $this->nativeExpressionTypes = $nativeExpressionTypes; - $this->inFunctionCallsStack = $inFunctionCallsStack; - $this->dynamicConstantNames = $dynamicConstantNames; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->objectFromNewClass = $objectFromNewClass; - $this->afterExtractCall = $afterExtractCall; - $this->parentScope = $parentScope; - } - - public function getFile(): string - { - return $this->context->getFile(); - } - - public function getFileDescription(): string - { - if ($this->context->getTraitReflection() === null) { - return $this->getFile(); - } - - /** @var ClassReflection $classReflection */ - $classReflection = $this->context->getClassReflection(); - - $className = sprintf('class %s', $classReflection->getDisplayName()); - if ($classReflection->isAnonymous()) { - $className = 'anonymous class'; - } - - $traitReflection = $this->context->getTraitReflection(); - if ($traitReflection->getFileName() === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return sprintf( - '%s (in context of %s)', - $traitReflection->getFileName(), - $className - ); - } - - public function isDeclareStrictTypes(): bool - { - return $this->declareStrictTypes; - } - - public function enterDeclareStrictTypes(): self - { - return $this->scopeFactory->create( - $this->context, - true, - [], - null, - null, - $this->variableTypes - ); - } - - public function isInClass(): bool - { - return $this->context->getClassReflection() !== null; - } - - public function isInTrait(): bool - { - return $this->context->getTraitReflection() !== null; - } - - public function getClassReflection(): ?ClassReflection - { - return $this->context->getClassReflection(); - } - - public function getTraitReflection(): ?ClassReflection - { - return $this->context->getTraitReflection(); - } - - /** - * @return \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null - */ - public function getFunction() - { - return $this->function; - } - - public function getFunctionName(): ?string - { - return $this->function !== null ? $this->function->getName() : null; - } - - public function getNamespace(): ?string - { - return $this->namespace; - } - - public function getParentScope(): ?Scope - { - return $this->parentScope; - } - - /** - * @return array - */ - private function getVariableTypes(): array - { - return $this->variableTypes; - } - - public function canAnyVariableExist(): bool - { - return ($this->function === null && !$this->isInAnonymousFunction()) || $this->afterExtractCall; - } - - public function afterExtractCall(): self - { - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - [], - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - true, - $this->parentScope - ); - } - - public function afterClearstatcacheCall(): self - { - $moreSpecificTypes = $this->moreSpecificTypes; - foreach (array_keys($moreSpecificTypes) as $exprString) { - // list from https://www.php.net/manual/en/function.clearstatcache.php - - // stat(), lstat(), file_exists(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), filectime(), fileatime(), filemtime(), fileinode(), filegroup(), fileowner(), filesize(), filetype(), and fileperms(). - foreach ([ - 'stat', - 'lstat', - 'file_exists', - 'is_writable', - 'is_readable', - 'is_executable', - 'is_file', - 'is_dir', - 'is_link', - 'filectime', - 'fileatime', - 'filemtime', - 'fileinode', - 'filegroup', - 'fileowner', - 'filesize', - 'filetype', - 'fileperms', - ] as $functionName) { - if (!Strings::startsWith((string) $exprString, $functionName . '(') && !Strings::startsWith((string) $exprString, '\\' . $functionName . '(')) { - continue; - } - - unset($moreSpecificTypes[$exprString]); - continue 2; - } - } - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function hasVariableType(string $variableName): TrinaryLogic - { - if ($this->isGlobalVariable($variableName)) { - return TrinaryLogic::createYes(); - } - - if (!isset($this->variableTypes[$variableName])) { - if ($this->canAnyVariableExist()) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - return $this->variableTypes[$variableName]->getCertainty(); - } - - public function getVariableType(string $variableName): Type - { - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new StringType(), new MixedType()); - } - - if ($this->hasVariableType($variableName)->no()) { - throw new \PHPStan\Analyser\UndefinedVariableException($this, $variableName); - } - - if (!array_key_exists($variableName, $this->variableTypes)) { - return new MixedType(); - } - - return $this->variableTypes[$variableName]->getType(); - } - - /** - * @return array - */ - public function getDefinedVariables(): array - { - $variables = []; - foreach ($this->variableTypes as $variableName => $holder) { - if (!$holder->getCertainty()->yes()) { - continue; - } - - $variables[] = $variableName; - } - - return $variables; - } - - private function isGlobalVariable(string $variableName): bool - { - return in_array($variableName, [ - 'GLOBALS', - '_SERVER', - '_GET', - '_POST', - '_FILES', - '_COOKIE', - '_SESSION', - '_REQUEST', - '_ENV', - ], true); - } - - public function hasConstant(Name $name): bool - { - $isCompilerHaltOffset = $name->toString() === '__COMPILER_HALT_OFFSET__'; - if ($isCompilerHaltOffset) { - return $this->fileHasCompilerHaltStatementCalls(); - } - if ($name->isFullyQualified()) { - if (array_key_exists($name->toCodeString(), $this->constantTypes)) { - return true; - } - } - - if ($this->getNamespace() !== null) { - $constantName = new FullyQualified([$this->getNamespace(), $name->toString()]); - if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return true; - } - } - - $constantName = new FullyQualified($name->toString()); - if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return true; - } - - if (!$this->reflectionProvider->hasConstant($name, $this)) { - return false; - } - - $constantReflection = $this->reflectionProvider->getConstant($name, $this); - - return $constantReflection->getFileName() !== $this->getFile(); - } - - private function fileHasCompilerHaltStatementCalls(): bool - { - $nodes = $this->parser->parseFile($this->getFile()); - foreach ($nodes as $node) { - if ($node instanceof \PhpParser\Node\Stmt\HaltCompiler) { - return true; - } - } - - return false; - } - - public function isInAnonymousFunction(): bool - { - return $this->anonymousFunctionReflection !== null; - } - - public function getAnonymousFunctionReflection(): ?ParametersAcceptor - { - return $this->anonymousFunctionReflection; - } - - public function getAnonymousFunctionReturnType(): ?\PHPStan\Type\Type - { - if ($this->anonymousFunctionReflection === null) { - return null; - } - - return $this->anonymousFunctionReflection->getReturnType(); - } - - public function getType(Expr $node): Type - { - $key = $this->getNodeKey($node); - - if (!array_key_exists($key, $this->resolvedTypes)) { - $this->resolvedTypes[$key] = $this->resolveType($node); - } - return $this->resolvedTypes[$key]; - } - - private function getNodeKey(Expr $node): string - { - /** @var string|null $key */ - $key = $node->getAttribute('phpstan_cache_printer'); - if ($key === null) { - $key = $this->printer->prettyPrintExpr($node); - $node->setAttribute('phpstan_cache_printer', $key); - } - - return $key; - } - - private function resolveType(Expr $node): Type - { - if ($node instanceof Expr\Exit_ || $node instanceof Expr\Throw_) { - return new NeverType(true); - } - - if ($node instanceof Expr\BinaryOp\Smaller) { - return $this->getType($node->left)->isSmallerThan($this->getType($node->right))->toBooleanType(); - } - - if ($node instanceof Expr\BinaryOp\SmallerOrEqual) { - return $this->getType($node->left)->isSmallerThanOrEqual($this->getType($node->right))->toBooleanType(); - } - - if ($node instanceof Expr\BinaryOp\Greater) { - return $this->getType($node->right)->isSmallerThan($this->getType($node->left))->toBooleanType(); - } - - if ($node instanceof Expr\BinaryOp\GreaterOrEqual) { - return $this->getType($node->right)->isSmallerThanOrEqual($this->getType($node->left))->toBooleanType(); - } - - if ( - $node instanceof Expr\BinaryOp\Equal - || $node instanceof Expr\BinaryOp\NotEqual - || $node instanceof Expr\Empty_ - ) { - return new BooleanType(); - } - - if ($node instanceof Expr\Isset_) { - $result = new ConstantBooleanType(true); - foreach ($node->vars as $var) { - if ($var instanceof Expr\ArrayDimFetch && $var->dim !== null) { - $variableType = $this->getType($var->var); - $dimType = $this->getType($var->dim); - $hasOffset = $variableType->hasOffsetValueType($dimType); - $offsetValueType = $variableType->getOffsetValueType($dimType); - $offsetValueIsNotNull = (new NullType())->isSuperTypeOf($offsetValueType)->negate(); - $isset = $hasOffset->and($offsetValueIsNotNull)->toBooleanType(); - if ($isset instanceof ConstantBooleanType) { - if (!$isset->getValue()) { - return $isset; - } - - continue; - } - - $result = $isset; - continue; - } - - if ($var instanceof Expr\Variable && is_string($var->name)) { - $variableType = $this->getType($var); - $isNullSuperType = (new NullType())->isSuperTypeOf($variableType); - $has = $this->hasVariableType($var->name); - if ($has->no() || $isNullSuperType->yes()) { - return new ConstantBooleanType(false); - } - - if ($has->maybe() || !$isNullSuperType->no()) { - $result = new BooleanType(); - } - continue; - } - - return new BooleanType(); - } - - return $result; - } - - if ($node instanceof \PhpParser\Node\Expr\BooleanNot) { - if ($this->treatPhpDocTypesAsCertain) { - $exprBooleanType = $this->getType($node->expr)->toBoolean(); - } else { - $exprBooleanType = $this->getNativeType($node->expr)->toBoolean(); - } - if ($exprBooleanType instanceof ConstantBooleanType) { - return new ConstantBooleanType(!$exprBooleanType->getValue()); - } - - return new BooleanType(); - } - - if ($node instanceof \PhpParser\Node\Expr\BitwiseNot) { - $exprType = $this->getType($node->expr); - return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } - if ($type instanceof ConstantStringType) { - return new ConstantStringType(~$type->getValue()); - } - if ($type instanceof StringType) { - return new StringType(); - } - if ($type instanceof IntegerType || $type instanceof FloatType) { - return new IntegerType(); //no const types here, result depends on PHP_INT_SIZE - } - return new ErrorType(); - }); - } - - if ( - $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanAnd - || $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalAnd - ) { - if ($this->treatPhpDocTypesAsCertain) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - } else { - $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); - } - - if ( - $leftBooleanType instanceof ConstantBooleanType - && !$leftBooleanType->getValue() - ) { - return new ConstantBooleanType(false); - } - - if ($this->treatPhpDocTypesAsCertain) { - $rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); - } else { - $rightBooleanType = $this->promoteNativeTypes()->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); - } - - if ( - $rightBooleanType instanceof ConstantBooleanType - && !$rightBooleanType->getValue() - ) { - return new ConstantBooleanType(false); - } - - if ( - $leftBooleanType instanceof ConstantBooleanType - && $leftBooleanType->getValue() - && $rightBooleanType instanceof ConstantBooleanType - && $rightBooleanType->getValue() - ) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - if ( - $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanOr - || $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalOr - ) { - if ($this->treatPhpDocTypesAsCertain) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - } else { - $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); - } - if ( - $leftBooleanType instanceof ConstantBooleanType - && $leftBooleanType->getValue() - ) { - return new ConstantBooleanType(true); - } - - if ($this->treatPhpDocTypesAsCertain) { - $rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); - } else { - $rightBooleanType = $this->promoteNativeTypes()->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); - } - - if ( - $rightBooleanType instanceof ConstantBooleanType - && $rightBooleanType->getValue() - ) { - return new ConstantBooleanType(true); - } - - if ( - $leftBooleanType instanceof ConstantBooleanType - && !$leftBooleanType->getValue() - && $rightBooleanType instanceof ConstantBooleanType - && !$rightBooleanType->getValue() - ) { - return new ConstantBooleanType(false); - } - - return new BooleanType(); - } - - if ($node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalXor) { - if ($this->treatPhpDocTypesAsCertain) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - $rightBooleanType = $this->getType($node->right)->toBoolean(); - } else { - $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); - $rightBooleanType = $this->getNativeType($node->right)->toBoolean(); - } - - if ( - $leftBooleanType instanceof ConstantBooleanType - && $rightBooleanType instanceof ConstantBooleanType - ) { - return new ConstantBooleanType( - $leftBooleanType->getValue() xor $rightBooleanType->getValue() - ); - } - - return new BooleanType(); - } - - if ($node instanceof Expr\BinaryOp\Identical) { - if ($this->treatPhpDocTypesAsCertain) { - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - } else { - $leftType = $this->getNativeType($node->left); - $rightType = $this->getNativeType($node->right); - } - - if ( - ( - $node->left instanceof Node\Expr\PropertyFetch - || $node->left instanceof Node\Expr\StaticPropertyFetch - ) - && $rightType instanceof NullType - && !$this->hasPropertyNativeType($node->left) - ) { - return new BooleanType(); - } - - if ( - ( - $node->right instanceof Node\Expr\PropertyFetch - || $node->right instanceof Node\Expr\StaticPropertyFetch - ) - && $leftType instanceof NullType - && !$this->hasPropertyNativeType($node->right) - ) { - return new BooleanType(); - } - - $isSuperset = $leftType->isSuperTypeOf($rightType); - if ($isSuperset->no()) { - return new ConstantBooleanType(false); - } elseif ( - $isSuperset->yes() - && $leftType instanceof ConstantScalarType - && $rightType instanceof ConstantScalarType - && $leftType->getValue() === $rightType->getValue() - ) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - if ($node instanceof Expr\BinaryOp\NotIdentical) { - if ($this->treatPhpDocTypesAsCertain) { - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - } else { - $leftType = $this->getNativeType($node->left); - $rightType = $this->getNativeType($node->right); - } - - if ( - ( - $node->left instanceof Node\Expr\PropertyFetch - || $node->left instanceof Node\Expr\StaticPropertyFetch - ) - && $rightType instanceof NullType - && !$this->hasPropertyNativeType($node->left) - ) { - return new BooleanType(); - } - - if ( - ( - $node->right instanceof Node\Expr\PropertyFetch - || $node->right instanceof Node\Expr\StaticPropertyFetch - ) - && $leftType instanceof NullType - && !$this->hasPropertyNativeType($node->right) - ) { - return new BooleanType(); - } - - $isSuperset = $leftType->isSuperTypeOf($rightType); - if ($isSuperset->no()) { - return new ConstantBooleanType(true); - } elseif ( - $isSuperset->yes() - && $leftType instanceof ConstantScalarType - && $rightType instanceof ConstantScalarType - && $leftType->getValue() === $rightType->getValue() - ) { - return new ConstantBooleanType(false); - } - - return new BooleanType(); - } - - if ($node instanceof Expr\Instanceof_) { - if ($this->treatPhpDocTypesAsCertain) { - $expressionType = $this->getType($node->expr); - } else { - $expressionType = $this->getNativeType($node->expr); - } - if ( - $this->isInTrait() - && TypeUtils::findThisType($expressionType) !== null - ) { - return new BooleanType(); - } - if ($expressionType instanceof NeverType) { - return new ConstantBooleanType(false); - } - - $uncertainty = false; - - if ($node->class instanceof Node\Name) { - $unresolvedClassName = $node->class->toString(); - if ( - strtolower($unresolvedClassName) === 'static' - && $this->isInClass() - ) { - $classType = new StaticType($this->getClassReflection()); - } else { - $className = $this->resolveName($node->class); - $classType = new ObjectType($className); - } - } else { - $classType = $this->getType($node->class); - $classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } - if ($type instanceof TypeWithClassName) { - $uncertainty = true; - return $type; - } - if ($type instanceof GenericClassStringType) { - $uncertainty = true; - return $type->getGenericType(); - } - if ($type instanceof ConstantStringType) { - return new ObjectType($type->getValue()); - } - return new MixedType(); - }); - } - - if ($classType->isSuperTypeOf(new MixedType())->yes()) { - return new BooleanType(); - } - - $isSuperType = $classType->isSuperTypeOf($expressionType); - - if ($isSuperType->no()) { - return new ConstantBooleanType(false); - } elseif ($isSuperType->yes() && !$uncertainty) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - if ($node instanceof Node\Expr\UnaryPlus) { - return $this->getType($node->expr)->toNumber(); - } - - if ($node instanceof Expr\ErrorSuppress - || $node instanceof Expr\Assign - ) { - return $this->getType($node->expr); - } - - if ($node instanceof Node\Expr\UnaryMinus) { - $type = $this->getType($node->expr)->toNumber(); - $scalarValues = TypeUtils::getConstantScalars($type); - if (count($scalarValues) > 0) { - $newTypes = []; - foreach ($scalarValues as $scalarValue) { - if ($scalarValue instanceof ConstantIntegerType) { - $newTypes[] = new ConstantIntegerType(-$scalarValue->getValue()); - } elseif ($scalarValue instanceof ConstantFloatType) { - $newTypes[] = new ConstantFloatType(-$scalarValue->getValue()); - } - } - - return TypeCombinator::union(...$newTypes); - } - - return $type; - } - - if ($node instanceof Expr\BinaryOp\Concat || $node instanceof Expr\AssignOp\Concat) { - if ($node instanceof Node\Expr\AssignOp) { - $left = $node->var; - $right = $node->expr; - } else { - $left = $node->left; - $right = $node->right; - } - - $leftStringType = $this->getType($left)->toString(); - $rightStringType = $this->getType($right)->toString(); - if (TypeCombinator::union( - $leftStringType, - $rightStringType - ) instanceof ErrorType) { - return new ErrorType(); - } - - if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') { - return $rightStringType; - } - - if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') { - return $leftStringType; - } - - if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) { - return $leftStringType->append($rightStringType); - } - - return new StringType(); - } - - if ( - $node instanceof Node\Expr\BinaryOp\Div - || $node instanceof Node\Expr\AssignOp\Div - || $node instanceof Node\Expr\BinaryOp\Mod - || $node instanceof Node\Expr\AssignOp\Mod - ) { - if ($node instanceof Node\Expr\AssignOp) { - $right = $node->expr; - } else { - $right = $node->right; - } - - $rightTypes = TypeUtils::getConstantScalars($this->getType($right)->toNumber()); - foreach ($rightTypes as $rightType) { - if ( - $rightType->getValue() === 0 - || $rightType->getValue() === 0.0 - ) { - return new ErrorType(); - } - } - } - - if ( - ( - $node instanceof Node\Expr\BinaryOp - || $node instanceof Node\Expr\AssignOp - ) && !$node instanceof Expr\BinaryOp\Coalesce && !$node instanceof Expr\AssignOp\Coalesce - ) { - if ($node instanceof Node\Expr\AssignOp) { - $left = $node->var; - $right = $node->expr; - } else { - $left = $node->left; - $right = $node->right; - } - - $leftTypes = TypeUtils::getConstantScalars($this->getType($left)); - $rightTypes = TypeUtils::getConstantScalars($this->getType($right)); - - if (count($leftTypes) > 0 && count($rightTypes) > 0) { - $resultTypes = []; - foreach ($leftTypes as $leftType) { - foreach ($rightTypes as $rightType) { - $resultTypes[] = $this->calculateFromScalars($node, $leftType, $rightType); - } - } - return TypeCombinator::union(...$resultTypes); - } - } - - if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Expr\AssignOp\Mod) { - return new IntegerType(); - } - - if ($node instanceof Expr\BinaryOp\Spaceship) { - return IntegerRangeType::fromInterval(-1, 1); - } - - if ($node instanceof Expr\Clone_) { - return $this->getType($node->expr); - } - - if ( - $node instanceof Expr\AssignOp\ShiftLeft - || $node instanceof Expr\BinaryOp\ShiftLeft - || $node instanceof Expr\AssignOp\ShiftRight - || $node instanceof Expr\BinaryOp\ShiftRight - ) { - if ($node instanceof Node\Expr\AssignOp) { - $left = $node->var; - $right = $node->expr; - } else { - $left = $node->left; - $right = $node->right; - } - - if (TypeCombinator::union( - $this->getType($left)->toNumber(), - $this->getType($right)->toNumber() - ) instanceof ErrorType) { - return new ErrorType(); - } - - return new IntegerType(); - } - - if ( - $node instanceof Expr\AssignOp\BitwiseAnd - || $node instanceof Expr\BinaryOp\BitwiseAnd - || $node instanceof Expr\AssignOp\BitwiseOr - || $node instanceof Expr\BinaryOp\BitwiseOr - || $node instanceof Expr\AssignOp\BitwiseXor - || $node instanceof Expr\BinaryOp\BitwiseXor - ) { - if ($node instanceof Node\Expr\AssignOp) { - $left = $node->var; - $right = $node->expr; - } else { - $left = $node->left; - $right = $node->right; - } - - $leftType = $this->getType($left); - $rightType = $this->getType($right); - $stringType = new StringType(); - - if ($stringType->isSuperTypeOf($leftType)->yes() && $stringType->isSuperTypeOf($rightType)->yes()) { - return $stringType; - } - - if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) { - return new ErrorType(); - } - - return new IntegerType(); - } - - if ( - $node instanceof Node\Expr\BinaryOp\Plus - || $node instanceof Node\Expr\BinaryOp\Minus - || $node instanceof Node\Expr\BinaryOp\Mul - || $node instanceof Node\Expr\BinaryOp\Pow - || $node instanceof Node\Expr\BinaryOp\Div - || $node instanceof Node\Expr\AssignOp\Plus - || $node instanceof Node\Expr\AssignOp\Minus - || $node instanceof Node\Expr\AssignOp\Mul - || $node instanceof Node\Expr\AssignOp\Pow - || $node instanceof Node\Expr\AssignOp\Div - ) { - if ($node instanceof Node\Expr\AssignOp) { - $left = $node->var; - $right = $node->expr; - } else { - $left = $node->left; - $right = $node->right; - } - - $leftType = $this->getType($left); - $rightType = $this->getType($right); - - $operatorSigil = null; - - if ($node instanceof BinaryOp) { - $operatorSigil = $node->getOperatorSigil(); - } - - if ($operatorSigil === null) { - $operatorSigil = self::OPERATOR_SIGIL_MAP[get_class($node)] ?? null; - } - - if ($operatorSigil !== null) { - $operatorTypeSpecifyingExtensions = $this->operatorTypeSpecifyingExtensionRegistry->getOperatorTypeSpecifyingExtensions($operatorSigil, $leftType, $rightType); - - /** @var Type[] $extensionTypes */ - $extensionTypes = []; - - foreach ($operatorTypeSpecifyingExtensions as $extension) { - $extensionTypes[] = $extension->specifyType($operatorSigil, $leftType, $rightType); - } - - if (count($extensionTypes) > 0) { - return TypeCombinator::union(...$extensionTypes); - } - } - - if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) { - $leftConstantArrays = TypeUtils::getConstantArrays($leftType); - $rightConstantArrays = TypeUtils::getConstantArrays($rightType); - - if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) { - $resultTypes = []; - foreach ($rightConstantArrays as $rightConstantArray) { - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); - foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) { - $newArrayBuilder->setOffsetValueType( - $leftKeyType, - $leftConstantArray->getOffsetValueType($leftKeyType) - ); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - } - - return TypeCombinator::union(...$resultTypes); - } - $arrayType = new ArrayType(new MixedType(), new MixedType()); - - if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) { - if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { - // to preserve BenevolentUnionType - $keyType = $leftType->getIterableKeyType(); - } else { - $keyTypes = []; - foreach ([ - $leftType->getIterableKeyType(), - $rightType->getIterableKeyType(), - ] as $keyType) { - $keyTypes[] = $keyType; - } - $keyType = TypeCombinator::union(...$keyTypes); - } - return new ArrayType( - $keyType, - TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()) - ); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - new ArrayType(new MixedType(), new MixedType()), - ]); - } - } - - $types = TypeCombinator::union($leftType, $rightType); - if ( - $leftType instanceof ArrayType - || $rightType instanceof ArrayType - || $types instanceof ArrayType - ) { - return new ErrorType(); - } - - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ( - (new FloatType())->isSuperTypeOf($leftNumberType)->yes() - || (new FloatType())->isSuperTypeOf($rightNumberType)->yes() - ) { - return new FloatType(); - } - - if ($node instanceof Expr\AssignOp\Pow || $node instanceof Expr\BinaryOp\Pow) { - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - ]); - } - - $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); - if ($node instanceof Expr\AssignOp\Div || $node instanceof Expr\BinaryOp\Div) { - if ($types instanceof MixedType || $resultType instanceof IntegerType) { - return new BenevolentUnionType([new IntegerType(), new FloatType()]); - } - - return new UnionType([new IntegerType(), new FloatType()]); - } - - if ($types instanceof MixedType - || $leftType instanceof BenevolentUnionType - || $rightType instanceof BenevolentUnionType - ) { - return TypeUtils::toBenevolentUnion($resultType); - } - - return $resultType; - } - - if ($node instanceof LNumber) { - return new ConstantIntegerType($node->value); - } elseif ($node instanceof String_) { - return new ConstantStringType($node->value); - } elseif ($node instanceof Node\Scalar\Encapsed) { - $constantString = new ConstantStringType(''); - foreach ($node->parts as $part) { - if ($part instanceof EncapsedStringPart) { - $partStringType = new ConstantStringType($part->value); - } else { - $partStringType = $this->getType($part)->toString(); - if ($partStringType instanceof ErrorType) { - return new ErrorType(); - } - if (!$partStringType instanceof ConstantStringType) { - return new StringType(); - } - } - - $constantString = $constantString->append($partStringType); - } - return $constantString; - } elseif ($node instanceof DNumber) { - return new ConstantFloatType($node->value); - } elseif ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) { - $parameters = []; - $isVariadic = false; - $firstOptionalParameterIndex = null; - foreach ($node->params as $i => $param) { - $isOptionalCandidate = $param->default !== null || $param->variadic; - - if ($isOptionalCandidate) { - if ($firstOptionalParameterIndex === null) { - $firstOptionalParameterIndex = $i; - } - } else { - $firstOptionalParameterIndex = null; - } - } - - foreach ($node->params as $i => $param) { - if ($param->variadic) { - $isVariadic = true; - } - if (!$param->var instanceof Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $parameters[] = new NativeParameterReflection( - $param->var->name, - $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex, - $this->getFunctionType($param->type, $param->type === null, false), - $param->byRef - ? PassedByReference::createCreatesNewVariable() - : PassedByReference::createNo(), - $param->variadic, - $param->default !== null ? $this->getType($param->default) : null - ); - } - - if ($node instanceof Expr\ArrowFunction) { - $returnType = $this->enterArrowFunctionWithoutReflection($node)->getType($node->expr); - if ($node->returnType !== null) { - $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); - } - } else { - $callableParameters = null; - $arg = $node->getAttribute('parent'); - if ($arg instanceof Arg) { - $funcCall = $arg->getAttribute('parent'); - $argOrder = $arg->getAttribute('expressionOrder'); - if ($funcCall instanceof FuncCall && $funcCall->name instanceof Name) { - $functionName = $this->reflectionProvider->resolveFunctionName($funcCall->name, $this); - if ( - $functionName === 'array_map' - && $argOrder === 0 - && isset($funcCall->args[1]) - ) { - $callableParameters = [ - new DummyParameter('item', $this->getType($funcCall->args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ]; - } - } - } - $closureScope = $this->enterAnonymousFunctionWithoutReflection($node, $callableParameters); - $closureReturnStatements = []; - $closureYieldStatements = []; - $closureExecutionEnds = []; - $this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $closureScope, static function (Node $node, Scope $scope) use ($closureScope, &$closureReturnStatements, &$closureYieldStatements, &$closureExecutionEnds): void { - if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { - return; - } - - if ($node instanceof ExecutionEndNode) { - if ($node->getStatementResult()->isAlwaysTerminating()) { - foreach ($node->getStatementResult()->getExitPoints() as $exitPoint) { - if ($exitPoint->getStatement() instanceof Node\Stmt\Return_) { - continue; - } - - $closureExecutionEnds[] = $node; - break; - } - - if (count($node->getStatementResult()->getExitPoints()) === 0) { - $closureExecutionEnds[] = $node; - } - } - - return; - } - - if ($node instanceof Node\Stmt\Return_) { - $closureReturnStatements[] = [$node, $scope]; - } - - if (!$node instanceof Expr\Yield_ && !$node instanceof Expr\YieldFrom) { - return; - } - - $closureYieldStatements[] = [$node, $scope]; - }); - - $returnTypes = []; - $hasNull = false; - foreach ($closureReturnStatements as [$returnNode, $returnScope]) { - if ($returnNode->expr === null) { - $hasNull = true; - continue; - } - - $returnTypes[] = $returnScope->getType($returnNode->expr); - } - - if (count($returnTypes) === 0) { - if (count($closureExecutionEnds) > 0 && !$hasNull) { - $returnType = new NeverType(true); - } else { - $returnType = new VoidType(); - } - } else { - if (count($closureExecutionEnds) > 0) { - $returnTypes[] = new NeverType(true); - } - if ($hasNull) { - $returnTypes[] = new NullType(); - } - $returnType = TypeCombinator::union(...$returnTypes); - } - - if (count($closureYieldStatements) > 0) { - $keyTypes = []; - $valueTypes = []; - foreach ($closureYieldStatements as [$yieldNode, $yieldScope]) { - if ($yieldNode instanceof Expr\Yield_) { - if ($yieldNode->key === null) { - $keyTypes[] = new IntegerType(); - } else { - $keyTypes[] = $yieldScope->getType($yieldNode->key); - } - - if ($yieldNode->value === null) { - $valueTypes[] = new NullType(); - } else { - $valueTypes[] = $yieldScope->getType($yieldNode->value); - } - - continue; - } - - $yieldFromType = $yieldScope->getType($yieldNode->expr); - $keyTypes[] = $yieldFromType->getIterableKeyType(); - $valueTypes[] = $yieldFromType->getIterableValueType(); - } - - $returnType = new GenericObjectType(\Generator::class, [ - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes), - new MixedType(), - $returnType, - ]); - } else { - $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); - } - } - - return new ClosureType( - $parameters, - $returnType, - $isVariadic - ); - } elseif ($node instanceof New_) { - if ($node->class instanceof Name) { - $type = $this->exactInstantiation($node, $node->class->toString()); - if ($type !== null) { - return $type; - } - - $lowercasedClassName = strtolower($node->class->toString()); - if ($lowercasedClassName === 'static') { - if (!$this->isInClass()) { - return new ErrorType(); - } - - return new StaticType($this->getClassReflection()); - } - if ($lowercasedClassName === 'parent') { - return new NonexistentParentClassType(); - } - - return new ObjectType($node->class->toString()); - } - if ($node->class instanceof Node\Stmt\Class_) { - $anonymousClassReflection = $this->reflectionProvider->getAnonymousClassReflection($node->class, $this); - - return new ObjectType($anonymousClassReflection->getName()); - } - - $exprType = $this->getType($node->class); - return $this->getTypeToInstantiateForNew($exprType); - - } elseif ($node instanceof Array_) { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - if (count($node->items) > 256) { - $arrayBuilder->degradeToGeneralArray(); - } - foreach ($node->items as $arrayItem) { - if ($arrayItem === null) { - continue; - } - - $valueType = $this->getType($arrayItem->value); - if ($arrayItem->unpack) { - if ($valueType instanceof ConstantArrayType) { - foreach ($valueType->getValueTypes() as $innerValueType) { - $arrayBuilder->setOffsetValueType(null, $innerValueType); - } - } else { - $arrayBuilder->degradeToGeneralArray(); - $arrayBuilder->setOffsetValueType(new IntegerType(), $valueType->getIterableValueType()); - } - } else { - $arrayBuilder->setOffsetValueType( - $arrayItem->key !== null ? $this->getType($arrayItem->key) : null, - $valueType - ); - } - } - return $arrayBuilder->getArray(); - - } elseif ($node instanceof Int_) { - return $this->getType($node->expr)->toInteger(); - } elseif ($node instanceof Bool_) { - return $this->getType($node->expr)->toBoolean(); - } elseif ($node instanceof Double) { - return $this->getType($node->expr)->toFloat(); - } elseif ($node instanceof \PhpParser\Node\Expr\Cast\String_) { - return $this->getType($node->expr)->toString(); - } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Array_) { - return $this->getType($node->expr)->toArray(); - } elseif ($node instanceof Node\Scalar\MagicConst\Line) { - return new ConstantIntegerType($node->getLine()); - } elseif ($node instanceof Node\Scalar\MagicConst\Class_) { - if (!$this->isInClass()) { - return new ConstantStringType(''); - } - - return new ConstantStringType($this->getClassReflection()->getName(), true); - } elseif ($node instanceof Node\Scalar\MagicConst\Dir) { - return new ConstantStringType(dirname($this->getFile())); - } elseif ($node instanceof Node\Scalar\MagicConst\File) { - return new ConstantStringType($this->getFile()); - } elseif ($node instanceof Node\Scalar\MagicConst\Namespace_) { - return new ConstantStringType($this->namespace ?? ''); - } elseif ($node instanceof Node\Scalar\MagicConst\Method) { - if ($this->isInAnonymousFunction()) { - return new ConstantStringType('{closure}'); - } - - $function = $this->getFunction(); - if ($function === null) { - return new ConstantStringType(''); - } - if ($function instanceof MethodReflection) { - return new ConstantStringType( - sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) - ); - } - - return new ConstantStringType($function->getName()); - } elseif ($node instanceof Node\Scalar\MagicConst\Function_) { - if ($this->isInAnonymousFunction()) { - return new ConstantStringType('{closure}'); - } - $function = $this->getFunction(); - if ($function === null) { - return new ConstantStringType(''); - } - - return new ConstantStringType($function->getName()); - } elseif ($node instanceof Node\Scalar\MagicConst\Trait_) { - if (!$this->isInTrait()) { - return new ConstantStringType(''); - } - return new ConstantStringType($this->getTraitReflection()->getName(), true); - } elseif ($node instanceof Object_) { - $castToObject = static function (Type $type): Type { - if ((new ObjectWithoutClassType())->isSuperTypeOf($type)->yes()) { - return $type; - } - - return new ObjectType('stdClass'); - }; - - $exprType = $this->getType($node->expr); - if ($exprType instanceof UnionType) { - return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes())); - } - - return $castToObject($exprType); - } elseif ($node instanceof Unset_) { - return new NullType(); - } elseif ($node instanceof Expr\PostInc || $node instanceof Expr\PostDec) { - return $this->getType($node->var); - } elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) { - $varType = $this->getType($node->var); - $varScalars = TypeUtils::getConstantScalars($varType); - if (count($varScalars) > 0) { - $newTypes = []; - - foreach ($varScalars as $scalar) { - $varValue = $scalar->getValue(); - if ($node instanceof Expr\PreInc) { - ++$varValue; - } else { - --$varValue; - } - - $newTypes[] = $this->getTypeFromValue($varValue); - } - return TypeCombinator::union(...$newTypes); - } elseif ($varType instanceof IntegerRangeType) { - return $varType->shift($node instanceof Expr\PreInc ? +1 : -1); - } - - $stringType = new StringType(); - if ($stringType->isSuperTypeOf($varType)->yes()) { - return $stringType; - } - - return $varType->toNumber(); - } elseif ($node instanceof Expr\Yield_) { - $functionReflection = $this->getFunction(); - if ($functionReflection === null) { - return new MixedType(); - } - - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!$returnType instanceof TypeWithClassName) { - return new MixedType(); - } - - $generatorSendType = GenericTypeVariableResolver::getType($returnType, \Generator::class, 'TSend'); - if ($generatorSendType === null) { - return new MixedType(); - } - - return $generatorSendType; - } elseif ($node instanceof Expr\YieldFrom) { - $yieldFromType = $this->getType($node->expr); - - if (!$yieldFromType instanceof TypeWithClassName) { - return new MixedType(); - } - - $generatorReturnType = GenericTypeVariableResolver::getType($yieldFromType, \Generator::class, 'TReturn'); - if ($generatorReturnType === null) { - return new MixedType(); - } - - return $generatorReturnType; - } elseif ($node instanceof Expr\Match_) { - $cond = $node->cond; - $types = []; - - $matchScope = $this; - foreach ($node->arms as $arm) { - if ($arm->conds === null) { - $types[] = $matchScope->getType($arm->body); - continue; - } - - if (count($arm->conds) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $filteringExpr = null; - foreach ($arm->conds as $armCond) { - $armCondExpr = new BinaryOp\Identical($cond, $armCond); - if ($filteringExpr === null) { - $filteringExpr = $armCondExpr; - continue; - } - - $filteringExpr = new BinaryOp\BooleanOr($filteringExpr, $armCondExpr); - } - - $truthyScope = $matchScope->filterByTruthyValue($filteringExpr); - $types[] = $truthyScope->getType($arm->body); - - $matchScope = $matchScope->filterByFalseyValue($filteringExpr); - } - - return TypeCombinator::union(...$types); - } - - $exprString = $this->getNodeKey($node); - if (isset($this->moreSpecificTypes[$exprString]) && $this->moreSpecificTypes[$exprString]->getCertainty()->yes()) { - return $this->moreSpecificTypes[$exprString]->getType(); - } - - if ($node instanceof Expr\AssignOp\Coalesce) { - return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes())); - } - - if ($node instanceof Expr\BinaryOp\Coalesce) { - if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) { - $dimType = $this->getType($node->left->dim); - $varType = $this->getType($node->left->var); - $hasOffset = $varType->hasOffsetValueType($dimType); - $leftType = $this->getType($node->left); - $rightType = $this->filterByFalseyValue( - new BinaryOp\NotIdentical($node->left, new ConstFetch(new Name('null'))) - )->getType($node->right); - if ($hasOffset->no()) { - return $rightType; - } elseif ($hasOffset->yes()) { - $offsetValueType = $varType->getOffsetValueType($dimType); - if ($offsetValueType->isSuperTypeOf(new NullType())->no()) { - return TypeCombinator::removeNull($leftType); - } - } - - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType - ); - } - - $leftType = $this->getType($node->left); - $rightType = $this->filterByFalseyValue( - new BinaryOp\NotIdentical($node->left, new ConstFetch(new Name('null'))) - )->getType($node->right); - if ($leftType instanceof ErrorType || $leftType instanceof NullType) { - return $rightType; - } - - if ( - TypeCombinator::containsNull($leftType) - || $node->left instanceof PropertyFetch - || ( - $node->left instanceof Variable - && is_string($node->left->name) - && !$this->hasVariableType($node->left->name)->yes() - ) - ) { - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType - ); - } - - return TypeCombinator::removeNull($leftType); - } - - if ($node instanceof ConstFetch) { - $constName = (string) $node->name; - $loweredConstName = strtolower($constName); - if ($loweredConstName === 'true') { - return new \PHPStan\Type\Constant\ConstantBooleanType(true); - } elseif ($loweredConstName === 'false') { - return new \PHPStan\Type\Constant\ConstantBooleanType(false); - } elseif ($loweredConstName === 'null') { - return new NullType(); - } - - if ($node->name->isFullyQualified()) { - if (array_key_exists($node->name->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($node->name->toString(), $this->constantTypes[$node->name->toCodeString()]); - } - } - - if ($this->getNamespace() !== null) { - $constantName = new FullyQualified([$this->getNamespace(), $constName]); - if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); - } - } - - $constantName = new FullyQualified($constName); - if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); - } - - if ($this->reflectionProvider->hasConstant($node->name, $this)) { - /** @var string $resolvedConstantName */ - $resolvedConstantName = $this->reflectionProvider->resolveConstantName($node->name, $this); - if ($resolvedConstantName === 'DIRECTORY_SEPARATOR') { - return new UnionType([ - new ConstantStringType('/'), - new ConstantStringType('\\'), - ]); - } - if ($resolvedConstantName === 'PATH_SEPARATOR') { - return new UnionType([ - new ConstantStringType(':'), - new ConstantStringType(';'), - ]); - } - if ($resolvedConstantName === 'PHP_EOL') { - return new UnionType([ - new ConstantStringType("\n"), - new ConstantStringType("\r\n"), - ]); - } - if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') { - return new IntegerType(); - } - - $constantType = $this->reflectionProvider->getConstant($node->name, $this)->getValueType(); - - return $this->resolveConstantType($resolvedConstantName, $constantType); - } - - return new ErrorType(); - } elseif ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Identifier) { - $constantName = $node->name->name; - if ($node->class instanceof Name) { - $constantClass = (string) $node->class; - $constantClassType = new ObjectType($constantClass); - $namesToResolve = [ - 'self', - 'parent', - ]; - if ($this->isInClass()) { - if ($this->getClassReflection()->isFinal()) { - $namesToResolve[] = 'static'; - } elseif (strtolower($constantClass) === 'static') { - if (strtolower($constantName) === 'class') { - return new GenericClassStringType(new StaticType($this->getClassReflection())); - } - return new MixedType(); - } - } - if (in_array(strtolower($constantClass), $namesToResolve, true)) { - $resolvedName = $this->resolveName($node->class); - if ($resolvedName === 'parent' && strtolower($constantName) === 'class') { - return new ClassStringType(); - } - $constantClassType = $this->resolveTypeByName($node->class); - } - - if (strtolower($constantName) === 'class') { - return new ConstantStringType($constantClassType->getClassName(), true); - } - } else { - $constantClassType = $this->getType($node->class); - } - - $referencedClasses = TypeUtils::getDirectClassNames($constantClassType); - if (strtolower($constantName) === 'class') { - if (count($referencedClasses) === 0) { - return new ErrorType(); - } - $classTypes = []; - foreach ($referencedClasses as $referencedClass) { - $classTypes[] = new GenericClassStringType(new ObjectType($referencedClass)); - } - - return TypeCombinator::union(...$classTypes); - } - $types = []; - foreach ($referencedClasses as $referencedClass) { - if (!$this->reflectionProvider->hasClass($referencedClass)) { - continue; - } - - $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); - if (!$propertyClassReflection->hasConstant($constantName)) { - continue; - } - - $constantType = $propertyClassReflection->getConstant($constantName)->getValueType(); - if ( - $constantType instanceof ConstantType - && in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) - ) { - $constantType = $constantType->generalize(); - } - $types[] = $constantType; - } - - if (count($types) > 0) { - return TypeCombinator::union(...$types); - } - - if (!$constantClassType->hasConstant($constantName)->yes()) { - return new ErrorType(); - } - - return $constantClassType->getConstant($constantName)->getValueType(); - } - - if ($node instanceof Expr\Ternary) { - if ($node->if === null) { - $conditionType = $this->getType($node->cond); - $booleanConditionType = $conditionType->toBoolean(); - if ($booleanConditionType instanceof ConstantBooleanType) { - if ($booleanConditionType->getValue()) { - return $this->filterByTruthyValue($node->cond)->getType($node->cond); - } - - return $this->filterByFalseyValue($node->cond)->getType($node->else); - } - return TypeCombinator::union( - TypeCombinator::remove($this->filterByTruthyValue($node->cond)->getType($node->cond), StaticTypeFactory::falsey()), - $this->filterByFalseyValue($node->cond)->getType($node->else) - ); - } - - $booleanConditionType = $this->getType($node->cond)->toBoolean(); - if ($booleanConditionType instanceof ConstantBooleanType) { - if ($booleanConditionType->getValue()) { - return $this->filterByTruthyValue($node->cond)->getType($node->if); - } - - return $this->filterByFalseyValue($node->cond)->getType($node->else); - } - - return TypeCombinator::union( - $this->filterByTruthyValue($node->cond)->getType($node->if), - $this->filterByFalseyValue($node->cond)->getType($node->else) - ); - } - - if ($node instanceof Variable && is_string($node->name)) { - if ($this->hasVariableType($node->name)->no()) { - return new ErrorType(); - } - - return $this->getVariableType($node->name); - } - - if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) { - return $this->getNullsafeShortCircuitingType( - $node->var, - $this->getTypeFromArrayDimFetch( - $node, - $this->getType($node->dim), - $this->getType($node->var) - ) - ); - } - - if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) { - $typeCallback = function () use ($node): Type { - $returnType = $this->methodCallReturnType( - $this->getType($node->var), - $node->name->name, - $node - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); - } - - if ($node instanceof Expr\NullsafeMethodCall) { - $varType = $this->getType($node->var); - if (!TypeCombinator::containsNull($varType)) { - return $this->getType(new MethodCall($node->var, $node->name, $node->args)); - } - - return TypeCombinator::union( - $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null')))) - ->getType(new MethodCall($node->var, $node->name, $node->args)), - new NullType() - ); - } - - if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) { - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - } else { - $staticMethodCalledOnType = $this->getType($node->class); - if ($staticMethodCalledOnType instanceof GenericClassStringType) { - $staticMethodCalledOnType = $staticMethodCalledOnType->getGenericType(); - } - } - - $returnType = $this->methodCallReturnType( - $staticMethodCalledOnType, - $node->name->toString(), - $node - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - $callType = $typeCallback(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $callType); - } - - return $callType; - } - - if ($node instanceof PropertyFetch && $node->name instanceof Node\Identifier) { - $typeCallback = function () use ($node): Type { - $returnType = $this->propertyFetchType( - $this->getType($node->var), - $node->name->name, - $node - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); - } - - if ($node instanceof Expr\NullsafePropertyFetch) { - $varType = $this->getType($node->var); - if (!TypeCombinator::containsNull($varType)) { - return $this->getType(new PropertyFetch($node->var, $node->name)); - } - - return TypeCombinator::union( - $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null')))) - ->getType(new PropertyFetch($node->var, $node->name)), - new NullType() - ); - } - - if ( - $node instanceof Expr\StaticPropertyFetch - && $node->name instanceof Node\VarLikeIdentifier - ) { - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); - } else { - $staticPropertyFetchedOnType = $this->getType($node->class); - if ($staticPropertyFetchedOnType instanceof GenericClassStringType) { - $staticPropertyFetchedOnType = $staticPropertyFetchedOnType->getGenericType(); - } - } - - $returnType = $this->propertyFetchType( - $staticPropertyFetchedOnType, - $node->name->toString(), - $node - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - $fetchType = $typeCallback(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $fetchType); - } - - return $fetchType; - } - - if ($node instanceof FuncCall) { - if ($node->name instanceof Expr) { - $calledOnType = $this->getType($node->name); - if ($calledOnType->isCallable()->no()) { - return new ErrorType(); - } - - return ParametersAcceptorSelector::selectFromArgs( - $this, - $node->args, - $calledOnType->getCallableParametersAcceptors($this) - )->getReturnType(); - } - - if (!$this->reflectionProvider->hasFunction($node->name, $this)) { - return new ErrorType(); - } - - $functionReflection = $this->reflectionProvider->getFunction($node->name, $this); - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicFunctionReturnTypeExtensions() as $dynamicFunctionReturnTypeExtension) { - if (!$dynamicFunctionReturnTypeExtension->isFunctionSupported($functionReflection)) { - continue; - } - - return $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall($functionReflection, $node, $this); - } - - return ParametersAcceptorSelector::selectFromArgs( - $this, - $node->args, - $functionReflection->getVariants() - )->getReturnType(); - } - - return new MixedType(); - } - - private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type - { - if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) { - $varType = $this->getType($expr->var); - if (TypeCombinator::containsNull($varType)) { - return TypeCombinator::addNull($type); - } - - return $type; - } - - if ($expr instanceof Expr\ArrayDimFetch) { - return $this->getNullsafeShortCircuitingType($expr->var, $type); - } - - if ($expr instanceof PropertyFetch) { - return $this->getNullsafeShortCircuitingType($expr->var, $type); - } - - if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($expr->class, $type); - } - - if ($expr instanceof MethodCall) { - return $this->getNullsafeShortCircuitingType($expr->var, $type); - } - - if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($expr->class, $type); - } - - return $type; - } - - private function resolveConstantType(string $constantName, Type $constantType): Type - { - if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { - return $constantType->generalize(); - } - - return $constantType; - } - - public function getNativeType(Expr $expr): Type - { - $key = $this->getNodeKey($expr); - - if (array_key_exists($key, $this->nativeExpressionTypes)) { - return $this->nativeExpressionTypes[$key]; - } - - if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - return $this->getNullsafeShortCircuitingType( - $expr->var, - $this->getTypeFromArrayDimFetch( - $expr, - $this->getNativeType($expr->dim), - $this->getNativeType($expr->var) - ) - ); - } - - return $this->getType($expr); - } - - public function doNotTreatPhpDocTypesAsCertain(): Scope - { - if (!$this->treatPhpDocTypesAsCertain) { - return $this; - } - - return new self( - $this->scopeFactory, - $this->reflectionProvider, - $this->dynamicReturnTypeExtensionRegistry, - $this->operatorTypeSpecifyingExtensionRegistry, - $this->printer, - $this->typeSpecifier, - $this->propertyReflectionFinder, - $this->parser, - $this->nodeScopeResolver, - $this->context, - $this->declareStrictTypes, - $this->constantTypes, - $this->function, - $this->namespace, - $this->variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->dynamicConstantNames, - false, - $this->objectFromNewClass, - $this->afterExtractCall, - $this->parentScope - ); - } - - private function promoteNativeTypes(): self - { - $variableTypes = $this->variableTypes; - foreach ($this->nativeExpressionTypes as $expressionType => $type) { - if (substr($expressionType, 0, 1) !== '$') { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableName = substr($expressionType, 1); - $has = $this->hasVariableType($variableName); - if ($has->no()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableTypes[$variableName] = new VariableTypeHolder($type, $has); - } - - return $this->scopeFactory->create( - $this->context, - $this->declareStrictTypes, - $this->constantTypes, - $this->function, - $this->namespace, - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - [] - ); - } - - /** - * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch - * @return bool - */ - private function hasPropertyNativeType($propertyFetch): bool - { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $this); - if ($propertyReflection === null) { - return false; - } - - if (!$propertyReflection->isNative()) { - return false; - } - - return !$propertyReflection->getNativeType() instanceof MixedType; - } - - protected function getTypeFromArrayDimFetch( - Expr\ArrayDimFetch $arrayDimFetch, - Type $offsetType, - Type $offsetAccessibleType - ): Type - { - if ($arrayDimFetch->dim === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ((new ObjectType(\ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()) { - return $this->getType( - new MethodCall( - $arrayDimFetch->var, - new Node\Identifier('offsetGet'), - [ - new Node\Arg($arrayDimFetch->dim), - ] - ) - ); - } - - return $offsetAccessibleType->getOffsetValueType($offsetType); - } - - private function calculateFromScalars(Expr $node, ConstantScalarType $leftType, ConstantScalarType $rightType): Type - { - if ($leftType instanceof StringType && $rightType instanceof StringType) { - /** @var string $leftValue */ - $leftValue = $leftType->getValue(); - /** @var string $rightValue */ - $rightValue = $rightType->getValue(); - - if ($node instanceof Expr\BinaryOp\BitwiseAnd || $node instanceof Expr\AssignOp\BitwiseAnd) { - return $this->getTypeFromValue($leftValue & $rightValue); - } - - if ($node instanceof Expr\BinaryOp\BitwiseOr || $node instanceof Expr\AssignOp\BitwiseOr) { - return $this->getTypeFromValue($leftValue | $rightValue); - } - - if ($node instanceof Expr\BinaryOp\BitwiseXor || $node instanceof Expr\AssignOp\BitwiseXor) { - return $this->getTypeFromValue($leftValue ^ $rightValue); - } - } - - $leftValue = $leftType->getValue(); - $rightValue = $rightType->getValue(); - - if ($node instanceof Node\Expr\BinaryOp\Spaceship) { - return $this->getTypeFromValue($leftValue <=> $rightValue); - } - - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - if (TypeCombinator::union($leftNumberType, $rightNumberType) instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - /** @var float|int $leftNumberValue */ - $leftNumberValue = $leftNumberType->getValue(); - - /** @var float|int $rightNumberValue */ - $rightNumberValue = $rightNumberType->getValue(); - - if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) { - return $this->getTypeFromValue($leftNumberValue + $rightNumberValue); - } - - if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) { - return $this->getTypeFromValue($leftNumberValue - $rightNumberValue); - } - - if ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) { - return $this->getTypeFromValue($leftNumberValue * $rightNumberValue); - } - - if ($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow) { - return $this->getTypeFromValue($leftNumberValue ** $rightNumberValue); - } - - if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { - return $this->getTypeFromValue($leftNumberValue / $rightNumberValue); - } - - if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Node\Expr\AssignOp\Mod) { - return $this->getTypeFromValue($leftNumberValue % $rightNumberValue); - } - - if ($node instanceof Expr\BinaryOp\ShiftLeft || $node instanceof Expr\AssignOp\ShiftLeft) { - return $this->getTypeFromValue($leftNumberValue << $rightNumberValue); - } - - if ($node instanceof Expr\BinaryOp\ShiftRight || $node instanceof Expr\AssignOp\ShiftRight) { - return $this->getTypeFromValue($leftNumberValue >> $rightNumberValue); - } - - if ($node instanceof Expr\BinaryOp\BitwiseAnd || $node instanceof Expr\AssignOp\BitwiseAnd) { - return $this->getTypeFromValue($leftNumberValue & $rightNumberValue); - } - - if ($node instanceof Expr\BinaryOp\BitwiseOr || $node instanceof Expr\AssignOp\BitwiseOr) { - return $this->getTypeFromValue($leftNumberValue | $rightNumberValue); - } - - if ($node instanceof Expr\BinaryOp\BitwiseXor || $node instanceof Expr\AssignOp\BitwiseXor) { - return $this->getTypeFromValue($leftNumberValue ^ $rightNumberValue); - } - - return new MixedType(); - } - - private function resolveExactName(Name $name): ?string - { - $originalClass = (string) $name; - - switch (strtolower($originalClass)) { - case 'self': - if (!$this->isInClass()) { - return null; - } - return $this->getClassReflection()->getName(); - case 'parent': - if (!$this->isInClass()) { - return null; - } - $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== false) { - return $currentClassReflection->getParentClass()->getName(); - } - return null; - case 'static': - return null; - } - - return $originalClass; - } - - public function resolveName(Name $name): string - { - $originalClass = (string) $name; - if ($this->isInClass()) { - if (in_array(strtolower($originalClass), [ - 'self', - 'static', - ], true)) { - if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { - return $this->inClosureBindScopeClass; - } - return $this->getClassReflection()->getName(); - } elseif ($originalClass === 'parent') { - $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== false) { - return $currentClassReflection->getParentClass()->getName(); - } - } - } - - return $originalClass; - } - - public function resolveTypeByName(Name $name): TypeWithClassName - { - if ($name->toLowerString() === 'static' && $this->isInClass()) { - if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { - return new StaticType($this->inClosureBindScopeClass); - } - - return new StaticType($this->getClassReflection()); - } - - $originalClass = $this->resolveName($name); - if ($this->isInClass()) { - if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { - $thisType = new ThisType($this->inClosureBindScopeClass); - } else { - $thisType = new ThisType($this->getClassReflection()); - } - $ancestor = $thisType->getAncestorWithClassName($originalClass); - if ($ancestor !== null) { - return $ancestor; - } - } - - return new ObjectType($originalClass); - } - - /** - * @param mixed $value - */ - public function getTypeFromValue($value): Type - { - return ConstantTypeHelper::getTypeFromValue($value); - } - - public function isSpecified(Expr $node): bool - { - $exprString = $this->getNodeKey($node); - - return isset($this->moreSpecificTypes[$exprString]) - && $this->moreSpecificTypes[$exprString]->getCertainty()->yes(); - } - - /** - * @param MethodReflection|FunctionReflection $reflection - * @return self - */ - public function pushInFunctionCall($reflection): self - { - $stack = $this->inFunctionCallsStack; - $stack[] = $reflection; - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $stack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function popInFunctionCall(): self - { - $stack = $this->inFunctionCallsStack; - array_pop($stack); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $stack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function isInClassExists(string $className): bool - { - foreach ($this->inFunctionCallsStack as $inFunctionCall) { - if (!$inFunctionCall instanceof FunctionReflection) { - continue; - } - - if (in_array($inFunctionCall->getName(), [ - 'class_exists', - 'interface_exists', - 'trait_exists', - ], true)) { - return true; - } - } - $expr = new FuncCall(new FullyQualified('class_exists'), [ - new Arg(new String_(ltrim($className, '\\'))), - ]); - - return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes(); - } - - public function enterClass(ClassReflection $classReflection): self - { - return $this->scopeFactory->create( - $this->context->enterClass($classReflection), - $this->isDeclareStrictTypes(), - $this->constantTypes, - null, - $this->getNamespace(), - [ - 'this' => VariableTypeHolder::createYes(new ThisType($classReflection)), - ] - ); - } - - public function enterTrait(ClassReflection $traitReflection): self - { - return $this->scopeFactory->create( - $this->context->enterTrait($traitReflection), - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - [], - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection - ); - } - - /** - * @param Node\Stmt\ClassMethod $classMethod - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $throwType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param bool|null $isPure - * @return self - */ - public function enterClassMethod( - Node\Stmt\ClassMethod $classMethod, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $throwType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - ?bool $isPure = null - ): self - { - if (!$this->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->enterFunctionLike( - new PhpMethodFromParserNodeReflection( - $this->getClassReflection(), - $classMethod, - $templateTypeMap, - $this->getRealParameterTypes($classMethod), - array_map(static function (Type $type): Type { - return TemplateTypeHelper::toArgument($type); - }, $phpDocParameterTypes), - $this->getRealParameterDefaultValues($classMethod), - $this->transformStaticType($this->getFunctionType($classMethod->returnType, $classMethod->returnType === null, false)), - $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, - $throwType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure - ), - !$classMethod->isStatic() - ); - } - - private function transformStaticType(Type $type): Type - { - return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { - if (!$this->isInClass()) { - return $type; - } - if ($type instanceof StaticType) { - $classReflection = $this->getClassReflection(); - $changedType = $type->changeBaseClass($classReflection); - if ($classReflection->isFinal()) { - $changedType = $changedType->getStaticObjectType(); - } - return $traverse($changedType); - } - - return $traverse($type); - }); - } - - /** - * @param Node\FunctionLike $functionLike - * @return Type[] - */ - private function getRealParameterTypes(Node\FunctionLike $functionLike): array - { - $realParameterTypes = []; - foreach ($functionLike->getParams() as $parameter) { - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $realParameterTypes[$parameter->var->name] = $this->getFunctionType( - $parameter->type, - $this->isParameterValueNullable($parameter), - false - ); - } - - return $realParameterTypes; - } - - /** - * @param Node\FunctionLike $functionLike - * @return Type[] - */ - private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): array - { - $realParameterDefaultValues = []; - foreach ($functionLike->getParams() as $parameter) { - if ($parameter->default === null) { - continue; - } - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $realParameterDefaultValues[$parameter->var->name] = $this->getType($parameter->default); - } - - return $realParameterDefaultValues; - } - - /** - * @param Node\Stmt\Function_ $function - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $throwType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param bool|null $isPure - * @return self - */ - public function enterFunction( - Node\Stmt\Function_ $function, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $throwType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - ?bool $isPure = null - ): self - { - return $this->enterFunctionLike( - new PhpFunctionFromParserNodeReflection( - $function, - $templateTypeMap, - $this->getRealParameterTypes($function), - array_map(static function (Type $type): Type { - return TemplateTypeHelper::toArgument($type); - }, $phpDocParameterTypes), - $this->getRealParameterDefaultValues($function), - $this->getFunctionType($function->returnType, $function->returnType === null, false), - $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, - $throwType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure - ), - false - ); - } - - private function enterFunctionLike( - PhpFunctionFromParserNodeReflection $functionReflection, - bool $preserveThis - ): self - { - $variableTypes = []; - $nativeExpressionTypes = []; - foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) { - $parameterType = $parameter->getType(); - if ($parameter->isVariadic()) { - $parameterType = new ArrayType(new IntegerType(), $parameterType); - } - $variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameterType); - $nativeExpressionTypes[sprintf('$%s', $parameter->getName())] = $parameter->getNativeType(); - } - - if ($preserveThis && array_key_exists('this', $this->variableTypes)) { - $variableTypes['this'] = $this->variableTypes['this']; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $functionReflection, - $this->getNamespace(), - $variableTypes, - [], - [], - null, - null, - true, - [], - $nativeExpressionTypes - ); - } - - public function enterNamespace(string $namespaceName): self - { - return $this->scopeFactory->create( - $this->context->beginFile(), - $this->isDeclareStrictTypes(), - $this->constantTypes, - null, - $namespaceName - ); - } - - public function enterClosureBind(?Type $thisType, string $scopeClass): self - { - $variableTypes = $this->getVariableTypes(); - - if ($thisType !== null) { - $variableTypes['this'] = VariableTypeHolder::createYes($thisType); - } else { - unset($variableTypes['this']); - } - - if ($scopeClass === 'static' && $this->isInClass()) { - $scopeClass = $this->getClassReflection()->getName(); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $scopeClass, - $this->anonymousFunctionReflection - ); - } - - public function restoreOriginalScopeAfterClosureBind(self $originalScope): self - { - $variableTypes = $this->getVariableTypes(); - if (isset($originalScope->variableTypes['this'])) { - $variableTypes['this'] = $originalScope->variableTypes['this']; - } else { - unset($variableTypes['this']); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $originalScope->inClosureBindScopeClass, - $this->anonymousFunctionReflection - ); - } - - public function enterClosureCall(Type $thisType): self - { - $variableTypes = $this->getVariableTypes(); - $variableTypes['this'] = VariableTypeHolder::createYes($thisType); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $thisType instanceof TypeWithClassName ? $thisType->getClassName() : null, - $this->anonymousFunctionReflection - ); - } - - public function isInClosureBind(): bool - { - return $this->inClosureBindScopeClass !== null; - } - - /** - * @param \PhpParser\Node\Expr\Closure $closure - * @param \PHPStan\Reflection\ParameterReflection[]|null $callableParameters - * @return self - */ - public function enterAnonymousFunction( - Expr\Closure $closure, - ?array $callableParameters = null - ): self - { - $anonymousFunctionReflection = $this->getType($closure); - if (!$anonymousFunctionReflection instanceof ClosureType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $scope = $this->enterAnonymousFunctionWithoutReflection($closure, $callableParameters); - - return $this->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->constantTypes, - $scope->getFunction(), - $scope->getNamespace(), - $scope->variableTypes, - $scope->moreSpecificTypes, - [], - $scope->inClosureBindScopeClass, - $anonymousFunctionReflection, - true, - [], - $scope->nativeExpressionTypes, - [], - false, - $this - ); - } - - /** - * @param \PhpParser\Node\Expr\Closure $closure - * @param \PHPStan\Reflection\ParameterReflection[]|null $callableParameters - * @return self - */ - private function enterAnonymousFunctionWithoutReflection( - Expr\Closure $closure, - ?array $callableParameters = null - ): self - { - $variableTypes = []; - foreach ($closure->params as $i => $parameter) { - $isNullable = $this->isParameterValueNullable($parameter); - $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic); - if ($callableParameters !== null) { - if (isset($callableParameters[$i])) { - $parameterType = TypehintHelper::decideType($parameterType, $callableParameters[$i]->getType()); - } elseif (count($callableParameters) > 0) { - $lastParameter = $callableParameters[count($callableParameters) - 1]; - if ($lastParameter->isVariadic()) { - $parameterType = TypehintHelper::decideType($parameterType, $lastParameter->getType()); - } else { - $parameterType = TypehintHelper::decideType($parameterType, new MixedType()); - } - } else { - $parameterType = TypehintHelper::decideType($parameterType, new MixedType()); - } - } - - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $variableTypes[$parameter->var->name] = VariableTypeHolder::createYes( - $parameterType - ); - } - - $nativeTypes = []; - $moreSpecificTypes = []; - foreach ($closure->uses as $use) { - if (!is_string($use->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($use->byRef) { - continue; - } - $variableName = $use->var->name; - if ($this->hasVariableType($variableName)->no()) { - $variableType = new ErrorType(); - } else { - $variableType = $this->getVariableType($variableName); - $nativeTypes[sprintf('$%s', $variableName)] = $this->getNativeType($use->var); - } - $variableTypes[$variableName] = VariableTypeHolder::createYes($variableType); - foreach ($this->moreSpecificTypes as $exprString => $moreSpecificType) { - $matches = \Nette\Utils\Strings::matchAll((string) $exprString, '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#'); - if ($matches === []) { - continue; - } - - $matches = array_column($matches, 1); - if (!in_array($variableName, $matches, true)) { - continue; - } - - $moreSpecificTypes[$exprString] = $moreSpecificType; - } - } - - if ($this->hasVariableType('this')->yes() && !$closure->static) { - $variableTypes['this'] = VariableTypeHolder::createYes($this->getVariableType('this')); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $moreSpecificTypes, - [], - $this->inClosureBindScopeClass, - new TrivialParametersAcceptor(), - true, - [], - $nativeTypes, - [], - false, - $this - ); - } - - public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self - { - $anonymousFunctionReflection = $this->getType($arrowFunction); - if (!$anonymousFunctionReflection instanceof ClosureType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $scope = $this->enterArrowFunctionWithoutReflection($arrowFunction); - - return $this->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->constantTypes, - $scope->getFunction(), - $scope->getNamespace(), - $scope->variableTypes, - $scope->moreSpecificTypes, - $scope->conditionalExpressions, - $scope->inClosureBindScopeClass, - $anonymousFunctionReflection, - true, - [], - [], - [], - $scope->afterExtractCall, - $scope->parentScope - ); - } - - private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction): self - { - $variableTypes = $this->variableTypes; - $mixed = new MixedType(); - $parameterVariables = []; - foreach ($arrowFunction->params as $parameter) { - if ($parameter->type === null) { - $parameterType = $mixed; - } else { - $isNullable = $this->isParameterValueNullable($parameter); - $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic); - } - - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableTypes[$parameter->var->name] = VariableTypeHolder::createYes($parameterType); - $parameterVariables[] = $parameter->var->name; - } - - if ($arrowFunction->static) { - unset($variableTypes['this']); - } - - $conditionalExpressions = []; - foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { - $newHolders = []; - foreach ($parameterVariables as $parameterVariable) { - $exprString = '$' . $parameterVariable; - if ($exprString === $conditionalExprString) { - continue 2; - } - } - - foreach ($holders as $holder) { - foreach ($parameterVariables as $parameterVariable) { - $exprString = '$' . $parameterVariable; - foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionalExprString2) { - if ($exprString === $conditionalExprString2) { - continue 3; - } - } - } - - $newHolders[] = $holder; - } - - if (count($newHolders) === 0) { - continue; - } - - $conditionalExpressions[$conditionalExprString] = $newHolders; - } - foreach ($parameterVariables as $parameterVariable) { - $exprString = '$' . $parameterVariable; - foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { - if ($exprString === $conditionalExprString) { - continue; - } - - $newHolders = []; - foreach ($holders as $holder) { - foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionalExprString2) { - if ($exprString === $conditionalExprString2) { - continue 2; - } - } - - $newHolders[] = $holder; - } - - if (count($newHolders) === 0) { - continue; - } - - $conditionalExpressions[$conditionalExprString] = $newHolders; - } - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $conditionalExpressions, - $this->inClosureBindScopeClass, - null, - true, - [], - [], - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function isParameterValueNullable(Node\Param $parameter): bool - { - if ($parameter->default instanceof ConstFetch) { - return strtolower((string) $parameter->default->name) === 'null'; - } - - return false; - } - - /** - * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type - * @param bool $isNullable - * @param bool $isVariadic - * @return Type - */ - public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type - { - if ($isNullable) { - return TypeCombinator::addNull( - $this->getFunctionType($type, false, $isVariadic) - ); - } - if ($isVariadic) { - return new ArrayType(new IntegerType(), $this->getFunctionType( - $type, - false, - false - )); - } - - if ($type instanceof Name) { - $className = (string) $type; - $lowercasedClassName = strtolower($className); - if ($lowercasedClassName === 'parent') { - if ($this->isInClass() && $this->getClassReflection()->getParentClass() !== false) { - return new ObjectType($this->getClassReflection()->getParentClass()->getName()); - } - - return new NonexistentParentClassType(); - } - } - - return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection()->getName() : null); - } - - public function enterForeach(Expr $iteratee, string $valueName, ?string $keyName): self - { - $iterateeType = $this->getType($iteratee); - $nativeIterateeType = $this->getNativeType($iteratee); - $scope = $this->assignVariable($valueName, $iterateeType->getIterableValueType()); - $scope->nativeExpressionTypes[sprintf('$%s', $valueName)] = $nativeIterateeType->getIterableValueType(); - - if ($keyName !== null) { - $scope = $scope->enterForeachKey($iteratee, $keyName); - } - - return $scope; - } - - public function enterForeachKey(Expr $iteratee, string $keyName): self - { - $iterateeType = $this->getType($iteratee); - $nativeIterateeType = $this->getNativeType($iteratee); - $scope = $this->assignVariable($keyName, $iterateeType->getIterableKeyType()); - $scope->nativeExpressionTypes[sprintf('$%s', $keyName)] = $nativeIterateeType->getIterableKeyType(); - - return $scope; - } - - /** - * @param \PhpParser\Node\Name[] $classes - * @param string|null $variableName - * @return self - */ - public function enterCatch(array $classes, ?string $variableName): self - { - $type = TypeCombinator::union(...array_map(static function (string $class): ObjectType { - return new ObjectType($class); - }, $classes)); - - return $this->enterCatchType($type, $variableName); - } - - public function enterCatchType(Type $catchType, ?string $variableName): self - { - if ($variableName === null) { - return $this; - } - - return $this->assignVariable( - $variableName, - TypeCombinator::intersect($catchType, new ObjectType(\Throwable::class)) - ); - } - - public function enterExpressionAssign(Expr $expr): self - { - $exprString = $this->getNodeKey($expr); - $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; - $currentlyAssignedExpressions[$exprString] = true; - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $currentlyAssignedExpressions, - $this->nativeExpressionTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function exitExpressionAssign(Expr $expr): self - { - $exprString = $this->getNodeKey($expr); - $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; - unset($currentlyAssignedExpressions[$exprString]); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $currentlyAssignedExpressions, - $this->nativeExpressionTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function isInExpressionAssign(Expr $expr): bool - { - $exprString = $this->getNodeKey($expr); - return array_key_exists($exprString, $this->currentlyAssignedExpressions); - } - - public function assignVariable(string $variableName, Type $type, ?TrinaryLogic $certainty = null): self - { - if ($certainty === null) { - $certainty = TrinaryLogic::createYes(); - } elseif ($certainty->no()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $variableTypes = $this->getVariableTypes(); - $variableTypes[$variableName] = new VariableTypeHolder($type, $certainty); - - $nativeTypes = $this->nativeExpressionTypes; - $nativeTypes[sprintf('$%s', $variableName)] = $type; - - $variableString = $this->printer->prettyPrintExpr(new Variable($variableName)); - $moreSpecificTypeHolders = $this->moreSpecificTypes; - foreach (array_keys($moreSpecificTypeHolders) as $key) { - $matches = \Nette\Utils\Strings::matchAll((string) $key, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#'); - - if ($matches === []) { - continue; - } - - $matches = array_column($matches, 0); - - if (!in_array($variableString, $matches, true)) { - continue; - } - - unset($moreSpecificTypeHolders[$key]); - } - - $conditionalExpressions = []; - foreach ($this->conditionalExpressions as $exprString => $holders) { - $exprVariableName = '$' . $variableName; - if ($exprString === $exprVariableName) { - continue; - } - - foreach ($holders as $holder) { - foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionExprString) { - if ($conditionExprString === $exprVariableName) { - continue 3; - } - } - } - - $conditionalExpressions[$exprString] = $holders; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $moreSpecificTypeHolders, - $conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $nativeTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function unsetExpression(Expr $expr): self - { - if ($expr instanceof Variable && is_string($expr->name)) { - if ($this->hasVariableType($expr->name)->no()) { - return $this; - } - $variableTypes = $this->getVariableTypes(); - unset($variableTypes[$expr->name]); - $nativeTypes = $this->nativeExpressionTypes; - - $exprString = sprintf('$%s', $expr->name); - unset($nativeTypes[$exprString]); - - $conditionalExpressions = $this->conditionalExpressions; - unset($conditionalExpressions[$exprString]); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - $nativeTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - $varType = $this->getType($expr->var); - $constantArrays = TypeUtils::getConstantArrays($varType); - if (count($constantArrays) > 0) { - $unsetArrays = []; - $dimType = $this->getType($expr->dim); - foreach ($constantArrays as $constantArray) { - $unsetArrays[] = $constantArray->unsetOffset($dimType); - } - return $this->specifyExpressionType( - $expr->var, - TypeCombinator::union(...$unsetArrays) - ); - } - - $args = [new Node\Arg($expr->var)]; - - $arrays = TypeUtils::getArrays($varType); - $scope = $this; - if (count($arrays) > 0) { - $scope = $scope->specifyExpressionType($expr->var, TypeCombinator::union(...$arrays)); - } - - return $scope->invalidateExpression($expr->var) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), $args)) - ->invalidateExpression(new FuncCall(new Name('count'), $args)); - } - - return $this; - } - - public function specifyExpressionType(Expr $expr, Type $type, ?Type $nativeType = null): self - { - if ($expr instanceof Node\Scalar || $expr instanceof Array_) { - return $this; - } - - if ($expr instanceof ConstFetch) { - $constantTypes = $this->constantTypes; - $constantName = new FullyQualified($expr->name->toString()); - $constantTypes[$constantName->toCodeString()] = $type; - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - $exprString = $this->getNodeKey($expr); - - $scope = $this; - - if ($expr instanceof Variable && is_string($expr->name)) { - $variableName = $expr->name; - - $variableTypes = $this->getVariableTypes(); - $variableTypes[$variableName] = VariableTypeHolder::createYes($type); - - if ($nativeType === null) { - $nativeType = $type; - } - - $nativeTypes = $this->nativeExpressionTypes; - $exprString = sprintf('$%s', $variableName); - $nativeTypes[$exprString] = $nativeType; - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $nativeTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - $constantArrays = TypeUtils::getConstantArrays($this->getType($expr->var)); - if (count($constantArrays) > 0) { - $setArrays = []; - $dimType = $this->getType($expr->dim); - foreach ($constantArrays as $constantArray) { - $setArrays[] = $constantArray->setOffsetValueType($dimType, $type); - } - $scope = $this->specifyExpressionType( - $expr->var, - TypeCombinator::union(...$setArrays) - ); - } - } - - if ($expr instanceof FuncCall && $expr->name instanceof Name && $type instanceof ConstantBooleanType && !$type->getValue()) { - $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this); - if ($functionName !== null && in_array(strtolower($functionName), [ - 'is_dir', - 'is_file', - 'file_exists', - ], true)) { - return $this; - } - } - - return $scope->addMoreSpecificTypes([ - $exprString => $type, - ]); - } - - public function assignExpression(Expr $expr, Type $type): self - { - $scope = $this; - if ($expr instanceof PropertyFetch || $expr instanceof Expr\StaticPropertyFetch) { - $scope = $this->invalidateExpression($expr); - } - - return $scope->specifyExpressionType($expr, $type); - } - - public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self - { - $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); - $moreSpecificTypeHolders = $this->moreSpecificTypes; - $nativeExpressionTypes = $this->nativeExpressionTypes; - $invalidated = false; - foreach (array_keys($moreSpecificTypeHolders) as $exprString) { - $exprString = (string) $exprString; - if (Strings::startsWith($exprString, $exprStringToInvalidate)) { - if ($exprString === $exprStringToInvalidate && $requireMoreCharacters) { - continue; - } - $nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1); - if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') === null) { - unset($moreSpecificTypeHolders[$exprString]); - unset($nativeExpressionTypes[$exprString]); - $invalidated = true; - continue; - } - } - $matches = \Nette\Utils\Strings::matchAll($exprString, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#'); - if ($matches === []) { - continue; - } - - $matches = array_column($matches, 0); - - if (!in_array($exprStringToInvalidate, $matches, true)) { - continue; - } - - unset($moreSpecificTypeHolders[$exprString]); - unset($nativeExpressionTypes[$exprString]); - $invalidated = true; - } - - if (!$invalidated) { - return $this; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $moreSpecificTypeHolders, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $nativeExpressionTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self - { - $exprType = $this->getType($expr); - $typeAfterRemove = TypeCombinator::remove($exprType, $typeToRemove); - if ( - !$expr instanceof Variable - && $exprType->equals($typeAfterRemove) - && !$exprType instanceof ErrorType - && !$exprType instanceof NeverType - ) { - return $this; - } - $scope = $this->specifyExpressionType( - $expr, - $typeAfterRemove - ); - if ($expr instanceof Variable && is_string($expr->name)) { - $scope->nativeExpressionTypes[sprintf('$%s', $expr->name)] = TypeCombinator::remove($this->getNativeType($expr), $typeToRemove); - } - - return $scope; - } - - /** - * @param \PhpParser\Node\Expr $expr - * @return \PHPStan\Analyser\MutatingScope - */ - public function filterByTruthyValue(Expr $expr): Scope - { - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createTruthy()); - return $this->filterBySpecifiedTypes($specifiedTypes); - } - - /** - * @param \PhpParser\Node\Expr $expr - * @return \PHPStan\Analyser\MutatingScope - */ - public function filterByFalseyValue(Expr $expr): Scope - { - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createFalsey()); - return $this->filterBySpecifiedTypes($specifiedTypes); - } - - public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self - { - $typeSpecifications = []; - foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) { - $typeSpecifications[] = [ - 'sure' => true, - 'exprString' => $exprString, - 'expr' => $expr, - 'type' => $type, - ]; - } - foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - $typeSpecifications[] = [ - 'sure' => false, - 'exprString' => $exprString, - 'expr' => $expr, - 'type' => $type, - ]; - } - - usort($typeSpecifications, static function (array $a, array $b): int { - $length = strlen((string) $a['exprString']) - strlen((string) $b['exprString']); - if ($length !== 0) { - return $length; - } - - return $b['sure'] - $a['sure']; - }); - - $scope = $this; - $typeGuards = []; - $skipVariables = []; - $saveConditionalVariables = []; - foreach ($typeSpecifications as $typeSpecification) { - $expr = $typeSpecification['expr']; - $type = $typeSpecification['type']; - $originalExprType = $this->getType($expr); - if ($typeSpecification['sure']) { - $scope = $scope->specifyExpressionType($expr, $specifiedTypes->shouldOverwrite() ? $type : TypeCombinator::intersect($type, $originalExprType)); - - if ($expr instanceof Variable && is_string($expr->name)) { - $scope->nativeExpressionTypes[sprintf('$%s', $expr->name)] = $specifiedTypes->shouldOverwrite() ? $type : TypeCombinator::intersect($type, $this->getNativeType($expr)); - } - } else { - $scope = $scope->removeTypeFromExpression($expr, $type); - } - - if ( - !$expr instanceof Variable - || !is_string($expr->name) - || $specifiedTypes->shouldOverwrite() - ) { - $match = \Nette\Utils\Strings::match((string) $typeSpecification['exprString'], '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#'); - if ($match !== null) { - $skipVariables[$match[1]] = true; - } - continue; - } - - if ($scope->hasVariableType($expr->name)->no()) { - continue; - } - - $saveConditionalVariables[$expr->name] = $scope->getVariableType($expr->name); - } - - foreach ($saveConditionalVariables as $variableName => $typeGuard) { - if (array_key_exists($variableName, $skipVariables)) { - continue; - } - - $typeGuards['$' . $variableName] = $typeGuard; - } - - $newConditionalExpressions = $specifiedTypes->getNewConditionalExpressionHolders(); - foreach ($this->conditionalExpressions as $variableExprString => $conditionalExpressions) { - if (array_key_exists($variableExprString, $typeGuards)) { - continue; - } - - $typeHolder = null; - - $variableName = substr($variableExprString, 1); - foreach ($conditionalExpressions as $conditionalExpression) { - $matchingConditions = []; - foreach ($conditionalExpression->getConditionExpressionTypes() as $conditionExprString => $conditionalType) { - if (!array_key_exists($conditionExprString, $typeGuards)) { - continue; - } - - if (!$typeGuards[$conditionExprString]->equals($conditionalType)) { - continue 2; - } - - $matchingConditions[$conditionExprString] = $conditionalType; - } - - if (count($matchingConditions) === 0) { - $newConditionalExpressions[$variableExprString][$conditionalExpression->getKey()] = $conditionalExpression; - continue; - } - - if (count($matchingConditions) < count($conditionalExpression->getConditionExpressionTypes())) { - $filteredConditions = $conditionalExpression->getConditionExpressionTypes(); - foreach (array_keys($matchingConditions) as $conditionExprString) { - unset($filteredConditions[$conditionExprString]); - } - - $holder = new ConditionalExpressionHolder($filteredConditions, $conditionalExpression->getTypeHolder()); - $newConditionalExpressions[$variableExprString][$holder->getKey()] = $holder; - continue; - } - - $typeHolder = $conditionalExpression->getTypeHolder(); - break; - } - - if ($typeHolder === null) { - continue; - } - - if ($typeHolder->getCertainty()->no()) { - unset($scope->variableTypes[$variableName]); - } else { - $scope->variableTypes[$variableName] = $typeHolder; - } - } - - return $scope->changeConditionalExpressions($newConditionalExpressions); - } - - /** - * @param array $newConditionalExpressionHolders - * @return self - */ - public function changeConditionalExpressions(array $newConditionalExpressionHolders): self - { - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->variableTypes, - $this->moreSpecificTypes, - $newConditionalExpressionHolders, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - /** - * @param string $exprString - * @param ConditionalExpressionHolder[] $conditionalExpressionHolders - * @return self - */ - public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): self - { - $conditionalExpressions = $this->conditionalExpressions; - $conditionalExpressions[$exprString] = $conditionalExpressionHolders; - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->variableTypes, - $this->moreSpecificTypes, - $conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function exitFirstLevelStatements(): self - { - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - false, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function isInFirstLevelStatement(): bool - { - return $this->inFirstLevelStatement; - } - - /** - * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod - * @param Type[] $types - * @return self - */ - private function addMoreSpecificTypes(array $types): self - { - $moreSpecificTypeHolders = $this->moreSpecificTypes; - foreach ($types as $exprString => $type) { - $moreSpecificTypeHolders[$exprString] = VariableTypeHolder::createYes($type); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $this->getVariableTypes(), - $moreSpecificTypeHolders, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->nativeExpressionTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function mergeWith(?self $otherScope): self - { - if ($otherScope === null) { - return $this; - } - - $variableHolderToType = static function (VariableTypeHolder $holder): Type { - return $holder->getType(); - }; - $typeToVariableHolder = static function (Type $type): VariableTypeHolder { - return new VariableTypeHolder($type, TrinaryLogic::createYes()); - }; - - $filterVariableHolders = static function (VariableTypeHolder $holder): bool { - return $holder->getCertainty()->yes(); - }; - - $ourVariableTypes = $this->getVariableTypes(); - $theirVariableTypes = $otherScope->getVariableTypes(); - if ($this->canAnyVariableExist()) { - foreach (array_keys($theirVariableTypes) as $name) { - if (array_key_exists($name, $ourVariableTypes)) { - continue; - } - - $ourVariableTypes[$name] = VariableTypeHolder::createMaybe(new MixedType()); - } - - foreach (array_keys($ourVariableTypes) as $name) { - if (array_key_exists($name, $theirVariableTypes)) { - continue; - } - - $theirVariableTypes[$name] = VariableTypeHolder::createMaybe(new MixedType()); - } - } - - $mergedVariableHolders = $this->mergeVariableHolders($ourVariableTypes, $theirVariableTypes); - $conditionalExpressions = $this->intersectConditionalExpressions($otherScope->conditionalExpressions); - $conditionalExpressions = $this->createConditionalExpressions( - $conditionalExpressions, - $ourVariableTypes, - $theirVariableTypes, - $mergedVariableHolders - ); - $conditionalExpressions = $this->createConditionalExpressions( - $conditionalExpressions, - $theirVariableTypes, - $ourVariableTypes, - $mergedVariableHolders - ); - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - array_map($variableHolderToType, array_filter($this->mergeVariableHolders( - array_map($typeToVariableHolder, $this->constantTypes), - array_map($typeToVariableHolder, $otherScope->constantTypes) - ), $filterVariableHolders)), - $this->getFunction(), - $this->getNamespace(), - $mergedVariableHolders, - $this->mergeVariableHolders($this->moreSpecificTypes, $otherScope->moreSpecificTypes), - $conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - array_map($variableHolderToType, array_filter($this->mergeVariableHolders( - array_map($typeToVariableHolder, $this->nativeExpressionTypes), - array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) - ), $filterVariableHolders)), - [], - $this->afterExtractCall && $otherScope->afterExtractCall, - $this->parentScope - ); - } - - /** - * @param array $otherConditionalExpressions - * @return array - */ - private function intersectConditionalExpressions(array $otherConditionalExpressions): array - { - $newConditionalExpressions = []; - foreach ($this->conditionalExpressions as $exprString => $holders) { - if (!array_key_exists($exprString, $otherConditionalExpressions)) { - continue; - } - - $otherHolders = $otherConditionalExpressions[$exprString]; - foreach (array_keys($holders) as $key) { - if (!array_key_exists($key, $otherHolders)) { - continue 2; - } - } - - $newConditionalExpressions[$exprString] = $holders; - } - - return $newConditionalExpressions; - } - - /** - * @param array $conditionalExpressions - * @param array $variableTypes - * @param array $theirVariableTypes - * @param array $mergedVariableHolders - * @return array - */ - private function createConditionalExpressions( - array $conditionalExpressions, - array $variableTypes, - array $theirVariableTypes, - array $mergedVariableHolders - ): array - { - $newVariableTypes = $variableTypes; - foreach ($theirVariableTypes as $name => $holder) { - if (!array_key_exists($name, $mergedVariableHolders)) { - continue; - } - - if (!$mergedVariableHolders[$name]->getType()->equals($holder->getType())) { - continue; - } - - unset($newVariableTypes[$name]); - } - - $typeGuards = []; - foreach ($newVariableTypes as $name => $holder) { - if (!$holder->getCertainty()->yes()) { - continue; - } - if (!array_key_exists($name, $mergedVariableHolders)) { - continue; - } - if ($mergedVariableHolders[$name]->getType()->equals($holder->getType())) { - continue; - } - - $typeGuards['$' . $name] = $holder->getType(); - } - - if (count($typeGuards) === 0) { - return $conditionalExpressions; - } - - foreach ($newVariableTypes as $name => $holder) { - if ( - array_key_exists($name, $mergedVariableHolders) - && $mergedVariableHolders[$name]->equals($holder) - ) { - continue; - } - - $exprString = '$' . $name; - $variableTypeGuards = $typeGuards; - unset($variableTypeGuards[$exprString]); - - if (count($variableTypeGuards) === 0) { - continue; - } - - $conditionalExpression = new ConditionalExpressionHolder($variableTypeGuards, $holder); - $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression; - } - - foreach (array_keys($mergedVariableHolders) as $name) { - if (array_key_exists($name, $variableTypes)) { - continue; - } - - $conditionalExpression = new ConditionalExpressionHolder($typeGuards, new VariableTypeHolder(new ErrorType(), TrinaryLogic::createNo())); - $conditionalExpressions['$' . $name][$conditionalExpression->getKey()] = $conditionalExpression; - } - - return $conditionalExpressions; - } - - /** - * @param VariableTypeHolder[] $ourVariableTypeHolders - * @param VariableTypeHolder[] $theirVariableTypeHolders - * @return VariableTypeHolder[] - */ - private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array - { - $intersectedVariableTypeHolders = []; - foreach ($ourVariableTypeHolders as $name => $variableTypeHolder) { - if (isset($theirVariableTypeHolders[$name])) { - $intersectedVariableTypeHolders[$name] = $variableTypeHolder->and($theirVariableTypeHolders[$name]); - } else { - $intersectedVariableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); - } - } - - foreach ($theirVariableTypeHolders as $name => $variableTypeHolder) { - if (isset($intersectedVariableTypeHolders[$name])) { - continue; - } - - $intersectedVariableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); - } - - return $intersectedVariableTypeHolders; - } - - public function processFinallyScope(self $finallyScope, self $originalFinallyScope): self - { - $variableHolderToType = static function (VariableTypeHolder $holder): Type { - return $holder->getType(); - }; - $typeToVariableHolder = static function (Type $type): VariableTypeHolder { - return new VariableTypeHolder($type, TrinaryLogic::createYes()); - }; - $filterVariableHolders = static function (VariableTypeHolder $holder): bool { - return $holder->getCertainty()->yes(); - }; - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - array_map($variableHolderToType, array_filter($this->processFinallyScopeVariableTypeHolders( - array_map($typeToVariableHolder, $this->constantTypes), - array_map($typeToVariableHolder, $finallyScope->constantTypes), - array_map($typeToVariableHolder, $originalFinallyScope->constantTypes) - ), $filterVariableHolders)), - $this->getFunction(), - $this->getNamespace(), - $this->processFinallyScopeVariableTypeHolders( - $this->getVariableTypes(), - $finallyScope->getVariableTypes(), - $originalFinallyScope->getVariableTypes() - ), - $this->processFinallyScopeVariableTypeHolders( - $this->moreSpecificTypes, - $finallyScope->moreSpecificTypes, - $originalFinallyScope->moreSpecificTypes - ), - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - array_map($variableHolderToType, array_filter($this->processFinallyScopeVariableTypeHolders( - array_map($typeToVariableHolder, $this->nativeExpressionTypes), - array_map($typeToVariableHolder, $finallyScope->nativeExpressionTypes), - array_map($typeToVariableHolder, $originalFinallyScope->nativeExpressionTypes) - ), $filterVariableHolders)), - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - /** - * @param VariableTypeHolder[] $ourVariableTypeHolders - * @param VariableTypeHolder[] $finallyVariableTypeHolders - * @param VariableTypeHolder[] $originalVariableTypeHolders - * @return VariableTypeHolder[] - */ - private function processFinallyScopeVariableTypeHolders( - array $ourVariableTypeHolders, - array $finallyVariableTypeHolders, - array $originalVariableTypeHolders - ): array - { - foreach ($finallyVariableTypeHolders as $name => $variableTypeHolder) { - if ( - isset($originalVariableTypeHolders[$name]) - && !$originalVariableTypeHolders[$name]->getType()->equals($variableTypeHolder->getType()) - ) { - $ourVariableTypeHolders[$name] = $variableTypeHolder; - continue; - } - - if (isset($originalVariableTypeHolders[$name])) { - continue; - } - - $ourVariableTypeHolders[$name] = $variableTypeHolder; - } - - return $ourVariableTypeHolders; - } - - /** - * @param self $closureScope - * @param self|null $prevScope - * @param Expr\ClosureUse[] $byRefUses - * @return self - */ - public function processClosureScope( - self $closureScope, - ?self $prevScope, - array $byRefUses - ): self - { - $nativeExpressionTypes = $this->nativeExpressionTypes; - $variableTypes = $this->variableTypes; - if (count($byRefUses) === 0) { - return $this; - } - - foreach ($byRefUses as $use) { - if (!is_string($use->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableName = $use->var->name; - - if (!$closureScope->hasVariableType($variableName)->yes()) { - $variableTypes[$variableName] = VariableTypeHolder::createYes(new NullType()); - $nativeExpressionTypes[sprintf('$%s', $variableName)] = new NullType(); - continue; - } - - $variableType = $closureScope->getVariableType($variableName); - - if ($prevScope !== null) { - $prevVariableType = $prevScope->getVariableType($variableName); - if (!$variableType->equals($prevVariableType)) { - $variableType = TypeCombinator::union($variableType, $prevVariableType); - $variableType = self::generalizeType($variableType, $prevVariableType); - } - } - - $variableTypes[$variableName] = VariableTypeHolder::createYes($variableType); - $nativeExpressionTypes[sprintf('$%s', $variableName)] = $variableType; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypes, - $this->moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - $nativeExpressionTypes, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope - ); - } - - public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope): self - { - $variableTypeHolders = $this->variableTypes; - $nativeTypes = $this->nativeExpressionTypes; - foreach ($finalScope->variableTypes as $name => $variableTypeHolder) { - $nativeTypes[sprintf('$%s', $name)] = $variableTypeHolder->getType(); - if (!isset($variableTypeHolders[$name])) { - $variableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); - continue; - } - - $variableTypeHolders[$name] = new VariableTypeHolder( - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($variableTypeHolders[$name]->getCertainty()) - ); - } - - $moreSpecificTypes = $this->moreSpecificTypes; - foreach ($finalScope->moreSpecificTypes as $exprString => $variableTypeHolder) { - if (!isset($moreSpecificTypes[$exprString])) { - $moreSpecificTypes[$exprString] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); - continue; - } - - $moreSpecificTypes[$exprString] = new VariableTypeHolder( - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($moreSpecificTypes[$exprString]->getCertainty()) - ); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->constantTypes, - $this->getFunction(), - $this->getNamespace(), - $variableTypeHolders, - $moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - $nativeTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - public function generalizeWith(self $otherScope): self - { - $variableTypeHolders = $this->generalizeVariableTypeHolders( - $this->getVariableTypes(), - $otherScope->getVariableTypes() - ); - - $moreSpecificTypes = $this->generalizeVariableTypeHolders( - $this->moreSpecificTypes, - $otherScope->moreSpecificTypes - ); - - $variableHolderToType = static function (VariableTypeHolder $holder): Type { - return $holder->getType(); - }; - $typeToVariableHolder = static function (Type $type): VariableTypeHolder { - return new VariableTypeHolder($type, TrinaryLogic::createYes()); - }; - $filterVariableHolders = static function (VariableTypeHolder $holder): bool { - return $holder->getCertainty()->yes(); - }; - $nativeTypes = array_map($variableHolderToType, array_filter($this->generalizeVariableTypeHolders( - array_map($typeToVariableHolder, $this->nativeExpressionTypes), - array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) - ), $filterVariableHolders)); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - array_map($variableHolderToType, array_filter($this->generalizeVariableTypeHolders( - array_map($typeToVariableHolder, $this->constantTypes), - array_map($typeToVariableHolder, $otherScope->constantTypes) - ), $filterVariableHolders)), - $this->getFunction(), - $this->getNamespace(), - $variableTypeHolders, - $moreSpecificTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClass, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - $nativeTypes, - [], - $this->afterExtractCall, - $this->parentScope - ); - } - - /** - * @param VariableTypeHolder[] $variableTypeHolders - * @param VariableTypeHolder[] $otherVariableTypeHolders - * @return VariableTypeHolder[] - */ - private function generalizeVariableTypeHolders( - array $variableTypeHolders, - array $otherVariableTypeHolders - ): array - { - foreach ($variableTypeHolders as $name => $variableTypeHolder) { - if (!isset($otherVariableTypeHolders[$name])) { - continue; - } - - $variableTypeHolders[$name] = new VariableTypeHolder( - self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$name]->getType()), - $variableTypeHolder->getCertainty() - ); - } - - return $variableTypeHolders; - } - - private static function generalizeType(Type $a, Type $b): Type - { - if ($a->equals($b)) { - return $a; - } - - $constantIntegers = ['a' => [], 'b' => []]; - $constantFloats = ['a' => [], 'b' => []]; - $constantBooleans = ['a' => [], 'b' => []]; - $constantStrings = ['a' => [], 'b' => []]; - $constantArrays = ['a' => [], 'b' => []]; - $generalArrays = ['a' => [], 'b' => []]; - $otherTypes = []; - - foreach ([ - 'a' => TypeUtils::flattenTypes($a), - 'b' => TypeUtils::flattenTypes($b), - ] as $key => $types) { - foreach ($types as $type) { - if ($type instanceof ConstantIntegerType) { - $constantIntegers[$key][] = $type; - continue; - } - if ($type instanceof ConstantFloatType) { - $constantFloats[$key][] = $type; - continue; - } - if ($type instanceof ConstantBooleanType) { - $constantBooleans[$key][] = $type; - continue; - } - if ($type instanceof ConstantStringType) { - $constantStrings[$key][] = $type; - continue; - } - if ($type instanceof ConstantArrayType) { - $constantArrays[$key][] = $type; - continue; - } - if ($type->isArray()->yes()) { - $generalArrays[$key][] = $type; - continue; - } - - $otherTypes[] = $type; - } - } - - $resultTypes = []; - foreach ([ - $constantIntegers, - $constantFloats, - $constantBooleans, - $constantStrings, - ] as $constantTypes) { - if (count($constantTypes['a']) === 0) { - continue; - } - if (count($constantTypes['b']) === 0) { - $resultTypes[] = TypeCombinator::union(...$constantTypes['a']); - continue; - } - - $aTypes = TypeCombinator::union(...$constantTypes['a']); - $bTypes = TypeCombinator::union(...$constantTypes['b']); - if ($aTypes->equals($bTypes)) { - $resultTypes[] = $aTypes; - continue; - } - - $resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0]); - } - - if (count($constantArrays['a']) > 0) { - if (count($constantArrays['b']) === 0) { - $resultTypes[] = TypeCombinator::union(...$constantArrays['a']); - } else { - $constantArraysA = TypeCombinator::union(...$constantArrays['a']); - $constantArraysB = TypeCombinator::union(...$constantArrays['b']); - if ($constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType())) { - $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { - $resultArrayBuilder->setOffsetValueType( - $keyType, - self::generalizeType( - $constantArraysA->getOffsetValueType($keyType), - $constantArraysB->getOffsetValueType($keyType) - ) - ); - } - - $resultTypes[] = $resultArrayBuilder->getArray(); - } else { - $resultTypes[] = new ArrayType( - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType())), - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType())) - ); - } - } - } - - if (count($generalArrays['a']) > 0) { - if (count($generalArrays['b']) === 0) { - $resultTypes[] = TypeCombinator::union(...$generalArrays['a']); - } else { - $generalArraysA = TypeCombinator::union(...$generalArrays['a']); - $generalArraysB = TypeCombinator::union(...$generalArrays['b']); - - $aValueType = $generalArraysA->getIterableValueType(); - $bValueType = $generalArraysB->getIterableValueType(); - $aArrays = TypeUtils::getAnyArrays($aValueType); - $bArrays = TypeUtils::getAnyArrays($bValueType); - if ( - count($aArrays) === 1 - && !$aArrays[0] instanceof ConstantArrayType - && count($bArrays) === 1 - && !$bArrays[0] instanceof ConstantArrayType - ) { - $aDepth = self::getArrayDepth($aArrays[0]); - $bDepth = self::getArrayDepth($bArrays[0]); - if ( - ($aDepth > 2 || $bDepth > 2) - && abs($aDepth - $bDepth) > 0 - ) { - $aValueType = new MixedType(); - $bValueType = new MixedType(); - } - } - - $resultTypes[] = new ArrayType( - TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType())), - TypeCombinator::union(self::generalizeType($aValueType, $bValueType)) - ); - } - } - - return TypeCombinator::union(...$resultTypes, ...$otherTypes); - } - - private static function getArrayDepth(ArrayType $type): int - { - $depth = 0; - while ($type instanceof ArrayType) { - $temp = $type->getIterableValueType(); - $arrays = TypeUtils::getAnyArrays($temp); - if (count($arrays) === 1) { - $type = $arrays[0]; - } else { - $type = $temp; - } - $depth++; - } - - return $depth; - } - - public function equals(self $otherScope): bool - { - if (!$this->context->equals($otherScope->context)) { - return false; - } - - if (!$this->compareVariableTypeHolders($this->variableTypes, $otherScope->variableTypes)) { - return false; - } - - if (!$this->compareVariableTypeHolders($this->moreSpecificTypes, $otherScope->moreSpecificTypes)) { - return false; - } - - $typeToVariableHolder = static function (Type $type): VariableTypeHolder { - return new VariableTypeHolder($type, TrinaryLogic::createYes()); - }; - - $nativeExpressionTypesResult = $this->compareVariableTypeHolders( - array_map($typeToVariableHolder, $this->nativeExpressionTypes), - array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) - ); - - if (!$nativeExpressionTypesResult) { - return false; - } - - return $this->compareVariableTypeHolders( - array_map($typeToVariableHolder, $this->constantTypes), - array_map($typeToVariableHolder, $otherScope->constantTypes) - ); - } - - /** - * @param VariableTypeHolder[] $variableTypeHolders - * @param VariableTypeHolder[] $otherVariableTypeHolders - * @return bool - */ - private function compareVariableTypeHolders(array $variableTypeHolders, array $otherVariableTypeHolders): bool - { - if (count($variableTypeHolders) !== count($otherVariableTypeHolders)) { - return false; - } - foreach ($variableTypeHolders as $name => $variableTypeHolder) { - if (!isset($otherVariableTypeHolders[$name])) { - return false; - } - - if (!$variableTypeHolder->getCertainty()->equals($otherVariableTypeHolders[$name]->getCertainty())) { - return false; - } - - if (!$variableTypeHolder->getType()->equals($otherVariableTypeHolders[$name]->getType())) { - return false; - } - - unset($otherVariableTypeHolders[$name]); - } - - return true; - } - - public function canAccessProperty(PropertyReflection $propertyReflection): bool - { - return $this->canAccessClassMember($propertyReflection); - } - - public function canCallMethod(MethodReflection $methodReflection): bool - { - if ($this->canAccessClassMember($methodReflection)) { - return true; - } - - return $this->canAccessClassMember($methodReflection->getPrototype()); - } - - public function canAccessConstant(ConstantReflection $constantReflection): bool - { - return $this->canAccessClassMember($constantReflection); - } - - private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool - { - if ($classMemberReflection->isPublic()) { - return true; - } - - if ($this->inClosureBindScopeClass !== null && $this->reflectionProvider->hasClass($this->inClosureBindScopeClass)) { - $currentClassReflection = $this->reflectionProvider->getClass($this->inClosureBindScopeClass); - } elseif ($this->isInClass()) { - $currentClassReflection = $this->getClassReflection(); - } else { - return false; - } - - $classReflectionName = $classMemberReflection->getDeclaringClass()->getName(); - if ($classMemberReflection->isPrivate()) { - return $currentClassReflection->getName() === $classReflectionName; - } - - // protected - - if ( - $currentClassReflection->getName() === $classReflectionName - || $currentClassReflection->isSubclassOf($classReflectionName) - ) { - return true; - } - - return $classMemberReflection->getDeclaringClass()->isSubclassOf($currentClassReflection->getName()); - } - - /** - * @return string[] - */ - public function debug(): array - { - $descriptions = []; - foreach ($this->getVariableTypes() as $name => $variableTypeHolder) { - $key = sprintf('$%s (%s)', $name, $variableTypeHolder->getCertainty()->describe()); - $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise()); - } - foreach ($this->moreSpecificTypes as $exprString => $typeHolder) { - $key = sprintf( - '%s-specified (%s)', - $exprString, - $typeHolder->getCertainty()->describe() - ); - $descriptions[$key] = $typeHolder->getType()->describe(VerbosityLevel::precise()); - } - foreach ($this->constantTypes as $name => $type) { - $key = sprintf('const %s', $name); - $descriptions[$key] = $type->describe(VerbosityLevel::precise()); - } - foreach ($this->nativeExpressionTypes as $exprString => $nativeType) { - $key = sprintf('native %s', $exprString); - $descriptions[$key] = $nativeType->describe(VerbosityLevel::precise()); - } - - return $descriptions; - } - - private function exactInstantiation(New_ $node, string $className): ?Type - { - $resolvedClassName = $this->resolveExactName(new Name($className)); - if ($resolvedClassName === null) { - return null; - } - - if (!$this->reflectionProvider->hasClass($resolvedClassName)) { - return null; - } - - $classReflection = $this->reflectionProvider->getClass($resolvedClassName); - if ($classReflection->hasConstructor()) { - $constructorMethod = $classReflection->getConstructor(); - } else { - $constructorMethod = new DummyConstructorReflection($classReflection); - } - - $resolvedTypes = []; - $methodCall = new Expr\StaticCall( - new Name($resolvedClassName), - new Node\Identifier($constructorMethod->getName()), - $node->args - ); - - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) { - continue; - } - - $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($constructorMethod, $methodCall, $this); - } - - if (count($resolvedTypes) > 0) { - return TypeCombinator::union(...$resolvedTypes); - } - - $methodResult = $this->getType($methodCall); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return $methodResult; - } - - $objectType = new ObjectType($resolvedClassName); - if (!$classReflection->isGeneric()) { - return $objectType; - } - - $parentNode = $node->getAttribute('parent'); - if ( - ( - $parentNode instanceof Expr\Assign - || $parentNode instanceof Expr\AssignRef - ) - && $parentNode->var instanceof PropertyFetch - ) { - $constructorVariant = ParametersAcceptorSelector::selectSingle($constructorMethod->getVariants()); - $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); - $originalClassTemplateTypes = $classTemplateTypes; - foreach ($constructorVariant->getParameters() as $parameter) { - TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type { - if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) { - $classTemplateType = $classTemplateTypes[$type->getName()]; - if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) { - unset($classTemplateTypes[$type->getName()]); - } - return $type; - } - - return $traverse($type); - }); - } - - if (count($classTemplateTypes) === count($originalClassTemplateTypes)) { - $propertyType = $this->getType($parentNode->var); - if ($objectType->isSuperTypeOf($propertyType)->yes()) { - return $propertyType; - } - } - } - - if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) { - return new GenericObjectType( - $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()) - ); - } - - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $this, - $methodCall->args, - $constructorMethod->getVariants() - ); - - return new GenericObjectType( - $resolvedClassName, - $classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()) - ); - } - - private function getTypeToInstantiateForNew(Type $type): Type - { - $decideType = static function (Type $type): ?Type { - if ($type instanceof ConstantStringType) { - return new ObjectType($type->getValue()); - } - if ($type instanceof TypeWithClassName) { - return $type; - } - if ($type instanceof GenericClassStringType) { - return $type->getGenericType(); - } - return null; - }; - - if ($type instanceof UnionType) { - $types = []; - foreach ($type->getTypes() as $innerType) { - $decidedType = $decideType($innerType); - if ($decidedType === null) { - if ($this->objectFromNewClass) { - return new ObjectWithoutClassType(); - } - - return new MixedType(false, new StringType()); - } - - $types[] = $decidedType; - } - - return TypeCombinator::union(...$types); - } - - $decidedType = $decideType($type); - if ($decidedType === null) { - if ($this->objectFromNewClass) { - return new ObjectWithoutClassType(); - } - - return new MixedType(false, new StringType()); - } - - return $decidedType; - } - - public function getMethodReflection(Type $typeWithMethod, string $methodName): ?MethodReflection - { - if ($typeWithMethod instanceof UnionType) { - $newTypes = []; - foreach ($typeWithMethod->getTypes() as $innerType) { - if (!$innerType->hasMethod($methodName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithMethod = TypeCombinator::union(...$newTypes); - } - - if (!$typeWithMethod->hasMethod($methodName)->yes()) { - return null; - } - - return $typeWithMethod->getMethod($methodName, $this); - } - - /** - * @param \PHPStan\Type\Type $typeWithMethod - * @param string $methodName - * @param MethodCall|\PhpParser\Node\Expr\StaticCall $methodCall - * @return \PHPStan\Type\Type|null - */ - private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type - { - $methodReflection = $this->getMethodReflection($typeWithMethod, $methodName); - if ($methodReflection === null) { - return null; - } - - $resolvedTypes = []; - foreach (TypeUtils::getDirectClassNames($typeWithMethod) as $className) { - if ($methodCall instanceof MethodCall) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) { - if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) { - continue; - } - - $resolvedTypes[] = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $methodCall, $this); - } - } else { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) { - continue; - } - - $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($methodReflection, $methodCall, $this); - } - } - } - - if (count($resolvedTypes) > 0) { - return TypeCombinator::union(...$resolvedTypes); - } - - return ParametersAcceptorSelector::selectFromArgs( - $this, - $methodCall->args, - $methodReflection->getVariants() - )->getReturnType(); - } - - public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection - { - if ($typeWithProperty instanceof UnionType) { - $newTypes = []; - foreach ($typeWithProperty->getTypes() as $innerType) { - if (!$innerType->hasProperty($propertyName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithProperty = TypeCombinator::union(...$newTypes); - } - if (!$typeWithProperty->hasProperty($propertyName)->yes()) { - return null; - } - - return $typeWithProperty->getProperty($propertyName, $this); - } - - /** - * @param \PHPStan\Type\Type $fetchedOnType - * @param string $propertyName - * @param PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch - * @return \PHPStan\Type\Type|null - */ - private function propertyFetchType(Type $fetchedOnType, string $propertyName, Expr $propertyFetch): ?Type - { - $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName); - if ($propertyReflection === null) { - return null; - } - - if ($this->isInExpressionAssign($propertyFetch)) { - return $propertyReflection->getWritableType(); - } - - return $propertyReflection->getReadableType(); - } + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + private Parser $parser; + + private NodeScopeResolver $nodeScopeResolver; + + private \PHPStan\Analyser\ScopeContext $context; + + /** @var \PHPStan\Type\Type[] */ + private array $resolvedTypes = []; + + private bool $declareStrictTypes; + + /** @var array */ + private array $constantTypes; + + /** @var \PHPStan\Reflection\FunctionReflection|MethodReflection|null */ + private $function; + + private ?string $namespace; + + /** @var \PHPStan\Analyser\VariableTypeHolder[] */ + private array $variableTypes; + + /** @var \PHPStan\Analyser\VariableTypeHolder[] */ + private array $moreSpecificTypes; + + /** @var array */ + private array $conditionalExpressions; + + private ?string $inClosureBindScopeClass; + + private ?ParametersAcceptor $anonymousFunctionReflection; + + private bool $inFirstLevelStatement; + + /** @var array */ + private array $currentlyAssignedExpressions; + + /** @var array */ + private array $inFunctionCallsStack; + + /** @var array */ + private array $nativeExpressionTypes; + + /** @var string[] */ + private array $dynamicConstantNames; + + private bool $treatPhpDocTypesAsCertain; + + private bool $objectFromNewClass; + + private bool $afterExtractCall; + + private ?Scope $parentScope; + + /** + * @param \PHPStan\Analyser\ScopeFactory $scopeFactory + * @param ReflectionProvider $reflectionProvider + * @param \PHPStan\Type\DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry + * @param \PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry + * @param \PhpParser\PrettyPrinter\Standard $printer + * @param \PHPStan\Analyser\TypeSpecifier $typeSpecifier + * @param \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder + * @param Parser $parser + * @param NodeScopeResolver $nodeScopeResolver + * @param \PHPStan\Analyser\ScopeContext $context + * @param bool $declareStrictTypes + * @param array $constantTypes + * @param \PHPStan\Reflection\FunctionReflection|MethodReflection|null $function + * @param string|null $namespace + * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes + * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes + * @param array $conditionalExpressions + * @param string|null $inClosureBindScopeClass + * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection + * @param bool $inFirstLevelStatement + * @param array $currentlyAssignedExpressions + * @param array $nativeExpressionTypes + * @param array $inFunctionCallsStack + * @param string[] $dynamicConstantNames + * @param bool $treatPhpDocTypesAsCertain + * @param bool $objectFromNewClass + * @param bool $afterExtractCall + * @param Scope|null $parentScope + */ + public function __construct( + ScopeFactory $scopeFactory, + ReflectionProvider $reflectionProvider, + DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry, + OperatorTypeSpecifyingExtensionRegistry $operatorTypeSpecifyingExtensionRegistry, + \PhpParser\PrettyPrinter\Standard $printer, + TypeSpecifier $typeSpecifier, + PropertyReflectionFinder $propertyReflectionFinder, + Parser $parser, + NodeScopeResolver $nodeScopeResolver, + ScopeContext $context, + bool $declareStrictTypes = false, + array $constantTypes = [], + $function = null, + ?string $namespace = null, + array $variablesTypes = [], + array $moreSpecificTypes = [], + array $conditionalExpressions = [], + ?string $inClosureBindScopeClass = null, + ?ParametersAcceptor $anonymousFunctionReflection = null, + bool $inFirstLevelStatement = true, + array $currentlyAssignedExpressions = [], + array $nativeExpressionTypes = [], + array $inFunctionCallsStack = [], + array $dynamicConstantNames = [], + bool $treatPhpDocTypesAsCertain = true, + bool $objectFromNewClass = false, + bool $afterExtractCall = false, + ?Scope $parentScope = null + ) { + if ($namespace === '') { + $namespace = null; + } + + $this->scopeFactory = $scopeFactory; + $this->reflectionProvider = $reflectionProvider; + $this->dynamicReturnTypeExtensionRegistry = $dynamicReturnTypeExtensionRegistry; + $this->operatorTypeSpecifyingExtensionRegistry = $operatorTypeSpecifyingExtensionRegistry; + $this->printer = $printer; + $this->typeSpecifier = $typeSpecifier; + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->parser = $parser; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->context = $context; + $this->declareStrictTypes = $declareStrictTypes; + $this->constantTypes = $constantTypes; + $this->function = $function; + $this->namespace = $namespace; + $this->variableTypes = $variablesTypes; + $this->moreSpecificTypes = $moreSpecificTypes; + $this->conditionalExpressions = $conditionalExpressions; + $this->inClosureBindScopeClass = $inClosureBindScopeClass; + $this->anonymousFunctionReflection = $anonymousFunctionReflection; + $this->inFirstLevelStatement = $inFirstLevelStatement; + $this->currentlyAssignedExpressions = $currentlyAssignedExpressions; + $this->nativeExpressionTypes = $nativeExpressionTypes; + $this->inFunctionCallsStack = $inFunctionCallsStack; + $this->dynamicConstantNames = $dynamicConstantNames; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->objectFromNewClass = $objectFromNewClass; + $this->afterExtractCall = $afterExtractCall; + $this->parentScope = $parentScope; + } + + public function getFile(): string + { + return $this->context->getFile(); + } + + public function getFileDescription(): string + { + if ($this->context->getTraitReflection() === null) { + return $this->getFile(); + } + + /** @var ClassReflection $classReflection */ + $classReflection = $this->context->getClassReflection(); + + $className = sprintf('class %s', $classReflection->getDisplayName()); + if ($classReflection->isAnonymous()) { + $className = 'anonymous class'; + } + + $traitReflection = $this->context->getTraitReflection(); + if ($traitReflection->getFileName() === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return sprintf( + '%s (in context of %s)', + $traitReflection->getFileName(), + $className + ); + } + + public function isDeclareStrictTypes(): bool + { + return $this->declareStrictTypes; + } + + public function enterDeclareStrictTypes(): self + { + return $this->scopeFactory->create( + $this->context, + true, + [], + null, + null, + $this->variableTypes + ); + } + + public function isInClass(): bool + { + return $this->context->getClassReflection() !== null; + } + + public function isInTrait(): bool + { + return $this->context->getTraitReflection() !== null; + } + + public function getClassReflection(): ?ClassReflection + { + return $this->context->getClassReflection(); + } + + public function getTraitReflection(): ?ClassReflection + { + return $this->context->getTraitReflection(); + } + + /** + * @return \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null + */ + public function getFunction() + { + return $this->function; + } + + public function getFunctionName(): ?string + { + return $this->function !== null ? $this->function->getName() : null; + } + + public function getNamespace(): ?string + { + return $this->namespace; + } + + public function getParentScope(): ?Scope + { + return $this->parentScope; + } + + /** + * @return array + */ + private function getVariableTypes(): array + { + return $this->variableTypes; + } + + public function canAnyVariableExist(): bool + { + return ($this->function === null && !$this->isInAnonymousFunction()) || $this->afterExtractCall; + } + + public function afterExtractCall(): self + { + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + [], + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + true, + $this->parentScope + ); + } + + public function afterClearstatcacheCall(): self + { + $moreSpecificTypes = $this->moreSpecificTypes; + foreach (array_keys($moreSpecificTypes) as $exprString) { + // list from https://www.php.net/manual/en/function.clearstatcache.php + + // stat(), lstat(), file_exists(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), filectime(), fileatime(), filemtime(), fileinode(), filegroup(), fileowner(), filesize(), filetype(), and fileperms(). + foreach ([ + 'stat', + 'lstat', + 'file_exists', + 'is_writable', + 'is_readable', + 'is_executable', + 'is_file', + 'is_dir', + 'is_link', + 'filectime', + 'fileatime', + 'filemtime', + 'fileinode', + 'filegroup', + 'fileowner', + 'filesize', + 'filetype', + 'fileperms', + ] as $functionName) { + if (!Strings::startsWith((string) $exprString, $functionName . '(') && !Strings::startsWith((string) $exprString, '\\' . $functionName . '(')) { + continue; + } + + unset($moreSpecificTypes[$exprString]); + continue 2; + } + } + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function hasVariableType(string $variableName): TrinaryLogic + { + if ($this->isGlobalVariable($variableName)) { + return TrinaryLogic::createYes(); + } + + if (!isset($this->variableTypes[$variableName])) { + if ($this->canAnyVariableExist()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + return $this->variableTypes[$variableName]->getCertainty(); + } + + public function getVariableType(string $variableName): Type + { + if ($this->isGlobalVariable($variableName)) { + return new ArrayType(new StringType(), new MixedType()); + } + + if ($this->hasVariableType($variableName)->no()) { + throw new \PHPStan\Analyser\UndefinedVariableException($this, $variableName); + } + + if (!array_key_exists($variableName, $this->variableTypes)) { + return new MixedType(); + } + + return $this->variableTypes[$variableName]->getType(); + } + + /** + * @return array + */ + public function getDefinedVariables(): array + { + $variables = []; + foreach ($this->variableTypes as $variableName => $holder) { + if (!$holder->getCertainty()->yes()) { + continue; + } + + $variables[] = $variableName; + } + + return $variables; + } + + private function isGlobalVariable(string $variableName): bool + { + return in_array($variableName, [ + 'GLOBALS', + '_SERVER', + '_GET', + '_POST', + '_FILES', + '_COOKIE', + '_SESSION', + '_REQUEST', + '_ENV', + ], true); + } + + public function hasConstant(Name $name): bool + { + $isCompilerHaltOffset = $name->toString() === '__COMPILER_HALT_OFFSET__'; + if ($isCompilerHaltOffset) { + return $this->fileHasCompilerHaltStatementCalls(); + } + if ($name->isFullyQualified()) { + if (array_key_exists($name->toCodeString(), $this->constantTypes)) { + return true; + } + } + + if ($this->getNamespace() !== null) { + $constantName = new FullyQualified([$this->getNamespace(), $name->toString()]); + if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { + return true; + } + } + + $constantName = new FullyQualified($name->toString()); + if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { + return true; + } + + if (!$this->reflectionProvider->hasConstant($name, $this)) { + return false; + } + + $constantReflection = $this->reflectionProvider->getConstant($name, $this); + + return $constantReflection->getFileName() !== $this->getFile(); + } + + private function fileHasCompilerHaltStatementCalls(): bool + { + $nodes = $this->parser->parseFile($this->getFile()); + foreach ($nodes as $node) { + if ($node instanceof \PhpParser\Node\Stmt\HaltCompiler) { + return true; + } + } + + return false; + } + + public function isInAnonymousFunction(): bool + { + return $this->anonymousFunctionReflection !== null; + } + + public function getAnonymousFunctionReflection(): ?ParametersAcceptor + { + return $this->anonymousFunctionReflection; + } + + public function getAnonymousFunctionReturnType(): ?\PHPStan\Type\Type + { + if ($this->anonymousFunctionReflection === null) { + return null; + } + + return $this->anonymousFunctionReflection->getReturnType(); + } + + public function getType(Expr $node): Type + { + $key = $this->getNodeKey($node); + + if (!array_key_exists($key, $this->resolvedTypes)) { + $this->resolvedTypes[$key] = $this->resolveType($node); + } + return $this->resolvedTypes[$key]; + } + + private function getNodeKey(Expr $node): string + { + /** @var string|null $key */ + $key = $node->getAttribute('phpstan_cache_printer'); + if ($key === null) { + $key = $this->printer->prettyPrintExpr($node); + $node->setAttribute('phpstan_cache_printer', $key); + } + + return $key; + } + + private function resolveType(Expr $node): Type + { + if ($node instanceof Expr\Exit_ || $node instanceof Expr\Throw_) { + return new NeverType(true); + } + + if ($node instanceof Expr\BinaryOp\Smaller) { + return $this->getType($node->left)->isSmallerThan($this->getType($node->right))->toBooleanType(); + } + + if ($node instanceof Expr\BinaryOp\SmallerOrEqual) { + return $this->getType($node->left)->isSmallerThanOrEqual($this->getType($node->right))->toBooleanType(); + } + + if ($node instanceof Expr\BinaryOp\Greater) { + return $this->getType($node->right)->isSmallerThan($this->getType($node->left))->toBooleanType(); + } + + if ($node instanceof Expr\BinaryOp\GreaterOrEqual) { + return $this->getType($node->right)->isSmallerThanOrEqual($this->getType($node->left))->toBooleanType(); + } + + if ( + $node instanceof Expr\BinaryOp\Equal + || $node instanceof Expr\BinaryOp\NotEqual + || $node instanceof Expr\Empty_ + ) { + return new BooleanType(); + } + + if ($node instanceof Expr\Isset_) { + $result = new ConstantBooleanType(true); + foreach ($node->vars as $var) { + if ($var instanceof Expr\ArrayDimFetch && $var->dim !== null) { + $variableType = $this->getType($var->var); + $dimType = $this->getType($var->dim); + $hasOffset = $variableType->hasOffsetValueType($dimType); + $offsetValueType = $variableType->getOffsetValueType($dimType); + $offsetValueIsNotNull = (new NullType())->isSuperTypeOf($offsetValueType)->negate(); + $isset = $hasOffset->and($offsetValueIsNotNull)->toBooleanType(); + if ($isset instanceof ConstantBooleanType) { + if (!$isset->getValue()) { + return $isset; + } + + continue; + } + + $result = $isset; + continue; + } + + if ($var instanceof Expr\Variable && is_string($var->name)) { + $variableType = $this->getType($var); + $isNullSuperType = (new NullType())->isSuperTypeOf($variableType); + $has = $this->hasVariableType($var->name); + if ($has->no() || $isNullSuperType->yes()) { + return new ConstantBooleanType(false); + } + + if ($has->maybe() || !$isNullSuperType->no()) { + $result = new BooleanType(); + } + continue; + } + + return new BooleanType(); + } + + return $result; + } + + if ($node instanceof \PhpParser\Node\Expr\BooleanNot) { + if ($this->treatPhpDocTypesAsCertain) { + $exprBooleanType = $this->getType($node->expr)->toBoolean(); + } else { + $exprBooleanType = $this->getNativeType($node->expr)->toBoolean(); + } + if ($exprBooleanType instanceof ConstantBooleanType) { + return new ConstantBooleanType(!$exprBooleanType->getValue()); + } + + return new BooleanType(); + } + + if ($node instanceof \PhpParser\Node\Expr\BitwiseNot) { + $exprType = $this->getType($node->expr); + return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + if ($type instanceof ConstantStringType) { + return new ConstantStringType(~$type->getValue()); + } + if ($type instanceof StringType) { + return new StringType(); + } + if ($type instanceof IntegerType || $type instanceof FloatType) { + return new IntegerType(); //no const types here, result depends on PHP_INT_SIZE + } + return new ErrorType(); + }); + } + + if ( + $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanAnd + || $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalAnd + ) { + if ($this->treatPhpDocTypesAsCertain) { + $leftBooleanType = $this->getType($node->left)->toBoolean(); + } else { + $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); + } + + if ( + $leftBooleanType instanceof ConstantBooleanType + && !$leftBooleanType->getValue() + ) { + return new ConstantBooleanType(false); + } + + if ($this->treatPhpDocTypesAsCertain) { + $rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); + } else { + $rightBooleanType = $this->promoteNativeTypes()->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); + } + + if ( + $rightBooleanType instanceof ConstantBooleanType + && !$rightBooleanType->getValue() + ) { + return new ConstantBooleanType(false); + } + + if ( + $leftBooleanType instanceof ConstantBooleanType + && $leftBooleanType->getValue() + && $rightBooleanType instanceof ConstantBooleanType + && $rightBooleanType->getValue() + ) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + if ( + $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanOr + || $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalOr + ) { + if ($this->treatPhpDocTypesAsCertain) { + $leftBooleanType = $this->getType($node->left)->toBoolean(); + } else { + $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); + } + if ( + $leftBooleanType instanceof ConstantBooleanType + && $leftBooleanType->getValue() + ) { + return new ConstantBooleanType(true); + } + + if ($this->treatPhpDocTypesAsCertain) { + $rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); + } else { + $rightBooleanType = $this->promoteNativeTypes()->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); + } + + if ( + $rightBooleanType instanceof ConstantBooleanType + && $rightBooleanType->getValue() + ) { + return new ConstantBooleanType(true); + } + + if ( + $leftBooleanType instanceof ConstantBooleanType + && !$leftBooleanType->getValue() + && $rightBooleanType instanceof ConstantBooleanType + && !$rightBooleanType->getValue() + ) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + } + + if ($node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalXor) { + if ($this->treatPhpDocTypesAsCertain) { + $leftBooleanType = $this->getType($node->left)->toBoolean(); + $rightBooleanType = $this->getType($node->right)->toBoolean(); + } else { + $leftBooleanType = $this->getNativeType($node->left)->toBoolean(); + $rightBooleanType = $this->getNativeType($node->right)->toBoolean(); + } + + if ( + $leftBooleanType instanceof ConstantBooleanType + && $rightBooleanType instanceof ConstantBooleanType + ) { + return new ConstantBooleanType( + $leftBooleanType->getValue() xor $rightBooleanType->getValue() + ); + } + + return new BooleanType(); + } + + if ($node instanceof Expr\BinaryOp\Identical) { + if ($this->treatPhpDocTypesAsCertain) { + $leftType = $this->getType($node->left); + $rightType = $this->getType($node->right); + } else { + $leftType = $this->getNativeType($node->left); + $rightType = $this->getNativeType($node->right); + } + + if ( + ( + $node->left instanceof Node\Expr\PropertyFetch + || $node->left instanceof Node\Expr\StaticPropertyFetch + ) + && $rightType instanceof NullType + && !$this->hasPropertyNativeType($node->left) + ) { + return new BooleanType(); + } + + if ( + ( + $node->right instanceof Node\Expr\PropertyFetch + || $node->right instanceof Node\Expr\StaticPropertyFetch + ) + && $leftType instanceof NullType + && !$this->hasPropertyNativeType($node->right) + ) { + return new BooleanType(); + } + + $isSuperset = $leftType->isSuperTypeOf($rightType); + if ($isSuperset->no()) { + return new ConstantBooleanType(false); + } elseif ( + $isSuperset->yes() + && $leftType instanceof ConstantScalarType + && $rightType instanceof ConstantScalarType + && $leftType->getValue() === $rightType->getValue() + ) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + if ($node instanceof Expr\BinaryOp\NotIdentical) { + if ($this->treatPhpDocTypesAsCertain) { + $leftType = $this->getType($node->left); + $rightType = $this->getType($node->right); + } else { + $leftType = $this->getNativeType($node->left); + $rightType = $this->getNativeType($node->right); + } + + if ( + ( + $node->left instanceof Node\Expr\PropertyFetch + || $node->left instanceof Node\Expr\StaticPropertyFetch + ) + && $rightType instanceof NullType + && !$this->hasPropertyNativeType($node->left) + ) { + return new BooleanType(); + } + + if ( + ( + $node->right instanceof Node\Expr\PropertyFetch + || $node->right instanceof Node\Expr\StaticPropertyFetch + ) + && $leftType instanceof NullType + && !$this->hasPropertyNativeType($node->right) + ) { + return new BooleanType(); + } + + $isSuperset = $leftType->isSuperTypeOf($rightType); + if ($isSuperset->no()) { + return new ConstantBooleanType(true); + } elseif ( + $isSuperset->yes() + && $leftType instanceof ConstantScalarType + && $rightType instanceof ConstantScalarType + && $leftType->getValue() === $rightType->getValue() + ) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + } + + if ($node instanceof Expr\Instanceof_) { + if ($this->treatPhpDocTypesAsCertain) { + $expressionType = $this->getType($node->expr); + } else { + $expressionType = $this->getNativeType($node->expr); + } + if ( + $this->isInTrait() + && TypeUtils::findThisType($expressionType) !== null + ) { + return new BooleanType(); + } + if ($expressionType instanceof NeverType) { + return new ConstantBooleanType(false); + } + + $uncertainty = false; + + if ($node->class instanceof Node\Name) { + $unresolvedClassName = $node->class->toString(); + if ( + strtolower($unresolvedClassName) === 'static' + && $this->isInClass() + ) { + $classType = new StaticType($this->getClassReflection()); + } else { + $className = $this->resolveName($node->class); + $classType = new ObjectType($className); + } + } else { + $classType = $this->getType($node->class); + $classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + if ($type instanceof TypeWithClassName) { + $uncertainty = true; + return $type; + } + if ($type instanceof GenericClassStringType) { + $uncertainty = true; + return $type->getGenericType(); + } + if ($type instanceof ConstantStringType) { + return new ObjectType($type->getValue()); + } + return new MixedType(); + }); + } + + if ($classType->isSuperTypeOf(new MixedType())->yes()) { + return new BooleanType(); + } + + $isSuperType = $classType->isSuperTypeOf($expressionType); + + if ($isSuperType->no()) { + return new ConstantBooleanType(false); + } elseif ($isSuperType->yes() && !$uncertainty) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + if ($node instanceof Node\Expr\UnaryPlus) { + return $this->getType($node->expr)->toNumber(); + } + + if ($node instanceof Expr\ErrorSuppress + || $node instanceof Expr\Assign + ) { + return $this->getType($node->expr); + } + + if ($node instanceof Node\Expr\UnaryMinus) { + $type = $this->getType($node->expr)->toNumber(); + $scalarValues = TypeUtils::getConstantScalars($type); + if (count($scalarValues) > 0) { + $newTypes = []; + foreach ($scalarValues as $scalarValue) { + if ($scalarValue instanceof ConstantIntegerType) { + $newTypes[] = new ConstantIntegerType(-$scalarValue->getValue()); + } elseif ($scalarValue instanceof ConstantFloatType) { + $newTypes[] = new ConstantFloatType(-$scalarValue->getValue()); + } + } + + return TypeCombinator::union(...$newTypes); + } + + return $type; + } + + if ($node instanceof Expr\BinaryOp\Concat || $node instanceof Expr\AssignOp\Concat) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + $leftStringType = $this->getType($left)->toString(); + $rightStringType = $this->getType($right)->toString(); + if (TypeCombinator::union( + $leftStringType, + $rightStringType + ) instanceof ErrorType) { + return new ErrorType(); + } + + if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') { + return $rightStringType; + } + + if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') { + return $leftStringType; + } + + if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) { + return $leftStringType->append($rightStringType); + } + + return new StringType(); + } + + if ( + $node instanceof Node\Expr\BinaryOp\Div + || $node instanceof Node\Expr\AssignOp\Div + || $node instanceof Node\Expr\BinaryOp\Mod + || $node instanceof Node\Expr\AssignOp\Mod + ) { + if ($node instanceof Node\Expr\AssignOp) { + $right = $node->expr; + } else { + $right = $node->right; + } + + $rightTypes = TypeUtils::getConstantScalars($this->getType($right)->toNumber()); + foreach ($rightTypes as $rightType) { + if ( + $rightType->getValue() === 0 + || $rightType->getValue() === 0.0 + ) { + return new ErrorType(); + } + } + } + + if ( + ( + $node instanceof Node\Expr\BinaryOp + || $node instanceof Node\Expr\AssignOp + ) && !$node instanceof Expr\BinaryOp\Coalesce && !$node instanceof Expr\AssignOp\Coalesce + ) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + $leftTypes = TypeUtils::getConstantScalars($this->getType($left)); + $rightTypes = TypeUtils::getConstantScalars($this->getType($right)); + + if (count($leftTypes) > 0 && count($rightTypes) > 0) { + $resultTypes = []; + foreach ($leftTypes as $leftType) { + foreach ($rightTypes as $rightType) { + $resultTypes[] = $this->calculateFromScalars($node, $leftType, $rightType); + } + } + return TypeCombinator::union(...$resultTypes); + } + } + + if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Expr\AssignOp\Mod) { + return new IntegerType(); + } + + if ($node instanceof Expr\BinaryOp\Spaceship) { + return IntegerRangeType::fromInterval(-1, 1); + } + + if ($node instanceof Expr\Clone_) { + return $this->getType($node->expr); + } + + if ( + $node instanceof Expr\AssignOp\ShiftLeft + || $node instanceof Expr\BinaryOp\ShiftLeft + || $node instanceof Expr\AssignOp\ShiftRight + || $node instanceof Expr\BinaryOp\ShiftRight + ) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + if (TypeCombinator::union( + $this->getType($left)->toNumber(), + $this->getType($right)->toNumber() + ) instanceof ErrorType) { + return new ErrorType(); + } + + return new IntegerType(); + } + + if ( + $node instanceof Expr\AssignOp\BitwiseAnd + || $node instanceof Expr\BinaryOp\BitwiseAnd + || $node instanceof Expr\AssignOp\BitwiseOr + || $node instanceof Expr\BinaryOp\BitwiseOr + || $node instanceof Expr\AssignOp\BitwiseXor + || $node instanceof Expr\BinaryOp\BitwiseXor + ) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + $leftType = $this->getType($left); + $rightType = $this->getType($right); + $stringType = new StringType(); + + if ($stringType->isSuperTypeOf($leftType)->yes() && $stringType->isSuperTypeOf($rightType)->yes()) { + return $stringType; + } + + if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) { + return new ErrorType(); + } + + return new IntegerType(); + } + + if ( + $node instanceof Node\Expr\BinaryOp\Plus + || $node instanceof Node\Expr\BinaryOp\Minus + || $node instanceof Node\Expr\BinaryOp\Mul + || $node instanceof Node\Expr\BinaryOp\Pow + || $node instanceof Node\Expr\BinaryOp\Div + || $node instanceof Node\Expr\AssignOp\Plus + || $node instanceof Node\Expr\AssignOp\Minus + || $node instanceof Node\Expr\AssignOp\Mul + || $node instanceof Node\Expr\AssignOp\Pow + || $node instanceof Node\Expr\AssignOp\Div + ) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + $leftType = $this->getType($left); + $rightType = $this->getType($right); + + $operatorSigil = null; + + if ($node instanceof BinaryOp) { + $operatorSigil = $node->getOperatorSigil(); + } + + if ($operatorSigil === null) { + $operatorSigil = self::OPERATOR_SIGIL_MAP[get_class($node)] ?? null; + } + + if ($operatorSigil !== null) { + $operatorTypeSpecifyingExtensions = $this->operatorTypeSpecifyingExtensionRegistry->getOperatorTypeSpecifyingExtensions($operatorSigil, $leftType, $rightType); + + /** @var Type[] $extensionTypes */ + $extensionTypes = []; + + foreach ($operatorTypeSpecifyingExtensions as $extension) { + $extensionTypes[] = $extension->specifyType($operatorSigil, $leftType, $rightType); + } + + if (count($extensionTypes) > 0) { + return TypeCombinator::union(...$extensionTypes); + } + } + + if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) { + $leftConstantArrays = TypeUtils::getConstantArrays($leftType); + $rightConstantArrays = TypeUtils::getConstantArrays($rightType); + + if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) { + $resultTypes = []; + foreach ($rightConstantArrays as $rightConstantArray) { + foreach ($leftConstantArrays as $leftConstantArray) { + $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); + foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) { + $newArrayBuilder->setOffsetValueType( + $leftKeyType, + $leftConstantArray->getOffsetValueType($leftKeyType) + ); + } + $resultTypes[] = $newArrayBuilder->getArray(); + } + } + + return TypeCombinator::union(...$resultTypes); + } + $arrayType = new ArrayType(new MixedType(), new MixedType()); + + if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) { + if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { + // to preserve BenevolentUnionType + $keyType = $leftType->getIterableKeyType(); + } else { + $keyTypes = []; + foreach ([ + $leftType->getIterableKeyType(), + $rightType->getIterableKeyType(), + ] as $keyType) { + $keyTypes[] = $keyType; + } + $keyType = TypeCombinator::union(...$keyTypes); + } + return new ArrayType( + $keyType, + TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()) + ); + } + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + new ArrayType(new MixedType(), new MixedType()), + ]); + } + } + + $types = TypeCombinator::union($leftType, $rightType); + if ( + $leftType instanceof ArrayType + || $rightType instanceof ArrayType + || $types instanceof ArrayType + ) { + return new ErrorType(); + } + + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ( + (new FloatType())->isSuperTypeOf($leftNumberType)->yes() + || (new FloatType())->isSuperTypeOf($rightNumberType)->yes() + ) { + return new FloatType(); + } + + if ($node instanceof Expr\AssignOp\Pow || $node instanceof Expr\BinaryOp\Pow) { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + } + + $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); + if ($node instanceof Expr\AssignOp\Div || $node instanceof Expr\BinaryOp\Div) { + if ($types instanceof MixedType || $resultType instanceof IntegerType) { + return new BenevolentUnionType([new IntegerType(), new FloatType()]); + } + + return new UnionType([new IntegerType(), new FloatType()]); + } + + if ($types instanceof MixedType + || $leftType instanceof BenevolentUnionType + || $rightType instanceof BenevolentUnionType + ) { + return TypeUtils::toBenevolentUnion($resultType); + } + + return $resultType; + } + + if ($node instanceof LNumber) { + return new ConstantIntegerType($node->value); + } elseif ($node instanceof String_) { + return new ConstantStringType($node->value); + } elseif ($node instanceof Node\Scalar\Encapsed) { + $constantString = new ConstantStringType(''); + foreach ($node->parts as $part) { + if ($part instanceof EncapsedStringPart) { + $partStringType = new ConstantStringType($part->value); + } else { + $partStringType = $this->getType($part)->toString(); + if ($partStringType instanceof ErrorType) { + return new ErrorType(); + } + if (!$partStringType instanceof ConstantStringType) { + return new StringType(); + } + } + + $constantString = $constantString->append($partStringType); + } + return $constantString; + } elseif ($node instanceof DNumber) { + return new ConstantFloatType($node->value); + } elseif ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) { + $parameters = []; + $isVariadic = false; + $firstOptionalParameterIndex = null; + foreach ($node->params as $i => $param) { + $isOptionalCandidate = $param->default !== null || $param->variadic; + + if ($isOptionalCandidate) { + if ($firstOptionalParameterIndex === null) { + $firstOptionalParameterIndex = $i; + } + } else { + $firstOptionalParameterIndex = null; + } + } + + foreach ($node->params as $i => $param) { + if ($param->variadic) { + $isVariadic = true; + } + if (!$param->var instanceof Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $parameters[] = new NativeParameterReflection( + $param->var->name, + $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex, + $this->getFunctionType($param->type, $param->type === null, false), + $param->byRef + ? PassedByReference::createCreatesNewVariable() + : PassedByReference::createNo(), + $param->variadic, + $param->default !== null ? $this->getType($param->default) : null + ); + } + + if ($node instanceof Expr\ArrowFunction) { + $returnType = $this->enterArrowFunctionWithoutReflection($node)->getType($node->expr); + if ($node->returnType !== null) { + $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); + } + } else { + $callableParameters = null; + $arg = $node->getAttribute('parent'); + if ($arg instanceof Arg) { + $funcCall = $arg->getAttribute('parent'); + $argOrder = $arg->getAttribute('expressionOrder'); + if ($funcCall instanceof FuncCall && $funcCall->name instanceof Name) { + $functionName = $this->reflectionProvider->resolveFunctionName($funcCall->name, $this); + if ( + $functionName === 'array_map' + && $argOrder === 0 + && isset($funcCall->args[1]) + ) { + $callableParameters = [ + new DummyParameter('item', $this->getType($funcCall->args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ]; + } + } + } + $closureScope = $this->enterAnonymousFunctionWithoutReflection($node, $callableParameters); + $closureReturnStatements = []; + $closureYieldStatements = []; + $closureExecutionEnds = []; + $this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $closureScope, static function (Node $node, Scope $scope) use ($closureScope, &$closureReturnStatements, &$closureYieldStatements, &$closureExecutionEnds): void { + if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { + return; + } + + if ($node instanceof ExecutionEndNode) { + if ($node->getStatementResult()->isAlwaysTerminating()) { + foreach ($node->getStatementResult()->getExitPoints() as $exitPoint) { + if ($exitPoint->getStatement() instanceof Node\Stmt\Return_) { + continue; + } + + $closureExecutionEnds[] = $node; + break; + } + + if (count($node->getStatementResult()->getExitPoints()) === 0) { + $closureExecutionEnds[] = $node; + } + } + + return; + } + + if ($node instanceof Node\Stmt\Return_) { + $closureReturnStatements[] = [$node, $scope]; + } + + if (!$node instanceof Expr\Yield_ && !$node instanceof Expr\YieldFrom) { + return; + } + + $closureYieldStatements[] = [$node, $scope]; + }); + + $returnTypes = []; + $hasNull = false; + foreach ($closureReturnStatements as [$returnNode, $returnScope]) { + if ($returnNode->expr === null) { + $hasNull = true; + continue; + } + + $returnTypes[] = $returnScope->getType($returnNode->expr); + } + + if (count($returnTypes) === 0) { + if (count($closureExecutionEnds) > 0 && !$hasNull) { + $returnType = new NeverType(true); + } else { + $returnType = new VoidType(); + } + } else { + if (count($closureExecutionEnds) > 0) { + $returnTypes[] = new NeverType(true); + } + if ($hasNull) { + $returnTypes[] = new NullType(); + } + $returnType = TypeCombinator::union(...$returnTypes); + } + + if (count($closureYieldStatements) > 0) { + $keyTypes = []; + $valueTypes = []; + foreach ($closureYieldStatements as [$yieldNode, $yieldScope]) { + if ($yieldNode instanceof Expr\Yield_) { + if ($yieldNode->key === null) { + $keyTypes[] = new IntegerType(); + } else { + $keyTypes[] = $yieldScope->getType($yieldNode->key); + } + + if ($yieldNode->value === null) { + $valueTypes[] = new NullType(); + } else { + $valueTypes[] = $yieldScope->getType($yieldNode->value); + } + + continue; + } + + $yieldFromType = $yieldScope->getType($yieldNode->expr); + $keyTypes[] = $yieldFromType->getIterableKeyType(); + $valueTypes[] = $yieldFromType->getIterableValueType(); + } + + $returnType = new GenericObjectType(\Generator::class, [ + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes), + new MixedType(), + $returnType, + ]); + } else { + $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); + } + } + + return new ClosureType( + $parameters, + $returnType, + $isVariadic + ); + } elseif ($node instanceof New_) { + if ($node->class instanceof Name) { + $type = $this->exactInstantiation($node, $node->class->toString()); + if ($type !== null) { + return $type; + } + + $lowercasedClassName = strtolower($node->class->toString()); + if ($lowercasedClassName === 'static') { + if (!$this->isInClass()) { + return new ErrorType(); + } + + return new StaticType($this->getClassReflection()); + } + if ($lowercasedClassName === 'parent') { + return new NonexistentParentClassType(); + } + + return new ObjectType($node->class->toString()); + } + if ($node->class instanceof Node\Stmt\Class_) { + $anonymousClassReflection = $this->reflectionProvider->getAnonymousClassReflection($node->class, $this); + + return new ObjectType($anonymousClassReflection->getName()); + } + + $exprType = $this->getType($node->class); + return $this->getTypeToInstantiateForNew($exprType); + } elseif ($node instanceof Array_) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + if (count($node->items) > 256) { + $arrayBuilder->degradeToGeneralArray(); + } + foreach ($node->items as $arrayItem) { + if ($arrayItem === null) { + continue; + } + + $valueType = $this->getType($arrayItem->value); + if ($arrayItem->unpack) { + if ($valueType instanceof ConstantArrayType) { + foreach ($valueType->getValueTypes() as $innerValueType) { + $arrayBuilder->setOffsetValueType(null, $innerValueType); + } + } else { + $arrayBuilder->degradeToGeneralArray(); + $arrayBuilder->setOffsetValueType(new IntegerType(), $valueType->getIterableValueType()); + } + } else { + $arrayBuilder->setOffsetValueType( + $arrayItem->key !== null ? $this->getType($arrayItem->key) : null, + $valueType + ); + } + } + return $arrayBuilder->getArray(); + } elseif ($node instanceof Int_) { + return $this->getType($node->expr)->toInteger(); + } elseif ($node instanceof Bool_) { + return $this->getType($node->expr)->toBoolean(); + } elseif ($node instanceof Double) { + return $this->getType($node->expr)->toFloat(); + } elseif ($node instanceof \PhpParser\Node\Expr\Cast\String_) { + return $this->getType($node->expr)->toString(); + } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Array_) { + return $this->getType($node->expr)->toArray(); + } elseif ($node instanceof Node\Scalar\MagicConst\Line) { + return new ConstantIntegerType($node->getLine()); + } elseif ($node instanceof Node\Scalar\MagicConst\Class_) { + if (!$this->isInClass()) { + return new ConstantStringType(''); + } + + return new ConstantStringType($this->getClassReflection()->getName(), true); + } elseif ($node instanceof Node\Scalar\MagicConst\Dir) { + return new ConstantStringType(dirname($this->getFile())); + } elseif ($node instanceof Node\Scalar\MagicConst\File) { + return new ConstantStringType($this->getFile()); + } elseif ($node instanceof Node\Scalar\MagicConst\Namespace_) { + return new ConstantStringType($this->namespace ?? ''); + } elseif ($node instanceof Node\Scalar\MagicConst\Method) { + if ($this->isInAnonymousFunction()) { + return new ConstantStringType('{closure}'); + } + + $function = $this->getFunction(); + if ($function === null) { + return new ConstantStringType(''); + } + if ($function instanceof MethodReflection) { + return new ConstantStringType( + sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) + ); + } + + return new ConstantStringType($function->getName()); + } elseif ($node instanceof Node\Scalar\MagicConst\Function_) { + if ($this->isInAnonymousFunction()) { + return new ConstantStringType('{closure}'); + } + $function = $this->getFunction(); + if ($function === null) { + return new ConstantStringType(''); + } + + return new ConstantStringType($function->getName()); + } elseif ($node instanceof Node\Scalar\MagicConst\Trait_) { + if (!$this->isInTrait()) { + return new ConstantStringType(''); + } + return new ConstantStringType($this->getTraitReflection()->getName(), true); + } elseif ($node instanceof Object_) { + $castToObject = static function (Type $type): Type { + if ((new ObjectWithoutClassType())->isSuperTypeOf($type)->yes()) { + return $type; + } + + return new ObjectType('stdClass'); + }; + + $exprType = $this->getType($node->expr); + if ($exprType instanceof UnionType) { + return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes())); + } + + return $castToObject($exprType); + } elseif ($node instanceof Unset_) { + return new NullType(); + } elseif ($node instanceof Expr\PostInc || $node instanceof Expr\PostDec) { + return $this->getType($node->var); + } elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) { + $varType = $this->getType($node->var); + $varScalars = TypeUtils::getConstantScalars($varType); + if (count($varScalars) > 0) { + $newTypes = []; + + foreach ($varScalars as $scalar) { + $varValue = $scalar->getValue(); + if ($node instanceof Expr\PreInc) { + ++$varValue; + } else { + --$varValue; + } + + $newTypes[] = $this->getTypeFromValue($varValue); + } + return TypeCombinator::union(...$newTypes); + } elseif ($varType instanceof IntegerRangeType) { + return $varType->shift($node instanceof Expr\PreInc ? +1 : -1); + } + + $stringType = new StringType(); + if ($stringType->isSuperTypeOf($varType)->yes()) { + return $stringType; + } + + return $varType->toNumber(); + } elseif ($node instanceof Expr\Yield_) { + $functionReflection = $this->getFunction(); + if ($functionReflection === null) { + return new MixedType(); + } + + $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if (!$returnType instanceof TypeWithClassName) { + return new MixedType(); + } + + $generatorSendType = GenericTypeVariableResolver::getType($returnType, \Generator::class, 'TSend'); + if ($generatorSendType === null) { + return new MixedType(); + } + + return $generatorSendType; + } elseif ($node instanceof Expr\YieldFrom) { + $yieldFromType = $this->getType($node->expr); + + if (!$yieldFromType instanceof TypeWithClassName) { + return new MixedType(); + } + + $generatorReturnType = GenericTypeVariableResolver::getType($yieldFromType, \Generator::class, 'TReturn'); + if ($generatorReturnType === null) { + return new MixedType(); + } + + return $generatorReturnType; + } elseif ($node instanceof Expr\Match_) { + $cond = $node->cond; + $types = []; + + $matchScope = $this; + foreach ($node->arms as $arm) { + if ($arm->conds === null) { + $types[] = $matchScope->getType($arm->body); + continue; + } + + if (count($arm->conds) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $filteringExpr = null; + foreach ($arm->conds as $armCond) { + $armCondExpr = new BinaryOp\Identical($cond, $armCond); + if ($filteringExpr === null) { + $filteringExpr = $armCondExpr; + continue; + } + + $filteringExpr = new BinaryOp\BooleanOr($filteringExpr, $armCondExpr); + } + + $truthyScope = $matchScope->filterByTruthyValue($filteringExpr); + $types[] = $truthyScope->getType($arm->body); + + $matchScope = $matchScope->filterByFalseyValue($filteringExpr); + } + + return TypeCombinator::union(...$types); + } + + $exprString = $this->getNodeKey($node); + if (isset($this->moreSpecificTypes[$exprString]) && $this->moreSpecificTypes[$exprString]->getCertainty()->yes()) { + return $this->moreSpecificTypes[$exprString]->getType(); + } + + if ($node instanceof Expr\AssignOp\Coalesce) { + return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes())); + } + + if ($node instanceof Expr\BinaryOp\Coalesce) { + if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) { + $dimType = $this->getType($node->left->dim); + $varType = $this->getType($node->left->var); + $hasOffset = $varType->hasOffsetValueType($dimType); + $leftType = $this->getType($node->left); + $rightType = $this->filterByFalseyValue( + new BinaryOp\NotIdentical($node->left, new ConstFetch(new Name('null'))) + )->getType($node->right); + if ($hasOffset->no()) { + return $rightType; + } elseif ($hasOffset->yes()) { + $offsetValueType = $varType->getOffsetValueType($dimType); + if ($offsetValueType->isSuperTypeOf(new NullType())->no()) { + return TypeCombinator::removeNull($leftType); + } + } + + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType + ); + } + + $leftType = $this->getType($node->left); + $rightType = $this->filterByFalseyValue( + new BinaryOp\NotIdentical($node->left, new ConstFetch(new Name('null'))) + )->getType($node->right); + if ($leftType instanceof ErrorType || $leftType instanceof NullType) { + return $rightType; + } + + if ( + TypeCombinator::containsNull($leftType) + || $node->left instanceof PropertyFetch + || ( + $node->left instanceof Variable + && is_string($node->left->name) + && !$this->hasVariableType($node->left->name)->yes() + ) + ) { + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType + ); + } + + return TypeCombinator::removeNull($leftType); + } + + if ($node instanceof ConstFetch) { + $constName = (string) $node->name; + $loweredConstName = strtolower($constName); + if ($loweredConstName === 'true') { + return new \PHPStan\Type\Constant\ConstantBooleanType(true); + } elseif ($loweredConstName === 'false') { + return new \PHPStan\Type\Constant\ConstantBooleanType(false); + } elseif ($loweredConstName === 'null') { + return new NullType(); + } + + if ($node->name->isFullyQualified()) { + if (array_key_exists($node->name->toCodeString(), $this->constantTypes)) { + return $this->resolveConstantType($node->name->toString(), $this->constantTypes[$node->name->toCodeString()]); + } + } + + if ($this->getNamespace() !== null) { + $constantName = new FullyQualified([$this->getNamespace(), $constName]); + if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { + return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); + } + } + + $constantName = new FullyQualified($constName); + if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { + return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); + } + + if ($this->reflectionProvider->hasConstant($node->name, $this)) { + /** @var string $resolvedConstantName */ + $resolvedConstantName = $this->reflectionProvider->resolveConstantName($node->name, $this); + if ($resolvedConstantName === 'DIRECTORY_SEPARATOR') { + return new UnionType([ + new ConstantStringType('/'), + new ConstantStringType('\\'), + ]); + } + if ($resolvedConstantName === 'PATH_SEPARATOR') { + return new UnionType([ + new ConstantStringType(':'), + new ConstantStringType(';'), + ]); + } + if ($resolvedConstantName === 'PHP_EOL') { + return new UnionType([ + new ConstantStringType("\n"), + new ConstantStringType("\r\n"), + ]); + } + if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') { + return new IntegerType(); + } + + $constantType = $this->reflectionProvider->getConstant($node->name, $this)->getValueType(); + + return $this->resolveConstantType($resolvedConstantName, $constantType); + } + + return new ErrorType(); + } elseif ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Identifier) { + $constantName = $node->name->name; + if ($node->class instanceof Name) { + $constantClass = (string) $node->class; + $constantClassType = new ObjectType($constantClass); + $namesToResolve = [ + 'self', + 'parent', + ]; + if ($this->isInClass()) { + if ($this->getClassReflection()->isFinal()) { + $namesToResolve[] = 'static'; + } elseif (strtolower($constantClass) === 'static') { + if (strtolower($constantName) === 'class') { + return new GenericClassStringType(new StaticType($this->getClassReflection())); + } + return new MixedType(); + } + } + if (in_array(strtolower($constantClass), $namesToResolve, true)) { + $resolvedName = $this->resolveName($node->class); + if ($resolvedName === 'parent' && strtolower($constantName) === 'class') { + return new ClassStringType(); + } + $constantClassType = $this->resolveTypeByName($node->class); + } + + if (strtolower($constantName) === 'class') { + return new ConstantStringType($constantClassType->getClassName(), true); + } + } else { + $constantClassType = $this->getType($node->class); + } + + $referencedClasses = TypeUtils::getDirectClassNames($constantClassType); + if (strtolower($constantName) === 'class') { + if (count($referencedClasses) === 0) { + return new ErrorType(); + } + $classTypes = []; + foreach ($referencedClasses as $referencedClass) { + $classTypes[] = new GenericClassStringType(new ObjectType($referencedClass)); + } + + return TypeCombinator::union(...$classTypes); + } + $types = []; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$propertyClassReflection->hasConstant($constantName)) { + continue; + } + + $constantType = $propertyClassReflection->getConstant($constantName)->getValueType(); + if ( + $constantType instanceof ConstantType + && in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) + ) { + $constantType = $constantType->generalize(); + } + $types[] = $constantType; + } + + if (count($types) > 0) { + return TypeCombinator::union(...$types); + } + + if (!$constantClassType->hasConstant($constantName)->yes()) { + return new ErrorType(); + } + + return $constantClassType->getConstant($constantName)->getValueType(); + } + + if ($node instanceof Expr\Ternary) { + if ($node->if === null) { + $conditionType = $this->getType($node->cond); + $booleanConditionType = $conditionType->toBoolean(); + if ($booleanConditionType instanceof ConstantBooleanType) { + if ($booleanConditionType->getValue()) { + return $this->filterByTruthyValue($node->cond)->getType($node->cond); + } + + return $this->filterByFalseyValue($node->cond)->getType($node->else); + } + return TypeCombinator::union( + TypeCombinator::remove($this->filterByTruthyValue($node->cond)->getType($node->cond), StaticTypeFactory::falsey()), + $this->filterByFalseyValue($node->cond)->getType($node->else) + ); + } + + $booleanConditionType = $this->getType($node->cond)->toBoolean(); + if ($booleanConditionType instanceof ConstantBooleanType) { + if ($booleanConditionType->getValue()) { + return $this->filterByTruthyValue($node->cond)->getType($node->if); + } + + return $this->filterByFalseyValue($node->cond)->getType($node->else); + } + + return TypeCombinator::union( + $this->filterByTruthyValue($node->cond)->getType($node->if), + $this->filterByFalseyValue($node->cond)->getType($node->else) + ); + } + + if ($node instanceof Variable && is_string($node->name)) { + if ($this->hasVariableType($node->name)->no()) { + return new ErrorType(); + } + + return $this->getVariableType($node->name); + } + + if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) { + return $this->getNullsafeShortCircuitingType( + $node->var, + $this->getTypeFromArrayDimFetch( + $node, + $this->getType($node->dim), + $this->getType($node->var) + ) + ); + } + + if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) { + $typeCallback = function () use ($node): Type { + $returnType = $this->methodCallReturnType( + $this->getType($node->var), + $node->name->name, + $node + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; + + return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + } + + if ($node instanceof Expr\NullsafeMethodCall) { + $varType = $this->getType($node->var); + if (!TypeCombinator::containsNull($varType)) { + return $this->getType(new MethodCall($node->var, $node->name, $node->args)); + } + + return TypeCombinator::union( + $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null')))) + ->getType(new MethodCall($node->var, $node->name, $node->args)), + new NullType() + ); + } + + if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) { + $typeCallback = function () use ($node): Type { + if ($node->class instanceof Name) { + $staticMethodCalledOnType = $this->resolveTypeByName($node->class); + } else { + $staticMethodCalledOnType = $this->getType($node->class); + if ($staticMethodCalledOnType instanceof GenericClassStringType) { + $staticMethodCalledOnType = $staticMethodCalledOnType->getGenericType(); + } + } + + $returnType = $this->methodCallReturnType( + $staticMethodCalledOnType, + $node->name->toString(), + $node + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; + + $callType = $typeCallback(); + if ($node->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($node->class, $callType); + } + + return $callType; + } + + if ($node instanceof PropertyFetch && $node->name instanceof Node\Identifier) { + $typeCallback = function () use ($node): Type { + $returnType = $this->propertyFetchType( + $this->getType($node->var), + $node->name->name, + $node + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; + + return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + } + + if ($node instanceof Expr\NullsafePropertyFetch) { + $varType = $this->getType($node->var); + if (!TypeCombinator::containsNull($varType)) { + return $this->getType(new PropertyFetch($node->var, $node->name)); + } + + return TypeCombinator::union( + $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null')))) + ->getType(new PropertyFetch($node->var, $node->name)), + new NullType() + ); + } + + if ( + $node instanceof Expr\StaticPropertyFetch + && $node->name instanceof Node\VarLikeIdentifier + ) { + $typeCallback = function () use ($node): Type { + if ($node->class instanceof Name) { + $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); + } else { + $staticPropertyFetchedOnType = $this->getType($node->class); + if ($staticPropertyFetchedOnType instanceof GenericClassStringType) { + $staticPropertyFetchedOnType = $staticPropertyFetchedOnType->getGenericType(); + } + } + + $returnType = $this->propertyFetchType( + $staticPropertyFetchedOnType, + $node->name->toString(), + $node + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; + + $fetchType = $typeCallback(); + if ($node->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($node->class, $fetchType); + } + + return $fetchType; + } + + if ($node instanceof FuncCall) { + if ($node->name instanceof Expr) { + $calledOnType = $this->getType($node->name); + if ($calledOnType->isCallable()->no()) { + return new ErrorType(); + } + + return ParametersAcceptorSelector::selectFromArgs( + $this, + $node->args, + $calledOnType->getCallableParametersAcceptors($this) + )->getReturnType(); + } + + if (!$this->reflectionProvider->hasFunction($node->name, $this)) { + return new ErrorType(); + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $this); + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicFunctionReturnTypeExtensions() as $dynamicFunctionReturnTypeExtension) { + if (!$dynamicFunctionReturnTypeExtension->isFunctionSupported($functionReflection)) { + continue; + } + + return $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall($functionReflection, $node, $this); + } + + return ParametersAcceptorSelector::selectFromArgs( + $this, + $node->args, + $functionReflection->getVariants() + )->getReturnType(); + } + + return new MixedType(); + } + + private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type + { + if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) { + $varType = $this->getType($expr->var); + if (TypeCombinator::containsNull($varType)) { + return TypeCombinator::addNull($type); + } + + return $type; + } + + if ($expr instanceof Expr\ArrayDimFetch) { + return $this->getNullsafeShortCircuitingType($expr->var, $type); + } + + if ($expr instanceof PropertyFetch) { + return $this->getNullsafeShortCircuitingType($expr->var, $type); + } + + if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($expr->class, $type); + } + + if ($expr instanceof MethodCall) { + return $this->getNullsafeShortCircuitingType($expr->var, $type); + } + + if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($expr->class, $type); + } + + return $type; + } + + private function resolveConstantType(string $constantName, Type $constantType): Type + { + if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { + return $constantType->generalize(); + } + + return $constantType; + } + + public function getNativeType(Expr $expr): Type + { + $key = $this->getNodeKey($expr); + + if (array_key_exists($key, $this->nativeExpressionTypes)) { + return $this->nativeExpressionTypes[$key]; + } + + if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { + return $this->getNullsafeShortCircuitingType( + $expr->var, + $this->getTypeFromArrayDimFetch( + $expr, + $this->getNativeType($expr->dim), + $this->getNativeType($expr->var) + ) + ); + } + + return $this->getType($expr); + } + + public function doNotTreatPhpDocTypesAsCertain(): Scope + { + if (!$this->treatPhpDocTypesAsCertain) { + return $this; + } + + return new self( + $this->scopeFactory, + $this->reflectionProvider, + $this->dynamicReturnTypeExtensionRegistry, + $this->operatorTypeSpecifyingExtensionRegistry, + $this->printer, + $this->typeSpecifier, + $this->propertyReflectionFinder, + $this->parser, + $this->nodeScopeResolver, + $this->context, + $this->declareStrictTypes, + $this->constantTypes, + $this->function, + $this->namespace, + $this->variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->dynamicConstantNames, + false, + $this->objectFromNewClass, + $this->afterExtractCall, + $this->parentScope + ); + } + + private function promoteNativeTypes(): self + { + $variableTypes = $this->variableTypes; + foreach ($this->nativeExpressionTypes as $expressionType => $type) { + if (substr($expressionType, 0, 1) !== '$') { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableName = substr($expressionType, 1); + $has = $this->hasVariableType($variableName); + if ($has->no()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableTypes[$variableName] = new VariableTypeHolder($type, $has); + } + + return $this->scopeFactory->create( + $this->context, + $this->declareStrictTypes, + $this->constantTypes, + $this->function, + $this->namespace, + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + [] + ); + } + + /** + * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch + * @return bool + */ + private function hasPropertyNativeType($propertyFetch): bool + { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $this); + if ($propertyReflection === null) { + return false; + } + + if (!$propertyReflection->isNative()) { + return false; + } + + return !$propertyReflection->getNativeType() instanceof MixedType; + } + + protected function getTypeFromArrayDimFetch( + Expr\ArrayDimFetch $arrayDimFetch, + Type $offsetType, + Type $offsetAccessibleType + ): Type { + if ($arrayDimFetch->dim === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ((new ObjectType(\ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()) { + return $this->getType( + new MethodCall( + $arrayDimFetch->var, + new Node\Identifier('offsetGet'), + [ + new Node\Arg($arrayDimFetch->dim), + ] + ) + ); + } + + return $offsetAccessibleType->getOffsetValueType($offsetType); + } + + private function calculateFromScalars(Expr $node, ConstantScalarType $leftType, ConstantScalarType $rightType): Type + { + if ($leftType instanceof StringType && $rightType instanceof StringType) { + /** @var string $leftValue */ + $leftValue = $leftType->getValue(); + /** @var string $rightValue */ + $rightValue = $rightType->getValue(); + + if ($node instanceof Expr\BinaryOp\BitwiseAnd || $node instanceof Expr\AssignOp\BitwiseAnd) { + return $this->getTypeFromValue($leftValue & $rightValue); + } + + if ($node instanceof Expr\BinaryOp\BitwiseOr || $node instanceof Expr\AssignOp\BitwiseOr) { + return $this->getTypeFromValue($leftValue | $rightValue); + } + + if ($node instanceof Expr\BinaryOp\BitwiseXor || $node instanceof Expr\AssignOp\BitwiseXor) { + return $this->getTypeFromValue($leftValue ^ $rightValue); + } + } + + $leftValue = $leftType->getValue(); + $rightValue = $rightType->getValue(); + + if ($node instanceof Node\Expr\BinaryOp\Spaceship) { + return $this->getTypeFromValue($leftValue <=> $rightValue); + } + + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + if (TypeCombinator::union($leftNumberType, $rightNumberType) instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + /** @var float|int $leftNumberValue */ + $leftNumberValue = $leftNumberType->getValue(); + + /** @var float|int $rightNumberValue */ + $rightNumberValue = $rightNumberType->getValue(); + + if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) { + return $this->getTypeFromValue($leftNumberValue + $rightNumberValue); + } + + if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) { + return $this->getTypeFromValue($leftNumberValue - $rightNumberValue); + } + + if ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) { + return $this->getTypeFromValue($leftNumberValue * $rightNumberValue); + } + + if ($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow) { + return $this->getTypeFromValue($leftNumberValue ** $rightNumberValue); + } + + if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { + return $this->getTypeFromValue($leftNumberValue / $rightNumberValue); + } + + if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Node\Expr\AssignOp\Mod) { + return $this->getTypeFromValue($leftNumberValue % $rightNumberValue); + } + + if ($node instanceof Expr\BinaryOp\ShiftLeft || $node instanceof Expr\AssignOp\ShiftLeft) { + return $this->getTypeFromValue($leftNumberValue << $rightNumberValue); + } + + if ($node instanceof Expr\BinaryOp\ShiftRight || $node instanceof Expr\AssignOp\ShiftRight) { + return $this->getTypeFromValue($leftNumberValue >> $rightNumberValue); + } + + if ($node instanceof Expr\BinaryOp\BitwiseAnd || $node instanceof Expr\AssignOp\BitwiseAnd) { + return $this->getTypeFromValue($leftNumberValue & $rightNumberValue); + } + + if ($node instanceof Expr\BinaryOp\BitwiseOr || $node instanceof Expr\AssignOp\BitwiseOr) { + return $this->getTypeFromValue($leftNumberValue | $rightNumberValue); + } + + if ($node instanceof Expr\BinaryOp\BitwiseXor || $node instanceof Expr\AssignOp\BitwiseXor) { + return $this->getTypeFromValue($leftNumberValue ^ $rightNumberValue); + } + + return new MixedType(); + } + + private function resolveExactName(Name $name): ?string + { + $originalClass = (string) $name; + + switch (strtolower($originalClass)) { + case 'self': + if (!$this->isInClass()) { + return null; + } + return $this->getClassReflection()->getName(); + case 'parent': + if (!$this->isInClass()) { + return null; + } + $currentClassReflection = $this->getClassReflection(); + if ($currentClassReflection->getParentClass() !== false) { + return $currentClassReflection->getParentClass()->getName(); + } + return null; + case 'static': + return null; + } + + return $originalClass; + } + + public function resolveName(Name $name): string + { + $originalClass = (string) $name; + if ($this->isInClass()) { + if (in_array(strtolower($originalClass), [ + 'self', + 'static', + ], true)) { + if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { + return $this->inClosureBindScopeClass; + } + return $this->getClassReflection()->getName(); + } elseif ($originalClass === 'parent') { + $currentClassReflection = $this->getClassReflection(); + if ($currentClassReflection->getParentClass() !== false) { + return $currentClassReflection->getParentClass()->getName(); + } + } + } + + return $originalClass; + } + + public function resolveTypeByName(Name $name): TypeWithClassName + { + if ($name->toLowerString() === 'static' && $this->isInClass()) { + if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { + return new StaticType($this->inClosureBindScopeClass); + } + + return new StaticType($this->getClassReflection()); + } + + $originalClass = $this->resolveName($name); + if ($this->isInClass()) { + if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { + $thisType = new ThisType($this->inClosureBindScopeClass); + } else { + $thisType = new ThisType($this->getClassReflection()); + } + $ancestor = $thisType->getAncestorWithClassName($originalClass); + if ($ancestor !== null) { + return $ancestor; + } + } + + return new ObjectType($originalClass); + } + + /** + * @param mixed $value + */ + public function getTypeFromValue($value): Type + { + return ConstantTypeHelper::getTypeFromValue($value); + } + + public function isSpecified(Expr $node): bool + { + $exprString = $this->getNodeKey($node); + + return isset($this->moreSpecificTypes[$exprString]) + && $this->moreSpecificTypes[$exprString]->getCertainty()->yes(); + } + + /** + * @param MethodReflection|FunctionReflection $reflection + * @return self + */ + public function pushInFunctionCall($reflection): self + { + $stack = $this->inFunctionCallsStack; + $stack[] = $reflection; + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $stack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function popInFunctionCall(): self + { + $stack = $this->inFunctionCallsStack; + array_pop($stack); + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $stack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function isInClassExists(string $className): bool + { + foreach ($this->inFunctionCallsStack as $inFunctionCall) { + if (!$inFunctionCall instanceof FunctionReflection) { + continue; + } + + if (in_array($inFunctionCall->getName(), [ + 'class_exists', + 'interface_exists', + 'trait_exists', + ], true)) { + return true; + } + } + $expr = new FuncCall(new FullyQualified('class_exists'), [ + new Arg(new String_(ltrim($className, '\\'))), + ]); + + return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes(); + } + + public function enterClass(ClassReflection $classReflection): self + { + return $this->scopeFactory->create( + $this->context->enterClass($classReflection), + $this->isDeclareStrictTypes(), + $this->constantTypes, + null, + $this->getNamespace(), + [ + 'this' => VariableTypeHolder::createYes(new ThisType($classReflection)), + ] + ); + } + + public function enterTrait(ClassReflection $traitReflection): self + { + return $this->scopeFactory->create( + $this->context->enterTrait($traitReflection), + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + [], + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection + ); + } + + /** + * @param Node\Stmt\ClassMethod $classMethod + * @param TemplateTypeMap $templateTypeMap + * @param Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $throwType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param bool|null $isPure + * @return self + */ + public function enterClassMethod( + Node\Stmt\ClassMethod $classMethod, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $throwType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?bool $isPure = null + ): self { + if (!$this->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->enterFunctionLike( + new PhpMethodFromParserNodeReflection( + $this->getClassReflection(), + $classMethod, + $templateTypeMap, + $this->getRealParameterTypes($classMethod), + array_map(static function (Type $type): Type { + return TemplateTypeHelper::toArgument($type); + }, $phpDocParameterTypes), + $this->getRealParameterDefaultValues($classMethod), + $this->transformStaticType($this->getFunctionType($classMethod->returnType, $classMethod->returnType === null, false)), + $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, + $throwType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $isPure + ), + !$classMethod->isStatic() + ); + } + + private function transformStaticType(Type $type): Type + { + return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { + if (!$this->isInClass()) { + return $type; + } + if ($type instanceof StaticType) { + $classReflection = $this->getClassReflection(); + $changedType = $type->changeBaseClass($classReflection); + if ($classReflection->isFinal()) { + $changedType = $changedType->getStaticObjectType(); + } + return $traverse($changedType); + } + + return $traverse($type); + }); + } + + /** + * @param Node\FunctionLike $functionLike + * @return Type[] + */ + private function getRealParameterTypes(Node\FunctionLike $functionLike): array + { + $realParameterTypes = []; + foreach ($functionLike->getParams() as $parameter) { + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $realParameterTypes[$parameter->var->name] = $this->getFunctionType( + $parameter->type, + $this->isParameterValueNullable($parameter), + false + ); + } + + return $realParameterTypes; + } + + /** + * @param Node\FunctionLike $functionLike + * @return Type[] + */ + private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): array + { + $realParameterDefaultValues = []; + foreach ($functionLike->getParams() as $parameter) { + if ($parameter->default === null) { + continue; + } + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $realParameterDefaultValues[$parameter->var->name] = $this->getType($parameter->default); + } + + return $realParameterDefaultValues; + } + + /** + * @param Node\Stmt\Function_ $function + * @param TemplateTypeMap $templateTypeMap + * @param Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $throwType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param bool|null $isPure + * @return self + */ + public function enterFunction( + Node\Stmt\Function_ $function, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $throwType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?bool $isPure = null + ): self { + return $this->enterFunctionLike( + new PhpFunctionFromParserNodeReflection( + $function, + $templateTypeMap, + $this->getRealParameterTypes($function), + array_map(static function (Type $type): Type { + return TemplateTypeHelper::toArgument($type); + }, $phpDocParameterTypes), + $this->getRealParameterDefaultValues($function), + $this->getFunctionType($function->returnType, $function->returnType === null, false), + $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, + $throwType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $isPure + ), + false + ); + } + + private function enterFunctionLike( + PhpFunctionFromParserNodeReflection $functionReflection, + bool $preserveThis + ): self { + $variableTypes = []; + $nativeExpressionTypes = []; + foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) { + $parameterType = $parameter->getType(); + if ($parameter->isVariadic()) { + $parameterType = new ArrayType(new IntegerType(), $parameterType); + } + $variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameterType); + $nativeExpressionTypes[sprintf('$%s', $parameter->getName())] = $parameter->getNativeType(); + } + + if ($preserveThis && array_key_exists('this', $this->variableTypes)) { + $variableTypes['this'] = $this->variableTypes['this']; + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $functionReflection, + $this->getNamespace(), + $variableTypes, + [], + [], + null, + null, + true, + [], + $nativeExpressionTypes + ); + } + + public function enterNamespace(string $namespaceName): self + { + return $this->scopeFactory->create( + $this->context->beginFile(), + $this->isDeclareStrictTypes(), + $this->constantTypes, + null, + $namespaceName + ); + } + + public function enterClosureBind(?Type $thisType, string $scopeClass): self + { + $variableTypes = $this->getVariableTypes(); + + if ($thisType !== null) { + $variableTypes['this'] = VariableTypeHolder::createYes($thisType); + } else { + unset($variableTypes['this']); + } + + if ($scopeClass === 'static' && $this->isInClass()) { + $scopeClass = $this->getClassReflection()->getName(); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $scopeClass, + $this->anonymousFunctionReflection + ); + } + + public function restoreOriginalScopeAfterClosureBind(self $originalScope): self + { + $variableTypes = $this->getVariableTypes(); + if (isset($originalScope->variableTypes['this'])) { + $variableTypes['this'] = $originalScope->variableTypes['this']; + } else { + unset($variableTypes['this']); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $originalScope->inClosureBindScopeClass, + $this->anonymousFunctionReflection + ); + } + + public function enterClosureCall(Type $thisType): self + { + $variableTypes = $this->getVariableTypes(); + $variableTypes['this'] = VariableTypeHolder::createYes($thisType); + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $thisType instanceof TypeWithClassName ? $thisType->getClassName() : null, + $this->anonymousFunctionReflection + ); + } + + public function isInClosureBind(): bool + { + return $this->inClosureBindScopeClass !== null; + } + + /** + * @param \PhpParser\Node\Expr\Closure $closure + * @param \PHPStan\Reflection\ParameterReflection[]|null $callableParameters + * @return self + */ + public function enterAnonymousFunction( + Expr\Closure $closure, + ?array $callableParameters = null + ): self { + $anonymousFunctionReflection = $this->getType($closure); + if (!$anonymousFunctionReflection instanceof ClosureType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $scope = $this->enterAnonymousFunctionWithoutReflection($closure, $callableParameters); + + return $this->scopeFactory->create( + $scope->context, + $scope->isDeclareStrictTypes(), + $scope->constantTypes, + $scope->getFunction(), + $scope->getNamespace(), + $scope->variableTypes, + $scope->moreSpecificTypes, + [], + $scope->inClosureBindScopeClass, + $anonymousFunctionReflection, + true, + [], + $scope->nativeExpressionTypes, + [], + false, + $this + ); + } + + /** + * @param \PhpParser\Node\Expr\Closure $closure + * @param \PHPStan\Reflection\ParameterReflection[]|null $callableParameters + * @return self + */ + private function enterAnonymousFunctionWithoutReflection( + Expr\Closure $closure, + ?array $callableParameters = null + ): self { + $variableTypes = []; + foreach ($closure->params as $i => $parameter) { + $isNullable = $this->isParameterValueNullable($parameter); + $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic); + if ($callableParameters !== null) { + if (isset($callableParameters[$i])) { + $parameterType = TypehintHelper::decideType($parameterType, $callableParameters[$i]->getType()); + } elseif (count($callableParameters) > 0) { + $lastParameter = $callableParameters[count($callableParameters) - 1]; + if ($lastParameter->isVariadic()) { + $parameterType = TypehintHelper::decideType($parameterType, $lastParameter->getType()); + } else { + $parameterType = TypehintHelper::decideType($parameterType, new MixedType()); + } + } else { + $parameterType = TypehintHelper::decideType($parameterType, new MixedType()); + } + } + + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $variableTypes[$parameter->var->name] = VariableTypeHolder::createYes( + $parameterType + ); + } + + $nativeTypes = []; + $moreSpecificTypes = []; + foreach ($closure->uses as $use) { + if (!is_string($use->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($use->byRef) { + continue; + } + $variableName = $use->var->name; + if ($this->hasVariableType($variableName)->no()) { + $variableType = new ErrorType(); + } else { + $variableType = $this->getVariableType($variableName); + $nativeTypes[sprintf('$%s', $variableName)] = $this->getNativeType($use->var); + } + $variableTypes[$variableName] = VariableTypeHolder::createYes($variableType); + foreach ($this->moreSpecificTypes as $exprString => $moreSpecificType) { + $matches = \Nette\Utils\Strings::matchAll((string) $exprString, '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#'); + if ($matches === []) { + continue; + } + + $matches = array_column($matches, 1); + if (!in_array($variableName, $matches, true)) { + continue; + } + + $moreSpecificTypes[$exprString] = $moreSpecificType; + } + } + + if ($this->hasVariableType('this')->yes() && !$closure->static) { + $variableTypes['this'] = VariableTypeHolder::createYes($this->getVariableType('this')); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $moreSpecificTypes, + [], + $this->inClosureBindScopeClass, + new TrivialParametersAcceptor(), + true, + [], + $nativeTypes, + [], + false, + $this + ); + } + + public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self + { + $anonymousFunctionReflection = $this->getType($arrowFunction); + if (!$anonymousFunctionReflection instanceof ClosureType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $scope = $this->enterArrowFunctionWithoutReflection($arrowFunction); + + return $this->scopeFactory->create( + $scope->context, + $scope->isDeclareStrictTypes(), + $scope->constantTypes, + $scope->getFunction(), + $scope->getNamespace(), + $scope->variableTypes, + $scope->moreSpecificTypes, + $scope->conditionalExpressions, + $scope->inClosureBindScopeClass, + $anonymousFunctionReflection, + true, + [], + [], + [], + $scope->afterExtractCall, + $scope->parentScope + ); + } + + private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction): self + { + $variableTypes = $this->variableTypes; + $mixed = new MixedType(); + $parameterVariables = []; + foreach ($arrowFunction->params as $parameter) { + if ($parameter->type === null) { + $parameterType = $mixed; + } else { + $isNullable = $this->isParameterValueNullable($parameter); + $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic); + } + + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableTypes[$parameter->var->name] = VariableTypeHolder::createYes($parameterType); + $parameterVariables[] = $parameter->var->name; + } + + if ($arrowFunction->static) { + unset($variableTypes['this']); + } + + $conditionalExpressions = []; + foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { + $newHolders = []; + foreach ($parameterVariables as $parameterVariable) { + $exprString = '$' . $parameterVariable; + if ($exprString === $conditionalExprString) { + continue 2; + } + } + + foreach ($holders as $holder) { + foreach ($parameterVariables as $parameterVariable) { + $exprString = '$' . $parameterVariable; + foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionalExprString2) { + if ($exprString === $conditionalExprString2) { + continue 3; + } + } + } + + $newHolders[] = $holder; + } + + if (count($newHolders) === 0) { + continue; + } + + $conditionalExpressions[$conditionalExprString] = $newHolders; + } + foreach ($parameterVariables as $parameterVariable) { + $exprString = '$' . $parameterVariable; + foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { + if ($exprString === $conditionalExprString) { + continue; + } + + $newHolders = []; + foreach ($holders as $holder) { + foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionalExprString2) { + if ($exprString === $conditionalExprString2) { + continue 2; + } + } + + $newHolders[] = $holder; + } + + if (count($newHolders) === 0) { + continue; + } + + $conditionalExpressions[$conditionalExprString] = $newHolders; + } + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $conditionalExpressions, + $this->inClosureBindScopeClass, + null, + true, + [], + [], + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function isParameterValueNullable(Node\Param $parameter): bool + { + if ($parameter->default instanceof ConstFetch) { + return strtolower((string) $parameter->default->name) === 'null'; + } + + return false; + } + + /** + * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type + * @param bool $isNullable + * @param bool $isVariadic + * @return Type + */ + public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type + { + if ($isNullable) { + return TypeCombinator::addNull( + $this->getFunctionType($type, false, $isVariadic) + ); + } + if ($isVariadic) { + return new ArrayType(new IntegerType(), $this->getFunctionType( + $type, + false, + false + )); + } + + if ($type instanceof Name) { + $className = (string) $type; + $lowercasedClassName = strtolower($className); + if ($lowercasedClassName === 'parent') { + if ($this->isInClass() && $this->getClassReflection()->getParentClass() !== false) { + return new ObjectType($this->getClassReflection()->getParentClass()->getName()); + } + + return new NonexistentParentClassType(); + } + } + + return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection()->getName() : null); + } + + public function enterForeach(Expr $iteratee, string $valueName, ?string $keyName): self + { + $iterateeType = $this->getType($iteratee); + $nativeIterateeType = $this->getNativeType($iteratee); + $scope = $this->assignVariable($valueName, $iterateeType->getIterableValueType()); + $scope->nativeExpressionTypes[sprintf('$%s', $valueName)] = $nativeIterateeType->getIterableValueType(); + + if ($keyName !== null) { + $scope = $scope->enterForeachKey($iteratee, $keyName); + } + + return $scope; + } + + public function enterForeachKey(Expr $iteratee, string $keyName): self + { + $iterateeType = $this->getType($iteratee); + $nativeIterateeType = $this->getNativeType($iteratee); + $scope = $this->assignVariable($keyName, $iterateeType->getIterableKeyType()); + $scope->nativeExpressionTypes[sprintf('$%s', $keyName)] = $nativeIterateeType->getIterableKeyType(); + + return $scope; + } + + /** + * @param \PhpParser\Node\Name[] $classes + * @param string|null $variableName + * @return self + */ + public function enterCatch(array $classes, ?string $variableName): self + { + $type = TypeCombinator::union(...array_map(static function (string $class): ObjectType { + return new ObjectType($class); + }, $classes)); + + return $this->enterCatchType($type, $variableName); + } + + public function enterCatchType(Type $catchType, ?string $variableName): self + { + if ($variableName === null) { + return $this; + } + + return $this->assignVariable( + $variableName, + TypeCombinator::intersect($catchType, new ObjectType(\Throwable::class)) + ); + } + + public function enterExpressionAssign(Expr $expr): self + { + $exprString = $this->getNodeKey($expr); + $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; + $currentlyAssignedExpressions[$exprString] = true; + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $currentlyAssignedExpressions, + $this->nativeExpressionTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function exitExpressionAssign(Expr $expr): self + { + $exprString = $this->getNodeKey($expr); + $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; + unset($currentlyAssignedExpressions[$exprString]); + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $currentlyAssignedExpressions, + $this->nativeExpressionTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function isInExpressionAssign(Expr $expr): bool + { + $exprString = $this->getNodeKey($expr); + return array_key_exists($exprString, $this->currentlyAssignedExpressions); + } + + public function assignVariable(string $variableName, Type $type, ?TrinaryLogic $certainty = null): self + { + if ($certainty === null) { + $certainty = TrinaryLogic::createYes(); + } elseif ($certainty->no()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $variableTypes = $this->getVariableTypes(); + $variableTypes[$variableName] = new VariableTypeHolder($type, $certainty); + + $nativeTypes = $this->nativeExpressionTypes; + $nativeTypes[sprintf('$%s', $variableName)] = $type; + + $variableString = $this->printer->prettyPrintExpr(new Variable($variableName)); + $moreSpecificTypeHolders = $this->moreSpecificTypes; + foreach (array_keys($moreSpecificTypeHolders) as $key) { + $matches = \Nette\Utils\Strings::matchAll((string) $key, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#'); + + if ($matches === []) { + continue; + } + + $matches = array_column($matches, 0); + + if (!in_array($variableString, $matches, true)) { + continue; + } + + unset($moreSpecificTypeHolders[$key]); + } + + $conditionalExpressions = []; + foreach ($this->conditionalExpressions as $exprString => $holders) { + $exprVariableName = '$' . $variableName; + if ($exprString === $exprVariableName) { + continue; + } + + foreach ($holders as $holder) { + foreach (array_keys($holder->getConditionExpressionTypes()) as $conditionExprString) { + if ($conditionExprString === $exprVariableName) { + continue 3; + } + } + } + + $conditionalExpressions[$exprString] = $holders; + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $moreSpecificTypeHolders, + $conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $nativeTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function unsetExpression(Expr $expr): self + { + if ($expr instanceof Variable && is_string($expr->name)) { + if ($this->hasVariableType($expr->name)->no()) { + return $this; + } + $variableTypes = $this->getVariableTypes(); + unset($variableTypes[$expr->name]); + $nativeTypes = $this->nativeExpressionTypes; + + $exprString = sprintf('$%s', $expr->name); + unset($nativeTypes[$exprString]); + + $conditionalExpressions = $this->conditionalExpressions; + unset($conditionalExpressions[$exprString]); + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + $nativeTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { + $varType = $this->getType($expr->var); + $constantArrays = TypeUtils::getConstantArrays($varType); + if (count($constantArrays) > 0) { + $unsetArrays = []; + $dimType = $this->getType($expr->dim); + foreach ($constantArrays as $constantArray) { + $unsetArrays[] = $constantArray->unsetOffset($dimType); + } + return $this->specifyExpressionType( + $expr->var, + TypeCombinator::union(...$unsetArrays) + ); + } + + $args = [new Node\Arg($expr->var)]; + + $arrays = TypeUtils::getArrays($varType); + $scope = $this; + if (count($arrays) > 0) { + $scope = $scope->specifyExpressionType($expr->var, TypeCombinator::union(...$arrays)); + } + + return $scope->invalidateExpression($expr->var) + ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), $args)) + ->invalidateExpression(new FuncCall(new Name('count'), $args)); + } + + return $this; + } + + public function specifyExpressionType(Expr $expr, Type $type, ?Type $nativeType = null): self + { + if ($expr instanceof Node\Scalar || $expr instanceof Array_) { + return $this; + } + + if ($expr instanceof ConstFetch) { + $constantTypes = $this->constantTypes; + $constantName = new FullyQualified($expr->name->toString()); + $constantTypes[$constantName->toCodeString()] = $type; + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + $exprString = $this->getNodeKey($expr); + + $scope = $this; + + if ($expr instanceof Variable && is_string($expr->name)) { + $variableName = $expr->name; + + $variableTypes = $this->getVariableTypes(); + $variableTypes[$variableName] = VariableTypeHolder::createYes($type); + + if ($nativeType === null) { + $nativeType = $type; + } + + $nativeTypes = $this->nativeExpressionTypes; + $exprString = sprintf('$%s', $variableName); + $nativeTypes[$exprString] = $nativeType; + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $nativeTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { + $constantArrays = TypeUtils::getConstantArrays($this->getType($expr->var)); + if (count($constantArrays) > 0) { + $setArrays = []; + $dimType = $this->getType($expr->dim); + foreach ($constantArrays as $constantArray) { + $setArrays[] = $constantArray->setOffsetValueType($dimType, $type); + } + $scope = $this->specifyExpressionType( + $expr->var, + TypeCombinator::union(...$setArrays) + ); + } + } + + if ($expr instanceof FuncCall && $expr->name instanceof Name && $type instanceof ConstantBooleanType && !$type->getValue()) { + $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this); + if ($functionName !== null && in_array(strtolower($functionName), [ + 'is_dir', + 'is_file', + 'file_exists', + ], true)) { + return $this; + } + } + + return $scope->addMoreSpecificTypes([ + $exprString => $type, + ]); + } + + public function assignExpression(Expr $expr, Type $type): self + { + $scope = $this; + if ($expr instanceof PropertyFetch || $expr instanceof Expr\StaticPropertyFetch) { + $scope = $this->invalidateExpression($expr); + } + + return $scope->specifyExpressionType($expr, $type); + } + + public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self + { + $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); + $moreSpecificTypeHolders = $this->moreSpecificTypes; + $nativeExpressionTypes = $this->nativeExpressionTypes; + $invalidated = false; + foreach (array_keys($moreSpecificTypeHolders) as $exprString) { + $exprString = (string) $exprString; + if (Strings::startsWith($exprString, $exprStringToInvalidate)) { + if ($exprString === $exprStringToInvalidate && $requireMoreCharacters) { + continue; + } + $nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1); + if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') === null) { + unset($moreSpecificTypeHolders[$exprString]); + unset($nativeExpressionTypes[$exprString]); + $invalidated = true; + continue; + } + } + $matches = \Nette\Utils\Strings::matchAll($exprString, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#'); + if ($matches === []) { + continue; + } + + $matches = array_column($matches, 0); + + if (!in_array($exprStringToInvalidate, $matches, true)) { + continue; + } + + unset($moreSpecificTypeHolders[$exprString]); + unset($nativeExpressionTypes[$exprString]); + $invalidated = true; + } + + if (!$invalidated) { + return $this; + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $moreSpecificTypeHolders, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $nativeExpressionTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self + { + $exprType = $this->getType($expr); + $typeAfterRemove = TypeCombinator::remove($exprType, $typeToRemove); + if ( + !$expr instanceof Variable + && $exprType->equals($typeAfterRemove) + && !$exprType instanceof ErrorType + && !$exprType instanceof NeverType + ) { + return $this; + } + $scope = $this->specifyExpressionType( + $expr, + $typeAfterRemove + ); + if ($expr instanceof Variable && is_string($expr->name)) { + $scope->nativeExpressionTypes[sprintf('$%s', $expr->name)] = TypeCombinator::remove($this->getNativeType($expr), $typeToRemove); + } + + return $scope; + } + + /** + * @param \PhpParser\Node\Expr $expr + * @return \PHPStan\Analyser\MutatingScope + */ + public function filterByTruthyValue(Expr $expr): Scope + { + $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createTruthy()); + return $this->filterBySpecifiedTypes($specifiedTypes); + } + + /** + * @param \PhpParser\Node\Expr $expr + * @return \PHPStan\Analyser\MutatingScope + */ + public function filterByFalseyValue(Expr $expr): Scope + { + $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createFalsey()); + return $this->filterBySpecifiedTypes($specifiedTypes); + } + + public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self + { + $typeSpecifications = []; + foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) { + $typeSpecifications[] = [ + 'sure' => true, + 'exprString' => $exprString, + 'expr' => $expr, + 'type' => $type, + ]; + } + foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) { + $typeSpecifications[] = [ + 'sure' => false, + 'exprString' => $exprString, + 'expr' => $expr, + 'type' => $type, + ]; + } + + usort($typeSpecifications, static function (array $a, array $b): int { + $length = strlen((string) $a['exprString']) - strlen((string) $b['exprString']); + if ($length !== 0) { + return $length; + } + + return $b['sure'] - $a['sure']; + }); + + $scope = $this; + $typeGuards = []; + $skipVariables = []; + $saveConditionalVariables = []; + foreach ($typeSpecifications as $typeSpecification) { + $expr = $typeSpecification['expr']; + $type = $typeSpecification['type']; + $originalExprType = $this->getType($expr); + if ($typeSpecification['sure']) { + $scope = $scope->specifyExpressionType($expr, $specifiedTypes->shouldOverwrite() ? $type : TypeCombinator::intersect($type, $originalExprType)); + + if ($expr instanceof Variable && is_string($expr->name)) { + $scope->nativeExpressionTypes[sprintf('$%s', $expr->name)] = $specifiedTypes->shouldOverwrite() ? $type : TypeCombinator::intersect($type, $this->getNativeType($expr)); + } + } else { + $scope = $scope->removeTypeFromExpression($expr, $type); + } + + if ( + !$expr instanceof Variable + || !is_string($expr->name) + || $specifiedTypes->shouldOverwrite() + ) { + $match = \Nette\Utils\Strings::match((string) $typeSpecification['exprString'], '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#'); + if ($match !== null) { + $skipVariables[$match[1]] = true; + } + continue; + } + + if ($scope->hasVariableType($expr->name)->no()) { + continue; + } + + $saveConditionalVariables[$expr->name] = $scope->getVariableType($expr->name); + } + + foreach ($saveConditionalVariables as $variableName => $typeGuard) { + if (array_key_exists($variableName, $skipVariables)) { + continue; + } + + $typeGuards['$' . $variableName] = $typeGuard; + } + + $newConditionalExpressions = $specifiedTypes->getNewConditionalExpressionHolders(); + foreach ($this->conditionalExpressions as $variableExprString => $conditionalExpressions) { + if (array_key_exists($variableExprString, $typeGuards)) { + continue; + } + + $typeHolder = null; + + $variableName = substr($variableExprString, 1); + foreach ($conditionalExpressions as $conditionalExpression) { + $matchingConditions = []; + foreach ($conditionalExpression->getConditionExpressionTypes() as $conditionExprString => $conditionalType) { + if (!array_key_exists($conditionExprString, $typeGuards)) { + continue; + } + + if (!$typeGuards[$conditionExprString]->equals($conditionalType)) { + continue 2; + } + + $matchingConditions[$conditionExprString] = $conditionalType; + } + + if (count($matchingConditions) === 0) { + $newConditionalExpressions[$variableExprString][$conditionalExpression->getKey()] = $conditionalExpression; + continue; + } + + if (count($matchingConditions) < count($conditionalExpression->getConditionExpressionTypes())) { + $filteredConditions = $conditionalExpression->getConditionExpressionTypes(); + foreach (array_keys($matchingConditions) as $conditionExprString) { + unset($filteredConditions[$conditionExprString]); + } + + $holder = new ConditionalExpressionHolder($filteredConditions, $conditionalExpression->getTypeHolder()); + $newConditionalExpressions[$variableExprString][$holder->getKey()] = $holder; + continue; + } + + $typeHolder = $conditionalExpression->getTypeHolder(); + break; + } + + if ($typeHolder === null) { + continue; + } + + if ($typeHolder->getCertainty()->no()) { + unset($scope->variableTypes[$variableName]); + } else { + $scope->variableTypes[$variableName] = $typeHolder; + } + } + + return $scope->changeConditionalExpressions($newConditionalExpressions); + } + + /** + * @param array $newConditionalExpressionHolders + * @return self + */ + public function changeConditionalExpressions(array $newConditionalExpressionHolders): self + { + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->variableTypes, + $this->moreSpecificTypes, + $newConditionalExpressionHolders, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + /** + * @param string $exprString + * @param ConditionalExpressionHolder[] $conditionalExpressionHolders + * @return self + */ + public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): self + { + $conditionalExpressions = $this->conditionalExpressions; + $conditionalExpressions[$exprString] = $conditionalExpressionHolders; + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->variableTypes, + $this->moreSpecificTypes, + $conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function exitFirstLevelStatements(): self + { + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + false, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function isInFirstLevelStatement(): bool + { + return $this->inFirstLevelStatement; + } + + /** + * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod + * @param Type[] $types + * @return self + */ + private function addMoreSpecificTypes(array $types): self + { + $moreSpecificTypeHolders = $this->moreSpecificTypes; + foreach ($types as $exprString => $type) { + $moreSpecificTypeHolders[$exprString] = VariableTypeHolder::createYes($type); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $moreSpecificTypeHolders, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->nativeExpressionTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function mergeWith(?self $otherScope): self + { + if ($otherScope === null) { + return $this; + } + + $variableHolderToType = static function (VariableTypeHolder $holder): Type { + return $holder->getType(); + }; + $typeToVariableHolder = static function (Type $type): VariableTypeHolder { + return new VariableTypeHolder($type, TrinaryLogic::createYes()); + }; + + $filterVariableHolders = static function (VariableTypeHolder $holder): bool { + return $holder->getCertainty()->yes(); + }; + + $ourVariableTypes = $this->getVariableTypes(); + $theirVariableTypes = $otherScope->getVariableTypes(); + if ($this->canAnyVariableExist()) { + foreach (array_keys($theirVariableTypes) as $name) { + if (array_key_exists($name, $ourVariableTypes)) { + continue; + } + + $ourVariableTypes[$name] = VariableTypeHolder::createMaybe(new MixedType()); + } + + foreach (array_keys($ourVariableTypes) as $name) { + if (array_key_exists($name, $theirVariableTypes)) { + continue; + } + + $theirVariableTypes[$name] = VariableTypeHolder::createMaybe(new MixedType()); + } + } + + $mergedVariableHolders = $this->mergeVariableHolders($ourVariableTypes, $theirVariableTypes); + $conditionalExpressions = $this->intersectConditionalExpressions($otherScope->conditionalExpressions); + $conditionalExpressions = $this->createConditionalExpressions( + $conditionalExpressions, + $ourVariableTypes, + $theirVariableTypes, + $mergedVariableHolders + ); + $conditionalExpressions = $this->createConditionalExpressions( + $conditionalExpressions, + $theirVariableTypes, + $ourVariableTypes, + $mergedVariableHolders + ); + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + array_map($variableHolderToType, array_filter($this->mergeVariableHolders( + array_map($typeToVariableHolder, $this->constantTypes), + array_map($typeToVariableHolder, $otherScope->constantTypes) + ), $filterVariableHolders)), + $this->getFunction(), + $this->getNamespace(), + $mergedVariableHolders, + $this->mergeVariableHolders($this->moreSpecificTypes, $otherScope->moreSpecificTypes), + $conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + array_map($variableHolderToType, array_filter($this->mergeVariableHolders( + array_map($typeToVariableHolder, $this->nativeExpressionTypes), + array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) + ), $filterVariableHolders)), + [], + $this->afterExtractCall && $otherScope->afterExtractCall, + $this->parentScope + ); + } + + /** + * @param array $otherConditionalExpressions + * @return array + */ + private function intersectConditionalExpressions(array $otherConditionalExpressions): array + { + $newConditionalExpressions = []; + foreach ($this->conditionalExpressions as $exprString => $holders) { + if (!array_key_exists($exprString, $otherConditionalExpressions)) { + continue; + } + + $otherHolders = $otherConditionalExpressions[$exprString]; + foreach (array_keys($holders) as $key) { + if (!array_key_exists($key, $otherHolders)) { + continue 2; + } + } + + $newConditionalExpressions[$exprString] = $holders; + } + + return $newConditionalExpressions; + } + + /** + * @param array $conditionalExpressions + * @param array $variableTypes + * @param array $theirVariableTypes + * @param array $mergedVariableHolders + * @return array + */ + private function createConditionalExpressions( + array $conditionalExpressions, + array $variableTypes, + array $theirVariableTypes, + array $mergedVariableHolders + ): array { + $newVariableTypes = $variableTypes; + foreach ($theirVariableTypes as $name => $holder) { + if (!array_key_exists($name, $mergedVariableHolders)) { + continue; + } + + if (!$mergedVariableHolders[$name]->getType()->equals($holder->getType())) { + continue; + } + + unset($newVariableTypes[$name]); + } + + $typeGuards = []; + foreach ($newVariableTypes as $name => $holder) { + if (!$holder->getCertainty()->yes()) { + continue; + } + if (!array_key_exists($name, $mergedVariableHolders)) { + continue; + } + if ($mergedVariableHolders[$name]->getType()->equals($holder->getType())) { + continue; + } + + $typeGuards['$' . $name] = $holder->getType(); + } + + if (count($typeGuards) === 0) { + return $conditionalExpressions; + } + + foreach ($newVariableTypes as $name => $holder) { + if ( + array_key_exists($name, $mergedVariableHolders) + && $mergedVariableHolders[$name]->equals($holder) + ) { + continue; + } + + $exprString = '$' . $name; + $variableTypeGuards = $typeGuards; + unset($variableTypeGuards[$exprString]); + + if (count($variableTypeGuards) === 0) { + continue; + } + + $conditionalExpression = new ConditionalExpressionHolder($variableTypeGuards, $holder); + $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression; + } + + foreach (array_keys($mergedVariableHolders) as $name) { + if (array_key_exists($name, $variableTypes)) { + continue; + } + + $conditionalExpression = new ConditionalExpressionHolder($typeGuards, new VariableTypeHolder(new ErrorType(), TrinaryLogic::createNo())); + $conditionalExpressions['$' . $name][$conditionalExpression->getKey()] = $conditionalExpression; + } + + return $conditionalExpressions; + } + + /** + * @param VariableTypeHolder[] $ourVariableTypeHolders + * @param VariableTypeHolder[] $theirVariableTypeHolders + * @return VariableTypeHolder[] + */ + private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array + { + $intersectedVariableTypeHolders = []; + foreach ($ourVariableTypeHolders as $name => $variableTypeHolder) { + if (isset($theirVariableTypeHolders[$name])) { + $intersectedVariableTypeHolders[$name] = $variableTypeHolder->and($theirVariableTypeHolders[$name]); + } else { + $intersectedVariableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); + } + } + + foreach ($theirVariableTypeHolders as $name => $variableTypeHolder) { + if (isset($intersectedVariableTypeHolders[$name])) { + continue; + } + + $intersectedVariableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); + } + + return $intersectedVariableTypeHolders; + } + + public function processFinallyScope(self $finallyScope, self $originalFinallyScope): self + { + $variableHolderToType = static function (VariableTypeHolder $holder): Type { + return $holder->getType(); + }; + $typeToVariableHolder = static function (Type $type): VariableTypeHolder { + return new VariableTypeHolder($type, TrinaryLogic::createYes()); + }; + $filterVariableHolders = static function (VariableTypeHolder $holder): bool { + return $holder->getCertainty()->yes(); + }; + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + array_map($variableHolderToType, array_filter($this->processFinallyScopeVariableTypeHolders( + array_map($typeToVariableHolder, $this->constantTypes), + array_map($typeToVariableHolder, $finallyScope->constantTypes), + array_map($typeToVariableHolder, $originalFinallyScope->constantTypes) + ), $filterVariableHolders)), + $this->getFunction(), + $this->getNamespace(), + $this->processFinallyScopeVariableTypeHolders( + $this->getVariableTypes(), + $finallyScope->getVariableTypes(), + $originalFinallyScope->getVariableTypes() + ), + $this->processFinallyScopeVariableTypeHolders( + $this->moreSpecificTypes, + $finallyScope->moreSpecificTypes, + $originalFinallyScope->moreSpecificTypes + ), + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + array_map($variableHolderToType, array_filter($this->processFinallyScopeVariableTypeHolders( + array_map($typeToVariableHolder, $this->nativeExpressionTypes), + array_map($typeToVariableHolder, $finallyScope->nativeExpressionTypes), + array_map($typeToVariableHolder, $originalFinallyScope->nativeExpressionTypes) + ), $filterVariableHolders)), + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + /** + * @param VariableTypeHolder[] $ourVariableTypeHolders + * @param VariableTypeHolder[] $finallyVariableTypeHolders + * @param VariableTypeHolder[] $originalVariableTypeHolders + * @return VariableTypeHolder[] + */ + private function processFinallyScopeVariableTypeHolders( + array $ourVariableTypeHolders, + array $finallyVariableTypeHolders, + array $originalVariableTypeHolders + ): array { + foreach ($finallyVariableTypeHolders as $name => $variableTypeHolder) { + if ( + isset($originalVariableTypeHolders[$name]) + && !$originalVariableTypeHolders[$name]->getType()->equals($variableTypeHolder->getType()) + ) { + $ourVariableTypeHolders[$name] = $variableTypeHolder; + continue; + } + + if (isset($originalVariableTypeHolders[$name])) { + continue; + } + + $ourVariableTypeHolders[$name] = $variableTypeHolder; + } + + return $ourVariableTypeHolders; + } + + /** + * @param self $closureScope + * @param self|null $prevScope + * @param Expr\ClosureUse[] $byRefUses + * @return self + */ + public function processClosureScope( + self $closureScope, + ?self $prevScope, + array $byRefUses + ): self { + $nativeExpressionTypes = $this->nativeExpressionTypes; + $variableTypes = $this->variableTypes; + if (count($byRefUses) === 0) { + return $this; + } + + foreach ($byRefUses as $use) { + if (!is_string($use->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableName = $use->var->name; + + if (!$closureScope->hasVariableType($variableName)->yes()) { + $variableTypes[$variableName] = VariableTypeHolder::createYes(new NullType()); + $nativeExpressionTypes[sprintf('$%s', $variableName)] = new NullType(); + continue; + } + + $variableType = $closureScope->getVariableType($variableName); + + if ($prevScope !== null) { + $prevVariableType = $prevScope->getVariableType($variableName); + if (!$variableType->equals($prevVariableType)) { + $variableType = TypeCombinator::union($variableType, $prevVariableType); + $variableType = self::generalizeType($variableType, $prevVariableType); + } + } + + $variableTypes[$variableName] = VariableTypeHolder::createYes($variableType); + $nativeExpressionTypes[sprintf('$%s', $variableName)] = $variableType; + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypes, + $this->moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + $nativeExpressionTypes, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope + ); + } + + public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope): self + { + $variableTypeHolders = $this->variableTypes; + $nativeTypes = $this->nativeExpressionTypes; + foreach ($finalScope->variableTypes as $name => $variableTypeHolder) { + $nativeTypes[sprintf('$%s', $name)] = $variableTypeHolder->getType(); + if (!isset($variableTypeHolders[$name])) { + $variableTypeHolders[$name] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); + continue; + } + + $variableTypeHolders[$name] = new VariableTypeHolder( + $variableTypeHolder->getType(), + $variableTypeHolder->getCertainty()->and($variableTypeHolders[$name]->getCertainty()) + ); + } + + $moreSpecificTypes = $this->moreSpecificTypes; + foreach ($finalScope->moreSpecificTypes as $exprString => $variableTypeHolder) { + if (!isset($moreSpecificTypes[$exprString])) { + $moreSpecificTypes[$exprString] = VariableTypeHolder::createMaybe($variableTypeHolder->getType()); + continue; + } + + $moreSpecificTypes[$exprString] = new VariableTypeHolder( + $variableTypeHolder->getType(), + $variableTypeHolder->getCertainty()->and($moreSpecificTypes[$exprString]->getCertainty()) + ); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $variableTypeHolders, + $moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + $nativeTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + public function generalizeWith(self $otherScope): self + { + $variableTypeHolders = $this->generalizeVariableTypeHolders( + $this->getVariableTypes(), + $otherScope->getVariableTypes() + ); + + $moreSpecificTypes = $this->generalizeVariableTypeHolders( + $this->moreSpecificTypes, + $otherScope->moreSpecificTypes + ); + + $variableHolderToType = static function (VariableTypeHolder $holder): Type { + return $holder->getType(); + }; + $typeToVariableHolder = static function (Type $type): VariableTypeHolder { + return new VariableTypeHolder($type, TrinaryLogic::createYes()); + }; + $filterVariableHolders = static function (VariableTypeHolder $holder): bool { + return $holder->getCertainty()->yes(); + }; + $nativeTypes = array_map($variableHolderToType, array_filter($this->generalizeVariableTypeHolders( + array_map($typeToVariableHolder, $this->nativeExpressionTypes), + array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) + ), $filterVariableHolders)); + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + array_map($variableHolderToType, array_filter($this->generalizeVariableTypeHolders( + array_map($typeToVariableHolder, $this->constantTypes), + array_map($typeToVariableHolder, $otherScope->constantTypes) + ), $filterVariableHolders)), + $this->getFunction(), + $this->getNamespace(), + $variableTypeHolders, + $moreSpecificTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + $nativeTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + + /** + * @param VariableTypeHolder[] $variableTypeHolders + * @param VariableTypeHolder[] $otherVariableTypeHolders + * @return VariableTypeHolder[] + */ + private function generalizeVariableTypeHolders( + array $variableTypeHolders, + array $otherVariableTypeHolders + ): array { + foreach ($variableTypeHolders as $name => $variableTypeHolder) { + if (!isset($otherVariableTypeHolders[$name])) { + continue; + } + + $variableTypeHolders[$name] = new VariableTypeHolder( + self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$name]->getType()), + $variableTypeHolder->getCertainty() + ); + } + + return $variableTypeHolders; + } + + private static function generalizeType(Type $a, Type $b): Type + { + if ($a->equals($b)) { + return $a; + } + + $constantIntegers = ['a' => [], 'b' => []]; + $constantFloats = ['a' => [], 'b' => []]; + $constantBooleans = ['a' => [], 'b' => []]; + $constantStrings = ['a' => [], 'b' => []]; + $constantArrays = ['a' => [], 'b' => []]; + $generalArrays = ['a' => [], 'b' => []]; + $otherTypes = []; + + foreach ([ + 'a' => TypeUtils::flattenTypes($a), + 'b' => TypeUtils::flattenTypes($b), + ] as $key => $types) { + foreach ($types as $type) { + if ($type instanceof ConstantIntegerType) { + $constantIntegers[$key][] = $type; + continue; + } + if ($type instanceof ConstantFloatType) { + $constantFloats[$key][] = $type; + continue; + } + if ($type instanceof ConstantBooleanType) { + $constantBooleans[$key][] = $type; + continue; + } + if ($type instanceof ConstantStringType) { + $constantStrings[$key][] = $type; + continue; + } + if ($type instanceof ConstantArrayType) { + $constantArrays[$key][] = $type; + continue; + } + if ($type->isArray()->yes()) { + $generalArrays[$key][] = $type; + continue; + } + + $otherTypes[] = $type; + } + } + + $resultTypes = []; + foreach ([ + $constantIntegers, + $constantFloats, + $constantBooleans, + $constantStrings, + ] as $constantTypes) { + if (count($constantTypes['a']) === 0) { + continue; + } + if (count($constantTypes['b']) === 0) { + $resultTypes[] = TypeCombinator::union(...$constantTypes['a']); + continue; + } + + $aTypes = TypeCombinator::union(...$constantTypes['a']); + $bTypes = TypeCombinator::union(...$constantTypes['b']); + if ($aTypes->equals($bTypes)) { + $resultTypes[] = $aTypes; + continue; + } + + $resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0]); + } + + if (count($constantArrays['a']) > 0) { + if (count($constantArrays['b']) === 0) { + $resultTypes[] = TypeCombinator::union(...$constantArrays['a']); + } else { + $constantArraysA = TypeCombinator::union(...$constantArrays['a']); + $constantArraysB = TypeCombinator::union(...$constantArrays['b']); + if ($constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType())) { + $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { + $resultArrayBuilder->setOffsetValueType( + $keyType, + self::generalizeType( + $constantArraysA->getOffsetValueType($keyType), + $constantArraysB->getOffsetValueType($keyType) + ) + ); + } + + $resultTypes[] = $resultArrayBuilder->getArray(); + } else { + $resultTypes[] = new ArrayType( + TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType())), + TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType())) + ); + } + } + } + + if (count($generalArrays['a']) > 0) { + if (count($generalArrays['b']) === 0) { + $resultTypes[] = TypeCombinator::union(...$generalArrays['a']); + } else { + $generalArraysA = TypeCombinator::union(...$generalArrays['a']); + $generalArraysB = TypeCombinator::union(...$generalArrays['b']); + + $aValueType = $generalArraysA->getIterableValueType(); + $bValueType = $generalArraysB->getIterableValueType(); + $aArrays = TypeUtils::getAnyArrays($aValueType); + $bArrays = TypeUtils::getAnyArrays($bValueType); + if ( + count($aArrays) === 1 + && !$aArrays[0] instanceof ConstantArrayType + && count($bArrays) === 1 + && !$bArrays[0] instanceof ConstantArrayType + ) { + $aDepth = self::getArrayDepth($aArrays[0]); + $bDepth = self::getArrayDepth($bArrays[0]); + if ( + ($aDepth > 2 || $bDepth > 2) + && abs($aDepth - $bDepth) > 0 + ) { + $aValueType = new MixedType(); + $bValueType = new MixedType(); + } + } + + $resultTypes[] = new ArrayType( + TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType())), + TypeCombinator::union(self::generalizeType($aValueType, $bValueType)) + ); + } + } + + return TypeCombinator::union(...$resultTypes, ...$otherTypes); + } + + private static function getArrayDepth(ArrayType $type): int + { + $depth = 0; + while ($type instanceof ArrayType) { + $temp = $type->getIterableValueType(); + $arrays = TypeUtils::getAnyArrays($temp); + if (count($arrays) === 1) { + $type = $arrays[0]; + } else { + $type = $temp; + } + $depth++; + } + + return $depth; + } + + public function equals(self $otherScope): bool + { + if (!$this->context->equals($otherScope->context)) { + return false; + } + + if (!$this->compareVariableTypeHolders($this->variableTypes, $otherScope->variableTypes)) { + return false; + } + + if (!$this->compareVariableTypeHolders($this->moreSpecificTypes, $otherScope->moreSpecificTypes)) { + return false; + } + + $typeToVariableHolder = static function (Type $type): VariableTypeHolder { + return new VariableTypeHolder($type, TrinaryLogic::createYes()); + }; + + $nativeExpressionTypesResult = $this->compareVariableTypeHolders( + array_map($typeToVariableHolder, $this->nativeExpressionTypes), + array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes) + ); + + if (!$nativeExpressionTypesResult) { + return false; + } + + return $this->compareVariableTypeHolders( + array_map($typeToVariableHolder, $this->constantTypes), + array_map($typeToVariableHolder, $otherScope->constantTypes) + ); + } + + /** + * @param VariableTypeHolder[] $variableTypeHolders + * @param VariableTypeHolder[] $otherVariableTypeHolders + * @return bool + */ + private function compareVariableTypeHolders(array $variableTypeHolders, array $otherVariableTypeHolders): bool + { + if (count($variableTypeHolders) !== count($otherVariableTypeHolders)) { + return false; + } + foreach ($variableTypeHolders as $name => $variableTypeHolder) { + if (!isset($otherVariableTypeHolders[$name])) { + return false; + } + + if (!$variableTypeHolder->getCertainty()->equals($otherVariableTypeHolders[$name]->getCertainty())) { + return false; + } + + if (!$variableTypeHolder->getType()->equals($otherVariableTypeHolders[$name]->getType())) { + return false; + } + + unset($otherVariableTypeHolders[$name]); + } + + return true; + } + + public function canAccessProperty(PropertyReflection $propertyReflection): bool + { + return $this->canAccessClassMember($propertyReflection); + } + + public function canCallMethod(MethodReflection $methodReflection): bool + { + if ($this->canAccessClassMember($methodReflection)) { + return true; + } + + return $this->canAccessClassMember($methodReflection->getPrototype()); + } + + public function canAccessConstant(ConstantReflection $constantReflection): bool + { + return $this->canAccessClassMember($constantReflection); + } + + private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool + { + if ($classMemberReflection->isPublic()) { + return true; + } + + if ($this->inClosureBindScopeClass !== null && $this->reflectionProvider->hasClass($this->inClosureBindScopeClass)) { + $currentClassReflection = $this->reflectionProvider->getClass($this->inClosureBindScopeClass); + } elseif ($this->isInClass()) { + $currentClassReflection = $this->getClassReflection(); + } else { + return false; + } + + $classReflectionName = $classMemberReflection->getDeclaringClass()->getName(); + if ($classMemberReflection->isPrivate()) { + return $currentClassReflection->getName() === $classReflectionName; + } + + // protected + + if ( + $currentClassReflection->getName() === $classReflectionName + || $currentClassReflection->isSubclassOf($classReflectionName) + ) { + return true; + } + + return $classMemberReflection->getDeclaringClass()->isSubclassOf($currentClassReflection->getName()); + } + + /** + * @return string[] + */ + public function debug(): array + { + $descriptions = []; + foreach ($this->getVariableTypes() as $name => $variableTypeHolder) { + $key = sprintf('$%s (%s)', $name, $variableTypeHolder->getCertainty()->describe()); + $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise()); + } + foreach ($this->moreSpecificTypes as $exprString => $typeHolder) { + $key = sprintf( + '%s-specified (%s)', + $exprString, + $typeHolder->getCertainty()->describe() + ); + $descriptions[$key] = $typeHolder->getType()->describe(VerbosityLevel::precise()); + } + foreach ($this->constantTypes as $name => $type) { + $key = sprintf('const %s', $name); + $descriptions[$key] = $type->describe(VerbosityLevel::precise()); + } + foreach ($this->nativeExpressionTypes as $exprString => $nativeType) { + $key = sprintf('native %s', $exprString); + $descriptions[$key] = $nativeType->describe(VerbosityLevel::precise()); + } + + return $descriptions; + } + + private function exactInstantiation(New_ $node, string $className): ?Type + { + $resolvedClassName = $this->resolveExactName(new Name($className)); + if ($resolvedClassName === null) { + return null; + } + + if (!$this->reflectionProvider->hasClass($resolvedClassName)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($resolvedClassName); + if ($classReflection->hasConstructor()) { + $constructorMethod = $classReflection->getConstructor(); + } else { + $constructorMethod = new DummyConstructorReflection($classReflection); + } + + $resolvedTypes = []; + $methodCall = new Expr\StaticCall( + new Name($resolvedClassName), + new Node\Identifier($constructorMethod->getName()), + $node->args + ); + + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) { + if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) { + continue; + } + + $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($constructorMethod, $methodCall, $this); + } + + if (count($resolvedTypes) > 0) { + return TypeCombinator::union(...$resolvedTypes); + } + + $methodResult = $this->getType($methodCall); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return $methodResult; + } + + $objectType = new ObjectType($resolvedClassName); + if (!$classReflection->isGeneric()) { + return $objectType; + } + + $parentNode = $node->getAttribute('parent'); + if ( + ( + $parentNode instanceof Expr\Assign + || $parentNode instanceof Expr\AssignRef + ) + && $parentNode->var instanceof PropertyFetch + ) { + $constructorVariant = ParametersAcceptorSelector::selectSingle($constructorMethod->getVariants()); + $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); + $originalClassTemplateTypes = $classTemplateTypes; + foreach ($constructorVariant->getParameters() as $parameter) { + TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type { + if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) { + $classTemplateType = $classTemplateTypes[$type->getName()]; + if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) { + unset($classTemplateTypes[$type->getName()]); + } + return $type; + } + + return $traverse($type); + }); + } + + if (count($classTemplateTypes) === count($originalClassTemplateTypes)) { + $propertyType = $this->getType($parentNode->var); + if ($objectType->isSuperTypeOf($propertyType)->yes()) { + return $propertyType; + } + } + } + + if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()) + ); + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $this, + $methodCall->args, + $constructorMethod->getVariants() + ); + + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()) + ); + } + + private function getTypeToInstantiateForNew(Type $type): Type + { + $decideType = static function (Type $type): ?Type { + if ($type instanceof ConstantStringType) { + return new ObjectType($type->getValue()); + } + if ($type instanceof TypeWithClassName) { + return $type; + } + if ($type instanceof GenericClassStringType) { + return $type->getGenericType(); + } + return null; + }; + + if ($type instanceof UnionType) { + $types = []; + foreach ($type->getTypes() as $innerType) { + $decidedType = $decideType($innerType); + if ($decidedType === null) { + if ($this->objectFromNewClass) { + return new ObjectWithoutClassType(); + } + + return new MixedType(false, new StringType()); + } + + $types[] = $decidedType; + } + + return TypeCombinator::union(...$types); + } + + $decidedType = $decideType($type); + if ($decidedType === null) { + if ($this->objectFromNewClass) { + return new ObjectWithoutClassType(); + } + + return new MixedType(false, new StringType()); + } + + return $decidedType; + } + + public function getMethodReflection(Type $typeWithMethod, string $methodName): ?MethodReflection + { + if ($typeWithMethod instanceof UnionType) { + $newTypes = []; + foreach ($typeWithMethod->getTypes() as $innerType) { + if (!$innerType->hasMethod($methodName)->yes()) { + continue; + } + + $newTypes[] = $innerType; + } + if (count($newTypes) === 0) { + return null; + } + $typeWithMethod = TypeCombinator::union(...$newTypes); + } + + if (!$typeWithMethod->hasMethod($methodName)->yes()) { + return null; + } + + return $typeWithMethod->getMethod($methodName, $this); + } + + /** + * @param \PHPStan\Type\Type $typeWithMethod + * @param string $methodName + * @param MethodCall|\PhpParser\Node\Expr\StaticCall $methodCall + * @return \PHPStan\Type\Type|null + */ + private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type + { + $methodReflection = $this->getMethodReflection($typeWithMethod, $methodName); + if ($methodReflection === null) { + return null; + } + + $resolvedTypes = []; + foreach (TypeUtils::getDirectClassNames($typeWithMethod) as $className) { + if ($methodCall instanceof MethodCall) { + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) { + if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) { + continue; + } + + $resolvedTypes[] = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $methodCall, $this); + } + } else { + foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { + if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) { + continue; + } + + $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($methodReflection, $methodCall, $this); + } + } + } + + if (count($resolvedTypes) > 0) { + return TypeCombinator::union(...$resolvedTypes); + } + + return ParametersAcceptorSelector::selectFromArgs( + $this, + $methodCall->args, + $methodReflection->getVariants() + )->getReturnType(); + } + + public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection + { + if ($typeWithProperty instanceof UnionType) { + $newTypes = []; + foreach ($typeWithProperty->getTypes() as $innerType) { + if (!$innerType->hasProperty($propertyName)->yes()) { + continue; + } + + $newTypes[] = $innerType; + } + if (count($newTypes) === 0) { + return null; + } + $typeWithProperty = TypeCombinator::union(...$newTypes); + } + if (!$typeWithProperty->hasProperty($propertyName)->yes()) { + return null; + } + + return $typeWithProperty->getProperty($propertyName, $this); + } + + /** + * @param \PHPStan\Type\Type $fetchedOnType + * @param string $propertyName + * @param PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch + * @return \PHPStan\Type\Type|null + */ + private function propertyFetchType(Type $fetchedOnType, string $propertyName, Expr $propertyFetch): ?Type + { + $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName); + if ($propertyReflection === null) { + return null; + } + + if ($this->isInExpressionAssign($propertyFetch)) { + return $propertyReflection->getWritableType(); + } + + return $propertyReflection->getReadableType(); + } } diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index ad0c2bcab7..30bc0032b4 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -1,4 +1,6 @@ - alias(string) => fullName(string) */ - private array $uses; - - private ?string $className; - - private ?string $functionName; - - private TemplateTypeMap $templateTypeMap; - - /** @var array */ - private array $typeAliasesMap; - - private bool $bypassTypeAliases; - - /** - * @param string|null $namespace - * @param array $uses alias(string) => fullName(string) - * @param string|null $className - * @param array $typeAliasesMap - */ - public function __construct(?string $namespace, array $uses, ?string $className = null, ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, array $typeAliasesMap = [], bool $bypassTypeAliases = false) - { - $this->namespace = $namespace; - $this->uses = $uses; - $this->className = $className; - $this->functionName = $functionName; - $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty(); - $this->typeAliasesMap = $typeAliasesMap; - $this->bypassTypeAliases = $bypassTypeAliases; - } - - public function getNamespace(): ?string - { - return $this->namespace; - } - - /** - * @return array - */ - public function getUses(): array - { - return $this->uses; - } - - public function hasUseAlias(string $name): bool - { - return isset($this->uses[strtolower($name)]); - } - - public function getClassName(): ?string - { - return $this->className; - } - - public function resolveStringName(string $name): string - { - if (strpos($name, '\\') === 0) { - return ltrim($name, '\\'); - } - - $nameParts = explode('\\', $name); - $firstNamePart = strtolower($nameParts[0]); - if (isset($this->uses[$firstNamePart])) { - if (count($nameParts) === 1) { - return $this->uses[$firstNamePart]; - } - array_shift($nameParts); - return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); - } - - if ($this->namespace !== null) { - return sprintf('%s\\%s', $this->namespace, $name); - } - - return $name; - } - - public function getTemplateTypeScope(): ?TemplateTypeScope - { - if ($this->className !== null) { - if ($this->functionName !== null) { - return TemplateTypeScope::createWithMethod($this->className, $this->functionName); - } - - return TemplateTypeScope::createWithClass($this->className); - } - - if ($this->functionName !== null) { - return TemplateTypeScope::createWithFunction($this->functionName); - } - - return null; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->templateTypeMap; - } - - public function resolveTemplateTypeName(string $name): ?Type - { - return $this->templateTypeMap->getType($name); - } - - public function withTemplateTypeMap(TemplateTypeMap $map): self - { - if ($map->isEmpty()) { - return $this; - } - - return new self( - $this->namespace, - $this->uses, - $this->className, - $this->functionName, - new TemplateTypeMap(array_merge( - $this->templateTypeMap->getTypes(), - $map->getTypes() - )), - $this->typeAliasesMap - ); - } - - public function unsetTemplateType(string $name): self - { - $map = $this->templateTypeMap; - if (!$map->hasType($name)) { - return $this; - } - - return new self( - $this->namespace, - $this->uses, - $this->className, - $this->functionName, - $this->templateTypeMap->unsetType($name), - $this->typeAliasesMap - ); - } - - public function bypassTypeAliases(): self - { - return new self($this->namespace, $this->uses, $this->className, $this->functionName, $this->templateTypeMap, $this->typeAliasesMap, true); - } - - public function shouldBypassTypeAliases(): bool - { - return $this->bypassTypeAliases; - } - - public function hasTypeAlias(string $alias): bool - { - return array_key_exists($alias, $this->typeAliasesMap); - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['namespace'], - $properties['uses'], - $properties['className'], - $properties['functionName'], - $properties['templateTypeMap'], - $properties['typeAliasesMap'] - ); - } - + private ?string $namespace; + + /** @var array alias(string) => fullName(string) */ + private array $uses; + + private ?string $className; + + private ?string $functionName; + + private TemplateTypeMap $templateTypeMap; + + /** @var array */ + private array $typeAliasesMap; + + private bool $bypassTypeAliases; + + /** + * @param string|null $namespace + * @param array $uses alias(string) => fullName(string) + * @param string|null $className + * @param array $typeAliasesMap + */ + public function __construct(?string $namespace, array $uses, ?string $className = null, ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, array $typeAliasesMap = [], bool $bypassTypeAliases = false) + { + $this->namespace = $namespace; + $this->uses = $uses; + $this->className = $className; + $this->functionName = $functionName; + $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty(); + $this->typeAliasesMap = $typeAliasesMap; + $this->bypassTypeAliases = $bypassTypeAliases; + } + + public function getNamespace(): ?string + { + return $this->namespace; + } + + /** + * @return array + */ + public function getUses(): array + { + return $this->uses; + } + + public function hasUseAlias(string $name): bool + { + return isset($this->uses[strtolower($name)]); + } + + public function getClassName(): ?string + { + return $this->className; + } + + public function resolveStringName(string $name): string + { + if (strpos($name, '\\') === 0) { + return ltrim($name, '\\'); + } + + $nameParts = explode('\\', $name); + $firstNamePart = strtolower($nameParts[0]); + if (isset($this->uses[$firstNamePart])) { + if (count($nameParts) === 1) { + return $this->uses[$firstNamePart]; + } + array_shift($nameParts); + return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts)); + } + + if ($this->namespace !== null) { + return sprintf('%s\\%s', $this->namespace, $name); + } + + return $name; + } + + public function getTemplateTypeScope(): ?TemplateTypeScope + { + if ($this->className !== null) { + if ($this->functionName !== null) { + return TemplateTypeScope::createWithMethod($this->className, $this->functionName); + } + + return TemplateTypeScope::createWithClass($this->className); + } + + if ($this->functionName !== null) { + return TemplateTypeScope::createWithFunction($this->functionName); + } + + return null; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->templateTypeMap; + } + + public function resolveTemplateTypeName(string $name): ?Type + { + return $this->templateTypeMap->getType($name); + } + + public function withTemplateTypeMap(TemplateTypeMap $map): self + { + if ($map->isEmpty()) { + return $this; + } + + return new self( + $this->namespace, + $this->uses, + $this->className, + $this->functionName, + new TemplateTypeMap(array_merge( + $this->templateTypeMap->getTypes(), + $map->getTypes() + )), + $this->typeAliasesMap + ); + } + + public function unsetTemplateType(string $name): self + { + $map = $this->templateTypeMap; + if (!$map->hasType($name)) { + return $this; + } + + return new self( + $this->namespace, + $this->uses, + $this->className, + $this->functionName, + $this->templateTypeMap->unsetType($name), + $this->typeAliasesMap + ); + } + + public function bypassTypeAliases(): self + { + return new self($this->namespace, $this->uses, $this->className, $this->functionName, $this->templateTypeMap, $this->typeAliasesMap, true); + } + + public function shouldBypassTypeAliases(): bool + { + return $this->bypassTypeAliases; + } + + public function hasTypeAlias(string $alias): bool + { + return array_key_exists($alias, $this->typeAliasesMap); + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['namespace'], + $properties['uses'], + $properties['className'], + $properties['functionName'], + $properties['templateTypeMap'], + $properties['typeAliasesMap'] + ); + } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index dd99a89ac7..5c9bf56588 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1,4 +1,6 @@ - methods(string[]) */ - private array $earlyTerminatingMethodCalls; - - /** @var array */ - private array $earlyTerminatingFunctionCalls; - - private bool $implicitThrows; - - private bool $preciseExceptionTracking; - - /** @var bool[] filePath(string) => bool(true) */ - private array $analysedFiles = []; - - /** - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param ClassReflector $classReflector - * @param Parser $parser - * @param FileTypeMapper $fileTypeMapper - * @param PhpDocInheritanceResolver $phpDocInheritanceResolver - * @param FileHelper $fileHelper - * @param TypeSpecifier $typeSpecifier - * @param bool $polluteScopeWithLoopInitialAssignments - * @param bool $polluteCatchScopeWithTryAssignments - * @param bool $polluteScopeWithAlwaysIterableForeach - * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) - * @param array $earlyTerminatingFunctionCalls - * @param bool $implicitThrows - * @param bool $preciseExceptionTracking - */ - public function __construct( - ReflectionProvider $reflectionProvider, - ClassReflector $classReflector, - ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, - Parser $parser, - FileTypeMapper $fileTypeMapper, - PhpVersion $phpVersion, - PhpDocInheritanceResolver $phpDocInheritanceResolver, - FileHelper $fileHelper, - TypeSpecifier $typeSpecifier, - DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, - bool $polluteScopeWithLoopInitialAssignments, - bool $polluteCatchScopeWithTryAssignments, - bool $polluteScopeWithAlwaysIterableForeach, - array $earlyTerminatingMethodCalls, - array $earlyTerminatingFunctionCalls, - bool $implicitThrows, - bool $preciseExceptionTracking - ) - { - $this->reflectionProvider = $reflectionProvider; - $this->classReflector = $classReflector; - $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; - $this->parser = $parser; - $this->fileTypeMapper = $fileTypeMapper; - $this->phpVersion = $phpVersion; - $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; - $this->fileHelper = $fileHelper; - $this->typeSpecifier = $typeSpecifier; - $this->dynamicThrowTypeExtensionProvider = $dynamicThrowTypeExtensionProvider; - $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; - $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; - $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; - $this->earlyTerminatingMethodCalls = $earlyTerminatingMethodCalls; - $this->earlyTerminatingFunctionCalls = $earlyTerminatingFunctionCalls; - $this->implicitThrows = $implicitThrows; - $this->preciseExceptionTracking = $preciseExceptionTracking; - } - - /** - * @param string[] $files - */ - public function setAnalysedFiles(array $files): void - { - $this->analysedFiles = array_fill_keys($files, true); - } - - /** - * @param \PhpParser\Node[] $nodes - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - */ - public function processNodes( - array $nodes, - MutatingScope $scope, - callable $nodeCallback - ): void - { - $nodesCount = count($nodes); - foreach ($nodes as $i => $node) { - if (!$node instanceof Node\Stmt) { - continue; - } - - $statementResult = $this->processStmtNode($node, $scope, $nodeCallback); - $scope = $statementResult->getScope(); - if (!$statementResult->isAlwaysTerminating()) { - continue; - } - - if ($i < $nodesCount - 1) { - $nextStmt = $nodes[$i + 1]; - if (!$nextStmt instanceof Node\Stmt) { - continue; - } - - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); - } - break; - } - } - - /** - * @param \PhpParser\Node $parentNode - * @param \PhpParser\Node\Stmt[] $stmts - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @return StatementResult - */ - public function processStmtNodes( - Node $parentNode, - array $stmts, - MutatingScope $scope, - callable $nodeCallback - ): StatementResult - { - $exitPoints = []; - $throwPoints = []; - $alreadyTerminated = false; - $hasYield = false; - $stmtCount = count($stmts); - $shouldCheckLastStatement = $parentNode instanceof Node\Stmt\Function_ - || $parentNode instanceof Node\Stmt\ClassMethod - || $parentNode instanceof Expr\Closure; - foreach ($stmts as $i => $stmt) { - $isLast = $i === $stmtCount - 1; - $statementResult = $this->processStmtNode( - $stmt, - $scope, - $nodeCallback - ); - $scope = $statementResult->getScope(); - $hasYield = $hasYield || $statementResult->hasYield(); - - if ($shouldCheckLastStatement && $isLast) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ - $parentNode = $parentNode; - $nodeCallback(new ExecutionEndNode( - $stmt, - new StatementResult( - $scope, - $hasYield, - $statementResult->isAlwaysTerminating(), - $statementResult->getExitPoints(), - $statementResult->getThrowPoints() - ), - $parentNode->returnType !== null - ), $scope); - } - - $exitPoints = array_merge($exitPoints, $statementResult->getExitPoints()); - $throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints()); - - if (!$statementResult->isAlwaysTerminating()) { - continue; - } - - $alreadyTerminated = true; - if ($i < $stmtCount - 1) { - $nextStmt = $stmts[$i + 1]; - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); - } - break; - } - - $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints); - if ($stmtCount === 0 && $shouldCheckLastStatement) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ - $parentNode = $parentNode; - $nodeCallback(new ExecutionEndNode( - $parentNode, - $statementResult, - $parentNode->returnType !== null - ), $scope); - } - - return $statementResult; - } - - /** - * @param \PhpParser\Node\Stmt $stmt - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @return StatementResult - */ - private function processStmtNode( - Node\Stmt $stmt, - MutatingScope $scope, - callable $nodeCallback - ): StatementResult - { - if ( - $stmt instanceof Throw_ - || $stmt instanceof Return_ - ) { - $scope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr); - } elseif ( - !$stmt instanceof Static_ - && !$stmt instanceof Foreach_ - && !$stmt instanceof Node\Stmt\Global_ - ) { - $scope = $this->processStmtVarAnnotation($scope, $stmt, null); - } - - if ($stmt instanceof Node\Stmt\ClassMethod) { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ( - $scope->isInTrait() - && $scope->getClassReflection()->hasNativeMethod($stmt->name->toString()) - ) { - $methodReflection = $scope->getClassReflection()->getNativeMethod($stmt->name->toString()); - if ($methodReflection instanceof NativeMethodReflection) { - return new StatementResult($scope, false, false, [], []); - } - if ($methodReflection instanceof PhpMethodReflection) { - $declaringTrait = $methodReflection->getDeclaringTrait(); - if ($declaringTrait === null || $declaringTrait->getName() !== $scope->getTraitReflection()->getName()) { - return new StatementResult($scope, false, false, [], []); - } - } - } - } - - $nodeCallback($stmt, $scope); - - $overridingThrowPoints = $this->getOverridingThrowPoints($stmt, $scope); - - if ($stmt instanceof Node\Stmt\Declare_) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->declares as $declare) { - $nodeCallback($declare, $scope); - $nodeCallback($declare->value, $scope); - if ( - $declare->key->name !== 'strict_types' - || !($declare->value instanceof Node\Scalar\LNumber) - || $declare->value->value !== 1 - ) { - continue; - } - - $scope = $scope->enterDeclareStrictTypes(); - } - } elseif ($stmt instanceof Node\Stmt\Function_) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); - } - } - } - [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); - - foreach ($stmt->params as $param) { - $this->processParamNode($param, $scope, $nodeCallback); - } - - if ($stmt->returnType !== null) { - $nodeCallback($stmt->returnType, $scope); - } - - $functionScope = $scope->enterFunction( - $stmt, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure - ); - $nodeCallback(new InFunctionNode($stmt), $functionScope); - - $gatheredReturnStatements = []; - $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements): void { - $nodeCallback($node, $scope); - if ($scope->getFunction() !== $functionScope->getFunction()) { - return; - } - if ($scope->isInAnonymousFunction()) { - return; - } - if (!$node instanceof Return_) { - return; - } - - $gatheredReturnStatements[] = new ReturnStatement($scope, $node); - }); - - $nodeCallback(new FunctionReturnStatementsNode( - $stmt, - $gatheredReturnStatements, - $statementResult - ), $functionScope); - } elseif ($stmt instanceof Node\Stmt\ClassMethod) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); - } - } - } - [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); - - foreach ($stmt->params as $param) { - $this->processParamNode($param, $scope, $nodeCallback); - } - - if ($stmt->returnType !== null) { - $nodeCallback($stmt->returnType, $scope); - } - - $methodScope = $scope->enterClassMethod( - $stmt, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure - ); - - if ($stmt->name->toLowerString() === '__construct') { - foreach ($stmt->params as $param) { - if ($param->flags === 0) { - continue; - } - - if (!$param->var instanceof Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $phpDoc = null; - if ($param->getDocComment() !== null) { - $phpDoc = $param->getDocComment()->getText(); - } - $nodeCallback(new ClassPropertyNode( - $param->var->name, - $param->flags, - $param->type, - $param->default, - $phpDoc, - true, - $param - ), $methodScope); - } - } - - if ($stmt->getAttribute('virtual', false) === false) { - $nodeCallback(new InClassMethodNode($stmt), $methodScope); - } - - if ($stmt->stmts !== null) { - $gatheredReturnStatements = []; - $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements): void { - $nodeCallback($node, $scope); - if ($scope->getFunction() !== $methodScope->getFunction()) { - return; - } - if ($scope->isInAnonymousFunction()) { - return; - } - if (!$node instanceof Return_) { - return; - } - - $gatheredReturnStatements[] = new ReturnStatement($scope, $node); - }); - $nodeCallback(new MethodReturnStatementsNode( - $stmt, - $gatheredReturnStatements, - $statementResult - ), $methodScope); - } - } elseif ($stmt instanceof Echo_) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->exprs as $echoExpr) { - $result = $this->processExprNode($echoExpr, $scope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - } - - $throwPoints = $overridingThrowPoints ?? $throwPoints; - } elseif ($stmt instanceof Return_) { - if ($stmt->expr !== null) { - $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - } else { - $hasYield = false; - $throwPoints = []; - } - - return new StatementResult($scope, $hasYield, true, [ - new StatementExitPoint($stmt, $scope), - ], $overridingThrowPoints ?? $throwPoints); - } elseif ($stmt instanceof Continue_ || $stmt instanceof Break_) { - if ($stmt->num !== null) { - $result = $this->processExprNode($stmt->num, $scope, $nodeCallback, ExpressionContext::createDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - } else { - $hasYield = false; - $throwPoints = []; - } - - return new StatementResult($scope, $hasYield, true, [ - new StatementExitPoint($stmt, $scope), - ], $overridingThrowPoints ?? $throwPoints); - } elseif ($stmt instanceof Node\Stmt\Expression) { - $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); - $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createTopLevel()); - $scope = $result->getScope(); - $scope = $scope->filterBySpecifiedTypes($this->typeSpecifier->specifyTypesInCondition( - $scope, - $stmt->expr, - TypeSpecifierContext::createNull() - )); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - if ($earlyTerminationExpr !== null) { - return new StatementResult($scope, $hasYield, true, [ - new StatementExitPoint($stmt, $scope), - ], $overridingThrowPoints ?? $throwPoints); - } - return new StatementResult($scope, $hasYield, false, [], $overridingThrowPoints ?? $throwPoints); - } elseif ($stmt instanceof Node\Stmt\Namespace_) { - if ($stmt->name !== null) { - $scope = $scope->enterNamespace($stmt->name->toString()); - } - - $scope = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback)->getScope(); - $hasYield = false; - $throwPoints = []; - } elseif ($stmt instanceof Node\Stmt\Trait_) { - return new StatementResult($scope, false, false, [], []); - } elseif ($stmt instanceof Node\Stmt\ClassLike) { - $hasYield = false; - $throwPoints = []; - if (isset($stmt->namespacedName)) { - $classReflection = $this->getCurrentClassReflection($stmt, $scope); - $classScope = $scope->enterClass($classReflection); - $nodeCallback(new InClassNode($stmt, $classReflection), $classScope); - } elseif ($stmt instanceof Class_) { - if ($stmt->name === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($stmt->getAttribute('anonymousClass', false) === false) { - $classReflection = $this->reflectionProvider->getClass($stmt->name->toString()); - } else { - $classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope); - } - $classScope = $scope->enterClass($classReflection); - $nodeCallback(new InClassNode($stmt, $classReflection), $classScope); - } else { - throw new \PHPStan\ShouldNotHappenException(); - } - - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $classScope); - } - } - } - - $classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); - $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer); - $nodeCallback(new ClassPropertiesNode($stmt, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope); - $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls()), $classScope); - $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches()), $classScope); - } elseif ($stmt instanceof Node\Stmt\Property) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); - } - } - } - foreach ($stmt->props as $prop) { - $this->processStmtNode($prop, $scope, $nodeCallback); - $docComment = $stmt->getDocComment(); - $nodeCallback( - new ClassPropertyNode( - $prop->name->toString(), - $stmt->flags, - $stmt->type, - $prop->default, - $docComment !== null ? $docComment->getText() : null, - false, - $prop - ), - $scope - ); - } - - if ($stmt->type !== null) { - $nodeCallback($stmt->type, $scope); - } - } elseif ($stmt instanceof Node\Stmt\PropertyProperty) { - $hasYield = false; - $throwPoints = []; - if ($stmt->default !== null) { - $this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } elseif ($stmt instanceof Throw_) { - $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = $result->getThrowPoints(); - $throwPoints[] = ThrowPoint::createExplicit($result->getScope(), $scope->getType($stmt->expr), $stmt, false); - return new StatementResult($result->getScope(), $result->hasYield(), true, [ - new StatementExitPoint($stmt, $scope), - ], $throwPoints); - } elseif ($stmt instanceof If_) { - $conditionType = $scope->getType($stmt->cond)->toBoolean(); - $ifAlwaysTrue = $conditionType instanceof ConstantBooleanType && $conditionType->getValue(); - $condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); - $exitPoints = []; - $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); - $finalScope = null; - $alwaysTerminating = true; - $hasYield = $condResult->hasYield(); - - $branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback); - - if (!$conditionType instanceof ConstantBooleanType || $conditionType->getValue()) { - $exitPoints = $branchScopeStatementResult->getExitPoints(); - $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); - $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; - $alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); - $hasYield = $branchScopeStatementResult->hasYield() || $hasYield; - } - - $scope = $condResult->getFalseyScope(); - $lastElseIfConditionIsTrue = false; - - $condScope = $scope; - foreach ($stmt->elseifs as $elseif) { - $nodeCallback($elseif, $scope); - $elseIfConditionType = $condScope->getType($elseif->cond)->toBoolean(); - $condResult = $this->processExprNode($elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); - $condScope = $condResult->getScope(); - $branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback); - - if ( - !$ifAlwaysTrue - && ( - !$lastElseIfConditionIsTrue - && ( - !$elseIfConditionType instanceof ConstantBooleanType - || $elseIfConditionType->getValue() - ) - ) - ) { - $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); - $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); - $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); - $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); - $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); - } - - if ( - $elseIfConditionType instanceof ConstantBooleanType - && $elseIfConditionType->getValue() - ) { - $lastElseIfConditionIsTrue = true; - } - - $condScope = $condScope->filterByFalseyValue($elseif->cond); - $scope = $condScope; - } - - if ($stmt->else === null) { - if (!$ifAlwaysTrue) { - $finalScope = $scope->mergeWith($finalScope); - $alwaysTerminating = false; - } - } else { - $nodeCallback($stmt->else, $scope); - $branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback); - - if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { - $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); - $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); - $branchScope = $branchScopeStatementResult->getScope(); - $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); - $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); - $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); - } - } - - if ($finalScope === null) { - $finalScope = $scope; - } - - return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints); - } elseif ($stmt instanceof Node\Stmt\TraitUse) { - $hasYield = false; - $throwPoints = []; - $this->processTraitUse($stmt, $scope, $nodeCallback); - } elseif ($stmt instanceof Foreach_) { - $condResult = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); - $scope = $condResult->getScope(); - $arrayComparisonExpr = new BinaryOp\NotIdentical( - $stmt->expr, - new Array_([]) - ); - $bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt); - $count = 0; - do { - $prevScope = $bodyScope; - $bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); - $bodyScope = $this->enterForeach($bodyScope, $stmt); - $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { - })->filterOutLoopExitPoints(); - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - $bodyScope = $bodyScopeResult->getScope(); - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } - if ($bodyScope->equals($prevScope)) { - break; - } - - if ($count >= self::GENERALIZE_AFTER_ITERATION) { - $bodyScope = $bodyScope->generalizeWith($prevScope); - } - $count++; - } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); - - $bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); - $bodyScope = $this->enterForeach($bodyScope, $stmt); - $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); - $finalScope = $finalScopeResult->getScope(); - foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); - } - foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); - } - - $isIterableAtLeastOnce = $scope->getType($stmt->expr)->isIterableAtLeastOnce(); - if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { - $finalScope = $scope; - } elseif ($isIterableAtLeastOnce->maybe()) { - if ($this->polluteScopeWithAlwaysIterableForeach) { - $finalScope = $finalScope->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr)); - } else { - $finalScope = $finalScope->mergeWith($scope); - } - } elseif (!$this->polluteScopeWithAlwaysIterableForeach) { - $finalScope = $scope->processAlwaysIterableForeachScopeWithoutPollute($finalScope); - // get types from finalScope, but don't create new variables - } - - if (!$isIterableAtLeastOnce->no()) { - $throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); - } - - return new StatementResult( - $finalScope, - $finalScopeResult->hasYield() || $condResult->hasYield(), - $isIterableAtLeastOnce->yes() && $finalScopeResult->isAlwaysTerminating(), - $finalScopeResult->getExitPointsForOuterLoop(), - $throwPoints - ); - } elseif ($stmt instanceof While_) { - $condResult = $this->processExprNode($stmt->cond, $scope, static function (): void { - }, ExpressionContext::createDeep()); - $bodyScope = $condResult->getTruthyScope(); - $count = 0; - do { - $prevScope = $bodyScope; - $bodyScope = $bodyScope->mergeWith($scope); - $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); - $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { - })->filterOutLoopExitPoints(); - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - $bodyScope = $bodyScopeResult->getScope(); - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } - if ($bodyScope->equals($prevScope)) { - break; - } - - if ($count >= self::GENERALIZE_AFTER_ITERATION) { - $bodyScope = $bodyScope->generalizeWith($prevScope); - } - $count++; - } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); - - $bodyScope = $bodyScope->mergeWith($scope); - $bodyScopeMaybeRan = $bodyScope; - $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); - $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); - $finalScope = $finalScopeResult->getScope(); - foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); - } - $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); - foreach ($breakExitPoints as $breakExitPoint) { - $finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); - } - - $beforeCondBooleanType = $scope->getType($stmt->cond)->toBoolean(); - $condBooleanType = $bodyScopeMaybeRan->getType($stmt->cond)->toBoolean(); - $isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); - $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); - $neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); - - if ($alwaysIterates) { - $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; - } elseif ($isIterableAtLeastOnce) { - $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); - } else { - $isAlwaysTerminating = false; - } - $condScope = $condResult->getFalseyScope(); - if (!$isIterableAtLeastOnce) { - if (!$this->polluteScopeWithLoopInitialAssignments) { - $condScope = $condScope->mergeWith($scope); - } - $finalScope = $finalScope->mergeWith($condScope); - } - - $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); - if (!$neverIterates) { - $throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); - } - - return new StatementResult( - $finalScope, - $finalScopeResult->hasYield() || $condResult->hasYield(), - $isAlwaysTerminating, - $finalScopeResult->getExitPointsForOuterLoop(), - $throwPoints - ); - } elseif ($stmt instanceof Do_) { - $finalScope = null; - $bodyScope = $scope; - $count = 0; - $hasYield = false; - $throwPoints = []; - - do { - $prevScope = $bodyScope; - $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { - })->filterOutLoopExitPoints(); - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - $bodyScope = $bodyScopeResult->getScope(); - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } - $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); - foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); - } - $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); - if ($bodyScope->equals($prevScope)) { - break; - } - - if ($count >= self::GENERALIZE_AFTER_ITERATION) { - $bodyScope = $bodyScope->generalizeWith($prevScope); - } - $count++; - } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); - - $bodyScope = $bodyScope->mergeWith($scope); - - $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); - $bodyScope = $bodyScopeResult->getScope(); - $condBooleanType = $bodyScope->getType($stmt->cond)->toBoolean(); - $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); - - if ($alwaysIterates) { - $alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0; - } else { - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - } - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } - $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); - if ($finalScope === null) { - $finalScope = $scope; - } - if (!$alwaysTerminating) { - $condResult = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); - $hasYield = $condResult->hasYield(); - $throwPoints = $condResult->getThrowPoints(); - $finalScope = $condResult->getFalseyScope(); - } - foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); - } - - return new StatementResult( - $finalScope, - $bodyScopeResult->hasYield() || $hasYield, - $alwaysTerminating, - $bodyScopeResult->getExitPointsForOuterLoop(), - array_merge($throwPoints, $bodyScopeResult->getThrowPoints()) - ); - } elseif ($stmt instanceof For_) { - $initScope = $scope; - $hasYield = false; - $throwPoints = []; - foreach ($stmt->init as $initExpr) { - $initResult = $this->processExprNode($initExpr, $initScope, $nodeCallback, ExpressionContext::createTopLevel()); - $initScope = $initResult->getScope(); - $hasYield = $hasYield || $initResult->hasYield(); - $throwPoints = array_merge($throwPoints, $initResult->getThrowPoints()); - } - - $bodyScope = $initScope; - foreach ($stmt->cond as $condExpr) { - $condResult = $this->processExprNode($condExpr, $bodyScope, static function (): void { - }, ExpressionContext::createDeep()); - $hasYield = $hasYield || $condResult->hasYield(); - $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); - $bodyScope = $condResult->getTruthyScope(); - } - - $count = 0; - do { - $prevScope = $bodyScope; - $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($condExpr, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); - } - $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { - })->filterOutLoopExitPoints(); - $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); - $bodyScope = $bodyScopeResult->getScope(); - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } - foreach ($stmt->loop as $loopExpr) { - $exprResult = $this->processExprNode($loopExpr, $bodyScope, static function (): void { - }, ExpressionContext::createTopLevel()); - $bodyScope = $exprResult->getScope(); - $hasYield = $hasYield || $exprResult->hasYield(); - $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); - } - - if ($bodyScope->equals($prevScope)) { - break; - } - - if ($count >= self::GENERALIZE_AFTER_ITERATION) { - $bodyScope = $bodyScope->generalizeWith($prevScope); - } - $count++; - } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); - - $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); - } - - $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); - $finalScope = $finalScopeResult->getScope(); - foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); - } - foreach ($stmt->loop as $loopExpr) { - $finalScope = $this->processExprNode($loopExpr, $finalScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); - } - foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); - } - - if ($this->polluteScopeWithLoopInitialAssignments) { - $scope = $initScope; - } - - $finalScope = $finalScope->mergeWith($scope); - - return new StatementResult( - $finalScope, - $finalScopeResult->hasYield() || $hasYield, - false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/, - $finalScopeResult->getExitPointsForOuterLoop(), - array_merge($throwPoints, $finalScopeResult->getThrowPoints()) - ); - } elseif ($stmt instanceof Switch_) { - $condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); - $scope = $condResult->getScope(); - $scopeForBranches = $scope; - $finalScope = null; - $prevScope = null; - $hasDefaultCase = false; - $alwaysTerminating = true; - $hasYield = $condResult->hasYield(); - $exitPointsForOuterLoop = []; - $throwPoints = $condResult->getThrowPoints(); - foreach ($stmt->cases as $caseNode) { - if ($caseNode->cond !== null) { - $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); - $caseResult = $this->processExprNode($caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); - $scopeForBranches = $caseResult->getScope(); - $hasYield = $hasYield || $caseResult->hasYield(); - $throwPoints = array_merge($throwPoints, $caseResult->getThrowPoints()); - $branchScope = $scopeForBranches->filterByTruthyValue($condExpr); - } else { - $hasDefaultCase = true; - $branchScope = $scopeForBranches; - } - - $branchScope = $branchScope->mergeWith($prevScope); - $branchScopeResult = $this->processStmtNodes($caseNode, $caseNode->stmts, $branchScope, $nodeCallback); - $branchScope = $branchScopeResult->getScope(); - $branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); - $hasYield = $hasYield || $branchFinalScopeResult->hasYield(); - foreach ($branchScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $alwaysTerminating = false; - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); - } - foreach ($branchScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); - } - $exitPointsForOuterLoop = array_merge($exitPointsForOuterLoop, $branchFinalScopeResult->getExitPointsForOuterLoop()); - $throwPoints = array_merge($throwPoints, $branchFinalScopeResult->getThrowPoints()); - if ($branchScopeResult->isAlwaysTerminating()) { - $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); - $prevScope = null; - if (isset($condExpr)) { - $scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr); - } - if (!$branchFinalScopeResult->isAlwaysTerminating()) { - $finalScope = $branchScope->mergeWith($finalScope); - } - } else { - $prevScope = $branchScope; - } - } - - if (!$hasDefaultCase) { - $alwaysTerminating = false; - } - - if ($prevScope !== null && isset($branchFinalScopeResult)) { - $finalScope = $prevScope->mergeWith($finalScope); - $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); - } - - if (!$hasDefaultCase || $finalScope === null) { - $finalScope = $scope->mergeWith($finalScope); - } - - return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints); - } elseif ($stmt instanceof TryCatch) { - $branchScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback); - $branchScope = $branchScopeResult->getScope(); - $finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; - - $exitPoints = []; - $alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); - $hasYield = $branchScopeResult->hasYield(); - - if ($stmt->finally !== null) { - $finallyScope = $branchScope; - } else { - $finallyScope = null; - } - foreach ($branchScopeResult->getExitPoints() as $exitPoint) { - if ($exitPoint->getStatement() instanceof Throw_) { - continue; - } - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); - } - $exitPoints[] = $exitPoint; - } - - $throwPoints = $branchScopeResult->getThrowPoints(); - $throwPointsForLater = []; - $pastCatchTypes = new NeverType(); - - foreach ($stmt->catches as $catchNode) { - $nodeCallback($catchNode, $scope); - - if ($this->preciseExceptionTracking || !$this->polluteCatchScopeWithTryAssignments) { - $catchType = TypeCombinator::union(...array_map(static function (Name $name): Type { - return new ObjectType($name->toString()); - }, $catchNode->types)); - $originalCatchType = $catchType; - $catchType = TypeCombinator::remove($catchType, $pastCatchTypes); - $pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); - $matchingThrowPoints = []; - $newThrowPoints = []; - foreach ($throwPoints as $throwPoint) { - if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { - continue; - } - $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); - if ($isSuperType->no()) { - continue; - } - $matchingThrowPoints[] = $throwPoint; - } - $hasExplicit = count($matchingThrowPoints) > 0; - foreach ($throwPoints as $throwPoint) { - $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); - if (!$hasExplicit && !$isSuperType->no()) { - $matchingThrowPoints[] = $throwPoint; - } - if ($isSuperType->yes()) { - continue; - } - $newThrowPoints[] = $throwPoint->subtractCatchType($catchType); - } - $throwPoints = $newThrowPoints; - - if (count($matchingThrowPoints) === 0) { - $throwableThrowPoints = []; - if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { - foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { - if (!$originalThrowPoint->canContainAnyThrowable()) { - continue; - } - - $throwableThrowPoints[] = $originalThrowPoint; - } - } - - if (count($throwableThrowPoints) === 0) { - $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType), $scope); - continue; - } - - $matchingThrowPoints = $throwableThrowPoints; - } - - $catchScope = null; - foreach ($matchingThrowPoints as $matchingThrowPoint) { - if ($catchScope === null) { - $catchScope = $matchingThrowPoint->getScope(); - } else { - $catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); - } - } - - $variableName = null; - if ($catchNode->var !== null) { - if (!is_string($catchNode->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableName = $catchNode->var->name; - } - - $catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback); - $catchScopeForFinally = $catchScopeResult->getScope(); - } else { - $initialScope = $scope; - if (count($throwPoints) > 0) { - $initialScope = $throwPoints[0]->getScope(); - } - - $catchScopeForFinally = $this->processCatchNode($catchNode, $branchScope, $nodeCallback)->getScope(); - $catchScopeResult = $this->processCatchNode($catchNode, $initialScope->mergeWith($branchScope), static function (): void { - }); - } - - $finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); - $alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); - $hasYield = $hasYield || $catchScopeResult->hasYield(); - $catchThrowPoints = $catchScopeResult->getThrowPoints(); - $throwPointsForLater = array_merge($throwPointsForLater, $catchThrowPoints); - - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($catchScopeForFinally); - } - foreach ($catchScopeResult->getExitPoints() as $exitPoint) { - if ($exitPoint->getStatement() instanceof Throw_) { - continue; - } - if ($finallyScope !== null) { - $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); - } - $exitPoints[] = $exitPoint; - } - - foreach ($catchThrowPoints as $catchThrowPoint) { - if ($finallyScope === null) { - continue; - } - $finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()); - } - } - - if ($finalScope === null) { - $finalScope = $scope; - } - - foreach ($throwPoints as $throwPoint) { - if ($finallyScope === null) { - continue; - } - $finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); - } - - if ($finallyScope !== null && $stmt->finally !== null) { - $originalFinallyScope = $finallyScope; - $finallyResult = $this->processStmtNodes($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback); - $alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); - $hasYield = $hasYield || $finallyResult->hasYield(); - $throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); - $finallyScope = $finallyResult->getScope(); - $finalScope = $finallyResult->isAlwaysTerminating() ? $finalScope : $finalScope->processFinallyScope($finallyScope, $originalFinallyScope); - if (count($finallyResult->getExitPoints()) > 0) { - $nodeCallback(new FinallyExitPointsNode( - $finallyResult->getExitPoints(), - $exitPoints - ), $scope); - } - $exitPoints = array_merge($exitPoints, $finallyResult->getExitPoints()); - } - - return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, array_merge($throwPoints, $throwPointsForLater)); - } elseif ($stmt instanceof Unset_) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->vars as $var) { - $scope = $this->lookForEnterVariableAssign($scope, $var); - $scope = $this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep())->getScope(); - $scope = $this->lookForExitVariableAssign($scope, $var); - $scope = $scope->unsetExpression($var); - } - } elseif ($stmt instanceof Node\Stmt\Use_) { - $hasYield = false; - $throwPoints = []; - foreach ($stmt->uses as $use) { - $this->processStmtNode($use, $scope, $nodeCallback); - } - } elseif ($stmt instanceof Node\Stmt\Global_) { - $hasYield = false; - $throwPoints = []; - $vars = []; - foreach ($stmt->vars as $var) { - if (!$var instanceof Variable) { - throw new \PHPStan\ShouldNotHappenException(); - } - $scope = $this->lookForEnterVariableAssign($scope, $var); - $this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep()); - $scope = $this->lookForExitVariableAssign($scope, $var); - - if (!is_string($var->name)) { - continue; - } - - $scope = $scope->assignVariable($var->name, new MixedType()); - $vars[] = $var->name; - } - $scope = $this->processVarAnnotation($scope, $vars, $stmt); - } elseif ($stmt instanceof Static_) { - $hasYield = false; - $throwPoints = []; - - $vars = []; - foreach ($stmt->vars as $var) { - $scope = $this->processStmtNode($var, $scope, $nodeCallback)->getScope(); - if (!is_string($var->var->name)) { - continue; - } - - $vars[] = $var->var->name; - } - - $scope = $this->processVarAnnotation($scope, $vars, $stmt); - } elseif ($stmt instanceof StaticVar) { - $hasYield = false; - $throwPoints = []; - if (!is_string($stmt->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($stmt->default !== null) { - $this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - $scope = $scope->enterExpressionAssign($stmt->var); - $this->processExprNode($stmt->var, $scope, $nodeCallback, ExpressionContext::createDeep()); - $scope = $scope->exitExpressionAssign($stmt->var); - $scope = $scope->assignVariable($stmt->var->name, new MixedType()); - } elseif ($stmt instanceof Node\Stmt\Const_ || $stmt instanceof Node\Stmt\ClassConst) { - $hasYield = false; - $throwPoints = []; - if ($stmt instanceof Node\Stmt\ClassConst) { - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); - } - } - } - } - foreach ($stmt->consts as $const) { - $nodeCallback($const, $scope); - $this->processExprNode($const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - if ($scope->getNamespace() !== null) { - $constName = [$scope->getNamespace(), $const->name->toString()]; - } else { - $constName = $const->name->toString(); - } - $scope = $scope->specifyExpressionType(new ConstFetch(new Name\FullyQualified($constName)), $scope->getType($const->value)); - } - } elseif ($stmt instanceof Node\Stmt\Nop) { - $scope = $this->processStmtVarAnnotation($scope, $stmt, null); - $hasYield = false; - $throwPoints = $overridingThrowPoints ?? []; - } else { - $hasYield = false; - $throwPoints = $overridingThrowPoints ?? []; - } - - return new StatementResult($scope, $hasYield, false, [], $throwPoints); - } - - /** - * @param Node\Stmt $statement - * @param MutatingScope $scope - * @return ThrowPoint[]|null - */ - private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $scope): ?array - { - foreach ($statement->getComments() as $comment) { - if (!$comment instanceof Doc) { - continue; - } - - $function = $scope->getFunction(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $comment->getText() - ); - - $throwsTag = $resolvedPhpDoc->getThrowsTag(); - if ($throwsTag !== null) { - return [ThrowPoint::createExplicit($scope, $throwsTag->getType(), $statement, false)]; - } - } - - return null; - } - - private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, Scope $scope): ClassReflection - { - $className = $stmt->namespacedName->toString(); - if (!$this->reflectionProvider->hasClass($className)) { - return $this->createAstClassReflection($stmt, $scope); - } - - $defaultClassReflection = $this->reflectionProvider->getClass($stmt->namespacedName->toString()); - if ($defaultClassReflection->getFileName() !== $scope->getFile()) { - return $this->createAstClassReflection($stmt, $scope); - } - - $startLine = $defaultClassReflection->getNativeReflection()->getStartLine(); - if ($startLine !== $stmt->getStartLine()) { - return $this->createAstClassReflection($stmt, $scope); - } - - return $defaultClassReflection; - } - - private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scope): ClassReflection - { - $nodeToReflection = new NodeToReflection(); - $betterReflectionClass = $nodeToReflection->__invoke( - $this->classReflector, - $stmt, - new LocatedSource(FileReader::read($scope->getFile()), $scope->getFile()), - $scope->getNamespace() !== null ? new Node\Stmt\Namespace_(new Name($scope->getNamespace())) : null - ); - if (!$betterReflectionClass instanceof \PHPStan\BetterReflection\Reflection\ReflectionClass) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return new ClassReflection( - $this->reflectionProvider, - $this->fileTypeMapper, - $this->phpVersion, - $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), - $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), - $betterReflectionClass->getName(), - new ReflectionClass($betterReflectionClass), - null, - null, - null, - sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()) - ); - } - - /** - * @param Node\Stmt\Catch_ $catchNode - * @param MutatingScope $catchScope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @return StatementResult - */ - private function processCatchNode( - Node\Stmt\Catch_ $catchNode, - MutatingScope $catchScope, - callable $nodeCallback - ): StatementResult - { - $variableName = null; - if ($catchNode->var !== null) { - if (!is_string($catchNode->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableName = $catchNode->var->name; - } - - $catchScope = $catchScope->enterCatch($catchNode->types, $variableName); - return $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope, $nodeCallback); - } - - private function lookForEnterVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope - { - if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) { - $scope = $scope->enterExpressionAssign($expr); - } - if (!$expr instanceof Variable) { - return $this->lookForVariableAssignCallback($scope, $expr, static function (MutatingScope $scope, Expr $expr): MutatingScope { - return $scope->enterExpressionAssign($expr); - }); - } - - return $scope; - } - - private function lookForExitVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope - { - $scope = $scope->exitExpressionAssign($expr); - if (!$expr instanceof Variable) { - return $this->lookForVariableAssignCallback($scope, $expr, static function (MutatingScope $scope, Expr $expr): MutatingScope { - return $scope->exitExpressionAssign($expr); - }); - } - - return $scope; - } - - /** - * @param MutatingScope $scope - * @param Expr $expr - * @param \Closure(MutatingScope $scope, Expr $expr): MutatingScope $callback - * @return MutatingScope - */ - private function lookForVariableAssignCallback(MutatingScope $scope, Expr $expr, \Closure $callback): MutatingScope - { - if ($expr instanceof Variable) { - $scope = $callback($scope, $expr); - } elseif ($expr instanceof ArrayDimFetch) { - while ($expr instanceof ArrayDimFetch) { - $expr = $expr->var; - } - - $scope = $this->lookForVariableAssignCallback($scope, $expr, $callback); - } elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) { - $scope = $this->lookForVariableAssignCallback($scope, $expr->var, $callback); - } elseif ($expr instanceof StaticPropertyFetch) { - if ($expr->class instanceof Expr) { - $scope = $this->lookForVariableAssignCallback($scope, $expr->class, $callback); - } - } elseif ($expr instanceof Array_ || $expr instanceof List_) { - foreach ($expr->items as $item) { - if ($item === null) { - continue; - } - - $scope = $this->lookForVariableAssignCallback($scope, $item->value, $callback); - } - } - - return $scope; - } - - private function ensureShallowNonNullability(MutatingScope $scope, Expr $exprToSpecify): EnsuredNonNullabilityResult - { - $exprType = $scope->getType($exprToSpecify); - $exprTypeWithoutNull = TypeCombinator::removeNull($exprType); - if (!$exprType->equals($exprTypeWithoutNull)) { - $nativeType = $scope->getNativeType($exprToSpecify); - $scope = $scope->specifyExpressionType( - $exprToSpecify, - $exprTypeWithoutNull, - TypeCombinator::removeNull($nativeType) - ); - - return new EnsuredNonNullabilityResult( - $scope, - [ - new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType), - ] - ); - } - - return new EnsuredNonNullabilityResult($scope, []); - } - - private function ensureNonNullability(MutatingScope $scope, Expr $expr, bool $findMethods): EnsuredNonNullabilityResult - { - $exprToSpecify = $expr; - $specifiedExpressions = []; - while (true) { - $result = $this->ensureShallowNonNullability($scope, $exprToSpecify); - $scope = $result->getScope(); - foreach ($result->getSpecifiedExpressions() as $specifiedExpression) { - $specifiedExpressions[] = $specifiedExpression; - } - - if ($exprToSpecify instanceof PropertyFetch) { - $exprToSpecify = $exprToSpecify->var; - } elseif ($exprToSpecify instanceof StaticPropertyFetch && $exprToSpecify->class instanceof Expr) { - $exprToSpecify = $exprToSpecify->class; - } elseif ($findMethods && $exprToSpecify instanceof MethodCall) { - $exprToSpecify = $exprToSpecify->var; - } elseif ($findMethods && $exprToSpecify instanceof StaticCall && $exprToSpecify->class instanceof Expr) { - $exprToSpecify = $exprToSpecify->class; - } else { - break; - } - } - - return new EnsuredNonNullabilityResult($scope, $specifiedExpressions); - } - - /** - * @param MutatingScope $scope - * @param EnsuredNonNullabilityResultExpression[] $specifiedExpressions - * @return MutatingScope - */ - private function revertNonNullability(MutatingScope $scope, array $specifiedExpressions): MutatingScope - { - foreach ($specifiedExpressions as $specifiedExpressionResult) { - $scope = $scope->specifyExpressionType( - $specifiedExpressionResult->getExpression(), - $specifiedExpressionResult->getOriginalType(), - $specifiedExpressionResult->getOriginalNativeType() - ); - } - - return $scope; - } - - private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr - { - if (($expr instanceof MethodCall || $expr instanceof Expr\StaticCall) && $expr->name instanceof Node\Identifier) { - if (count($this->earlyTerminatingMethodCalls) > 0) { - if ($expr instanceof MethodCall) { - $methodCalledOnType = $scope->getType($expr->var); - } else { - if ($expr->class instanceof Name) { - $methodCalledOnType = $scope->resolveTypeByName($expr->class); - } else { - $methodCalledOnType = $scope->getType($expr->class); - } - } - - $directClassNames = TypeUtils::getDirectClassNames($methodCalledOnType); - foreach ($directClassNames as $referencedClass) { - if (!$this->reflectionProvider->hasClass($referencedClass)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($referencedClass); - foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { - if (!isset($this->earlyTerminatingMethodCalls[$className])) { - continue; - } - - if (in_array((string) $expr->name, $this->earlyTerminatingMethodCalls[$className], true)) { - return $expr; - } - } - } - } - } - - if ($expr instanceof FuncCall && $expr->name instanceof Name) { - if (in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { - return $expr; - } - } - - $exprType = $scope->getType($expr); - if ($exprType instanceof NeverType && $exprType->isExplicit()) { - return $expr; - } - - return null; - } - - /** - * @param \PhpParser\Node\Expr $expr - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @param \PHPStan\Analyser\ExpressionContext $context - * @return \PHPStan\Analyser\ExpressionResult - */ - private function processExprNode(Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult - { - $this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); - - if ($expr instanceof Variable) { - $hasYield = false; - $throwPoints = []; - if ($expr->name instanceof Expr) { - return $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - } - } elseif ($expr instanceof Assign || $expr instanceof AssignRef) { - if (!$expr->var instanceof Array_ && !$expr->var instanceof List_) { - $result = $this->processAssignVar( - $scope, - $expr->var, - $expr->expr, - $nodeCallback, - $context, - function (MutatingScope $scope) use ($expr, $nodeCallback, $context): ExpressionResult { - if ($expr instanceof AssignRef) { - $scope = $scope->enterExpressionAssign($expr->expr); - } - - if ($expr->var instanceof Variable && is_string($expr->var->name)) { - $context = $context->enterRightSideAssign( - $expr->var->name, - $scope->getType($expr->expr) - ); - } - - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - - if ($expr instanceof AssignRef) { - $scope = $scope->exitExpressionAssign($expr->expr); - } - - return new ExpressionResult($scope, $hasYield, $throwPoints); - }, - true - ); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $varChangedScope = false; - if ($expr->var instanceof Variable && is_string($expr->var->name)) { - $scope = $this->processVarAnnotation($scope, [$expr->var->name], $expr, $varChangedScope); - } - - if (!$varChangedScope) { - $scope = $this->processStmtVarAnnotation($scope, new Node\Stmt\Expression($expr, [ - 'comments' => $expr->getAttribute('comments'), - ]), null); - } - } else { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - foreach ($expr->var->items as $arrayItem) { - if ($arrayItem === null) { - continue; - } - - $itemScope = $scope; - if ($arrayItem->value instanceof ArrayDimFetch && $arrayItem->value->dim === null) { - $itemScope = $itemScope->enterExpressionAssign($arrayItem->value); - } - $itemScope = $this->lookForEnterVariableAssign($itemScope, $arrayItem->value); - - $itemResult = $this->processExprNode($arrayItem, $itemScope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $itemResult->hasYield(); - $throwPoints = array_merge($throwPoints, $itemResult->getThrowPoints()); - $scope = $result->getScope(); - } - $scope = $this->lookForArrayDestructuringArray($scope, $expr->var, $scope->getType($expr->expr)); - $vars = $this->getAssignedVariables($expr->var); - - if (count($vars) > 0) { - $varChangedScope = false; - $scope = $this->processVarAnnotation($scope, $vars, $expr, $varChangedScope); - if (!$varChangedScope) { - $scope = $this->processStmtVarAnnotation($scope, new Node\Stmt\Expression($expr, [ - 'comments' => $expr->getAttribute('comments'), - ]), null); - } - } - } - } elseif ($expr instanceof Expr\AssignOp) { - $result = $this->processAssignVar( - $scope, - $expr->var, - $expr, - $nodeCallback, - $context, - function (MutatingScope $scope) use ($expr, $nodeCallback, $context): ExpressionResult { - return $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - }, - $expr instanceof Expr\AssignOp\Coalesce - ); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - } elseif ($expr instanceof FuncCall) { - $parametersAcceptor = null; - $functionReflection = null; - $throwPoints = []; - if ($expr->name instanceof Expr) { - $nameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = $nameResult->getThrowPoints(); - $scope = $nameResult->getScope(); - } elseif ($this->reflectionProvider->hasFunction($expr->name, $scope)) { - $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $scope, - $expr->args, - $functionReflection->getVariants() - ); - } - $result = $this->processArgs($functionReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - - if (isset($functionReflection)) { - $functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $expr, $scope); - if ($functionThrowPoint !== null) { - $throwPoints[] = $functionThrowPoint; - } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - } - - if ( - isset($functionReflection) - && in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) - ) { - $scope = $scope->invalidateExpression(new FuncCall(new Name('json_last_error'), [])) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error'), [])) - ->invalidateExpression(new FuncCall(new Name('json_last_error_msg'), [])) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error_msg'), [])); - } - - if ( - isset($functionReflection) - && in_array($functionReflection->getName(), ['array_pop', 'array_shift'], true) - && count($expr->args) >= 1 - ) { - $arrayArg = $expr->args[0]->value; - $constantArrays = TypeUtils::getConstantArrays($scope->getType($arrayArg)); - $scope = $scope->invalidateExpression($arrayArg) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), [$expr->args[0]])) - ->invalidateExpression(new FuncCall(new Name('count'), [$expr->args[0]])); - if (count($constantArrays) > 0) { - $resultArrayTypes = []; - - foreach ($constantArrays as $constantArray) { - if ($functionReflection->getName() === 'array_pop') { - $resultArrayTypes[] = $constantArray->removeLast(); - } else { - $resultArrayTypes[] = $constantArray->removeFirst(); - } - } - - $scope = $scope->specifyExpressionType( - $arrayArg, - TypeCombinator::union(...$resultArrayTypes) - ); - } else { - $arrays = TypeUtils::getAnyArrays($scope->getType($arrayArg)); - if (count($arrays) > 0) { - $scope = $scope->specifyExpressionType($arrayArg, TypeCombinator::union(...$arrays)); - } - } - } - - if ( - isset($functionReflection) - && in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) - && count($expr->args) >= 2 - ) { - $argumentTypes = []; - foreach (array_slice($expr->args, 1) as $callArg) { - $callArgType = $scope->getType($callArg->value); - if ($callArg->unpack) { - $iterableValueType = $callArgType->getIterableValueType(); - if ($iterableValueType instanceof UnionType) { - foreach ($iterableValueType->getTypes() as $innerType) { - $argumentTypes[] = $innerType; - } - } else { - $argumentTypes[] = $iterableValueType; - } - continue; - } - - $argumentTypes[] = $callArgType; - } - - $arrayArg = $expr->args[0]->value; - $originalArrayType = $scope->getType($arrayArg); - $constantArrays = TypeUtils::getConstantArrays($originalArrayType); - if ( - $functionReflection->getName() === 'array_push' - || ($originalArrayType->isArray()->yes() && count($constantArrays) === 0) - ) { - $arrayType = $originalArrayType; - foreach ($argumentTypes as $argType) { - $arrayType = $arrayType->setOffsetValueType(null, $argType); - } - - $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); - } elseif (count($constantArrays) > 0) { - $defaultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($argumentTypes as $argType) { - $defaultArrayBuilder->setOffsetValueType(null, $argType); - } - - $defaultArrayType = $defaultArrayBuilder->getArray(); - - $arrayTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayType = $defaultArrayType; - foreach ($constantArray->getKeyTypes() as $i => $keyType) { - $valueType = $constantArray->getValueTypes()[$i]; - if ($keyType instanceof ConstantIntegerType) { - $keyType = null; - } - $arrayType = $arrayType->setOffsetValueType($keyType, $valueType); - } - $arrayTypes[] = $arrayType; - } - - $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType( - $arrayArg, - TypeCombinator::union(...$arrayTypes) - ); - } - } - - if ( - isset($functionReflection) - && in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) - ) { - $scope = $scope->assignVariable('http_response_header', new ArrayType(new IntegerType(), new StringType())); - } - - if (isset($functionReflection) && $functionReflection->getName() === 'extract') { - $scope = $scope->afterExtractCall(); - } - - if (isset($functionReflection) && ($functionReflection->getName() === 'clearstatcache' || $functionReflection->getName() === 'unlink')) { - $scope = $scope->afterClearstatcacheCall(); - } - - if (isset($functionReflection) && $functionReflection->hasSideEffects()->yes()) { - foreach ($expr->args as $arg) { - $scope = $scope->invalidateExpression($arg->value, true); - } - } - - } elseif ($expr instanceof MethodCall) { - $originalScope = $scope; - if ( - ($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) - && $expr->name instanceof Node\Identifier - && strtolower($expr->name->name) === 'call' - && isset($expr->args[0]) - ) { - $closureCallScope = $scope->enterClosureCall($scope->getType($expr->args[0]->value)); - } - - $result = $this->processExprNode($expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - if (isset($closureCallScope)) { - $scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); - } - $parametersAcceptor = null; - $methodReflection = null; - if ($expr->name instanceof Expr) { - $methodNameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); - $scope = $methodNameResult->getScope(); - } else { - $calledOnType = $scope->getType($expr->var); - $methodName = $expr->name->name; - $methodReflection = $scope->getMethodReflection($calledOnType, $methodName); - if ($methodReflection !== null) { - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $scope, - $expr->args, - $methodReflection->getVariants() - ); - $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $expr, $scope); - if ($methodThrowPoint !== null) { - $throwPoints[] = $methodThrowPoint; - } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - } - } - $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); - $scope = $result->getScope(); - if ($methodReflection !== null) { - $hasSideEffects = $methodReflection->hasSideEffects(); - if ($hasSideEffects->yes()) { - $scope = $scope->invalidateExpression($expr->var, true); - foreach ($expr->args as $arg) { - $scope = $scope->invalidateExpression($arg->value, true); - } - } - } - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } elseif ($expr instanceof Expr\NullsafeMethodCall) { - $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $expr->var); - $exprResult = $this->processExprNode(new MethodCall($expr->var, $expr->name, $expr->args, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); - $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); - - return new ExpressionResult( - $scope, - $exprResult->hasYield(), - $exprResult->getThrowPoints(), - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByTruthyValue($expr); - }, - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByFalseyValue($expr); - } - ); - } elseif ($expr instanceof StaticCall) { - $hasYield = false; - $throwPoints = []; - if ($expr->class instanceof Expr) { - $objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr->class)); - if (count($objectClasses) !== 1) { - $objectClasses = TypeUtils::getDirectClassNames($scope->getType(new New_($expr->class))); - } - if (count($objectClasses) === 1) { - $objectExprResult = $this->processExprNode(new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, static function (): void { - }, $context->enterDeep()); - $additionalThrowPoints = $objectExprResult->getThrowPoints(); - } else { - $additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; - } - $classResult = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $classResult->hasYield(); - $throwPoints = array_merge($throwPoints, $classResult->getThrowPoints()); - foreach ($additionalThrowPoints as $throwPoint) { - $throwPoints[] = $throwPoint; - } - $scope = $classResult->getScope(); - } - - $parametersAcceptor = null; - $methodReflection = null; - if ($expr->name instanceof Expr) { - $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } elseif ($expr->class instanceof Name) { - $className = $scope->resolveName($expr->class); - if ($this->reflectionProvider->hasClass($className)) { - $classReflection = $this->reflectionProvider->getClass($className); - if (is_string($expr->name)) { - $methodName = $expr->name; - } else { - $methodName = $expr->name->name; - } - if ($classReflection->hasMethod($methodName)) { - $methodReflection = $classReflection->getMethod($methodName, $scope); - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $scope, - $expr->args, - $methodReflection->getVariants() - ); - $methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $expr, $scope); - if ($methodThrowPoint !== null) { - $throwPoints[] = $methodThrowPoint; - } - if ( - $classReflection->getName() === 'Closure' - && strtolower($methodName) === 'bind' - ) { - $thisType = null; - if (isset($expr->args[1])) { - $argType = $scope->getType($expr->args[1]->value); - if ($argType instanceof NullType) { - $thisType = null; - } else { - $thisType = $argType; - } - } - $scopeClass = 'static'; - if (isset($expr->args[2])) { - $argValue = $expr->args[2]->value; - $argValueType = $scope->getType($argValue); - - $directClassNames = TypeUtils::getDirectClassNames($argValueType); - if (count($directClassNames) === 1) { - $scopeClass = $directClassNames[0]; - $thisType = new ObjectType($scopeClass); - } elseif ( - $argValue instanceof Expr\ClassConstFetch - && $argValue->name instanceof Node\Identifier - && strtolower($argValue->name->name) === 'class' - && $argValue->class instanceof Name - ) { - $scopeClass = $scope->resolveName($argValue->class); - $thisType = new ObjectType($scopeClass); - } elseif ($argValueType instanceof ConstantStringType) { - $scopeClass = $argValueType->getValue(); - $thisType = new ObjectType($scopeClass); - } - } - $closureBindScope = $scope->enterClosureBind($thisType, $scopeClass); - } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - } - } - $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context, $closureBindScope ?? null); - $scope = $result->getScope(); - $scopeFunction = $scope->getFunction(); - if ( - $methodReflection !== null - && !$methodReflection->isStatic() - && $methodReflection->hasSideEffects()->yes() - && $scopeFunction instanceof MethodReflection - && !$scopeFunction->isStatic() - && $scope->isInClass() - && ( - $scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName() - || $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName()) - ) - ) { - $scope = $scope->invalidateExpression(new Variable('this'), true); - } - - if ($methodReflection !== null) { - if ($methodReflection->hasSideEffects()->yes()) { - foreach ($expr->args as $arg) { - $scope = $scope->invalidateExpression($arg->value, true); - } - } - } - - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } elseif ($expr instanceof PropertyFetch) { - $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - if ($expr->name instanceof Expr) { - $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $scope = $result->getScope(); - } - } elseif ($expr instanceof Expr\NullsafePropertyFetch) { - $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $expr->var); - $exprResult = $this->processExprNode(new PropertyFetch($expr->var, $expr->name, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); - $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); - - return new ExpressionResult( - $scope, - $exprResult->hasYield(), - $exprResult->getThrowPoints(), - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByTruthyValue($expr); - }, - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByFalseyValue($expr); - } - ); - } elseif ($expr instanceof StaticPropertyFetch) { - $hasYield = false; - $throwPoints = []; - if ($expr->class instanceof Expr) { - $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - } - if ($expr->name instanceof Expr) { - $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } - } elseif ($expr instanceof Expr\Closure) { - return $this->processClosureNode($expr, $scope, $nodeCallback, $context, null); - } elseif ($expr instanceof Expr\ClosureUse) { - $this->processExprNode($expr->var, $scope, $nodeCallback, $context); - $hasYield = false; - $throwPoints = []; - } elseif ($expr instanceof Expr\ArrowFunction) { - foreach ($expr->params as $param) { - $this->processParamNode($param, $scope, $nodeCallback); - } - if ($expr->returnType !== null) { - $nodeCallback($expr->returnType, $scope); - } - - $arrowFunctionScope = $scope->enterArrowFunction($expr); - $nodeCallback(new InArrowFunctionNode($expr), $arrowFunctionScope); - $this->processExprNode($expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel()); - $hasYield = false; - $throwPoints = []; - - } elseif ($expr instanceof ErrorSuppress) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - } elseif ($expr instanceof Exit_) { - $hasYield = false; - $throwPoints = []; - if ($expr->expr !== null) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - } - } elseif ($expr instanceof Node\Scalar\Encapsed) { - $hasYield = false; - $throwPoints = []; - foreach ($expr->parts as $part) { - $result = $this->processExprNode($part, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } - } elseif ($expr instanceof ArrayDimFetch) { - $hasYield = false; - $throwPoints = []; - if ($expr->dim !== null) { - $result = $this->processExprNode($expr->dim, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - } - - $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } elseif ($expr instanceof Array_) { - $itemNodes = []; - $hasYield = false; - $throwPoints = []; - foreach ($expr->items as $arrayItem) { - $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); - if ($arrayItem === null) { - continue; - } - $result = $this->processExprNode($arrayItem, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } - $nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope); - } elseif ($expr instanceof ArrayItem) { - $hasYield = false; - $throwPoints = []; - if ($expr->key !== null) { - $result = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - } - $result = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - } elseif ($expr instanceof BooleanAnd || $expr instanceof BinaryOp\LogicalAnd) { - $leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); - $rightResult = $this->processExprNode($expr->right, $leftResult->getTruthyScope(), $nodeCallback, $context); - $leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); - - $nodeCallback(new BooleanAndNode($expr, $leftResult->getTruthyScope()), $scope); - - return new ExpressionResult( - $leftMergedWithRightScope, - $leftResult->hasYield() || $rightResult->hasYield(), - array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), - static function () use ($expr, $rightResult): MutatingScope { - return $rightResult->getScope()->filterByTruthyValue($expr); - }, - static function () use ($leftMergedWithRightScope, $expr): MutatingScope { - return $leftMergedWithRightScope->filterByFalseyValue($expr); - } - ); - } elseif ($expr instanceof BooleanOr || $expr instanceof BinaryOp\LogicalOr) { - $leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); - $rightResult = $this->processExprNode($expr->right, $leftResult->getFalseyScope(), $nodeCallback, $context); - $leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); - - $nodeCallback(new BooleanOrNode($expr, $leftResult->getFalseyScope()), $scope); - - return new ExpressionResult( - $leftMergedWithRightScope, - $leftResult->hasYield() || $rightResult->hasYield(), - array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), - static function () use ($leftMergedWithRightScope, $expr): MutatingScope { - return $leftMergedWithRightScope->filterByTruthyValue($expr); - }, - static function () use ($expr, $rightResult): MutatingScope { - return $rightResult->getScope()->filterByFalseyValue($expr); - } - ); - } elseif ($expr instanceof Coalesce) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left, false); - - if ($expr->left instanceof PropertyFetch || $expr->left instanceof StaticPropertyFetch || $expr->left instanceof Expr\NullsafePropertyFetch) { - $scope = $nonNullabilityResult->getScope(); - } else { - $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->left); - } - $result = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); - - if (!$expr->left instanceof PropertyFetch) { - $scope = $this->lookForExitVariableAssign($scope, $expr->left); - } - $result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope()->mergeWith($scope); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } elseif ($expr instanceof BinaryOp) { - $result = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } elseif ($expr instanceof Expr\Include_) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = $result->getThrowPoints(); - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - $hasYield = $result->hasYield(); - $scope = $result->getScope(); - } elseif ( - $expr instanceof Expr\BitwiseNot - || $expr instanceof Cast - || $expr instanceof Expr\Clone_ - || $expr instanceof Expr\Eval_ - || $expr instanceof Expr\Print_ - || $expr instanceof Expr\UnaryMinus - || $expr instanceof Expr\UnaryPlus - ) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = $result->getThrowPoints(); - $hasYield = $result->hasYield(); - - $scope = $result->getScope(); - } elseif ($expr instanceof Expr\YieldFrom) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = $result->getThrowPoints(); - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - $hasYield = true; - - $scope = $result->getScope(); - } elseif ($expr instanceof BooleanNot) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - } elseif ($expr instanceof Expr\ClassConstFetch) { - $hasYield = false; - $throwPoints = []; - if ($expr->class instanceof Expr) { - $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - } - } elseif ($expr instanceof Expr\Empty_) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr, true); - $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->expr); - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); - $scope = $this->lookForExitVariableAssign($scope, $expr->expr); - } elseif ($expr instanceof Expr\Isset_) { - $hasYield = false; - $throwPoints = []; - $nonNullabilityResults = []; - foreach ($expr->vars as $var) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $var, true); - $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $var); - $result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $nonNullabilityResults[] = $nonNullabilityResult; - $scope = $this->lookForExitVariableAssign($scope, $var); - } - foreach (array_reverse($nonNullabilityResults) as $nonNullabilityResult) { - $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); - } - } elseif ($expr instanceof Instanceof_) { - $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - if ($expr->class instanceof Expr) { - $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } - } elseif ($expr instanceof List_) { - // only in assign and foreach, processed elsewhere - return new ExpressionResult($scope, false, []); - } elseif ($expr instanceof New_) { - $parametersAcceptor = null; - $constructorReflection = null; - $hasYield = false; - $throwPoints = []; - if ($expr->class instanceof Expr) { - $objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr)); - if (count($objectClasses) === 1) { - $objectExprResult = $this->processExprNode(new New_(new Name($objectClasses[0])), $scope, static function (): void { - }, $context->enterDeep()); - $additionalThrowPoints = $objectExprResult->getThrowPoints(); - } else { - $additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; - } - - $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - foreach ($additionalThrowPoints as $throwPoint) { - $throwPoints[] = $throwPoint; - } - } elseif ($expr->class instanceof Class_) { - $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope); // populates $expr->class->name - $this->processStmtNode($expr->class, $scope, $nodeCallback); - } else { - $className = $scope->resolveName($expr->class); - if ($this->reflectionProvider->hasClass($className)) { - $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->hasConstructor()) { - $constructorReflection = $classReflection->getConstructor(); - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $scope, - $expr->args, - $constructorReflection->getVariants() - ); - $constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->args, $scope); - if ($constructorThrowPoint !== null) { - $throwPoints[] = $constructorThrowPoint; - } - } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); - } - } - $result = $this->processArgs($constructorReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - } elseif ( - $expr instanceof Expr\PreInc - || $expr instanceof Expr\PostInc - || $expr instanceof Expr\PreDec - || $expr instanceof Expr\PostDec - ) { - $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); - $scope = $result->getScope(); - $hasYield = $result->hasYield(); - $throwPoints = []; - if ( - $expr->var instanceof Variable - || $expr->var instanceof ArrayDimFetch - || $expr->var instanceof PropertyFetch - || $expr->var instanceof StaticPropertyFetch - ) { - $newExpr = $expr; - if ($expr instanceof Expr\PostInc) { - $newExpr = new Expr\PreInc($expr->var); - } elseif ($expr instanceof Expr\PostDec) { - $newExpr = new Expr\PreDec($expr->var); - } - - if (!$scope->getType($expr->var)->equals($scope->getType($newExpr))) { - $scope = $this->processAssignVar( - $scope, - $expr->var, - $newExpr, - static function (): void { - }, - $context, - static function (MutatingScope $scope): ExpressionResult { - return new ExpressionResult($scope, false, []); - }, - false - )->getScope(); - } else { - $scope = $scope->invalidateExpression($expr->var); - } - } - } elseif ($expr instanceof Ternary) { - $ternaryCondResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $context->enterDeep()); - $throwPoints = $ternaryCondResult->getThrowPoints(); - $ifTrueScope = $ternaryCondResult->getTruthyScope(); - $ifFalseScope = $ternaryCondResult->getFalseyScope(); - - if ($expr->if !== null) { - $ifResult = $this->processExprNode($expr->if, $ifTrueScope, $nodeCallback, $context); - $throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); - $ifTrueScope = $ifResult->getScope(); - } - - $elseResult = $this->processExprNode($expr->else, $ifFalseScope, $nodeCallback, $context); - $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); - $ifFalseScope = $elseResult->getScope(); - - $finalScope = $ifTrueScope->mergeWith($ifFalseScope); - - return new ExpressionResult( - $finalScope, - $ternaryCondResult->hasYield(), - $throwPoints, - static function () use ($finalScope, $expr): MutatingScope { - return $finalScope->filterByTruthyValue($expr); - }, - static function () use ($finalScope, $expr): MutatingScope { - return $finalScope->filterByFalseyValue($expr); - } - ); - - } elseif ($expr instanceof Expr\Yield_) { - $throwPoints = [ - ThrowPoint::createImplicit($scope, $expr), - ]; - if ($expr->key !== null) { - $keyResult = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); - $scope = $keyResult->getScope(); - $throwPoints = $keyResult->getThrowPoints(); - } - if ($expr->value !== null) { - $valueResult = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); - $scope = $valueResult->getScope(); - $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); - } - $hasYield = true; - } elseif ($expr instanceof Expr\Match_) { - $deepContext = $context->enterDeep(); - $condResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $deepContext); - $scope = $condResult->getScope(); - $hasYield = $condResult->hasYield(); - $throwPoints = $condResult->getThrowPoints(); - $matchScope = $scope; - $armNodes = []; - foreach ($expr->arms as $arm) { - if ($arm->conds === null) { - $armResult = $this->processExprNode($arm->body, $matchScope, $nodeCallback, ExpressionContext::createTopLevel()); - $matchScope = $armResult->getScope(); - $hasYield = $hasYield || $armResult->hasYield(); - $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); - $scope = $scope->mergeWith($matchScope); - $armNodes[] = new MatchExpressionArm([], $arm->getLine()); - continue; - } - - if (count($arm->conds) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $filteringExpr = null; - $armCondScope = $matchScope; - $condNodes = []; - foreach ($arm->conds as $armCond) { - $condNodes[] = new MatchExpressionArmCondition($armCond, $armCondScope, $armCond->getLine()); - $armCondResult = $this->processExprNode($armCond, $armCondScope, $nodeCallback, $deepContext); - $hasYield = $hasYield || $armCondResult->hasYield(); - $throwPoints = array_merge($throwPoints, $armCondResult->getThrowPoints()); - $armCondExpr = new BinaryOp\Identical($expr->cond, $armCond); - $armCondScope = $armCondResult->getScope()->filterByFalseyValue($armCondExpr); - if ($filteringExpr === null) { - $filteringExpr = $armCondExpr; - continue; - } - - $filteringExpr = new BinaryOp\BooleanOr($filteringExpr, $armCondExpr); - } - - $armNodes[] = new MatchExpressionArm($condNodes, $arm->getLine()); - - $armResult = $this->processExprNode( - $arm->body, - $matchScope->filterByTruthyValue($filteringExpr), - $nodeCallback, - ExpressionContext::createTopLevel() - ); - $armScope = $armResult->getScope(); - $scope = $scope->mergeWith($armScope); - $hasYield = $hasYield || $armResult->hasYield(); - $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); - $matchScope = $matchScope->filterByFalseyValue($filteringExpr); - } - - $nodeCallback(new MatchExpressionNode($expr->cond, $armNodes, $expr, $matchScope), $scope); - } else { - $hasYield = false; - $throwPoints = []; - } - - return new ExpressionResult( - $scope, - $hasYield, - $throwPoints, - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByTruthyValue($expr); - }, - static function () use ($scope, $expr): MutatingScope { - return $scope->filterByFalseyValue($expr); - } - ); - } - - private function getFunctionThrowPoint( - FunctionReflection $functionReflection, - ?ParametersAcceptor $parametersAcceptor, - FuncCall $funcCall, - MutatingScope $scope - ): ?ThrowPoint - { - foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicFunctionThrowTypeExtensions() as $extension) { - if (!$extension->isFunctionSupported($functionReflection)) { - continue; - } - - $throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $funcCall, $scope); - if ($throwType === null) { - return null; - } - - return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); - } - - if ($functionReflection->getThrowType() !== null) { - $throwType = $functionReflection->getThrowType(); - if (!$throwType instanceof VoidType) { - return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true); - } - } elseif ($this->implicitThrows) { - $requiredParameters = null; - if ($parametersAcceptor !== null) { - $requiredParameters = 0; - foreach ($parametersAcceptor->getParameters() as $parameter) { - if ($parameter->isOptional()) { - continue; - } - - $requiredParameters++; - } - } - if ( - !$functionReflection->isBuiltin() - || $requiredParameters === null - || $requiredParameters > 0 - || count($funcCall->args) > 0 - ) { - $functionReturnedType = $scope->getType($funcCall); - if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) { - return ThrowPoint::createImplicit($scope, $funcCall); - } - } - } - - return null; - } - - private function getMethodThrowPoint(MethodReflection $methodReflection, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint - { - foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) { - if (!$extension->isMethodSupported($methodReflection)) { - continue; - } - - $throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $methodCall, $scope); - if ($throwType === null) { - return null; - } - - return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); - } - - if ($methodReflection->getThrowType() !== null) { - $throwType = $methodReflection->getThrowType(); - if (!$throwType instanceof VoidType) { - return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); - } - } elseif ($this->implicitThrows) { - $methodReturnedType = $scope->getType($methodCall); - if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { - return ThrowPoint::createImplicit($scope, $methodCall); - } - } - - return null; - } - - /** - * @param Node\Arg[] $args - */ - private function getConstructorThrowPoint(MethodReflection $constructorReflection, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint - { - $methodCall = new StaticCall($className, $constructorReflection->getName(), $args); - foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { - if (!$extension->isStaticMethodSupported($constructorReflection)) { - continue; - } - - $throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $methodCall, $scope); - if ($throwType === null) { - return null; - } - - return ThrowPoint::createExplicit($scope, $throwType, $new, false); - } - - if ($constructorReflection->getThrowType() !== null) { - $throwType = $constructorReflection->getThrowType(); - if (!$throwType instanceof VoidType) { - return ThrowPoint::createExplicit($scope, $throwType, $new, true); - } - } elseif ($this->implicitThrows) { - if ($classReflection->getName() !== \Throwable::class && !$classReflection->isSubclassOf(\Throwable::class)) { - return ThrowPoint::createImplicit($scope, $methodCall); - } - } - - return null; - } - - private function getStaticMethodThrowPoint(MethodReflection $methodReflection, StaticCall $methodCall, MutatingScope $scope): ?ThrowPoint - { - foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { - if (!$extension->isStaticMethodSupported($methodReflection)) { - continue; - } - - $throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $methodCall, $scope); - if ($throwType === null) { - return null; - } - - return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); - } - - if ($methodReflection->getThrowType() !== null) { - $throwType = $methodReflection->getThrowType(); - if (!$throwType instanceof VoidType) { - return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); - } - } elseif ($this->implicitThrows) { - $methodReturnedType = $scope->getType($methodCall); - if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { - return ThrowPoint::createImplicit($scope, $methodCall); - } - } - - return null; - } - - /** - * @param Expr $expr - * @return string[] - */ - private function getAssignedVariables(Expr $expr): array - { - if ($expr instanceof Expr\Variable) { - if (is_string($expr->name)) { - return [$expr->name]; - } - - return []; - } - - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { - $names = []; - foreach ($expr->items as $item) { - if ($item === null) { - continue; - } - - $names = array_merge($names, $this->getAssignedVariables($item->value)); - } - - return $names; - } - - return []; - } - - /** - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @param Expr $expr - * @param MutatingScope $scope - * @param ExpressionContext $context - */ - private function callNodeCallbackWithExpression( - callable $nodeCallback, - Expr $expr, - MutatingScope $scope, - ExpressionContext $context - ): void - { - if ($context->isDeep()) { - $scope = $scope->exitFirstLevelStatements(); - } - $nodeCallback($expr, $scope); - } - - /** - * @param \PhpParser\Node\Expr\Closure $expr - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @param ExpressionContext $context - * @param Type|null $passedToType - * @return \PHPStan\Analyser\ExpressionResult - */ - private function processClosureNode( - Expr\Closure $expr, - MutatingScope $scope, - callable $nodeCallback, - ExpressionContext $context, - ?Type $passedToType - ): ExpressionResult - { - foreach ($expr->params as $param) { - $this->processParamNode($param, $scope, $nodeCallback); - } - - $byRefUses = []; - - if ($passedToType !== null && !$passedToType->isCallable()->no()) { - $callableParameters = null; - $acceptors = $passedToType->getCallableParametersAcceptors($scope); - if (count($acceptors) === 1) { - $callableParameters = $acceptors[0]->getParameters(); - } - } else { - $callableParameters = null; - } - - $useScope = $scope; - foreach ($expr->uses as $use) { - if ($use->byRef) { - $byRefUses[] = $use; - $useScope = $useScope->enterExpressionAssign($use->var); - - $inAssignRightSideVariableName = $context->getInAssignRightSideVariableName(); - $inAssignRightSideType = $context->getInAssignRightSideType(); - if ( - $inAssignRightSideVariableName === $use->var->name - && $inAssignRightSideType !== null - ) { - if ($inAssignRightSideType instanceof ClosureType) { - $variableType = $inAssignRightSideType; - } else { - $alreadyHasVariableType = $scope->hasVariableType($inAssignRightSideVariableName); - if ($alreadyHasVariableType->no()) { - $variableType = TypeCombinator::union(new NullType(), $inAssignRightSideType); - } else { - $variableType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideType); - } - } - $scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType); - } - } - $this->processExprNode($use, $useScope, $nodeCallback, $context); - if (!$use->byRef) { - continue; - } - - $useScope = $useScope->exitExpressionAssign($use->var); - } - - if ($expr->returnType !== null) { - $nodeCallback($expr->returnType, $scope); - } - - $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); - $nodeCallback(new InClosureNode($expr), $closureScope); - - $gatheredReturnStatements = []; - $gatheredYieldStatements = []; - $closureStmtsCallback = static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope): void { - $nodeCallback($node, $scope); - if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { - return; - } - if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { - $gatheredYieldStatements[] = $node; - } - if (!$node instanceof Return_) { - return; - } - - $gatheredReturnStatements[] = new ReturnStatement($scope, $node); - }; - if (count($byRefUses) === 0) { - $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); - $nodeCallback(new ClosureReturnStatementsNode( - $expr, - $gatheredReturnStatements, - $gatheredYieldStatements, - $statementResult - ), $closureScope); - - return new ExpressionResult($scope, false, []); - } - - $count = 0; - do { - $prevScope = $closureScope; - - $intermediaryClosureScopeResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, static function (): void { - }); - $intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); - foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { - $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); - } - $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); - if ($closureScope->equals($prevScope)) { - break; - } - $count++; - } while ($count < self::LOOP_SCOPE_ITERATIONS); - - $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); - $nodeCallback(new ClosureReturnStatementsNode( - $expr, - $gatheredReturnStatements, - $gatheredYieldStatements, - $statementResult - ), $closureScope); - - return new ExpressionResult($scope->processClosureScope($closureScope, null, $byRefUses), false, []); - } - - private function lookForArrayDestructuringArray(MutatingScope $scope, Expr $expr, Type $valueType): MutatingScope - { - if ($expr instanceof Array_ || $expr instanceof List_) { - foreach ($expr->items as $key => $item) { - /** @var \PhpParser\Node\Expr\ArrayItem|null $itemValue */ - $itemValue = $item; - if ($itemValue === null) { - continue; - } - - $keyType = $itemValue->key === null ? new ConstantIntegerType($key) : $scope->getType($itemValue->key); - $scope = $this->specifyItemFromArrayDestructuring($scope, $itemValue, $valueType, $keyType); - } - } elseif ($expr instanceof Variable && is_string($expr->name)) { - $scope = $scope->assignVariable($expr->name, new MixedType()); - } elseif ($expr instanceof ArrayDimFetch && $expr->var instanceof Variable && is_string($expr->var->name)) { - $scope = $scope->assignVariable($expr->var->name, new MixedType()); - } - - return $scope; - } - - private function specifyItemFromArrayDestructuring(MutatingScope $scope, ArrayItem $arrayItem, Type $valueType, Type $keyType): MutatingScope - { - $type = $valueType->getOffsetValueType($keyType); - - $itemNode = $arrayItem->value; - if ($itemNode instanceof Variable && is_string($itemNode->name)) { - $scope = $scope->assignVariable($itemNode->name, $type); - } elseif ($itemNode instanceof ArrayDimFetch && $itemNode->var instanceof Variable && is_string($itemNode->var->name)) { - $currentType = $scope->hasVariableType($itemNode->var->name)->no() - ? new ConstantArrayType([], []) - : $scope->getVariableType($itemNode->var->name); - $dimType = null; - if ($itemNode->dim !== null) { - $dimType = $scope->getType($itemNode->dim); - } - $scope = $scope->assignVariable($itemNode->var->name, $currentType->setOffsetValueType($dimType, $type)); - } else { - $scope = $this->lookForArrayDestructuringArray($scope, $itemNode, $type); - } - - return $scope; - } - - /** - * @param \PhpParser\Node\Param $param - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - */ - private function processParamNode( - Node\Param $param, - MutatingScope $scope, - callable $nodeCallback - ): void - { - foreach ($param->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); - } - } - } - $nodeCallback($param, $scope); - if ($param->type !== null) { - $nodeCallback($param->type, $scope); - } - if ($param->default === null) { - return; - } - - $this->processExprNode($param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - - /** - * @param \PHPStan\Reflection\MethodReflection|\PHPStan\Reflection\FunctionReflection|null $calleeReflection - * @param ParametersAcceptor|null $parametersAcceptor - * @param \PhpParser\Node\Arg[] $args - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @param ExpressionContext $context - * @param \PHPStan\Analyser\MutatingScope|null $closureBindScope - * @return \PHPStan\Analyser\ExpressionResult - */ - private function processArgs( - $calleeReflection, - ?ParametersAcceptor $parametersAcceptor, - array $args, - MutatingScope $scope, - callable $nodeCallback, - ExpressionContext $context, - ?MutatingScope $closureBindScope = null - ): ExpressionResult - { - if ($parametersAcceptor !== null) { - $parameters = $parametersAcceptor->getParameters(); - } - - if ($calleeReflection !== null) { - $scope = $scope->pushInFunctionCall($calleeReflection); - } - - $hasYield = false; - $throwPoints = []; - foreach ($args as $i => $arg) { - $nodeCallback($arg, $scope); - if (isset($parameters) && $parametersAcceptor !== null) { - $assignByReference = false; - if (isset($parameters[$i])) { - $assignByReference = $parameters[$i]->passedByReference()->createsNewVariable(); - $parameterType = $parameters[$i]->getType(); - } elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { - $lastParameter = $parameters[count($parameters) - 1]; - $assignByReference = $lastParameter->passedByReference()->createsNewVariable(); - $parameterType = $lastParameter->getType(); - } - - if ($assignByReference) { - $argValue = $arg->value; - if ($argValue instanceof Variable && is_string($argValue->name)) { - $scope = $scope->assignVariable($argValue->name, new MixedType()); - } - } - - if ($calleeReflection instanceof FunctionReflection) { - if ( - $i === 0 - && $calleeReflection->getName() === 'array_map' - && isset($args[1]) - ) { - $parameterType = new CallableType([ - new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], new MixedType(), false); - } - - if ( - $i === 1 - && $calleeReflection->getName() === 'array_filter' - && isset($args[0]) - ) { - if (isset($args[2])) { - $mode = $scope->getType($args[2]->value); - if ($mode instanceof ConstantIntegerType) { - if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { - $arrayFilterParameters = [ - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { - $arrayFilterParameters = [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } - } - } - $parameterType = new CallableType( - $arrayFilterParameters ?? [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], - new MixedType(), - false - ); - } - } - } - - $originalScope = $scope; - $scopeToPass = $scope; - if ($i === 0 && $closureBindScope !== null) { - $scopeToPass = $closureBindScope; - } - - if ($arg->value instanceof Expr\Closure) { - $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); - $result = $this->processClosureNode($arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); - } else { - $result = $this->processExprNode($arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); - } - $scope = $result->getScope(); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - if ($i !== 0 || $closureBindScope === null) { - continue; - } - - $scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); - } - - if ($calleeReflection !== null) { - $scope = $scope->popInFunctionCall(); - } - - return new ExpressionResult($scope, $hasYield, $throwPoints); - } - - /** - * @param \PHPStan\Analyser\MutatingScope $scope - * @param \PhpParser\Node\Expr $var - * @param \PhpParser\Node\Expr $assignedExpr - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @param ExpressionContext $context - * @param \Closure(MutatingScope $scope): ExpressionResult $processExprCallback - * @param bool $enterExpressionAssign - * @return ExpressionResult - */ - private function processAssignVar( - MutatingScope $scope, - Expr $var, - Expr $assignedExpr, - callable $nodeCallback, - ExpressionContext $context, - \Closure $processExprCallback, - bool $enterExpressionAssign - ): ExpressionResult - { - $nodeCallback($var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope); - $hasYield = false; - $throwPoints = []; - if ($var instanceof Variable && is_string($var->name)) { - $result = $processExprCallback($scope); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $assignedExpr = $this->unwrapAssign($assignedExpr); - $type = $scope->getType($assignedExpr); - $truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); - $falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); - - $conditionalExpressions = []; - - $truthyType = TypeCombinator::remove($type, StaticTypeFactory::falsey()); - $falseyType = TypeCombinator::intersect($type, StaticTypeFactory::falsey()); - - $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); - $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); - $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); - $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); - - $scope = $result->getScope()->assignVariable($var->name, $type); - foreach ($conditionalExpressions as $exprString => $holders) { - $scope = $scope->addConditionalExpressions($exprString, $holders); - } - } elseif ($var instanceof ArrayDimFetch) { - $dimExprStack = []; - $originalVar = $var; - while ($var instanceof ArrayDimFetch) { - $dimExprStack[] = $var->dim; - $var = $var->var; - } - - // 1. eval root expr - if ($enterExpressionAssign && $var instanceof Variable) { - $scope = $scope->enterExpressionAssign($var); - } - $result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $result->hasYield(); - $throwPoints = $result->getThrowPoints(); - $scope = $result->getScope(); - if ($enterExpressionAssign && $var instanceof Variable) { - $scope = $scope->exitExpressionAssign($var); - } - - // 2. eval dimensions - $offsetTypes = []; - foreach (array_reverse($dimExprStack) as $dimExpr) { - if ($dimExpr === null) { - $offsetTypes[] = null; - - } else { - $offsetTypes[] = $scope->getType($dimExpr); - - if ($enterExpressionAssign) { - $scope->enterExpressionAssign($dimExpr); - } - $result = $this->processExprNode($dimExpr, $scope, $nodeCallback, $context->enterDeep()); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - - if ($enterExpressionAssign) { - $scope = $scope->exitExpressionAssign($dimExpr); - } - } - } - - $valueToWrite = $scope->getType($assignedExpr); - $originalValueToWrite = $valueToWrite; - - // 3. eval assigned expr - $result = $processExprCallback($scope); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - - $varType = $scope->getType($var); - if (!(new ObjectType(\ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { - // 4. compose types - if ($varType instanceof ErrorType) { - $varType = new ConstantArrayType([], []); - } - $offsetValueType = $varType; - $offsetValueTypeStack = [$offsetValueType]; - foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { - if ($offsetType === null) { - $offsetValueType = new ConstantArrayType([], []); - - } else { - $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); - if ($offsetValueType instanceof ErrorType) { - $offsetValueType = new ConstantArrayType([], []); - } - } - - $offsetValueTypeStack[] = $offsetValueType; - } - - foreach (array_reverse($offsetTypes) as $i => $offsetType) { - /** @var Type $offsetValueType */ - $offsetValueType = array_pop($offsetValueTypeStack); - - /** @phpstan-ignore-next-line */ - $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); - } - - if ($var instanceof Variable && is_string($var->name)) { - $scope = $scope->assignVariable($var->name, $valueToWrite); - } else { - $scope = $scope->assignExpression( - $var, - $valueToWrite - ); - } - - if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { - $currentVarType = $scope->getType($originalVar); - if (!$originalValueToWrite->isSuperTypeOf($currentVarType)->yes()) { - $scope = $scope->assignExpression( - $originalVar, - $originalValueToWrite - ); - } - } - } - } elseif ($var instanceof PropertyFetch) { - $objectResult = $this->processExprNode($var->var, $scope, $nodeCallback, $context); - $hasYield = $objectResult->hasYield(); - $throwPoints = $objectResult->getThrowPoints(); - $scope = $objectResult->getScope(); - - $propertyName = null; - if ($var->name instanceof Node\Identifier) { - $propertyName = $var->name->name; - } else { - $propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); - $hasYield = $hasYield || $propertyNameResult->hasYield(); - $throwPoints = array_merge($throwPoints, $propertyNameResult->getThrowPoints()); - $scope = $propertyNameResult->getScope(); - } - - $result = $processExprCallback($scope); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - - $propertyHolderType = $scope->getType($var->var); - if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { - $propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); - if ($propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); - } - } else { - // fallback - $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); - } - - } elseif ($var instanceof Expr\StaticPropertyFetch) { - if ($var->class instanceof \PhpParser\Node\Name) { - $propertyHolderType = $scope->resolveTypeByName($var->class); - } else { - $this->processExprNode($var->class, $scope, $nodeCallback, $context); - $propertyHolderType = $scope->getType($var->class); - } - - $propertyName = null; - if ($var->name instanceof Node\Identifier) { - $propertyName = $var->name->name; - $hasYield = false; - $throwPoints = []; - } else { - $propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); - $hasYield = $propertyNameResult->hasYield(); - $throwPoints = $propertyNameResult->getThrowPoints(); - $scope = $propertyNameResult->getScope(); - } - - $result = $processExprCallback($scope); - $hasYield = $hasYield || $result->hasYield(); - $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); - $scope = $result->getScope(); - - if ($propertyName !== null) { - $propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName); - if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); - } - } else { - // fallback - $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); - } - } - - return new ExpressionResult($scope, $hasYield, $throwPoints); - } - - private function unwrapAssign(Expr $expr): Expr - { - if ($expr instanceof Assign) { - return $this->unwrapAssign($expr->expr); - } - - return $expr; - } - - /** - * @param Scope $scope - * @param string $variableName - * @param array $conditionalExpressions - * @param SpecifiedTypes $specifiedTypes - * @param Type $variableType - * @return array - */ - private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array - { - foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) { - if (!$expr instanceof Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } - - if (!isset($conditionalExpressions[$exprString])) { - $conditionalExpressions[$exprString] = []; - } - - $conditionalExpressions[$exprString][] = new ConditionalExpressionHolder([ - '$' . $variableName => $variableType, - ], VariableTypeHolder::createYes( - TypeCombinator::intersect($scope->getType($expr), $exprType) - )); - } - - return $conditionalExpressions; - } - - /** - * @param Scope $scope - * @param string $variableName - * @param array $conditionalExpressions - * @param SpecifiedTypes $specifiedTypes - * @param Type $variableType - * @return array - */ - private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array - { - foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) { - if (!$expr instanceof Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } - - if (!isset($conditionalExpressions[$exprString])) { - $conditionalExpressions[$exprString] = []; - } - - $conditionalExpressions[$exprString][] = new ConditionalExpressionHolder([ - '$' . $variableName => $variableType, - ], VariableTypeHolder::createYes( - TypeCombinator::remove($scope->getType($expr), $exprType) - )); - } - - return $conditionalExpressions; - } - - private function processStmtVarAnnotation(MutatingScope $scope, Node\Stmt $stmt, ?Expr $defaultExpr): MutatingScope - { - $function = $scope->getFunction(); - $variableLessTags = []; - - foreach ($stmt->getComments() as $comment) { - if (!$comment instanceof Doc) { - continue; - } - - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $comment->getText() - ); - - $assignedVariable = null; - if ( - $stmt instanceof Node\Stmt\Expression - && ($stmt->expr instanceof Assign || $stmt->expr instanceof AssignRef) - && $stmt->expr->var instanceof Variable - && is_string($stmt->expr->var->name) - ) { - $assignedVariable = $stmt->expr->var->name; - } - - foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { - if (is_int($name)) { - $variableLessTags[] = $varTag; - continue; - } - - if ($name === $assignedVariable) { - continue; - } - - $certainty = $scope->hasVariableType($name); - if ($certainty->no()) { - continue; - } - - if ($scope->isInClass() && $scope->getFunction() === null) { - continue; - } - - if ($scope->canAnyVariableExist()) { - $certainty = TrinaryLogic::createYes(); - } - - $scope = $scope->assignVariable($name, $varTag->getType(), $certainty); - } - } - - if (count($variableLessTags) === 1 && $defaultExpr !== null) { - $scope = $scope->specifyExpressionType($defaultExpr, $variableLessTags[0]->getType()); - } - - return $scope; - } - - /** - * @param MutatingScope $scope - * @param array $variableNames - * @param Node $node - * @param bool $changed - * @return MutatingScope - */ - private function processVarAnnotation(MutatingScope $scope, array $variableNames, Node $node, bool &$changed = false): MutatingScope - { - $function = $scope->getFunction(); - $varTags = []; - foreach ($node->getComments() as $comment) { - if (!$comment instanceof Doc) { - continue; - } - - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $comment->getText() - ); - foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { - $varTags[$key] = $varTag; - } - } - - if (count($varTags) === 0) { - return $scope; - } - - foreach ($variableNames as $variableName) { - if (!isset($varTags[$variableName])) { - continue; - } - - $variableType = $varTags[$variableName]->getType(); - $changed = true; - $scope = $scope->assignVariable($variableName, $variableType); - } - - if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { - $variableType = $varTags[0]->getType(); - $changed = true; - $scope = $scope->assignVariable($variableNames[0], $variableType); - } - - return $scope; - } - - private function enterForeach(MutatingScope $scope, Foreach_ $stmt): MutatingScope - { - if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { - $scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); - } - $iterateeType = $scope->getType($stmt->expr); - $vars = []; - if ($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name)) { - $scope = $scope->enterForeach( - $stmt->expr, - $stmt->valueVar->name, - $stmt->keyVar !== null - && $stmt->keyVar instanceof Variable - && is_string($stmt->keyVar->name) - ? $stmt->keyVar->name - : null - ); - $vars[] = $stmt->valueVar->name; - } - - if ( - $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) - ) { - $scope = $scope->enterForeachKey($stmt->expr, $stmt->keyVar->name); - $vars[] = $stmt->keyVar->name; - } - - if ( - $stmt->getDocComment() === null - && $iterateeType instanceof ConstantArrayType - && $stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name) - && $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) - ) { - $conditionalHolders = []; - foreach ($iterateeType->getKeyTypes() as $i => $keyType) { - $valueType = $iterateeType->getValueTypes()[$i]; - $conditionalHolders[] = new ConditionalExpressionHolder([ - '$' . $stmt->keyVar->name => $keyType, - ], new VariableTypeHolder($valueType, TrinaryLogic::createYes())); - } - - $scope = $scope->addConditionalExpressions( - '$' . $stmt->valueVar->name, - $conditionalHolders - ); - } - - if ($stmt->valueVar instanceof List_ || $stmt->valueVar instanceof Array_) { - $exprType = $scope->getType($stmt->expr); - $itemType = $exprType->getIterableValueType(); - $scope = $this->lookForArrayDestructuringArray($scope, $stmt->valueVar, $itemType); - $vars = array_merge($vars, $this->getAssignedVariables($stmt->valueVar)); - } - - $scope = $this->processVarAnnotation($scope, $vars, $stmt); - - return $scope; - } - - /** - * @param \PhpParser\Node\Stmt\TraitUse $node - * @param MutatingScope $classScope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - */ - private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, callable $nodeCallback): void - { - foreach ($node->traits as $trait) { - $traitName = (string) $trait; - if (!$this->reflectionProvider->hasClass($traitName)) { - continue; - } - $traitReflection = $this->reflectionProvider->getClass($traitName); - $traitFileName = $traitReflection->getFileName(); - if ($traitFileName === false) { - continue; // trait from eval or from PHP itself - } - $fileName = $this->fileHelper->normalizePath($traitFileName); - if (!isset($this->analysedFiles[$fileName])) { - continue; - } - $parserNodes = $this->parser->parseFile($fileName); - $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $nodeCallback); - } - } - - /** - * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node - * @param ClassReflection $traitReflection - * @param \PHPStan\Analyser\MutatingScope $scope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - */ - private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, callable $nodeCallback): void - { - if ($node instanceof Node) { - if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { - $this->processStmtNodes($node, $node->stmts, $scope->enterTrait($traitReflection), $nodeCallback); - return; - } - if ($node instanceof Node\Stmt\ClassLike) { - return; - } - if ($node instanceof Node\FunctionLike) { - return; - } - foreach ($node->getSubNodeNames() as $subNodeName) { - $subNode = $node->{$subNodeName}; - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); - } - } elseif (is_array($node)) { - foreach ($node as $subNode) { - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); - } - } - } - - /** - * @param Scope $scope - * @param Node\FunctionLike $functionLike - * @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null} - */ - public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array - { - $templateTypeMap = TemplateTypeMap::createEmpty(); - $phpDocParameterTypes = []; - $phpDocReturnType = null; - $phpDocThrowType = null; - $deprecatedDescription = null; - $isDeprecated = false; - $isInternal = false; - $isFinal = false; - $isPure = false; - $docComment = $functionLike->getDocComment() !== null - ? $functionLike->getDocComment()->getText() - : null; - - $file = $scope->getFile(); - $class = $scope->isInClass() ? $scope->getClassReflection()->getName() : null; - $trait = $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null; - $resolvedPhpDoc = null; - $functionName = null; - - if ($functionLike instanceof Node\Stmt\ClassMethod) { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $functionName = $functionLike->name->name; - $positionalParameterNames = array_map(static function (Node\Param $param): string { - if (!$param->var instanceof Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $param->var->name; - }, $functionLike->getParams()); - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( - $docComment, - $file, - $scope->getClassReflection(), - $trait, - $functionLike->name->name, - $positionalParameterNames - ); - - if ($functionLike->name->toLowerString() === '__construct') { - foreach ($functionLike->params as $param) { - if ($param->flags === 0) { - continue; - } - - if ($param->getDocComment() === null) { - continue; - } - - if ( - !$param->var instanceof Variable - || !is_string($param->var->name) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $paramPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $class, - $trait, - '__construct', - $param->getDocComment()->getText() - ); - $varTags = $paramPhpDoc->getVarTags(); - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } elseif (isset($varTags[$param->var->name])) { - $phpDocType = $varTags[$param->var->name]->getType(); - } else { - continue; - } - - $phpDocParameterTypes[$param->var->name] = $phpDocType; - } - } - } elseif ($functionLike instanceof Node\Stmt\Function_) { - $functionName = trim($scope->getNamespace() . '\\' . $functionLike->name->name, '\\'); - } - - if ($docComment !== null && $resolvedPhpDoc === null) { - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $class, - $trait, - $functionName, - $docComment - ); - } - - if ($resolvedPhpDoc !== null) { - $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); - foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { - if (array_key_exists($paramName, $phpDocParameterTypes)) { - continue; - } - $paramType = $paramTag->getType(); - if ($scope->isInClass()) { - $paramType = $this->transformStaticType($scope->getClassReflection(), $paramType); - } - $phpDocParameterTypes[$paramName] = $paramType; - } - $nativeReturnType = $scope->getFunctionType($functionLike->getReturnType(), false, false); - $phpDocReturnType = $this->getPhpDocReturnType($resolvedPhpDoc, $nativeReturnType); - if ($phpDocReturnType !== null && $scope->isInClass()) { - $phpDocReturnType = $this->transformStaticType($scope->getClassReflection(), $phpDocReturnType); - } - $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $isPure = $resolvedPhpDoc->isPure(); - } - - return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure]; - } - - private function transformStaticType(ClassReflection $declaringClass, Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { - if ($type instanceof StaticType) { - $changedType = $type->changeBaseClass($declaringClass); - if ($declaringClass->isFinal()) { - $changedType = $changedType->getStaticObjectType(); - } - return $traverse($changedType); - } - - return $traverse($type); - }); - } - - private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type - { - $returnTag = $resolvedPhpDoc->getReturnTag(); - - if ($returnTag === null) { - return null; - } - - $phpDocReturnType = $returnTag->getType(); - - if ($returnTag->isExplicit()) { - return $phpDocReturnType; - } - - if ($nativeReturnType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocReturnType))->yes()) { - return $phpDocReturnType; - } - - return null; - } - + private const LOOP_SCOPE_ITERATIONS = 3; + private const GENERALIZE_AFTER_ITERATION = 1; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private ClassReflector $classReflector; + + private ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider; + + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + + private PhpVersion $phpVersion; + + private \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver; + + private \PHPStan\File\FileHelper $fileHelper; + + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider; + + private bool $polluteScopeWithLoopInitialAssignments; + + private bool $polluteCatchScopeWithTryAssignments; + + private bool $polluteScopeWithAlwaysIterableForeach; + + /** @var string[][] className(string) => methods(string[]) */ + private array $earlyTerminatingMethodCalls; + + /** @var array */ + private array $earlyTerminatingFunctionCalls; + + private bool $implicitThrows; + + private bool $preciseExceptionTracking; + + /** @var bool[] filePath(string) => bool(true) */ + private array $analysedFiles = []; + + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param ClassReflector $classReflector + * @param Parser $parser + * @param FileTypeMapper $fileTypeMapper + * @param PhpDocInheritanceResolver $phpDocInheritanceResolver + * @param FileHelper $fileHelper + * @param TypeSpecifier $typeSpecifier + * @param bool $polluteScopeWithLoopInitialAssignments + * @param bool $polluteCatchScopeWithTryAssignments + * @param bool $polluteScopeWithAlwaysIterableForeach + * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) + * @param array $earlyTerminatingFunctionCalls + * @param bool $implicitThrows + * @param bool $preciseExceptionTracking + */ + public function __construct( + ReflectionProvider $reflectionProvider, + ClassReflector $classReflector, + ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, + Parser $parser, + FileTypeMapper $fileTypeMapper, + PhpVersion $phpVersion, + PhpDocInheritanceResolver $phpDocInheritanceResolver, + FileHelper $fileHelper, + TypeSpecifier $typeSpecifier, + DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, + bool $polluteScopeWithLoopInitialAssignments, + bool $polluteCatchScopeWithTryAssignments, + bool $polluteScopeWithAlwaysIterableForeach, + array $earlyTerminatingMethodCalls, + array $earlyTerminatingFunctionCalls, + bool $implicitThrows, + bool $preciseExceptionTracking + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classReflector = $classReflector; + $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; + $this->parser = $parser; + $this->fileTypeMapper = $fileTypeMapper; + $this->phpVersion = $phpVersion; + $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; + $this->fileHelper = $fileHelper; + $this->typeSpecifier = $typeSpecifier; + $this->dynamicThrowTypeExtensionProvider = $dynamicThrowTypeExtensionProvider; + $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; + $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; + $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; + $this->earlyTerminatingMethodCalls = $earlyTerminatingMethodCalls; + $this->earlyTerminatingFunctionCalls = $earlyTerminatingFunctionCalls; + $this->implicitThrows = $implicitThrows; + $this->preciseExceptionTracking = $preciseExceptionTracking; + } + + /** + * @param string[] $files + */ + public function setAnalysedFiles(array $files): void + { + $this->analysedFiles = array_fill_keys($files, true); + } + + /** + * @param \PhpParser\Node[] $nodes + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + public function processNodes( + array $nodes, + MutatingScope $scope, + callable $nodeCallback + ): void { + $nodesCount = count($nodes); + foreach ($nodes as $i => $node) { + if (!$node instanceof Node\Stmt) { + continue; + } + + $statementResult = $this->processStmtNode($node, $scope, $nodeCallback); + $scope = $statementResult->getScope(); + if (!$statementResult->isAlwaysTerminating()) { + continue; + } + + if ($i < $nodesCount - 1) { + $nextStmt = $nodes[$i + 1]; + if (!$nextStmt instanceof Node\Stmt) { + continue; + } + + $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + } + break; + } + } + + /** + * @param \PhpParser\Node $parentNode + * @param \PhpParser\Node\Stmt[] $stmts + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @return StatementResult + */ + public function processStmtNodes( + Node $parentNode, + array $stmts, + MutatingScope $scope, + callable $nodeCallback + ): StatementResult { + $exitPoints = []; + $throwPoints = []; + $alreadyTerminated = false; + $hasYield = false; + $stmtCount = count($stmts); + $shouldCheckLastStatement = $parentNode instanceof Node\Stmt\Function_ + || $parentNode instanceof Node\Stmt\ClassMethod + || $parentNode instanceof Expr\Closure; + foreach ($stmts as $i => $stmt) { + $isLast = $i === $stmtCount - 1; + $statementResult = $this->processStmtNode( + $stmt, + $scope, + $nodeCallback + ); + $scope = $statementResult->getScope(); + $hasYield = $hasYield || $statementResult->hasYield(); + + if ($shouldCheckLastStatement && $isLast) { + /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ + $parentNode = $parentNode; + $nodeCallback(new ExecutionEndNode( + $stmt, + new StatementResult( + $scope, + $hasYield, + $statementResult->isAlwaysTerminating(), + $statementResult->getExitPoints(), + $statementResult->getThrowPoints() + ), + $parentNode->returnType !== null + ), $scope); + } + + $exitPoints = array_merge($exitPoints, $statementResult->getExitPoints()); + $throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints()); + + if (!$statementResult->isAlwaysTerminating()) { + continue; + } + + $alreadyTerminated = true; + if ($i < $stmtCount - 1) { + $nextStmt = $stmts[$i + 1]; + $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + } + break; + } + + $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints); + if ($stmtCount === 0 && $shouldCheckLastStatement) { + /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ + $parentNode = $parentNode; + $nodeCallback(new ExecutionEndNode( + $parentNode, + $statementResult, + $parentNode->returnType !== null + ), $scope); + } + + return $statementResult; + } + + /** + * @param \PhpParser\Node\Stmt $stmt + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @return StatementResult + */ + private function processStmtNode( + Node\Stmt $stmt, + MutatingScope $scope, + callable $nodeCallback + ): StatementResult { + if ( + $stmt instanceof Throw_ + || $stmt instanceof Return_ + ) { + $scope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr); + } elseif ( + !$stmt instanceof Static_ + && !$stmt instanceof Foreach_ + && !$stmt instanceof Node\Stmt\Global_ + ) { + $scope = $this->processStmtVarAnnotation($scope, $stmt, null); + } + + if ($stmt instanceof Node\Stmt\ClassMethod) { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ( + $scope->isInTrait() + && $scope->getClassReflection()->hasNativeMethod($stmt->name->toString()) + ) { + $methodReflection = $scope->getClassReflection()->getNativeMethod($stmt->name->toString()); + if ($methodReflection instanceof NativeMethodReflection) { + return new StatementResult($scope, false, false, [], []); + } + if ($methodReflection instanceof PhpMethodReflection) { + $declaringTrait = $methodReflection->getDeclaringTrait(); + if ($declaringTrait === null || $declaringTrait->getName() !== $scope->getTraitReflection()->getName()) { + return new StatementResult($scope, false, false, [], []); + } + } + } + } + + $nodeCallback($stmt, $scope); + + $overridingThrowPoints = $this->getOverridingThrowPoints($stmt, $scope); + + if ($stmt instanceof Node\Stmt\Declare_) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->declares as $declare) { + $nodeCallback($declare, $scope); + $nodeCallback($declare->value, $scope); + if ( + $declare->key->name !== 'strict_types' + || !($declare->value instanceof Node\Scalar\LNumber) + || $declare->value->value !== 1 + ) { + continue; + } + + $scope = $scope->enterDeclareStrictTypes(); + } + } elseif ($stmt instanceof Node\Stmt\Function_) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $scope); + } + } + } + [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); + + foreach ($stmt->params as $param) { + $this->processParamNode($param, $scope, $nodeCallback); + } + + if ($stmt->returnType !== null) { + $nodeCallback($stmt->returnType, $scope); + } + + $functionScope = $scope->enterFunction( + $stmt, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $isPure + ); + $nodeCallback(new InFunctionNode($stmt), $functionScope); + + $gatheredReturnStatements = []; + $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements): void { + $nodeCallback($node, $scope); + if ($scope->getFunction() !== $functionScope->getFunction()) { + return; + } + if ($scope->isInAnonymousFunction()) { + return; + } + if (!$node instanceof Return_) { + return; + } + + $gatheredReturnStatements[] = new ReturnStatement($scope, $node); + }); + + $nodeCallback(new FunctionReturnStatementsNode( + $stmt, + $gatheredReturnStatements, + $statementResult + ), $functionScope); + } elseif ($stmt instanceof Node\Stmt\ClassMethod) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $scope); + } + } + } + [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); + + foreach ($stmt->params as $param) { + $this->processParamNode($param, $scope, $nodeCallback); + } + + if ($stmt->returnType !== null) { + $nodeCallback($stmt->returnType, $scope); + } + + $methodScope = $scope->enterClassMethod( + $stmt, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $isPure + ); + + if ($stmt->name->toLowerString() === '__construct') { + foreach ($stmt->params as $param) { + if ($param->flags === 0) { + continue; + } + + if (!$param->var instanceof Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $phpDoc = null; + if ($param->getDocComment() !== null) { + $phpDoc = $param->getDocComment()->getText(); + } + $nodeCallback(new ClassPropertyNode( + $param->var->name, + $param->flags, + $param->type, + $param->default, + $phpDoc, + true, + $param + ), $methodScope); + } + } + + if ($stmt->getAttribute('virtual', false) === false) { + $nodeCallback(new InClassMethodNode($stmt), $methodScope); + } + + if ($stmt->stmts !== null) { + $gatheredReturnStatements = []; + $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements): void { + $nodeCallback($node, $scope); + if ($scope->getFunction() !== $methodScope->getFunction()) { + return; + } + if ($scope->isInAnonymousFunction()) { + return; + } + if (!$node instanceof Return_) { + return; + } + + $gatheredReturnStatements[] = new ReturnStatement($scope, $node); + }); + $nodeCallback(new MethodReturnStatementsNode( + $stmt, + $gatheredReturnStatements, + $statementResult + ), $methodScope); + } + } elseif ($stmt instanceof Echo_) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->exprs as $echoExpr) { + $result = $this->processExprNode($echoExpr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + } + + $throwPoints = $overridingThrowPoints ?? $throwPoints; + } elseif ($stmt instanceof Return_) { + if ($stmt->expr !== null) { + $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + } else { + $hasYield = false; + $throwPoints = []; + } + + return new StatementResult($scope, $hasYield, true, [ + new StatementExitPoint($stmt, $scope), + ], $overridingThrowPoints ?? $throwPoints); + } elseif ($stmt instanceof Continue_ || $stmt instanceof Break_) { + if ($stmt->num !== null) { + $result = $this->processExprNode($stmt->num, $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + } else { + $hasYield = false; + $throwPoints = []; + } + + return new StatementResult($scope, $hasYield, true, [ + new StatementExitPoint($stmt, $scope), + ], $overridingThrowPoints ?? $throwPoints); + } elseif ($stmt instanceof Node\Stmt\Expression) { + $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); + $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createTopLevel()); + $scope = $result->getScope(); + $scope = $scope->filterBySpecifiedTypes($this->typeSpecifier->specifyTypesInCondition( + $scope, + $stmt->expr, + TypeSpecifierContext::createNull() + )); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + if ($earlyTerminationExpr !== null) { + return new StatementResult($scope, $hasYield, true, [ + new StatementExitPoint($stmt, $scope), + ], $overridingThrowPoints ?? $throwPoints); + } + return new StatementResult($scope, $hasYield, false, [], $overridingThrowPoints ?? $throwPoints); + } elseif ($stmt instanceof Node\Stmt\Namespace_) { + if ($stmt->name !== null) { + $scope = $scope->enterNamespace($stmt->name->toString()); + } + + $scope = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback)->getScope(); + $hasYield = false; + $throwPoints = []; + } elseif ($stmt instanceof Node\Stmt\Trait_) { + return new StatementResult($scope, false, false, [], []); + } elseif ($stmt instanceof Node\Stmt\ClassLike) { + $hasYield = false; + $throwPoints = []; + if (isset($stmt->namespacedName)) { + $classReflection = $this->getCurrentClassReflection($stmt, $scope); + $classScope = $scope->enterClass($classReflection); + $nodeCallback(new InClassNode($stmt, $classReflection), $classScope); + } elseif ($stmt instanceof Class_) { + if ($stmt->name === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($stmt->getAttribute('anonymousClass', false) === false) { + $classReflection = $this->reflectionProvider->getClass($stmt->name->toString()); + } else { + $classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope); + } + $classScope = $scope->enterClass($classReflection); + $nodeCallback(new InClassNode($stmt, $classReflection), $classScope); + } else { + throw new \PHPStan\ShouldNotHappenException(); + } + + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $classScope); + } + } + } + + $classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); + $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer); + $nodeCallback(new ClassPropertiesNode($stmt, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope); + $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls()), $classScope); + $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches()), $classScope); + } elseif ($stmt instanceof Node\Stmt\Property) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $scope); + } + } + } + foreach ($stmt->props as $prop) { + $this->processStmtNode($prop, $scope, $nodeCallback); + $docComment = $stmt->getDocComment(); + $nodeCallback( + new ClassPropertyNode( + $prop->name->toString(), + $stmt->flags, + $stmt->type, + $prop->default, + $docComment !== null ? $docComment->getText() : null, + false, + $prop + ), + $scope + ); + } + + if ($stmt->type !== null) { + $nodeCallback($stmt->type, $scope); + } + } elseif ($stmt instanceof Node\Stmt\PropertyProperty) { + $hasYield = false; + $throwPoints = []; + if ($stmt->default !== null) { + $this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + } + } elseif ($stmt instanceof Throw_) { + $result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $throwPoints = $result->getThrowPoints(); + $throwPoints[] = ThrowPoint::createExplicit($result->getScope(), $scope->getType($stmt->expr), $stmt, false); + return new StatementResult($result->getScope(), $result->hasYield(), true, [ + new StatementExitPoint($stmt, $scope), + ], $throwPoints); + } elseif ($stmt instanceof If_) { + $conditionType = $scope->getType($stmt->cond)->toBoolean(); + $ifAlwaysTrue = $conditionType instanceof ConstantBooleanType && $conditionType->getValue(); + $condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); + $exitPoints = []; + $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); + $finalScope = null; + $alwaysTerminating = true; + $hasYield = $condResult->hasYield(); + + $branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback); + + if (!$conditionType instanceof ConstantBooleanType || $conditionType->getValue()) { + $exitPoints = $branchScopeStatementResult->getExitPoints(); + $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); + $branchScope = $branchScopeStatementResult->getScope(); + $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; + $alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); + $hasYield = $branchScopeStatementResult->hasYield() || $hasYield; + } + + $scope = $condResult->getFalseyScope(); + $lastElseIfConditionIsTrue = false; + + $condScope = $scope; + foreach ($stmt->elseifs as $elseif) { + $nodeCallback($elseif, $scope); + $elseIfConditionType = $condScope->getType($elseif->cond)->toBoolean(); + $condResult = $this->processExprNode($elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep()); + $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); + $condScope = $condResult->getScope(); + $branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback); + + if ( + !$ifAlwaysTrue + && ( + !$lastElseIfConditionIsTrue + && ( + !$elseIfConditionType instanceof ConstantBooleanType + || $elseIfConditionType->getValue() + ) + ) + ) { + $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); + $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); + $branchScope = $branchScopeStatementResult->getScope(); + $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); + $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); + $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); + } + + if ( + $elseIfConditionType instanceof ConstantBooleanType + && $elseIfConditionType->getValue() + ) { + $lastElseIfConditionIsTrue = true; + } + + $condScope = $condScope->filterByFalseyValue($elseif->cond); + $scope = $condScope; + } + + if ($stmt->else === null) { + if (!$ifAlwaysTrue) { + $finalScope = $scope->mergeWith($finalScope); + $alwaysTerminating = false; + } + } else { + $nodeCallback($stmt->else, $scope); + $branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback); + + if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { + $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); + $throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); + $branchScope = $branchScopeStatementResult->getScope(); + $finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); + $alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); + $hasYield = $hasYield || $branchScopeStatementResult->hasYield(); + } + } + + if ($finalScope === null) { + $finalScope = $scope; + } + + return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints); + } elseif ($stmt instanceof Node\Stmt\TraitUse) { + $hasYield = false; + $throwPoints = []; + $this->processTraitUse($stmt, $scope, $nodeCallback); + } elseif ($stmt instanceof Foreach_) { + $condResult = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); + $scope = $condResult->getScope(); + $arrayComparisonExpr = new BinaryOp\NotIdentical( + $stmt->expr, + new Array_([]) + ); + $bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt); + $count = 0; + do { + $prevScope = $bodyScope; + $bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); + $bodyScope = $this->enterForeach($bodyScope, $stmt); + $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { + })->filterOutLoopExitPoints(); + $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + $bodyScope = $bodyScopeResult->getScope(); + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } + if ($bodyScope->equals($prevScope)) { + break; + } + + if ($count >= self::GENERALIZE_AFTER_ITERATION) { + $bodyScope = $bodyScope->generalizeWith($prevScope); + } + $count++; + } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); + + $bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); + $bodyScope = $this->enterForeach($bodyScope, $stmt); + $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); + $finalScope = $finalScopeResult->getScope(); + foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); + } + foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } + + $isIterableAtLeastOnce = $scope->getType($stmt->expr)->isIterableAtLeastOnce(); + if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { + $finalScope = $scope; + } elseif ($isIterableAtLeastOnce->maybe()) { + if ($this->polluteScopeWithAlwaysIterableForeach) { + $finalScope = $finalScope->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr)); + } else { + $finalScope = $finalScope->mergeWith($scope); + } + } elseif (!$this->polluteScopeWithAlwaysIterableForeach) { + $finalScope = $scope->processAlwaysIterableForeachScopeWithoutPollute($finalScope); + // get types from finalScope, but don't create new variables + } + + if (!$isIterableAtLeastOnce->no()) { + $throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); + } + + return new StatementResult( + $finalScope, + $finalScopeResult->hasYield() || $condResult->hasYield(), + $isIterableAtLeastOnce->yes() && $finalScopeResult->isAlwaysTerminating(), + $finalScopeResult->getExitPointsForOuterLoop(), + $throwPoints + ); + } elseif ($stmt instanceof While_) { + $condResult = $this->processExprNode($stmt->cond, $scope, static function (): void { + }, ExpressionContext::createDeep()); + $bodyScope = $condResult->getTruthyScope(); + $count = 0; + do { + $prevScope = $bodyScope; + $bodyScope = $bodyScope->mergeWith($scope); + $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { + }, ExpressionContext::createDeep())->getTruthyScope(); + $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { + })->filterOutLoopExitPoints(); + $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + $bodyScope = $bodyScopeResult->getScope(); + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } + if ($bodyScope->equals($prevScope)) { + break; + } + + if ($count >= self::GENERALIZE_AFTER_ITERATION) { + $bodyScope = $bodyScope->generalizeWith($prevScope); + } + $count++; + } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); + + $bodyScope = $bodyScope->mergeWith($scope); + $bodyScopeMaybeRan = $bodyScope; + $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); + $finalScope = $finalScopeResult->getScope(); + foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); + } + $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); + } + + $beforeCondBooleanType = $scope->getType($stmt->cond)->toBoolean(); + $condBooleanType = $bodyScopeMaybeRan->getType($stmt->cond)->toBoolean(); + $isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); + $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); + $neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); + + if ($alwaysIterates) { + $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; + } elseif ($isIterableAtLeastOnce) { + $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); + } else { + $isAlwaysTerminating = false; + } + $condScope = $condResult->getFalseyScope(); + if (!$isIterableAtLeastOnce) { + if (!$this->polluteScopeWithLoopInitialAssignments) { + $condScope = $condScope->mergeWith($scope); + } + $finalScope = $finalScope->mergeWith($condScope); + } + + $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); + if (!$neverIterates) { + $throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); + } + + return new StatementResult( + $finalScope, + $finalScopeResult->hasYield() || $condResult->hasYield(), + $isAlwaysTerminating, + $finalScopeResult->getExitPointsForOuterLoop(), + $throwPoints + ); + } elseif ($stmt instanceof Do_) { + $finalScope = null; + $bodyScope = $scope; + $count = 0; + $hasYield = false; + $throwPoints = []; + + do { + $prevScope = $bodyScope; + $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { + })->filterOutLoopExitPoints(); + $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + $bodyScope = $bodyScopeResult->getScope(); + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } + $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); + foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } + $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { + }, ExpressionContext::createDeep())->getTruthyScope(); + if ($bodyScope->equals($prevScope)) { + break; + } + + if ($count >= self::GENERALIZE_AFTER_ITERATION) { + $bodyScope = $bodyScope->generalizeWith($prevScope); + } + $count++; + } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); + + $bodyScope = $bodyScope->mergeWith($scope); + + $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); + $bodyScope = $bodyScopeResult->getScope(); + $condBooleanType = $bodyScope->getType($stmt->cond)->toBoolean(); + $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); + + if ($alwaysIterates) { + $alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0; + } else { + $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + } + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } + $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); + if ($finalScope === null) { + $finalScope = $scope; + } + if (!$alwaysTerminating) { + $condResult = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); + $hasYield = $condResult->hasYield(); + $throwPoints = $condResult->getThrowPoints(); + $finalScope = $condResult->getFalseyScope(); + } + foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } + + return new StatementResult( + $finalScope, + $bodyScopeResult->hasYield() || $hasYield, + $alwaysTerminating, + $bodyScopeResult->getExitPointsForOuterLoop(), + array_merge($throwPoints, $bodyScopeResult->getThrowPoints()) + ); + } elseif ($stmt instanceof For_) { + $initScope = $scope; + $hasYield = false; + $throwPoints = []; + foreach ($stmt->init as $initExpr) { + $initResult = $this->processExprNode($initExpr, $initScope, $nodeCallback, ExpressionContext::createTopLevel()); + $initScope = $initResult->getScope(); + $hasYield = $hasYield || $initResult->hasYield(); + $throwPoints = array_merge($throwPoints, $initResult->getThrowPoints()); + } + + $bodyScope = $initScope; + foreach ($stmt->cond as $condExpr) { + $condResult = $this->processExprNode($condExpr, $bodyScope, static function (): void { + }, ExpressionContext::createDeep()); + $hasYield = $hasYield || $condResult->hasYield(); + $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); + $bodyScope = $condResult->getTruthyScope(); + } + + $count = 0; + do { + $prevScope = $bodyScope; + $bodyScope = $bodyScope->mergeWith($initScope); + foreach ($stmt->cond as $condExpr) { + $bodyScope = $this->processExprNode($condExpr, $bodyScope, static function (): void { + }, ExpressionContext::createDeep())->getTruthyScope(); + } + $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { + })->filterOutLoopExitPoints(); + $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); + $bodyScope = $bodyScopeResult->getScope(); + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } + foreach ($stmt->loop as $loopExpr) { + $exprResult = $this->processExprNode($loopExpr, $bodyScope, static function (): void { + }, ExpressionContext::createTopLevel()); + $bodyScope = $exprResult->getScope(); + $hasYield = $hasYield || $exprResult->hasYield(); + $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); + } + + if ($bodyScope->equals($prevScope)) { + break; + } + + if ($count >= self::GENERALIZE_AFTER_ITERATION) { + $bodyScope = $bodyScope->generalizeWith($prevScope); + } + $count++; + } while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); + + $bodyScope = $bodyScope->mergeWith($initScope); + foreach ($stmt->cond as $condExpr) { + $bodyScope = $this->processExprNode($condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + } + + $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); + $finalScope = $finalScopeResult->getScope(); + foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); + } + foreach ($stmt->loop as $loopExpr) { + $finalScope = $this->processExprNode($loopExpr, $finalScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); + } + foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } + + if ($this->polluteScopeWithLoopInitialAssignments) { + $scope = $initScope; + } + + $finalScope = $finalScope->mergeWith($scope); + + return new StatementResult( + $finalScope, + $finalScopeResult->hasYield() || $hasYield, + false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/, + $finalScopeResult->getExitPointsForOuterLoop(), + array_merge($throwPoints, $finalScopeResult->getThrowPoints()) + ); + } elseif ($stmt instanceof Switch_) { + $condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $condResult->getScope(); + $scopeForBranches = $scope; + $finalScope = null; + $prevScope = null; + $hasDefaultCase = false; + $alwaysTerminating = true; + $hasYield = $condResult->hasYield(); + $exitPointsForOuterLoop = []; + $throwPoints = $condResult->getThrowPoints(); + foreach ($stmt->cases as $caseNode) { + if ($caseNode->cond !== null) { + $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); + $caseResult = $this->processExprNode($caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); + $scopeForBranches = $caseResult->getScope(); + $hasYield = $hasYield || $caseResult->hasYield(); + $throwPoints = array_merge($throwPoints, $caseResult->getThrowPoints()); + $branchScope = $scopeForBranches->filterByTruthyValue($condExpr); + } else { + $hasDefaultCase = true; + $branchScope = $scopeForBranches; + } + + $branchScope = $branchScope->mergeWith($prevScope); + $branchScopeResult = $this->processStmtNodes($caseNode, $caseNode->stmts, $branchScope, $nodeCallback); + $branchScope = $branchScopeResult->getScope(); + $branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); + $hasYield = $hasYield || $branchFinalScopeResult->hasYield(); + foreach ($branchScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { + $alwaysTerminating = false; + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } + foreach ($branchScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); + } + $exitPointsForOuterLoop = array_merge($exitPointsForOuterLoop, $branchFinalScopeResult->getExitPointsForOuterLoop()); + $throwPoints = array_merge($throwPoints, $branchFinalScopeResult->getThrowPoints()); + if ($branchScopeResult->isAlwaysTerminating()) { + $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); + $prevScope = null; + if (isset($condExpr)) { + $scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr); + } + if (!$branchFinalScopeResult->isAlwaysTerminating()) { + $finalScope = $branchScope->mergeWith($finalScope); + } + } else { + $prevScope = $branchScope; + } + } + + if (!$hasDefaultCase) { + $alwaysTerminating = false; + } + + if ($prevScope !== null && isset($branchFinalScopeResult)) { + $finalScope = $prevScope->mergeWith($finalScope); + $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); + } + + if (!$hasDefaultCase || $finalScope === null) { + $finalScope = $scope->mergeWith($finalScope); + } + + return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints); + } elseif ($stmt instanceof TryCatch) { + $branchScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback); + $branchScope = $branchScopeResult->getScope(); + $finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; + + $exitPoints = []; + $alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); + $hasYield = $branchScopeResult->hasYield(); + + if ($stmt->finally !== null) { + $finallyScope = $branchScope; + } else { + $finallyScope = null; + } + foreach ($branchScopeResult->getExitPoints() as $exitPoint) { + if ($exitPoint->getStatement() instanceof Throw_) { + continue; + } + if ($finallyScope !== null) { + $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); + } + $exitPoints[] = $exitPoint; + } + + $throwPoints = $branchScopeResult->getThrowPoints(); + $throwPointsForLater = []; + $pastCatchTypes = new NeverType(); + + foreach ($stmt->catches as $catchNode) { + $nodeCallback($catchNode, $scope); + + if ($this->preciseExceptionTracking || !$this->polluteCatchScopeWithTryAssignments) { + $catchType = TypeCombinator::union(...array_map(static function (Name $name): Type { + return new ObjectType($name->toString()); + }, $catchNode->types)); + $originalCatchType = $catchType; + $catchType = TypeCombinator::remove($catchType, $pastCatchTypes); + $pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); + $matchingThrowPoints = []; + $newThrowPoints = []; + foreach ($throwPoints as $throwPoint) { + if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { + continue; + } + $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); + if ($isSuperType->no()) { + continue; + } + $matchingThrowPoints[] = $throwPoint; + } + $hasExplicit = count($matchingThrowPoints) > 0; + foreach ($throwPoints as $throwPoint) { + $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); + if (!$hasExplicit && !$isSuperType->no()) { + $matchingThrowPoints[] = $throwPoint; + } + if ($isSuperType->yes()) { + continue; + } + $newThrowPoints[] = $throwPoint->subtractCatchType($catchType); + } + $throwPoints = $newThrowPoints; + + if (count($matchingThrowPoints) === 0) { + $throwableThrowPoints = []; + if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { + foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { + if (!$originalThrowPoint->canContainAnyThrowable()) { + continue; + } + + $throwableThrowPoints[] = $originalThrowPoint; + } + } + + if (count($throwableThrowPoints) === 0) { + $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType), $scope); + continue; + } + + $matchingThrowPoints = $throwableThrowPoints; + } + + $catchScope = null; + foreach ($matchingThrowPoints as $matchingThrowPoint) { + if ($catchScope === null) { + $catchScope = $matchingThrowPoint->getScope(); + } else { + $catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); + } + } + + $variableName = null; + if ($catchNode->var !== null) { + if (!is_string($catchNode->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableName = $catchNode->var->name; + } + + $catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback); + $catchScopeForFinally = $catchScopeResult->getScope(); + } else { + $initialScope = $scope; + if (count($throwPoints) > 0) { + $initialScope = $throwPoints[0]->getScope(); + } + + $catchScopeForFinally = $this->processCatchNode($catchNode, $branchScope, $nodeCallback)->getScope(); + $catchScopeResult = $this->processCatchNode($catchNode, $initialScope->mergeWith($branchScope), static function (): void { + }); + } + + $finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); + $alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); + $hasYield = $hasYield || $catchScopeResult->hasYield(); + $catchThrowPoints = $catchScopeResult->getThrowPoints(); + $throwPointsForLater = array_merge($throwPointsForLater, $catchThrowPoints); + + if ($finallyScope !== null) { + $finallyScope = $finallyScope->mergeWith($catchScopeForFinally); + } + foreach ($catchScopeResult->getExitPoints() as $exitPoint) { + if ($exitPoint->getStatement() instanceof Throw_) { + continue; + } + if ($finallyScope !== null) { + $finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); + } + $exitPoints[] = $exitPoint; + } + + foreach ($catchThrowPoints as $catchThrowPoint) { + if ($finallyScope === null) { + continue; + } + $finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()); + } + } + + if ($finalScope === null) { + $finalScope = $scope; + } + + foreach ($throwPoints as $throwPoint) { + if ($finallyScope === null) { + continue; + } + $finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); + } + + if ($finallyScope !== null && $stmt->finally !== null) { + $originalFinallyScope = $finallyScope; + $finallyResult = $this->processStmtNodes($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback); + $alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); + $hasYield = $hasYield || $finallyResult->hasYield(); + $throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); + $finallyScope = $finallyResult->getScope(); + $finalScope = $finallyResult->isAlwaysTerminating() ? $finalScope : $finalScope->processFinallyScope($finallyScope, $originalFinallyScope); + if (count($finallyResult->getExitPoints()) > 0) { + $nodeCallback(new FinallyExitPointsNode( + $finallyResult->getExitPoints(), + $exitPoints + ), $scope); + } + $exitPoints = array_merge($exitPoints, $finallyResult->getExitPoints()); + } + + return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, array_merge($throwPoints, $throwPointsForLater)); + } elseif ($stmt instanceof Unset_) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->vars as $var) { + $scope = $this->lookForEnterVariableAssign($scope, $var); + $scope = $this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep())->getScope(); + $scope = $this->lookForExitVariableAssign($scope, $var); + $scope = $scope->unsetExpression($var); + } + } elseif ($stmt instanceof Node\Stmt\Use_) { + $hasYield = false; + $throwPoints = []; + foreach ($stmt->uses as $use) { + $this->processStmtNode($use, $scope, $nodeCallback); + } + } elseif ($stmt instanceof Node\Stmt\Global_) { + $hasYield = false; + $throwPoints = []; + $vars = []; + foreach ($stmt->vars as $var) { + if (!$var instanceof Variable) { + throw new \PHPStan\ShouldNotHappenException(); + } + $scope = $this->lookForEnterVariableAssign($scope, $var); + $this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $this->lookForExitVariableAssign($scope, $var); + + if (!is_string($var->name)) { + continue; + } + + $scope = $scope->assignVariable($var->name, new MixedType()); + $vars[] = $var->name; + } + $scope = $this->processVarAnnotation($scope, $vars, $stmt); + } elseif ($stmt instanceof Static_) { + $hasYield = false; + $throwPoints = []; + + $vars = []; + foreach ($stmt->vars as $var) { + $scope = $this->processStmtNode($var, $scope, $nodeCallback)->getScope(); + if (!is_string($var->var->name)) { + continue; + } + + $vars[] = $var->var->name; + } + + $scope = $this->processVarAnnotation($scope, $vars, $stmt); + } elseif ($stmt instanceof StaticVar) { + $hasYield = false; + $throwPoints = []; + if (!is_string($stmt->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($stmt->default !== null) { + $this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + } + $scope = $scope->enterExpressionAssign($stmt->var); + $this->processExprNode($stmt->var, $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $scope->exitExpressionAssign($stmt->var); + $scope = $scope->assignVariable($stmt->var->name, new MixedType()); + } elseif ($stmt instanceof Node\Stmt\Const_ || $stmt instanceof Node\Stmt\ClassConst) { + $hasYield = false; + $throwPoints = []; + if ($stmt instanceof Node\Stmt\ClassConst) { + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $scope); + } + } + } + } + foreach ($stmt->consts as $const) { + $nodeCallback($const, $scope); + $this->processExprNode($const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); + if ($scope->getNamespace() !== null) { + $constName = [$scope->getNamespace(), $const->name->toString()]; + } else { + $constName = $const->name->toString(); + } + $scope = $scope->specifyExpressionType(new ConstFetch(new Name\FullyQualified($constName)), $scope->getType($const->value)); + } + } elseif ($stmt instanceof Node\Stmt\Nop) { + $scope = $this->processStmtVarAnnotation($scope, $stmt, null); + $hasYield = false; + $throwPoints = $overridingThrowPoints ?? []; + } else { + $hasYield = false; + $throwPoints = $overridingThrowPoints ?? []; + } + + return new StatementResult($scope, $hasYield, false, [], $throwPoints); + } + + /** + * @param Node\Stmt $statement + * @param MutatingScope $scope + * @return ThrowPoint[]|null + */ + private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $scope): ?array + { + foreach ($statement->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $function = $scope->getFunction(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $comment->getText() + ); + + $throwsTag = $resolvedPhpDoc->getThrowsTag(); + if ($throwsTag !== null) { + return [ThrowPoint::createExplicit($scope, $throwsTag->getType(), $statement, false)]; + } + } + + return null; + } + + private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, Scope $scope): ClassReflection + { + $className = $stmt->namespacedName->toString(); + if (!$this->reflectionProvider->hasClass($className)) { + return $this->createAstClassReflection($stmt, $scope); + } + + $defaultClassReflection = $this->reflectionProvider->getClass($stmt->namespacedName->toString()); + if ($defaultClassReflection->getFileName() !== $scope->getFile()) { + return $this->createAstClassReflection($stmt, $scope); + } + + $startLine = $defaultClassReflection->getNativeReflection()->getStartLine(); + if ($startLine !== $stmt->getStartLine()) { + return $this->createAstClassReflection($stmt, $scope); + } + + return $defaultClassReflection; + } + + private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scope): ClassReflection + { + $nodeToReflection = new NodeToReflection(); + $betterReflectionClass = $nodeToReflection->__invoke( + $this->classReflector, + $stmt, + new LocatedSource(FileReader::read($scope->getFile()), $scope->getFile()), + $scope->getNamespace() !== null ? new Node\Stmt\Namespace_(new Name($scope->getNamespace())) : null + ); + if (!$betterReflectionClass instanceof \PHPStan\BetterReflection\Reflection\ReflectionClass) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return new ClassReflection( + $this->reflectionProvider, + $this->fileTypeMapper, + $this->phpVersion, + $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $betterReflectionClass->getName(), + new ReflectionClass($betterReflectionClass), + null, + null, + null, + sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()) + ); + } + + /** + * @param Node\Stmt\Catch_ $catchNode + * @param MutatingScope $catchScope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @return StatementResult + */ + private function processCatchNode( + Node\Stmt\Catch_ $catchNode, + MutatingScope $catchScope, + callable $nodeCallback + ): StatementResult { + $variableName = null; + if ($catchNode->var !== null) { + if (!is_string($catchNode->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $variableName = $catchNode->var->name; + } + + $catchScope = $catchScope->enterCatch($catchNode->types, $variableName); + return $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope, $nodeCallback); + } + + private function lookForEnterVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope + { + if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) { + $scope = $scope->enterExpressionAssign($expr); + } + if (!$expr instanceof Variable) { + return $this->lookForVariableAssignCallback($scope, $expr, static function (MutatingScope $scope, Expr $expr): MutatingScope { + return $scope->enterExpressionAssign($expr); + }); + } + + return $scope; + } + + private function lookForExitVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope + { + $scope = $scope->exitExpressionAssign($expr); + if (!$expr instanceof Variable) { + return $this->lookForVariableAssignCallback($scope, $expr, static function (MutatingScope $scope, Expr $expr): MutatingScope { + return $scope->exitExpressionAssign($expr); + }); + } + + return $scope; + } + + /** + * @param MutatingScope $scope + * @param Expr $expr + * @param \Closure(MutatingScope $scope, Expr $expr): MutatingScope $callback + * @return MutatingScope + */ + private function lookForVariableAssignCallback(MutatingScope $scope, Expr $expr, \Closure $callback): MutatingScope + { + if ($expr instanceof Variable) { + $scope = $callback($scope, $expr); + } elseif ($expr instanceof ArrayDimFetch) { + while ($expr instanceof ArrayDimFetch) { + $expr = $expr->var; + } + + $scope = $this->lookForVariableAssignCallback($scope, $expr, $callback); + } elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) { + $scope = $this->lookForVariableAssignCallback($scope, $expr->var, $callback); + } elseif ($expr instanceof StaticPropertyFetch) { + if ($expr->class instanceof Expr) { + $scope = $this->lookForVariableAssignCallback($scope, $expr->class, $callback); + } + } elseif ($expr instanceof Array_ || $expr instanceof List_) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + $scope = $this->lookForVariableAssignCallback($scope, $item->value, $callback); + } + } + + return $scope; + } + + private function ensureShallowNonNullability(MutatingScope $scope, Expr $exprToSpecify): EnsuredNonNullabilityResult + { + $exprType = $scope->getType($exprToSpecify); + $exprTypeWithoutNull = TypeCombinator::removeNull($exprType); + if (!$exprType->equals($exprTypeWithoutNull)) { + $nativeType = $scope->getNativeType($exprToSpecify); + $scope = $scope->specifyExpressionType( + $exprToSpecify, + $exprTypeWithoutNull, + TypeCombinator::removeNull($nativeType) + ); + + return new EnsuredNonNullabilityResult( + $scope, + [ + new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType), + ] + ); + } + + return new EnsuredNonNullabilityResult($scope, []); + } + + private function ensureNonNullability(MutatingScope $scope, Expr $expr, bool $findMethods): EnsuredNonNullabilityResult + { + $exprToSpecify = $expr; + $specifiedExpressions = []; + while (true) { + $result = $this->ensureShallowNonNullability($scope, $exprToSpecify); + $scope = $result->getScope(); + foreach ($result->getSpecifiedExpressions() as $specifiedExpression) { + $specifiedExpressions[] = $specifiedExpression; + } + + if ($exprToSpecify instanceof PropertyFetch) { + $exprToSpecify = $exprToSpecify->var; + } elseif ($exprToSpecify instanceof StaticPropertyFetch && $exprToSpecify->class instanceof Expr) { + $exprToSpecify = $exprToSpecify->class; + } elseif ($findMethods && $exprToSpecify instanceof MethodCall) { + $exprToSpecify = $exprToSpecify->var; + } elseif ($findMethods && $exprToSpecify instanceof StaticCall && $exprToSpecify->class instanceof Expr) { + $exprToSpecify = $exprToSpecify->class; + } else { + break; + } + } + + return new EnsuredNonNullabilityResult($scope, $specifiedExpressions); + } + + /** + * @param MutatingScope $scope + * @param EnsuredNonNullabilityResultExpression[] $specifiedExpressions + * @return MutatingScope + */ + private function revertNonNullability(MutatingScope $scope, array $specifiedExpressions): MutatingScope + { + foreach ($specifiedExpressions as $specifiedExpressionResult) { + $scope = $scope->specifyExpressionType( + $specifiedExpressionResult->getExpression(), + $specifiedExpressionResult->getOriginalType(), + $specifiedExpressionResult->getOriginalNativeType() + ); + } + + return $scope; + } + + private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr + { + if (($expr instanceof MethodCall || $expr instanceof Expr\StaticCall) && $expr->name instanceof Node\Identifier) { + if (count($this->earlyTerminatingMethodCalls) > 0) { + if ($expr instanceof MethodCall) { + $methodCalledOnType = $scope->getType($expr->var); + } else { + if ($expr->class instanceof Name) { + $methodCalledOnType = $scope->resolveTypeByName($expr->class); + } else { + $methodCalledOnType = $scope->getType($expr->class); + } + } + + $directClassNames = TypeUtils::getDirectClassNames($methodCalledOnType); + foreach ($directClassNames as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { + if (!isset($this->earlyTerminatingMethodCalls[$className])) { + continue; + } + + if (in_array((string) $expr->name, $this->earlyTerminatingMethodCalls[$className], true)) { + return $expr; + } + } + } + } + } + + if ($expr instanceof FuncCall && $expr->name instanceof Name) { + if (in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { + return $expr; + } + } + + $exprType = $scope->getType($expr); + if ($exprType instanceof NeverType && $exprType->isExplicit()) { + return $expr; + } + + return null; + } + + /** + * @param \PhpParser\Node\Expr $expr + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @param \PHPStan\Analyser\ExpressionContext $context + * @return \PHPStan\Analyser\ExpressionResult + */ + private function processExprNode(Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult + { + $this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); + + if ($expr instanceof Variable) { + $hasYield = false; + $throwPoints = []; + if ($expr->name instanceof Expr) { + return $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + } + } elseif ($expr instanceof Assign || $expr instanceof AssignRef) { + if (!$expr->var instanceof Array_ && !$expr->var instanceof List_) { + $result = $this->processAssignVar( + $scope, + $expr->var, + $expr->expr, + $nodeCallback, + $context, + function (MutatingScope $scope) use ($expr, $nodeCallback, $context): ExpressionResult { + if ($expr instanceof AssignRef) { + $scope = $scope->enterExpressionAssign($expr->expr); + } + + if ($expr->var instanceof Variable && is_string($expr->var->name)) { + $context = $context->enterRightSideAssign( + $expr->var->name, + $scope->getType($expr->expr) + ); + } + + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + + if ($expr instanceof AssignRef) { + $scope = $scope->exitExpressionAssign($expr->expr); + } + + return new ExpressionResult($scope, $hasYield, $throwPoints); + }, + true + ); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $varChangedScope = false; + if ($expr->var instanceof Variable && is_string($expr->var->name)) { + $scope = $this->processVarAnnotation($scope, [$expr->var->name], $expr, $varChangedScope); + } + + if (!$varChangedScope) { + $scope = $this->processStmtVarAnnotation($scope, new Node\Stmt\Expression($expr, [ + 'comments' => $expr->getAttribute('comments'), + ]), null); + } + } else { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + foreach ($expr->var->items as $arrayItem) { + if ($arrayItem === null) { + continue; + } + + $itemScope = $scope; + if ($arrayItem->value instanceof ArrayDimFetch && $arrayItem->value->dim === null) { + $itemScope = $itemScope->enterExpressionAssign($arrayItem->value); + } + $itemScope = $this->lookForEnterVariableAssign($itemScope, $arrayItem->value); + + $itemResult = $this->processExprNode($arrayItem, $itemScope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $itemResult->hasYield(); + $throwPoints = array_merge($throwPoints, $itemResult->getThrowPoints()); + $scope = $result->getScope(); + } + $scope = $this->lookForArrayDestructuringArray($scope, $expr->var, $scope->getType($expr->expr)); + $vars = $this->getAssignedVariables($expr->var); + + if (count($vars) > 0) { + $varChangedScope = false; + $scope = $this->processVarAnnotation($scope, $vars, $expr, $varChangedScope); + if (!$varChangedScope) { + $scope = $this->processStmtVarAnnotation($scope, new Node\Stmt\Expression($expr, [ + 'comments' => $expr->getAttribute('comments'), + ]), null); + } + } + } + } elseif ($expr instanceof Expr\AssignOp) { + $result = $this->processAssignVar( + $scope, + $expr->var, + $expr, + $nodeCallback, + $context, + function (MutatingScope $scope) use ($expr, $nodeCallback, $context): ExpressionResult { + return $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + }, + $expr instanceof Expr\AssignOp\Coalesce + ); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + } elseif ($expr instanceof FuncCall) { + $parametersAcceptor = null; + $functionReflection = null; + $throwPoints = []; + if ($expr->name instanceof Expr) { + $nameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $nameResult->getThrowPoints(); + $scope = $nameResult->getScope(); + } elseif ($this->reflectionProvider->hasFunction($expr->name, $scope)) { + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->args, + $functionReflection->getVariants() + ); + } + $result = $this->processArgs($functionReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + + if (isset($functionReflection)) { + $functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $expr, $scope); + if ($functionThrowPoint !== null) { + $throwPoints[] = $functionThrowPoint; + } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + + if ( + isset($functionReflection) + && in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) + ) { + $scope = $scope->invalidateExpression(new FuncCall(new Name('json_last_error'), [])) + ->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error'), [])) + ->invalidateExpression(new FuncCall(new Name('json_last_error_msg'), [])) + ->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error_msg'), [])); + } + + if ( + isset($functionReflection) + && in_array($functionReflection->getName(), ['array_pop', 'array_shift'], true) + && count($expr->args) >= 1 + ) { + $arrayArg = $expr->args[0]->value; + $constantArrays = TypeUtils::getConstantArrays($scope->getType($arrayArg)); + $scope = $scope->invalidateExpression($arrayArg) + ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), [$expr->args[0]])) + ->invalidateExpression(new FuncCall(new Name('count'), [$expr->args[0]])); + if (count($constantArrays) > 0) { + $resultArrayTypes = []; + + foreach ($constantArrays as $constantArray) { + if ($functionReflection->getName() === 'array_pop') { + $resultArrayTypes[] = $constantArray->removeLast(); + } else { + $resultArrayTypes[] = $constantArray->removeFirst(); + } + } + + $scope = $scope->specifyExpressionType( + $arrayArg, + TypeCombinator::union(...$resultArrayTypes) + ); + } else { + $arrays = TypeUtils::getAnyArrays($scope->getType($arrayArg)); + if (count($arrays) > 0) { + $scope = $scope->specifyExpressionType($arrayArg, TypeCombinator::union(...$arrays)); + } + } + } + + if ( + isset($functionReflection) + && in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) + && count($expr->args) >= 2 + ) { + $argumentTypes = []; + foreach (array_slice($expr->args, 1) as $callArg) { + $callArgType = $scope->getType($callArg->value); + if ($callArg->unpack) { + $iterableValueType = $callArgType->getIterableValueType(); + if ($iterableValueType instanceof UnionType) { + foreach ($iterableValueType->getTypes() as $innerType) { + $argumentTypes[] = $innerType; + } + } else { + $argumentTypes[] = $iterableValueType; + } + continue; + } + + $argumentTypes[] = $callArgType; + } + + $arrayArg = $expr->args[0]->value; + $originalArrayType = $scope->getType($arrayArg); + $constantArrays = TypeUtils::getConstantArrays($originalArrayType); + if ( + $functionReflection->getName() === 'array_push' + || ($originalArrayType->isArray()->yes() && count($constantArrays) === 0) + ) { + $arrayType = $originalArrayType; + foreach ($argumentTypes as $argType) { + $arrayType = $arrayType->setOffsetValueType(null, $argType); + } + + $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); + } elseif (count($constantArrays) > 0) { + $defaultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($argumentTypes as $argType) { + $defaultArrayBuilder->setOffsetValueType(null, $argType); + } + + $defaultArrayType = $defaultArrayBuilder->getArray(); + + $arrayTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayType = $defaultArrayType; + foreach ($constantArray->getKeyTypes() as $i => $keyType) { + $valueType = $constantArray->getValueTypes()[$i]; + if ($keyType instanceof ConstantIntegerType) { + $keyType = null; + } + $arrayType = $arrayType->setOffsetValueType($keyType, $valueType); + } + $arrayTypes[] = $arrayType; + } + + $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType( + $arrayArg, + TypeCombinator::union(...$arrayTypes) + ); + } + } + + if ( + isset($functionReflection) + && in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) + ) { + $scope = $scope->assignVariable('http_response_header', new ArrayType(new IntegerType(), new StringType())); + } + + if (isset($functionReflection) && $functionReflection->getName() === 'extract') { + $scope = $scope->afterExtractCall(); + } + + if (isset($functionReflection) && ($functionReflection->getName() === 'clearstatcache' || $functionReflection->getName() === 'unlink')) { + $scope = $scope->afterClearstatcacheCall(); + } + + if (isset($functionReflection) && $functionReflection->hasSideEffects()->yes()) { + foreach ($expr->args as $arg) { + $scope = $scope->invalidateExpression($arg->value, true); + } + } + } elseif ($expr instanceof MethodCall) { + $originalScope = $scope; + if ( + ($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) + && $expr->name instanceof Node\Identifier + && strtolower($expr->name->name) === 'call' + && isset($expr->args[0]) + ) { + $closureCallScope = $scope->enterClosureCall($scope->getType($expr->args[0]->value)); + } + + $result = $this->processExprNode($expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + if (isset($closureCallScope)) { + $scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); + } + $parametersAcceptor = null; + $methodReflection = null; + if ($expr->name instanceof Expr) { + $methodNameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); + $scope = $methodNameResult->getScope(); + } else { + $calledOnType = $scope->getType($expr->var); + $methodName = $expr->name->name; + $methodReflection = $scope->getMethodReflection($calledOnType, $methodName); + if ($methodReflection !== null) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->args, + $methodReflection->getVariants() + ); + $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $expr, $scope); + if ($methodThrowPoint !== null) { + $throwPoints[] = $methodThrowPoint; + } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + } + $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $scope = $result->getScope(); + if ($methodReflection !== null) { + $hasSideEffects = $methodReflection->hasSideEffects(); + if ($hasSideEffects->yes()) { + $scope = $scope->invalidateExpression($expr->var, true); + foreach ($expr->args as $arg) { + $scope = $scope->invalidateExpression($arg->value, true); + } + } + } + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } elseif ($expr instanceof Expr\NullsafeMethodCall) { + $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $expr->var); + $exprResult = $this->processExprNode(new MethodCall($expr->var, $expr->name, $expr->args, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); + + return new ExpressionResult( + $scope, + $exprResult->hasYield(), + $exprResult->getThrowPoints(), + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByTruthyValue($expr); + }, + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByFalseyValue($expr); + } + ); + } elseif ($expr instanceof StaticCall) { + $hasYield = false; + $throwPoints = []; + if ($expr->class instanceof Expr) { + $objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr->class)); + if (count($objectClasses) !== 1) { + $objectClasses = TypeUtils::getDirectClassNames($scope->getType(new New_($expr->class))); + } + if (count($objectClasses) === 1) { + $objectExprResult = $this->processExprNode(new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, static function (): void { + }, $context->enterDeep()); + $additionalThrowPoints = $objectExprResult->getThrowPoints(); + } else { + $additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; + } + $classResult = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $classResult->hasYield(); + $throwPoints = array_merge($throwPoints, $classResult->getThrowPoints()); + foreach ($additionalThrowPoints as $throwPoint) { + $throwPoints[] = $throwPoint; + } + $scope = $classResult->getScope(); + } + + $parametersAcceptor = null; + $methodReflection = null; + if ($expr->name instanceof Expr) { + $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } elseif ($expr->class instanceof Name) { + $className = $scope->resolveName($expr->class); + if ($this->reflectionProvider->hasClass($className)) { + $classReflection = $this->reflectionProvider->getClass($className); + if (is_string($expr->name)) { + $methodName = $expr->name; + } else { + $methodName = $expr->name->name; + } + if ($classReflection->hasMethod($methodName)) { + $methodReflection = $classReflection->getMethod($methodName, $scope); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->args, + $methodReflection->getVariants() + ); + $methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $expr, $scope); + if ($methodThrowPoint !== null) { + $throwPoints[] = $methodThrowPoint; + } + if ( + $classReflection->getName() === 'Closure' + && strtolower($methodName) === 'bind' + ) { + $thisType = null; + if (isset($expr->args[1])) { + $argType = $scope->getType($expr->args[1]->value); + if ($argType instanceof NullType) { + $thisType = null; + } else { + $thisType = $argType; + } + } + $scopeClass = 'static'; + if (isset($expr->args[2])) { + $argValue = $expr->args[2]->value; + $argValueType = $scope->getType($argValue); + + $directClassNames = TypeUtils::getDirectClassNames($argValueType); + if (count($directClassNames) === 1) { + $scopeClass = $directClassNames[0]; + $thisType = new ObjectType($scopeClass); + } elseif ( + $argValue instanceof Expr\ClassConstFetch + && $argValue->name instanceof Node\Identifier + && strtolower($argValue->name->name) === 'class' + && $argValue->class instanceof Name + ) { + $scopeClass = $scope->resolveName($argValue->class); + $thisType = new ObjectType($scopeClass); + } elseif ($argValueType instanceof ConstantStringType) { + $scopeClass = $argValueType->getValue(); + $thisType = new ObjectType($scopeClass); + } + } + $closureBindScope = $scope->enterClosureBind($thisType, $scopeClass); + } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + } + $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context, $closureBindScope ?? null); + $scope = $result->getScope(); + $scopeFunction = $scope->getFunction(); + if ( + $methodReflection !== null + && !$methodReflection->isStatic() + && $methodReflection->hasSideEffects()->yes() + && $scopeFunction instanceof MethodReflection + && !$scopeFunction->isStatic() + && $scope->isInClass() + && ( + $scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName() + || $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName()) + ) + ) { + $scope = $scope->invalidateExpression(new Variable('this'), true); + } + + if ($methodReflection !== null) { + if ($methodReflection->hasSideEffects()->yes()) { + foreach ($expr->args as $arg) { + $scope = $scope->invalidateExpression($arg->value, true); + } + } + } + + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } elseif ($expr instanceof PropertyFetch) { + $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + if ($expr->name instanceof Expr) { + $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $scope = $result->getScope(); + } + } elseif ($expr instanceof Expr\NullsafePropertyFetch) { + $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $expr->var); + $exprResult = $this->processExprNode(new PropertyFetch($expr->var, $expr->name, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); + + return new ExpressionResult( + $scope, + $exprResult->hasYield(), + $exprResult->getThrowPoints(), + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByTruthyValue($expr); + }, + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByFalseyValue($expr); + } + ); + } elseif ($expr instanceof StaticPropertyFetch) { + $hasYield = false; + $throwPoints = []; + if ($expr->class instanceof Expr) { + $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + } + if ($expr->name instanceof Expr) { + $result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } + } elseif ($expr instanceof Expr\Closure) { + return $this->processClosureNode($expr, $scope, $nodeCallback, $context, null); + } elseif ($expr instanceof Expr\ClosureUse) { + $this->processExprNode($expr->var, $scope, $nodeCallback, $context); + $hasYield = false; + $throwPoints = []; + } elseif ($expr instanceof Expr\ArrowFunction) { + foreach ($expr->params as $param) { + $this->processParamNode($param, $scope, $nodeCallback); + } + if ($expr->returnType !== null) { + $nodeCallback($expr->returnType, $scope); + } + + $arrowFunctionScope = $scope->enterArrowFunction($expr); + $nodeCallback(new InArrowFunctionNode($expr), $arrowFunctionScope); + $this->processExprNode($expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel()); + $hasYield = false; + $throwPoints = []; + } elseif ($expr instanceof ErrorSuppress) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + } elseif ($expr instanceof Exit_) { + $hasYield = false; + $throwPoints = []; + if ($expr->expr !== null) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + } + } elseif ($expr instanceof Node\Scalar\Encapsed) { + $hasYield = false; + $throwPoints = []; + foreach ($expr->parts as $part) { + $result = $this->processExprNode($part, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } + } elseif ($expr instanceof ArrayDimFetch) { + $hasYield = false; + $throwPoints = []; + if ($expr->dim !== null) { + $result = $this->processExprNode($expr->dim, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + } + + $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } elseif ($expr instanceof Array_) { + $itemNodes = []; + $hasYield = false; + $throwPoints = []; + foreach ($expr->items as $arrayItem) { + $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); + if ($arrayItem === null) { + continue; + } + $result = $this->processExprNode($arrayItem, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } + $nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope); + } elseif ($expr instanceof ArrayItem) { + $hasYield = false; + $throwPoints = []; + if ($expr->key !== null) { + $result = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + } + $result = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + } elseif ($expr instanceof BooleanAnd || $expr instanceof BinaryOp\LogicalAnd) { + $leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); + $rightResult = $this->processExprNode($expr->right, $leftResult->getTruthyScope(), $nodeCallback, $context); + $leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); + + $nodeCallback(new BooleanAndNode($expr, $leftResult->getTruthyScope()), $scope); + + return new ExpressionResult( + $leftMergedWithRightScope, + $leftResult->hasYield() || $rightResult->hasYield(), + array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), + static function () use ($expr, $rightResult): MutatingScope { + return $rightResult->getScope()->filterByTruthyValue($expr); + }, + static function () use ($leftMergedWithRightScope, $expr): MutatingScope { + return $leftMergedWithRightScope->filterByFalseyValue($expr); + } + ); + } elseif ($expr instanceof BooleanOr || $expr instanceof BinaryOp\LogicalOr) { + $leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); + $rightResult = $this->processExprNode($expr->right, $leftResult->getFalseyScope(), $nodeCallback, $context); + $leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); + + $nodeCallback(new BooleanOrNode($expr, $leftResult->getFalseyScope()), $scope); + + return new ExpressionResult( + $leftMergedWithRightScope, + $leftResult->hasYield() || $rightResult->hasYield(), + array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), + static function () use ($leftMergedWithRightScope, $expr): MutatingScope { + return $leftMergedWithRightScope->filterByTruthyValue($expr); + }, + static function () use ($expr, $rightResult): MutatingScope { + return $rightResult->getScope()->filterByFalseyValue($expr); + } + ); + } elseif ($expr instanceof Coalesce) { + $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left, false); + + if ($expr->left instanceof PropertyFetch || $expr->left instanceof StaticPropertyFetch || $expr->left instanceof Expr\NullsafePropertyFetch) { + $scope = $nonNullabilityResult->getScope(); + } else { + $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->left); + } + $result = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); + + if (!$expr->left instanceof PropertyFetch) { + $scope = $this->lookForExitVariableAssign($scope, $expr->left); + } + $result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope()->mergeWith($scope); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } elseif ($expr instanceof BinaryOp) { + $result = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } elseif ($expr instanceof Expr\Include_) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $result->getThrowPoints(); + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + $hasYield = $result->hasYield(); + $scope = $result->getScope(); + } elseif ( + $expr instanceof Expr\BitwiseNot + || $expr instanceof Cast + || $expr instanceof Expr\Clone_ + || $expr instanceof Expr\Eval_ + || $expr instanceof Expr\Print_ + || $expr instanceof Expr\UnaryMinus + || $expr instanceof Expr\UnaryPlus + ) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $result->getThrowPoints(); + $hasYield = $result->hasYield(); + + $scope = $result->getScope(); + } elseif ($expr instanceof Expr\YieldFrom) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $result->getThrowPoints(); + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + $hasYield = true; + + $scope = $result->getScope(); + } elseif ($expr instanceof BooleanNot) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + } elseif ($expr instanceof Expr\ClassConstFetch) { + $hasYield = false; + $throwPoints = []; + if ($expr->class instanceof Expr) { + $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + } + } elseif ($expr instanceof Expr\Empty_) { + $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr, true); + $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->expr); + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); + $scope = $this->lookForExitVariableAssign($scope, $expr->expr); + } elseif ($expr instanceof Expr\Isset_) { + $hasYield = false; + $throwPoints = []; + $nonNullabilityResults = []; + foreach ($expr->vars as $var) { + $nonNullabilityResult = $this->ensureNonNullability($scope, $var, true); + $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $var); + $result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $nonNullabilityResults[] = $nonNullabilityResult; + $scope = $this->lookForExitVariableAssign($scope, $var); + } + foreach (array_reverse($nonNullabilityResults) as $nonNullabilityResult) { + $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); + } + } elseif ($expr instanceof Instanceof_) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + if ($expr->class instanceof Expr) { + $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } + } elseif ($expr instanceof List_) { + // only in assign and foreach, processed elsewhere + return new ExpressionResult($scope, false, []); + } elseif ($expr instanceof New_) { + $parametersAcceptor = null; + $constructorReflection = null; + $hasYield = false; + $throwPoints = []; + if ($expr->class instanceof Expr) { + $objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr)); + if (count($objectClasses) === 1) { + $objectExprResult = $this->processExprNode(new New_(new Name($objectClasses[0])), $scope, static function (): void { + }, $context->enterDeep()); + $additionalThrowPoints = $objectExprResult->getThrowPoints(); + } else { + $additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; + } + + $result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + foreach ($additionalThrowPoints as $throwPoint) { + $throwPoints[] = $throwPoint; + } + } elseif ($expr->class instanceof Class_) { + $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope); // populates $expr->class->name + $this->processStmtNode($expr->class, $scope, $nodeCallback); + } else { + $className = $scope->resolveName($expr->class); + if ($this->reflectionProvider->hasClass($className)) { + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasConstructor()) { + $constructorReflection = $classReflection->getConstructor(); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->args, + $constructorReflection->getVariants() + ); + $constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->args, $scope); + if ($constructorThrowPoint !== null) { + $throwPoints[] = $constructorThrowPoint; + } + } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + } + $result = $this->processArgs($constructorReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + } elseif ( + $expr instanceof Expr\PreInc + || $expr instanceof Expr\PostInc + || $expr instanceof Expr\PreDec + || $expr instanceof Expr\PostDec + ) { + $result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = []; + if ( + $expr->var instanceof Variable + || $expr->var instanceof ArrayDimFetch + || $expr->var instanceof PropertyFetch + || $expr->var instanceof StaticPropertyFetch + ) { + $newExpr = $expr; + if ($expr instanceof Expr\PostInc) { + $newExpr = new Expr\PreInc($expr->var); + } elseif ($expr instanceof Expr\PostDec) { + $newExpr = new Expr\PreDec($expr->var); + } + + if (!$scope->getType($expr->var)->equals($scope->getType($newExpr))) { + $scope = $this->processAssignVar( + $scope, + $expr->var, + $newExpr, + static function (): void { + }, + $context, + static function (MutatingScope $scope): ExpressionResult { + return new ExpressionResult($scope, false, []); + }, + false + )->getScope(); + } else { + $scope = $scope->invalidateExpression($expr->var); + } + } + } elseif ($expr instanceof Ternary) { + $ternaryCondResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $ternaryCondResult->getThrowPoints(); + $ifTrueScope = $ternaryCondResult->getTruthyScope(); + $ifFalseScope = $ternaryCondResult->getFalseyScope(); + + if ($expr->if !== null) { + $ifResult = $this->processExprNode($expr->if, $ifTrueScope, $nodeCallback, $context); + $throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); + $ifTrueScope = $ifResult->getScope(); + } + + $elseResult = $this->processExprNode($expr->else, $ifFalseScope, $nodeCallback, $context); + $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); + $ifFalseScope = $elseResult->getScope(); + + $finalScope = $ifTrueScope->mergeWith($ifFalseScope); + + return new ExpressionResult( + $finalScope, + $ternaryCondResult->hasYield(), + $throwPoints, + static function () use ($finalScope, $expr): MutatingScope { + return $finalScope->filterByTruthyValue($expr); + }, + static function () use ($finalScope, $expr): MutatingScope { + return $finalScope->filterByFalseyValue($expr); + } + ); + } elseif ($expr instanceof Expr\Yield_) { + $throwPoints = [ + ThrowPoint::createImplicit($scope, $expr), + ]; + if ($expr->key !== null) { + $keyResult = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); + $scope = $keyResult->getScope(); + $throwPoints = $keyResult->getThrowPoints(); + } + if ($expr->value !== null) { + $valueResult = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); + $scope = $valueResult->getScope(); + $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); + } + $hasYield = true; + } elseif ($expr instanceof Expr\Match_) { + $deepContext = $context->enterDeep(); + $condResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $deepContext); + $scope = $condResult->getScope(); + $hasYield = $condResult->hasYield(); + $throwPoints = $condResult->getThrowPoints(); + $matchScope = $scope; + $armNodes = []; + foreach ($expr->arms as $arm) { + if ($arm->conds === null) { + $armResult = $this->processExprNode($arm->body, $matchScope, $nodeCallback, ExpressionContext::createTopLevel()); + $matchScope = $armResult->getScope(); + $hasYield = $hasYield || $armResult->hasYield(); + $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); + $scope = $scope->mergeWith($matchScope); + $armNodes[] = new MatchExpressionArm([], $arm->getLine()); + continue; + } + + if (count($arm->conds) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $filteringExpr = null; + $armCondScope = $matchScope; + $condNodes = []; + foreach ($arm->conds as $armCond) { + $condNodes[] = new MatchExpressionArmCondition($armCond, $armCondScope, $armCond->getLine()); + $armCondResult = $this->processExprNode($armCond, $armCondScope, $nodeCallback, $deepContext); + $hasYield = $hasYield || $armCondResult->hasYield(); + $throwPoints = array_merge($throwPoints, $armCondResult->getThrowPoints()); + $armCondExpr = new BinaryOp\Identical($expr->cond, $armCond); + $armCondScope = $armCondResult->getScope()->filterByFalseyValue($armCondExpr); + if ($filteringExpr === null) { + $filteringExpr = $armCondExpr; + continue; + } + + $filteringExpr = new BinaryOp\BooleanOr($filteringExpr, $armCondExpr); + } + + $armNodes[] = new MatchExpressionArm($condNodes, $arm->getLine()); + + $armResult = $this->processExprNode( + $arm->body, + $matchScope->filterByTruthyValue($filteringExpr), + $nodeCallback, + ExpressionContext::createTopLevel() + ); + $armScope = $armResult->getScope(); + $scope = $scope->mergeWith($armScope); + $hasYield = $hasYield || $armResult->hasYield(); + $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); + $matchScope = $matchScope->filterByFalseyValue($filteringExpr); + } + + $nodeCallback(new MatchExpressionNode($expr->cond, $armNodes, $expr, $matchScope), $scope); + } else { + $hasYield = false; + $throwPoints = []; + } + + return new ExpressionResult( + $scope, + $hasYield, + $throwPoints, + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByTruthyValue($expr); + }, + static function () use ($scope, $expr): MutatingScope { + return $scope->filterByFalseyValue($expr); + } + ); + } + + private function getFunctionThrowPoint( + FunctionReflection $functionReflection, + ?ParametersAcceptor $parametersAcceptor, + FuncCall $funcCall, + MutatingScope $scope + ): ?ThrowPoint { + foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicFunctionThrowTypeExtensions() as $extension) { + if (!$extension->isFunctionSupported($functionReflection)) { + continue; + } + + $throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $funcCall, $scope); + if ($throwType === null) { + return null; + } + + return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); + } + + if ($functionReflection->getThrowType() !== null) { + $throwType = $functionReflection->getThrowType(); + if (!$throwType instanceof VoidType) { + return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true); + } + } elseif ($this->implicitThrows) { + $requiredParameters = null; + if ($parametersAcceptor !== null) { + $requiredParameters = 0; + foreach ($parametersAcceptor->getParameters() as $parameter) { + if ($parameter->isOptional()) { + continue; + } + + $requiredParameters++; + } + } + if ( + !$functionReflection->isBuiltin() + || $requiredParameters === null + || $requiredParameters > 0 + || count($funcCall->args) > 0 + ) { + $functionReturnedType = $scope->getType($funcCall); + if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) { + return ThrowPoint::createImplicit($scope, $funcCall); + } + } + } + + return null; + } + + private function getMethodThrowPoint(MethodReflection $methodReflection, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint + { + foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) { + if (!$extension->isMethodSupported($methodReflection)) { + continue; + } + + $throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $methodCall, $scope); + if ($throwType === null) { + return null; + } + + return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); + } + + if ($methodReflection->getThrowType() !== null) { + $throwType = $methodReflection->getThrowType(); + if (!$throwType instanceof VoidType) { + return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); + } + } elseif ($this->implicitThrows) { + $methodReturnedType = $scope->getType($methodCall); + if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { + return ThrowPoint::createImplicit($scope, $methodCall); + } + } + + return null; + } + + /** + * @param Node\Arg[] $args + */ + private function getConstructorThrowPoint(MethodReflection $constructorReflection, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint + { + $methodCall = new StaticCall($className, $constructorReflection->getName(), $args); + foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { + if (!$extension->isStaticMethodSupported($constructorReflection)) { + continue; + } + + $throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $methodCall, $scope); + if ($throwType === null) { + return null; + } + + return ThrowPoint::createExplicit($scope, $throwType, $new, false); + } + + if ($constructorReflection->getThrowType() !== null) { + $throwType = $constructorReflection->getThrowType(); + if (!$throwType instanceof VoidType) { + return ThrowPoint::createExplicit($scope, $throwType, $new, true); + } + } elseif ($this->implicitThrows) { + if ($classReflection->getName() !== \Throwable::class && !$classReflection->isSubclassOf(\Throwable::class)) { + return ThrowPoint::createImplicit($scope, $methodCall); + } + } + + return null; + } + + private function getStaticMethodThrowPoint(MethodReflection $methodReflection, StaticCall $methodCall, MutatingScope $scope): ?ThrowPoint + { + foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { + if (!$extension->isStaticMethodSupported($methodReflection)) { + continue; + } + + $throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $methodCall, $scope); + if ($throwType === null) { + return null; + } + + return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); + } + + if ($methodReflection->getThrowType() !== null) { + $throwType = $methodReflection->getThrowType(); + if (!$throwType instanceof VoidType) { + return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); + } + } elseif ($this->implicitThrows) { + $methodReturnedType = $scope->getType($methodCall); + if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { + return ThrowPoint::createImplicit($scope, $methodCall); + } + } + + return null; + } + + /** + * @param Expr $expr + * @return string[] + */ + private function getAssignedVariables(Expr $expr): array + { + if ($expr instanceof Expr\Variable) { + if (is_string($expr->name)) { + return [$expr->name]; + } + + return []; + } + + if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + $names = []; + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + $names = array_merge($names, $this->getAssignedVariables($item->value)); + } + + return $names; + } + + return []; + } + + /** + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @param Expr $expr + * @param MutatingScope $scope + * @param ExpressionContext $context + */ + private function callNodeCallbackWithExpression( + callable $nodeCallback, + Expr $expr, + MutatingScope $scope, + ExpressionContext $context + ): void { + if ($context->isDeep()) { + $scope = $scope->exitFirstLevelStatements(); + } + $nodeCallback($expr, $scope); + } + + /** + * @param \PhpParser\Node\Expr\Closure $expr + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @param ExpressionContext $context + * @param Type|null $passedToType + * @return \PHPStan\Analyser\ExpressionResult + */ + private function processClosureNode( + Expr\Closure $expr, + MutatingScope $scope, + callable $nodeCallback, + ExpressionContext $context, + ?Type $passedToType + ): ExpressionResult { + foreach ($expr->params as $param) { + $this->processParamNode($param, $scope, $nodeCallback); + } + + $byRefUses = []; + + if ($passedToType !== null && !$passedToType->isCallable()->no()) { + $callableParameters = null; + $acceptors = $passedToType->getCallableParametersAcceptors($scope); + if (count($acceptors) === 1) { + $callableParameters = $acceptors[0]->getParameters(); + } + } else { + $callableParameters = null; + } + + $useScope = $scope; + foreach ($expr->uses as $use) { + if ($use->byRef) { + $byRefUses[] = $use; + $useScope = $useScope->enterExpressionAssign($use->var); + + $inAssignRightSideVariableName = $context->getInAssignRightSideVariableName(); + $inAssignRightSideType = $context->getInAssignRightSideType(); + if ( + $inAssignRightSideVariableName === $use->var->name + && $inAssignRightSideType !== null + ) { + if ($inAssignRightSideType instanceof ClosureType) { + $variableType = $inAssignRightSideType; + } else { + $alreadyHasVariableType = $scope->hasVariableType($inAssignRightSideVariableName); + if ($alreadyHasVariableType->no()) { + $variableType = TypeCombinator::union(new NullType(), $inAssignRightSideType); + } else { + $variableType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideType); + } + } + $scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType); + } + } + $this->processExprNode($use, $useScope, $nodeCallback, $context); + if (!$use->byRef) { + continue; + } + + $useScope = $useScope->exitExpressionAssign($use->var); + } + + if ($expr->returnType !== null) { + $nodeCallback($expr->returnType, $scope); + } + + $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); + $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); + $nodeCallback(new InClosureNode($expr), $closureScope); + + $gatheredReturnStatements = []; + $gatheredYieldStatements = []; + $closureStmtsCallback = static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope): void { + $nodeCallback($node, $scope); + if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { + return; + } + if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { + $gatheredYieldStatements[] = $node; + } + if (!$node instanceof Return_) { + return; + } + + $gatheredReturnStatements[] = new ReturnStatement($scope, $node); + }; + if (count($byRefUses) === 0) { + $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); + $nodeCallback(new ClosureReturnStatementsNode( + $expr, + $gatheredReturnStatements, + $gatheredYieldStatements, + $statementResult + ), $closureScope); + + return new ExpressionResult($scope, false, []); + } + + $count = 0; + do { + $prevScope = $closureScope; + + $intermediaryClosureScopeResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, static function (): void { + }); + $intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); + foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { + $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); + } + $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); + $closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); + if ($closureScope->equals($prevScope)) { + break; + } + $count++; + } while ($count < self::LOOP_SCOPE_ITERATIONS); + + $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); + $nodeCallback(new ClosureReturnStatementsNode( + $expr, + $gatheredReturnStatements, + $gatheredYieldStatements, + $statementResult + ), $closureScope); + + return new ExpressionResult($scope->processClosureScope($closureScope, null, $byRefUses), false, []); + } + + private function lookForArrayDestructuringArray(MutatingScope $scope, Expr $expr, Type $valueType): MutatingScope + { + if ($expr instanceof Array_ || $expr instanceof List_) { + foreach ($expr->items as $key => $item) { + /** @var \PhpParser\Node\Expr\ArrayItem|null $itemValue */ + $itemValue = $item; + if ($itemValue === null) { + continue; + } + + $keyType = $itemValue->key === null ? new ConstantIntegerType($key) : $scope->getType($itemValue->key); + $scope = $this->specifyItemFromArrayDestructuring($scope, $itemValue, $valueType, $keyType); + } + } elseif ($expr instanceof Variable && is_string($expr->name)) { + $scope = $scope->assignVariable($expr->name, new MixedType()); + } elseif ($expr instanceof ArrayDimFetch && $expr->var instanceof Variable && is_string($expr->var->name)) { + $scope = $scope->assignVariable($expr->var->name, new MixedType()); + } + + return $scope; + } + + private function specifyItemFromArrayDestructuring(MutatingScope $scope, ArrayItem $arrayItem, Type $valueType, Type $keyType): MutatingScope + { + $type = $valueType->getOffsetValueType($keyType); + + $itemNode = $arrayItem->value; + if ($itemNode instanceof Variable && is_string($itemNode->name)) { + $scope = $scope->assignVariable($itemNode->name, $type); + } elseif ($itemNode instanceof ArrayDimFetch && $itemNode->var instanceof Variable && is_string($itemNode->var->name)) { + $currentType = $scope->hasVariableType($itemNode->var->name)->no() + ? new ConstantArrayType([], []) + : $scope->getVariableType($itemNode->var->name); + $dimType = null; + if ($itemNode->dim !== null) { + $dimType = $scope->getType($itemNode->dim); + } + $scope = $scope->assignVariable($itemNode->var->name, $currentType->setOffsetValueType($dimType, $type)); + } else { + $scope = $this->lookForArrayDestructuringArray($scope, $itemNode, $type); + } + + return $scope; + } + + /** + * @param \PhpParser\Node\Param $param + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + private function processParamNode( + Node\Param $param, + MutatingScope $scope, + callable $nodeCallback + ): void { + foreach ($param->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $nodeCallback($arg->value, $scope); + } + } + } + $nodeCallback($param, $scope); + if ($param->type !== null) { + $nodeCallback($param->type, $scope); + } + if ($param->default === null) { + return; + } + + $this->processExprNode($param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + } + + /** + * @param \PHPStan\Reflection\MethodReflection|\PHPStan\Reflection\FunctionReflection|null $calleeReflection + * @param ParametersAcceptor|null $parametersAcceptor + * @param \PhpParser\Node\Arg[] $args + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @param ExpressionContext $context + * @param \PHPStan\Analyser\MutatingScope|null $closureBindScope + * @return \PHPStan\Analyser\ExpressionResult + */ + private function processArgs( + $calleeReflection, + ?ParametersAcceptor $parametersAcceptor, + array $args, + MutatingScope $scope, + callable $nodeCallback, + ExpressionContext $context, + ?MutatingScope $closureBindScope = null + ): ExpressionResult { + if ($parametersAcceptor !== null) { + $parameters = $parametersAcceptor->getParameters(); + } + + if ($calleeReflection !== null) { + $scope = $scope->pushInFunctionCall($calleeReflection); + } + + $hasYield = false; + $throwPoints = []; + foreach ($args as $i => $arg) { + $nodeCallback($arg, $scope); + if (isset($parameters) && $parametersAcceptor !== null) { + $assignByReference = false; + if (isset($parameters[$i])) { + $assignByReference = $parameters[$i]->passedByReference()->createsNewVariable(); + $parameterType = $parameters[$i]->getType(); + } elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { + $lastParameter = $parameters[count($parameters) - 1]; + $assignByReference = $lastParameter->passedByReference()->createsNewVariable(); + $parameterType = $lastParameter->getType(); + } + + if ($assignByReference) { + $argValue = $arg->value; + if ($argValue instanceof Variable && is_string($argValue->name)) { + $scope = $scope->assignVariable($argValue->name, new MixedType()); + } + } + + if ($calleeReflection instanceof FunctionReflection) { + if ( + $i === 0 + && $calleeReflection->getName() === 'array_map' + && isset($args[1]) + ) { + $parameterType = new CallableType([ + new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], new MixedType(), false); + } + + if ( + $i === 1 + && $calleeReflection->getName() === 'array_filter' + && isset($args[0]) + ) { + if (isset($args[2])) { + $mode = $scope->getType($args[2]->value); + if ($mode instanceof ConstantIntegerType) { + if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { + $arrayFilterParameters = [ + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { + $arrayFilterParameters = [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } + } + } + $parameterType = new CallableType( + $arrayFilterParameters ?? [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], + new MixedType(), + false + ); + } + } + } + + $originalScope = $scope; + $scopeToPass = $scope; + if ($i === 0 && $closureBindScope !== null) { + $scopeToPass = $closureBindScope; + } + + if ($arg->value instanceof Expr\Closure) { + $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); + $result = $this->processClosureNode($arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); + } else { + $result = $this->processExprNode($arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); + } + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + if ($i !== 0 || $closureBindScope === null) { + continue; + } + + $scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); + } + + if ($calleeReflection !== null) { + $scope = $scope->popInFunctionCall(); + } + + return new ExpressionResult($scope, $hasYield, $throwPoints); + } + + /** + * @param \PHPStan\Analyser\MutatingScope $scope + * @param \PhpParser\Node\Expr $var + * @param \PhpParser\Node\Expr $assignedExpr + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + * @param ExpressionContext $context + * @param \Closure(MutatingScope $scope): ExpressionResult $processExprCallback + * @param bool $enterExpressionAssign + * @return ExpressionResult + */ + private function processAssignVar( + MutatingScope $scope, + Expr $var, + Expr $assignedExpr, + callable $nodeCallback, + ExpressionContext $context, + \Closure $processExprCallback, + bool $enterExpressionAssign + ): ExpressionResult { + $nodeCallback($var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope); + $hasYield = false; + $throwPoints = []; + if ($var instanceof Variable && is_string($var->name)) { + $result = $processExprCallback($scope); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $assignedExpr = $this->unwrapAssign($assignedExpr); + $type = $scope->getType($assignedExpr); + $truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); + $falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); + + $conditionalExpressions = []; + + $truthyType = TypeCombinator::remove($type, StaticTypeFactory::falsey()); + $falseyType = TypeCombinator::intersect($type, StaticTypeFactory::falsey()); + + $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); + $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); + $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); + $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); + + $scope = $result->getScope()->assignVariable($var->name, $type); + foreach ($conditionalExpressions as $exprString => $holders) { + $scope = $scope->addConditionalExpressions($exprString, $holders); + } + } elseif ($var instanceof ArrayDimFetch) { + $dimExprStack = []; + $originalVar = $var; + while ($var instanceof ArrayDimFetch) { + $dimExprStack[] = $var->dim; + $var = $var->var; + } + + // 1. eval root expr + if ($enterExpressionAssign && $var instanceof Variable) { + $scope = $scope->enterExpressionAssign($var); + } + $result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + $scope = $result->getScope(); + if ($enterExpressionAssign && $var instanceof Variable) { + $scope = $scope->exitExpressionAssign($var); + } + + // 2. eval dimensions + $offsetTypes = []; + foreach (array_reverse($dimExprStack) as $dimExpr) { + if ($dimExpr === null) { + $offsetTypes[] = null; + } else { + $offsetTypes[] = $scope->getType($dimExpr); + + if ($enterExpressionAssign) { + $scope->enterExpressionAssign($dimExpr); + } + $result = $this->processExprNode($dimExpr, $scope, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + + if ($enterExpressionAssign) { + $scope = $scope->exitExpressionAssign($dimExpr); + } + } + } + + $valueToWrite = $scope->getType($assignedExpr); + $originalValueToWrite = $valueToWrite; + + // 3. eval assigned expr + $result = $processExprCallback($scope); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + + $varType = $scope->getType($var); + if (!(new ObjectType(\ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { + // 4. compose types + if ($varType instanceof ErrorType) { + $varType = new ConstantArrayType([], []); + } + $offsetValueType = $varType; + $offsetValueTypeStack = [$offsetValueType]; + foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { + if ($offsetType === null) { + $offsetValueType = new ConstantArrayType([], []); + } else { + $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); + if ($offsetValueType instanceof ErrorType) { + $offsetValueType = new ConstantArrayType([], []); + } + } + + $offsetValueTypeStack[] = $offsetValueType; + } + + foreach (array_reverse($offsetTypes) as $i => $offsetType) { + /** @var Type $offsetValueType */ + $offsetValueType = array_pop($offsetValueTypeStack); + + /** @phpstan-ignore-next-line */ + $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); + } + + if ($var instanceof Variable && is_string($var->name)) { + $scope = $scope->assignVariable($var->name, $valueToWrite); + } else { + $scope = $scope->assignExpression( + $var, + $valueToWrite + ); + } + + if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { + $currentVarType = $scope->getType($originalVar); + if (!$originalValueToWrite->isSuperTypeOf($currentVarType)->yes()) { + $scope = $scope->assignExpression( + $originalVar, + $originalValueToWrite + ); + } + } + } + } elseif ($var instanceof PropertyFetch) { + $objectResult = $this->processExprNode($var->var, $scope, $nodeCallback, $context); + $hasYield = $objectResult->hasYield(); + $throwPoints = $objectResult->getThrowPoints(); + $scope = $objectResult->getScope(); + + $propertyName = null; + if ($var->name instanceof Node\Identifier) { + $propertyName = $var->name->name; + } else { + $propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); + $hasYield = $hasYield || $propertyNameResult->hasYield(); + $throwPoints = array_merge($throwPoints, $propertyNameResult->getThrowPoints()); + $scope = $propertyNameResult->getScope(); + } + + $result = $processExprCallback($scope); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + + $propertyHolderType = $scope->getType($var->var); + if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { + $propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); + if ($propertyReflection->canChangeTypeAfterAssignment()) { + $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); + } + } else { + // fallback + $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); + } + } elseif ($var instanceof Expr\StaticPropertyFetch) { + if ($var->class instanceof \PhpParser\Node\Name) { + $propertyHolderType = $scope->resolveTypeByName($var->class); + } else { + $this->processExprNode($var->class, $scope, $nodeCallback, $context); + $propertyHolderType = $scope->getType($var->class); + } + + $propertyName = null; + if ($var->name instanceof Node\Identifier) { + $propertyName = $var->name->name; + $hasYield = false; + $throwPoints = []; + } else { + $propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); + $hasYield = $propertyNameResult->hasYield(); + $throwPoints = $propertyNameResult->getThrowPoints(); + $scope = $propertyNameResult->getScope(); + } + + $result = $processExprCallback($scope); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $scope = $result->getScope(); + + if ($propertyName !== null) { + $propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName); + if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { + $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); + } + } else { + // fallback + $scope = $scope->assignExpression($var, $scope->getType($assignedExpr)); + } + } + + return new ExpressionResult($scope, $hasYield, $throwPoints); + } + + private function unwrapAssign(Expr $expr): Expr + { + if ($expr instanceof Assign) { + return $this->unwrapAssign($expr->expr); + } + + return $expr; + } + + /** + * @param Scope $scope + * @param string $variableName + * @param array $conditionalExpressions + * @param SpecifiedTypes $specifiedTypes + * @param Type $variableType + * @return array + */ + private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array + { + foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) { + if (!$expr instanceof Variable) { + continue; + } + if (!is_string($expr->name)) { + continue; + } + + if (!isset($conditionalExpressions[$exprString])) { + $conditionalExpressions[$exprString] = []; + } + + $conditionalExpressions[$exprString][] = new ConditionalExpressionHolder([ + '$' . $variableName => $variableType, + ], VariableTypeHolder::createYes( + TypeCombinator::intersect($scope->getType($expr), $exprType) + )); + } + + return $conditionalExpressions; + } + + /** + * @param Scope $scope + * @param string $variableName + * @param array $conditionalExpressions + * @param SpecifiedTypes $specifiedTypes + * @param Type $variableType + * @return array + */ + private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array + { + foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) { + if (!$expr instanceof Variable) { + continue; + } + if (!is_string($expr->name)) { + continue; + } + + if (!isset($conditionalExpressions[$exprString])) { + $conditionalExpressions[$exprString] = []; + } + + $conditionalExpressions[$exprString][] = new ConditionalExpressionHolder([ + '$' . $variableName => $variableType, + ], VariableTypeHolder::createYes( + TypeCombinator::remove($scope->getType($expr), $exprType) + )); + } + + return $conditionalExpressions; + } + + private function processStmtVarAnnotation(MutatingScope $scope, Node\Stmt $stmt, ?Expr $defaultExpr): MutatingScope + { + $function = $scope->getFunction(); + $variableLessTags = []; + + foreach ($stmt->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $comment->getText() + ); + + $assignedVariable = null; + if ( + $stmt instanceof Node\Stmt\Expression + && ($stmt->expr instanceof Assign || $stmt->expr instanceof AssignRef) + && $stmt->expr->var instanceof Variable + && is_string($stmt->expr->var->name) + ) { + $assignedVariable = $stmt->expr->var->name; + } + + foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { + if (is_int($name)) { + $variableLessTags[] = $varTag; + continue; + } + + if ($name === $assignedVariable) { + continue; + } + + $certainty = $scope->hasVariableType($name); + if ($certainty->no()) { + continue; + } + + if ($scope->isInClass() && $scope->getFunction() === null) { + continue; + } + + if ($scope->canAnyVariableExist()) { + $certainty = TrinaryLogic::createYes(); + } + + $scope = $scope->assignVariable($name, $varTag->getType(), $certainty); + } + } + + if (count($variableLessTags) === 1 && $defaultExpr !== null) { + $scope = $scope->specifyExpressionType($defaultExpr, $variableLessTags[0]->getType()); + } + + return $scope; + } + + /** + * @param MutatingScope $scope + * @param array $variableNames + * @param Node $node + * @param bool $changed + * @return MutatingScope + */ + private function processVarAnnotation(MutatingScope $scope, array $variableNames, Node $node, bool &$changed = false): MutatingScope + { + $function = $scope->getFunction(); + $varTags = []; + foreach ($node->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $comment->getText() + ); + foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { + $varTags[$key] = $varTag; + } + } + + if (count($varTags) === 0) { + return $scope; + } + + foreach ($variableNames as $variableName) { + if (!isset($varTags[$variableName])) { + continue; + } + + $variableType = $varTags[$variableName]->getType(); + $changed = true; + $scope = $scope->assignVariable($variableName, $variableType); + } + + if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { + $variableType = $varTags[0]->getType(); + $changed = true; + $scope = $scope->assignVariable($variableNames[0], $variableType); + } + + return $scope; + } + + private function enterForeach(MutatingScope $scope, Foreach_ $stmt): MutatingScope + { + if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { + $scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); + } + $iterateeType = $scope->getType($stmt->expr); + $vars = []; + if ($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name)) { + $scope = $scope->enterForeach( + $stmt->expr, + $stmt->valueVar->name, + $stmt->keyVar !== null + && $stmt->keyVar instanceof Variable + && is_string($stmt->keyVar->name) + ? $stmt->keyVar->name + : null + ); + $vars[] = $stmt->valueVar->name; + } + + if ( + $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) + ) { + $scope = $scope->enterForeachKey($stmt->expr, $stmt->keyVar->name); + $vars[] = $stmt->keyVar->name; + } + + if ( + $stmt->getDocComment() === null + && $iterateeType instanceof ConstantArrayType + && $stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name) + && $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) + ) { + $conditionalHolders = []; + foreach ($iterateeType->getKeyTypes() as $i => $keyType) { + $valueType = $iterateeType->getValueTypes()[$i]; + $conditionalHolders[] = new ConditionalExpressionHolder([ + '$' . $stmt->keyVar->name => $keyType, + ], new VariableTypeHolder($valueType, TrinaryLogic::createYes())); + } + + $scope = $scope->addConditionalExpressions( + '$' . $stmt->valueVar->name, + $conditionalHolders + ); + } + + if ($stmt->valueVar instanceof List_ || $stmt->valueVar instanceof Array_) { + $exprType = $scope->getType($stmt->expr); + $itemType = $exprType->getIterableValueType(); + $scope = $this->lookForArrayDestructuringArray($scope, $stmt->valueVar, $itemType); + $vars = array_merge($vars, $this->getAssignedVariables($stmt->valueVar)); + } + + $scope = $this->processVarAnnotation($scope, $vars, $stmt); + + return $scope; + } + + /** + * @param \PhpParser\Node\Stmt\TraitUse $node + * @param MutatingScope $classScope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, callable $nodeCallback): void + { + foreach ($node->traits as $trait) { + $traitName = (string) $trait; + if (!$this->reflectionProvider->hasClass($traitName)) { + continue; + } + $traitReflection = $this->reflectionProvider->getClass($traitName); + $traitFileName = $traitReflection->getFileName(); + if ($traitFileName === false) { + continue; // trait from eval or from PHP itself + } + $fileName = $this->fileHelper->normalizePath($traitFileName); + if (!isset($this->analysedFiles[$fileName])) { + continue; + } + $parserNodes = $this->parser->parseFile($fileName); + $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $nodeCallback); + } + } + + /** + * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node + * @param ClassReflection $traitReflection + * @param \PHPStan\Analyser\MutatingScope $scope + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, callable $nodeCallback): void + { + if ($node instanceof Node) { + if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { + $this->processStmtNodes($node, $node->stmts, $scope->enterTrait($traitReflection), $nodeCallback); + return; + } + if ($node instanceof Node\Stmt\ClassLike) { + return; + } + if ($node instanceof Node\FunctionLike) { + return; + } + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->{$subNodeName}; + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); + } + } elseif (is_array($node)) { + foreach ($node as $subNode) { + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); + } + } + } + + /** + * @param Scope $scope + * @param Node\FunctionLike $functionLike + * @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null} + */ + public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array + { + $templateTypeMap = TemplateTypeMap::createEmpty(); + $phpDocParameterTypes = []; + $phpDocReturnType = null; + $phpDocThrowType = null; + $deprecatedDescription = null; + $isDeprecated = false; + $isInternal = false; + $isFinal = false; + $isPure = false; + $docComment = $functionLike->getDocComment() !== null + ? $functionLike->getDocComment()->getText() + : null; + + $file = $scope->getFile(); + $class = $scope->isInClass() ? $scope->getClassReflection()->getName() : null; + $trait = $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null; + $resolvedPhpDoc = null; + $functionName = null; + + if ($functionLike instanceof Node\Stmt\ClassMethod) { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $functionName = $functionLike->name->name; + $positionalParameterNames = array_map(static function (Node\Param $param): string { + if (!$param->var instanceof Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $param->var->name; + }, $functionLike->getParams()); + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $docComment, + $file, + $scope->getClassReflection(), + $trait, + $functionLike->name->name, + $positionalParameterNames + ); + + if ($functionLike->name->toLowerString() === '__construct') { + foreach ($functionLike->params as $param) { + if ($param->flags === 0) { + continue; + } + + if ($param->getDocComment() === null) { + continue; + } + + if ( + !$param->var instanceof Variable + || !is_string($param->var->name) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $paramPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $class, + $trait, + '__construct', + $param->getDocComment()->getText() + ); + $varTags = $paramPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } elseif (isset($varTags[$param->var->name])) { + $phpDocType = $varTags[$param->var->name]->getType(); + } else { + continue; + } + + $phpDocParameterTypes[$param->var->name] = $phpDocType; + } + } + } elseif ($functionLike instanceof Node\Stmt\Function_) { + $functionName = trim($scope->getNamespace() . '\\' . $functionLike->name->name, '\\'); + } + + if ($docComment !== null && $resolvedPhpDoc === null) { + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $class, + $trait, + $functionName, + $docComment + ); + } + + if ($resolvedPhpDoc !== null) { + $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); + foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { + if (array_key_exists($paramName, $phpDocParameterTypes)) { + continue; + } + $paramType = $paramTag->getType(); + if ($scope->isInClass()) { + $paramType = $this->transformStaticType($scope->getClassReflection(), $paramType); + } + $phpDocParameterTypes[$paramName] = $paramType; + } + $nativeReturnType = $scope->getFunctionType($functionLike->getReturnType(), false, false); + $phpDocReturnType = $this->getPhpDocReturnType($resolvedPhpDoc, $nativeReturnType); + if ($phpDocReturnType !== null && $scope->isInClass()) { + $phpDocReturnType = $this->transformStaticType($scope->getClassReflection(), $phpDocReturnType); + } + $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); + $isPure = $resolvedPhpDoc->isPure(); + } + + return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure]; + } + + private function transformStaticType(ClassReflection $declaringClass, Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { + if ($type instanceof StaticType) { + $changedType = $type->changeBaseClass($declaringClass); + if ($declaringClass->isFinal()) { + $changedType = $changedType->getStaticObjectType(); + } + return $traverse($changedType); + } + + return $traverse($type); + }); + } + + private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type + { + $returnTag = $resolvedPhpDoc->getReturnTag(); + + if ($returnTag === null) { + return null; + } + + $phpDocReturnType = $returnTag->getType(); + + if ($returnTag->isExplicit()) { + return $phpDocReturnType; + } + + if ($nativeReturnType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocReturnType))->yes()) { + return $phpDocReturnType; + } + + return null; + } } diff --git a/src/Analyser/NullsafeOperatorHelper.php b/src/Analyser/NullsafeOperatorHelper.php index 5c4ce696f9..687f67ff65 100644 --- a/src/Analyser/NullsafeOperatorHelper.php +++ b/src/Analyser/NullsafeOperatorHelper.php @@ -1,4 +1,6 @@ -var), $expr->name, $expr->args); - } - - if ($expr instanceof Expr\MethodCall) { - $var = self::getNullsafeShortcircuitedExpr($expr->var); - if ($expr->var === $var) { - return $expr; - } - - return new Expr\MethodCall($var, $expr->name, $expr->args); - } - - if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { - $class = self::getNullsafeShortcircuitedExpr($expr->class); - if ($expr->class === $class) { - return $expr; - } - - return new Expr\StaticCall($class, $expr->name, $expr->args); - } - - if ($expr instanceof Expr\ArrayDimFetch) { - $var = self::getNullsafeShortcircuitedExpr($expr->var); - if ($expr->var === $var) { - return $expr; - } - - return new Expr\ArrayDimFetch($var, $expr->dim); - } - - if ($expr instanceof Expr\NullsafePropertyFetch) { - return new Expr\PropertyFetch(self::getNullsafeShortcircuitedExpr($expr->var), $expr->name); - } - - if ($expr instanceof Expr\PropertyFetch) { - $var = self::getNullsafeShortcircuitedExpr($expr->var); - if ($expr->var === $var) { - return $expr; - } - - return new Expr\PropertyFetch($var, $expr->name); - } - - if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - $class = self::getNullsafeShortcircuitedExpr($expr->class); - if ($expr->class === $class) { - return $expr; - } - - return new Expr\StaticPropertyFetch($class, $expr->name); - } - - return $expr; - } - + public static function getNullsafeShortcircuitedExpr(Expr $expr): Expr + { + if ($expr instanceof Expr\NullsafeMethodCall) { + return new Expr\MethodCall(self::getNullsafeShortcircuitedExpr($expr->var), $expr->name, $expr->args); + } + + if ($expr instanceof Expr\MethodCall) { + $var = self::getNullsafeShortcircuitedExpr($expr->var); + if ($expr->var === $var) { + return $expr; + } + + return new Expr\MethodCall($var, $expr->name, $expr->args); + } + + if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { + $class = self::getNullsafeShortcircuitedExpr($expr->class); + if ($expr->class === $class) { + return $expr; + } + + return new Expr\StaticCall($class, $expr->name, $expr->args); + } + + if ($expr instanceof Expr\ArrayDimFetch) { + $var = self::getNullsafeShortcircuitedExpr($expr->var); + if ($expr->var === $var) { + return $expr; + } + + return new Expr\ArrayDimFetch($var, $expr->dim); + } + + if ($expr instanceof Expr\NullsafePropertyFetch) { + return new Expr\PropertyFetch(self::getNullsafeShortcircuitedExpr($expr->var), $expr->name); + } + + if ($expr instanceof Expr\PropertyFetch) { + $var = self::getNullsafeShortcircuitedExpr($expr->var); + if ($expr->var === $var) { + return $expr; + } + + return new Expr\PropertyFetch($var, $expr->name); + } + + if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { + $class = self::getNullsafeShortcircuitedExpr($expr->class); + if ($expr->class === $class) { + return $expr; + } + + return new Expr\StaticPropertyFetch($class, $expr->name); + } + + return $expr; + } } diff --git a/src/Analyser/OutOfClassScope.php b/src/Analyser/OutOfClassScope.php index 538c9ba914..be0b530b3a 100644 --- a/src/Analyser/OutOfClassScope.php +++ b/src/Analyser/OutOfClassScope.php @@ -1,4 +1,6 @@ -isPublic(); - } - - public function canCallMethod(MethodReflection $methodReflection): bool - { - return $methodReflection->isPublic(); - } - - public function canAccessConstant(ConstantReflection $constantReflection): bool - { - return $constantReflection->isPublic(); - } - + public function isInClass(): bool + { + return false; + } + + public function getClassReflection(): ?ClassReflection + { + return null; + } + + public function canAccessProperty(PropertyReflection $propertyReflection): bool + { + return $propertyReflection->isPublic(); + } + + public function canCallMethod(MethodReflection $methodReflection): bool + { + return $methodReflection->isPublic(); + } + + public function canAccessConstant(ConstantReflection $constantReflection): bool + { + return $constantReflection->isPublic(); + } } diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index d18cb4a449..246c74d636 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -1,4 +1,6 @@ -> */ - private array $errors; - - /** @var array> */ - private array $dependencies; - - /** @var array> */ - private array $exportedNodes; - - /** - * @param string[] $filesToAnalyse - * @param bool $fullAnalysis - * @param int $lastFullAnalysisTime - * @param mixed[] $meta - * @param array> $errors - * @param array> $dependencies - * @param array> $exportedNodes - */ - public function __construct( - array $filesToAnalyse, - bool $fullAnalysis, - int $lastFullAnalysisTime, - array $meta, - array $errors, - array $dependencies, - array $exportedNodes - ) - { - $this->filesToAnalyse = $filesToAnalyse; - $this->fullAnalysis = $fullAnalysis; - $this->lastFullAnalysisTime = $lastFullAnalysisTime; - $this->meta = $meta; - $this->errors = $errors; - $this->dependencies = $dependencies; - $this->exportedNodes = $exportedNodes; - } - - /** - * @return string[] - */ - public function getFilesToAnalyse(): array - { - return $this->filesToAnalyse; - } - - public function isFullAnalysis(): bool - { - return $this->fullAnalysis; - } - - public function getLastFullAnalysisTime(): int - { - return $this->lastFullAnalysisTime; - } - - /** - * @return mixed[] - */ - public function getMeta(): array - { - return $this->meta; - } - - /** - * @return array> - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return array> - */ - public function getDependencies(): array - { - return $this->dependencies; - } - - /** - * @return array> - */ - public function getExportedNodes(): array - { - return $this->exportedNodes; - } - + private bool $fullAnalysis; + + /** @var string[] */ + private array $filesToAnalyse; + + private int $lastFullAnalysisTime; + + /** @var mixed[] */ + private array $meta; + + /** @var array> */ + private array $errors; + + /** @var array> */ + private array $dependencies; + + /** @var array> */ + private array $exportedNodes; + + /** + * @param string[] $filesToAnalyse + * @param bool $fullAnalysis + * @param int $lastFullAnalysisTime + * @param mixed[] $meta + * @param array> $errors + * @param array> $dependencies + * @param array> $exportedNodes + */ + public function __construct( + array $filesToAnalyse, + bool $fullAnalysis, + int $lastFullAnalysisTime, + array $meta, + array $errors, + array $dependencies, + array $exportedNodes + ) { + $this->filesToAnalyse = $filesToAnalyse; + $this->fullAnalysis = $fullAnalysis; + $this->lastFullAnalysisTime = $lastFullAnalysisTime; + $this->meta = $meta; + $this->errors = $errors; + $this->dependencies = $dependencies; + $this->exportedNodes = $exportedNodes; + } + + /** + * @return string[] + */ + public function getFilesToAnalyse(): array + { + return $this->filesToAnalyse; + } + + public function isFullAnalysis(): bool + { + return $this->fullAnalysis; + } + + public function getLastFullAnalysisTime(): int + { + return $this->lastFullAnalysisTime; + } + + /** + * @return mixed[] + */ + public function getMeta(): array + { + return $this->meta; + } + + /** + * @return array> + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return array> + */ + public function getDependencies(): array + { + return $this->dependencies; + } + + /** + * @return array> + */ + public function getExportedNodes(): array + { + return $this->exportedNodes; + } } diff --git a/src/Analyser/ResultCache/ResultCacheClearer.php b/src/Analyser/ResultCache/ResultCacheClearer.php index e6628002bb..296c98d2e2 100644 --- a/src/Analyser/ResultCache/ResultCacheClearer.php +++ b/src/Analyser/ResultCache/ResultCacheClearer.php @@ -1,4 +1,6 @@ -cacheFilePath = $cacheFilePath; - $this->tempResultCachePath = $tempResultCachePath; - } - - public function clear(): string - { - $dir = dirname($this->cacheFilePath); - if (!is_file($this->cacheFilePath)) { - return $dir; - } - - @unlink($this->cacheFilePath); - - return $dir; - } - - public function clearTemporaryCaches(): void - { - $finder = new Finder(); - foreach ($finder->files()->name('*.php')->in($this->tempResultCachePath) as $tmpResultCacheFile) { - @unlink($tmpResultCacheFile->getPathname()); - } - } - + private string $cacheFilePath; + + private string $tempResultCachePath; + + public function __construct(string $cacheFilePath, string $tempResultCachePath) + { + $this->cacheFilePath = $cacheFilePath; + $this->tempResultCachePath = $tempResultCachePath; + } + + public function clear(): string + { + $dir = dirname($this->cacheFilePath); + if (!is_file($this->cacheFilePath)) { + return $dir; + } + + @unlink($this->cacheFilePath); + + return $dir; + } + + public function clearTemporaryCaches(): void + { + $finder = new Finder(); + foreach ($finder->files()->name('*.php')->in($this->tempResultCachePath) as $tmpResultCacheFile) { + @unlink($tmpResultCacheFile->getPathname()); + } + } } diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index a00e958339..fd247e20df 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -1,4 +1,6 @@ - */ - private array $fileHashes = []; - - /** @var array */ - private array $fileReplacements = []; - - /** @var array */ - private array $alreadyProcessed = []; - - /** - * @param ExportedNodeFetcher $exportedNodeFetcher - * @param FileFinder $scanFileFinder - * @param ReflectionProvider $reflectionProvider - * @param string $cacheFilePath - * @param string $tempResultCachePath - * @param string[] $analysedPaths - * @param string[] $composerAutoloaderProjectPaths - * @param string[] $stubFiles - * @param string $usedLevel - * @param string|null $cliAutoloadFile - * @param string[] $bootstrapFiles - * @param string[] $scanFiles - * @param string[] $scanDirectories - * @param array $fileReplacements - */ - public function __construct( - ExportedNodeFetcher $exportedNodeFetcher, - FileFinder $scanFileFinder, - ReflectionProvider $reflectionProvider, - string $cacheFilePath, - string $tempResultCachePath, - array $analysedPaths, - array $composerAutoloaderProjectPaths, - array $stubFiles, - string $usedLevel, - ?string $cliAutoloadFile, - array $bootstrapFiles, - array $scanFiles, - array $scanDirectories, - array $fileReplacements - ) - { - $this->exportedNodeFetcher = $exportedNodeFetcher; - $this->scanFileFinder = $scanFileFinder; - $this->reflectionProvider = $reflectionProvider; - $this->cacheFilePath = $cacheFilePath; - $this->tempResultCachePath = $tempResultCachePath; - $this->analysedPaths = $analysedPaths; - $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - $this->stubFiles = $stubFiles; - $this->usedLevel = $usedLevel; - $this->cliAutoloadFile = $cliAutoloadFile; - $this->bootstrapFiles = $bootstrapFiles; - $this->scanFiles = $scanFiles; - $this->scanDirectories = $scanDirectories; - $this->fileReplacements = $fileReplacements; - } - - /** - * @param string[] $allAnalysedFiles - * @param mixed[]|null $projectConfigArray - * @param bool $debug - * @return ResultCache - */ - public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?array $projectConfigArray, Output $output, ?string $resultCacheName = null): ResultCache - { - if ($debug) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because of debug mode.'); - } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); - } - if ($onlyFiles) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.'); - } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); - } - - $cacheFilePath = $this->cacheFilePath; - if ($resultCacheName !== null) { - $tmpCacheFile = $this->tempResultCachePath . '/' . $resultCacheName . '.php'; - if (is_file($tmpCacheFile)) { - $cacheFilePath = $tmpCacheFile; - } - } - - if (!is_file($cacheFilePath)) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because the cache file does not exist.'); - } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); - } - - try { - $data = require $cacheFilePath; - } catch (\Throwable $e) { - if ($output->isDebug()) { - $output->writeLineFormatted(sprintf('Result cache not used because an error occurred while loading the cache file: %s', $e->getMessage())); - } - - @unlink($cacheFilePath); - - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); - } - - if (!is_array($data)) { - @unlink($cacheFilePath); - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because the cache file is corrupted.'); - } - - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); - } - - $meta = $this->getMeta($allAnalysedFiles, $projectConfigArray); - if ($this->isMetaDifferent($data['meta'], $meta)) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because the metadata do not match.'); - } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); - } - - if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.'); - } - // run full analysis if the result cache is older than 7 days - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); - } - - foreach ($data['projectExtensionFiles'] as $extensionFile => $fileHash) { - if (!is_file($extensionFile)) { - if ($output->isDebug()) { - $output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile)); - } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); - } - - if ($this->getFileHash($extensionFile) === $fileHash) { - continue; - } - - if ($output->isDebug()) { - $output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile)); - } - - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); - } - - $invertedDependencies = $data['dependencies']; - $deletedFiles = array_fill_keys(array_keys($invertedDependencies), true); - $filesToAnalyse = []; - $invertedDependenciesToReturn = []; - $errors = $data['errorsCallback'](); - $exportedNodes = $data['exportedNodesCallback'](); - $filteredErrors = []; - $filteredExportedNodes = []; - $newFileAppeared = false; - foreach ($allAnalysedFiles as $analysedFile) { - if (array_key_exists($analysedFile, $errors)) { - $filteredErrors[$analysedFile] = $errors[$analysedFile]; - } - if (array_key_exists($analysedFile, $exportedNodes)) { - $filteredExportedNodes[$analysedFile] = $exportedNodes[$analysedFile]; - } - if (!array_key_exists($analysedFile, $invertedDependencies)) { - // new file - $filesToAnalyse[] = $analysedFile; - $newFileAppeared = true; - continue; - } - - unset($deletedFiles[$analysedFile]); - - $analysedFileData = $invertedDependencies[$analysedFile]; - $cachedFileHash = $analysedFileData['fileHash']; - $dependentFiles = $analysedFileData['dependentFiles']; - $invertedDependenciesToReturn[$analysedFile] = $dependentFiles; - $currentFileHash = $this->getFileHash($analysedFile); - - if ($cachedFileHash === $currentFileHash) { - continue; - } - - $filesToAnalyse[] = $analysedFile; - if (!array_key_exists($analysedFile, $filteredExportedNodes)) { - continue; - } - - $cachedFileExportedNodes = $filteredExportedNodes[$analysedFile]; - if (count($dependentFiles) === 0) { - continue; - } - if (!$this->exportedNodesChanged($analysedFile, $cachedFileExportedNodes)) { - continue; - } - - foreach ($dependentFiles as $dependentFile) { - if (!is_file($dependentFile)) { - continue; - } - $filesToAnalyse[] = $dependentFile; - } - } - - foreach (array_keys($deletedFiles) as $deletedFile) { - if (!array_key_exists($deletedFile, $invertedDependencies)) { - continue; - } - - $deletedFileData = $invertedDependencies[$deletedFile]; - $dependentFiles = $deletedFileData['dependentFiles']; - foreach ($dependentFiles as $dependentFile) { - if (!is_file($dependentFile)) { - continue; - } - $filesToAnalyse[] = $dependentFile; - } - } - - if ($newFileAppeared) { - foreach (array_keys($filteredErrors) as $fileWithError) { - $filesToAnalyse[] = $fileWithError; - } - } - - return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $invertedDependenciesToReturn, $filteredExportedNodes); - } - - /** - * @param mixed[] $cachedMeta - * @param mixed[] $currentMeta - * @return bool - */ - private function isMetaDifferent(array $cachedMeta, array $currentMeta): bool - { - $projectConfig = $currentMeta['projectConfig']; - if ($projectConfig !== null) { - $currentMeta['projectConfig'] = Neon::encode($currentMeta['projectConfig']); - } - - return $cachedMeta !== $currentMeta; - } - - /** - * @param string $analysedFile - * @param array $cachedFileExportedNodes - * @return bool - */ - private function exportedNodesChanged(string $analysedFile, array $cachedFileExportedNodes): bool - { - if (array_key_exists($analysedFile, $this->fileReplacements)) { - $analysedFile = $this->fileReplacements[$analysedFile]; - } - $fileExportedNodes = $this->exportedNodeFetcher->fetchNodes($analysedFile); - if (count($fileExportedNodes) !== count($cachedFileExportedNodes)) { - return true; - } - - foreach ($fileExportedNodes as $i => $fileExportedNode) { - $cachedExportedNode = $cachedFileExportedNodes[$i]; - if (!$cachedExportedNode->equals($fileExportedNode)) { - return true; - } - } - - return false; - } - - /** - * @param AnalyserResult $analyserResult - * @param ResultCache $resultCache - * @param bool|string $save - * @return ResultCacheProcessResult - */ - public function process(AnalyserResult $analyserResult, ResultCache $resultCache, Output $output, bool $onlyFiles, $save): ResultCacheProcessResult - { - $internalErrors = $analyserResult->getInternalErrors(); - $freshErrorsByFile = []; - foreach ($analyserResult->getErrors() as $error) { - $freshErrorsByFile[$error->getFilePath()][] = $error; - } - - $meta = $resultCache->getMeta(); - $doSave = function (array $errorsByFile, ?array $dependencies, array $exportedNodes, ?string $resultCacheName) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { - if ($onlyFiles) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.'); - } - return false; - } - if ($dependencies === null) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache was not saved because of error in dependencies.'); - } - return false; - } - - if (count($internalErrors) > 0) { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache was not saved because of internal errors.'); - } - return false; - } - - foreach ($errorsByFile as $errors) { - foreach ($errors as $error) { - if (!$error->hasNonIgnorableException()) { - continue; - } - - if ($output->isDebug()) { - $output->writeLineFormatted(sprintf('Result cache was not saved because of non-ignorable exception: %s', $error->getMessage())); - } - - return false; - } - } - - $this->save($resultCache->getLastFullAnalysisTime(), $resultCacheName, $errorsByFile, $dependencies, $exportedNodes, $meta); - - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache is saved.'); - } - - return true; - }; - - if ($resultCache->isFullAnalysis()) { - $saved = false; - if ($save !== false) { - $saved = $doSave($freshErrorsByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), is_string($save) ? $save : null); - } else { - if ($output->isDebug()) { - $output->writeLineFormatted('Result cache was not saved because it was not requested.'); - } - } - - return new ResultCacheProcessResult($analyserResult, $saved); - } - - $errorsByFile = $this->mergeErrors($resultCache, $freshErrorsByFile); - $dependencies = $this->mergeDependencies($resultCache, $analyserResult->getDependencies()); - $exportedNodes = $this->mergeExportedNodes($resultCache, $analyserResult->getExportedNodes()); - - $saved = false; - if ($save !== false) { - $saved = $doSave($errorsByFile, $dependencies, $exportedNodes, is_string($save) ? $save : null); - } - - $flatErrors = []; - foreach ($errorsByFile as $fileErrors) { - foreach ($fileErrors as $fileError) { - $flatErrors[] = $fileError; - } - } - - return new ResultCacheProcessResult(new AnalyserResult( - $flatErrors, - $internalErrors, - $dependencies, - $exportedNodes, - $analyserResult->hasReachedInternalErrorsCountLimit() - ), $saved); - } - - /** - * @param ResultCache $resultCache - * @param array> $freshErrorsByFile - * @return array> - */ - private function mergeErrors(ResultCache $resultCache, array $freshErrorsByFile): array - { - $errorsByFile = $resultCache->getErrors(); - foreach ($resultCache->getFilesToAnalyse() as $file) { - if (!array_key_exists($file, $freshErrorsByFile)) { - unset($errorsByFile[$file]); - continue; - } - $errorsByFile[$file] = $freshErrorsByFile[$file]; - } - - return $errorsByFile; - } - - /** - * @param ResultCache $resultCache - * @param array>|null $freshDependencies - * @return array>|null - */ - private function mergeDependencies(ResultCache $resultCache, ?array $freshDependencies): ?array - { - if ($freshDependencies === null) { - return null; - } - - $cachedDependencies = []; - $resultCacheDependencies = $resultCache->getDependencies(); - $filesNoOneIsDependingOn = array_fill_keys(array_keys($resultCacheDependencies), true); - foreach ($resultCacheDependencies as $file => $filesDependingOnFile) { - foreach ($filesDependingOnFile as $fileDependingOnFile) { - $cachedDependencies[$fileDependingOnFile][] = $file; - unset($filesNoOneIsDependingOn[$fileDependingOnFile]); - } - } - - foreach (array_keys($filesNoOneIsDependingOn) as $file) { - if (array_key_exists($file, $cachedDependencies)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $cachedDependencies[$file] = []; - } - - $newDependencies = $cachedDependencies; - foreach ($resultCache->getFilesToAnalyse() as $file) { - if (!array_key_exists($file, $freshDependencies)) { - unset($newDependencies[$file]); - continue; - } - - $newDependencies[$file] = $freshDependencies[$file]; - } - - return $newDependencies; - } - - /** - * @param ResultCache $resultCache - * @param array> $freshExportedNodes - * @return array> - */ - private function mergeExportedNodes(ResultCache $resultCache, array $freshExportedNodes): array - { - $newExportedNodes = $resultCache->getExportedNodes(); - foreach ($resultCache->getFilesToAnalyse() as $file) { - if (!array_key_exists($file, $freshExportedNodes)) { - unset($newExportedNodes[$file]); - continue; - } - - $newExportedNodes[$file] = $freshExportedNodes[$file]; - } - - return $newExportedNodes; - } - - /** - * @param int $lastFullAnalysisTime - * @param string|null $resultCacheName - * @param array> $errors - * @param array> $dependencies - * @param array> $exportedNodes - * @param mixed[] $meta - */ - private function save( - int $lastFullAnalysisTime, - ?string $resultCacheName, - array $errors, - array $dependencies, - array $exportedNodes, - array $meta - ): void - { - $invertedDependencies = []; - $filesNoOneIsDependingOn = array_fill_keys(array_keys($dependencies), true); - foreach ($dependencies as $file => $fileDependencies) { - foreach ($fileDependencies as $fileDep) { - if (!array_key_exists($fileDep, $invertedDependencies)) { - $invertedDependencies[$fileDep] = [ - 'fileHash' => $this->getFileHash($fileDep), - 'dependentFiles' => [], - ]; - unset($filesNoOneIsDependingOn[$fileDep]); - } - $invertedDependencies[$fileDep]['dependentFiles'][] = $file; - } - } - - foreach (array_keys($filesNoOneIsDependingOn) as $file) { - if (array_key_exists($file, $invertedDependencies)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!is_file($file)) { - continue; - } - - $invertedDependencies[$file] = [ - 'fileHash' => $this->getFileHash($file), - 'dependentFiles' => [], - ]; - } - - ksort($errors); - ksort($invertedDependencies); - - foreach ($invertedDependencies as $file => $fileData) { - $dependentFiles = $fileData['dependentFiles']; - sort($dependentFiles); - $invertedDependencies[$file]['dependentFiles'] = $dependentFiles; - } - - $template = <<<'php' + private const CACHE_VERSION = 'v9-project-extensions'; + + private ExportedNodeFetcher $exportedNodeFetcher; + + private FileFinder $scanFileFinder; + + private ReflectionProvider $reflectionProvider; + + private string $cacheFilePath; + + private string $tempResultCachePath; + + /** @var string[] */ + private array $analysedPaths; + + /** @var string[] */ + private array $composerAutoloaderProjectPaths; + + /** @var string[] */ + private array $stubFiles; + + private string $usedLevel; + + private ?string $cliAutoloadFile; + + /** @var string[] */ + private array $bootstrapFiles; + + /** @var string[] */ + private array $scanFiles; + + /** @var string[] */ + private array $scanDirectories; + + /** @var array */ + private array $fileHashes = []; + + /** @var array */ + private array $fileReplacements = []; + + /** @var array */ + private array $alreadyProcessed = []; + + /** + * @param ExportedNodeFetcher $exportedNodeFetcher + * @param FileFinder $scanFileFinder + * @param ReflectionProvider $reflectionProvider + * @param string $cacheFilePath + * @param string $tempResultCachePath + * @param string[] $analysedPaths + * @param string[] $composerAutoloaderProjectPaths + * @param string[] $stubFiles + * @param string $usedLevel + * @param string|null $cliAutoloadFile + * @param string[] $bootstrapFiles + * @param string[] $scanFiles + * @param string[] $scanDirectories + * @param array $fileReplacements + */ + public function __construct( + ExportedNodeFetcher $exportedNodeFetcher, + FileFinder $scanFileFinder, + ReflectionProvider $reflectionProvider, + string $cacheFilePath, + string $tempResultCachePath, + array $analysedPaths, + array $composerAutoloaderProjectPaths, + array $stubFiles, + string $usedLevel, + ?string $cliAutoloadFile, + array $bootstrapFiles, + array $scanFiles, + array $scanDirectories, + array $fileReplacements + ) { + $this->exportedNodeFetcher = $exportedNodeFetcher; + $this->scanFileFinder = $scanFileFinder; + $this->reflectionProvider = $reflectionProvider; + $this->cacheFilePath = $cacheFilePath; + $this->tempResultCachePath = $tempResultCachePath; + $this->analysedPaths = $analysedPaths; + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + $this->stubFiles = $stubFiles; + $this->usedLevel = $usedLevel; + $this->cliAutoloadFile = $cliAutoloadFile; + $this->bootstrapFiles = $bootstrapFiles; + $this->scanFiles = $scanFiles; + $this->scanDirectories = $scanDirectories; + $this->fileReplacements = $fileReplacements; + } + + /** + * @param string[] $allAnalysedFiles + * @param mixed[]|null $projectConfigArray + * @param bool $debug + * @return ResultCache + */ + public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?array $projectConfigArray, Output $output, ?string $resultCacheName = null): ResultCache + { + if ($debug) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because of debug mode.'); + } + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); + } + if ($onlyFiles) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.'); + } + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); + } + + $cacheFilePath = $this->cacheFilePath; + if ($resultCacheName !== null) { + $tmpCacheFile = $this->tempResultCachePath . '/' . $resultCacheName . '.php'; + if (is_file($tmpCacheFile)) { + $cacheFilePath = $tmpCacheFile; + } + } + + if (!is_file($cacheFilePath)) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because the cache file does not exist.'); + } + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); + } + + try { + $data = require $cacheFilePath; + } catch (\Throwable $e) { + if ($output->isDebug()) { + $output->writeLineFormatted(sprintf('Result cache not used because an error occurred while loading the cache file: %s', $e->getMessage())); + } + + @unlink($cacheFilePath); + + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); + } + + if (!is_array($data)) { + @unlink($cacheFilePath); + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because the cache file is corrupted.'); + } + + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], []); + } + + $meta = $this->getMeta($allAnalysedFiles, $projectConfigArray); + if ($this->isMetaDifferent($data['meta'], $meta)) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because the metadata do not match.'); + } + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); + } + + if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.'); + } + // run full analysis if the result cache is older than 7 days + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); + } + + foreach ($data['projectExtensionFiles'] as $extensionFile => $fileHash) { + if (!is_file($extensionFile)) { + if ($output->isDebug()) { + $output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile)); + } + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); + } + + if ($this->getFileHash($extensionFile) === $fileHash) { + continue; + } + + if ($output->isDebug()) { + $output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile)); + } + + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], []); + } + + $invertedDependencies = $data['dependencies']; + $deletedFiles = array_fill_keys(array_keys($invertedDependencies), true); + $filesToAnalyse = []; + $invertedDependenciesToReturn = []; + $errors = $data['errorsCallback'](); + $exportedNodes = $data['exportedNodesCallback'](); + $filteredErrors = []; + $filteredExportedNodes = []; + $newFileAppeared = false; + foreach ($allAnalysedFiles as $analysedFile) { + if (array_key_exists($analysedFile, $errors)) { + $filteredErrors[$analysedFile] = $errors[$analysedFile]; + } + if (array_key_exists($analysedFile, $exportedNodes)) { + $filteredExportedNodes[$analysedFile] = $exportedNodes[$analysedFile]; + } + if (!array_key_exists($analysedFile, $invertedDependencies)) { + // new file + $filesToAnalyse[] = $analysedFile; + $newFileAppeared = true; + continue; + } + + unset($deletedFiles[$analysedFile]); + + $analysedFileData = $invertedDependencies[$analysedFile]; + $cachedFileHash = $analysedFileData['fileHash']; + $dependentFiles = $analysedFileData['dependentFiles']; + $invertedDependenciesToReturn[$analysedFile] = $dependentFiles; + $currentFileHash = $this->getFileHash($analysedFile); + + if ($cachedFileHash === $currentFileHash) { + continue; + } + + $filesToAnalyse[] = $analysedFile; + if (!array_key_exists($analysedFile, $filteredExportedNodes)) { + continue; + } + + $cachedFileExportedNodes = $filteredExportedNodes[$analysedFile]; + if (count($dependentFiles) === 0) { + continue; + } + if (!$this->exportedNodesChanged($analysedFile, $cachedFileExportedNodes)) { + continue; + } + + foreach ($dependentFiles as $dependentFile) { + if (!is_file($dependentFile)) { + continue; + } + $filesToAnalyse[] = $dependentFile; + } + } + + foreach (array_keys($deletedFiles) as $deletedFile) { + if (!array_key_exists($deletedFile, $invertedDependencies)) { + continue; + } + + $deletedFileData = $invertedDependencies[$deletedFile]; + $dependentFiles = $deletedFileData['dependentFiles']; + foreach ($dependentFiles as $dependentFile) { + if (!is_file($dependentFile)) { + continue; + } + $filesToAnalyse[] = $dependentFile; + } + } + + if ($newFileAppeared) { + foreach (array_keys($filteredErrors) as $fileWithError) { + $filesToAnalyse[] = $fileWithError; + } + } + + return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $invertedDependenciesToReturn, $filteredExportedNodes); + } + + /** + * @param mixed[] $cachedMeta + * @param mixed[] $currentMeta + * @return bool + */ + private function isMetaDifferent(array $cachedMeta, array $currentMeta): bool + { + $projectConfig = $currentMeta['projectConfig']; + if ($projectConfig !== null) { + $currentMeta['projectConfig'] = Neon::encode($currentMeta['projectConfig']); + } + + return $cachedMeta !== $currentMeta; + } + + /** + * @param string $analysedFile + * @param array $cachedFileExportedNodes + * @return bool + */ + private function exportedNodesChanged(string $analysedFile, array $cachedFileExportedNodes): bool + { + if (array_key_exists($analysedFile, $this->fileReplacements)) { + $analysedFile = $this->fileReplacements[$analysedFile]; + } + $fileExportedNodes = $this->exportedNodeFetcher->fetchNodes($analysedFile); + if (count($fileExportedNodes) !== count($cachedFileExportedNodes)) { + return true; + } + + foreach ($fileExportedNodes as $i => $fileExportedNode) { + $cachedExportedNode = $cachedFileExportedNodes[$i]; + if (!$cachedExportedNode->equals($fileExportedNode)) { + return true; + } + } + + return false; + } + + /** + * @param AnalyserResult $analyserResult + * @param ResultCache $resultCache + * @param bool|string $save + * @return ResultCacheProcessResult + */ + public function process(AnalyserResult $analyserResult, ResultCache $resultCache, Output $output, bool $onlyFiles, $save): ResultCacheProcessResult + { + $internalErrors = $analyserResult->getInternalErrors(); + $freshErrorsByFile = []; + foreach ($analyserResult->getErrors() as $error) { + $freshErrorsByFile[$error->getFilePath()][] = $error; + } + + $meta = $resultCache->getMeta(); + $doSave = function (array $errorsByFile, ?array $dependencies, array $exportedNodes, ?string $resultCacheName) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { + if ($onlyFiles) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.'); + } + return false; + } + if ($dependencies === null) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache was not saved because of error in dependencies.'); + } + return false; + } + + if (count($internalErrors) > 0) { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache was not saved because of internal errors.'); + } + return false; + } + + foreach ($errorsByFile as $errors) { + foreach ($errors as $error) { + if (!$error->hasNonIgnorableException()) { + continue; + } + + if ($output->isDebug()) { + $output->writeLineFormatted(sprintf('Result cache was not saved because of non-ignorable exception: %s', $error->getMessage())); + } + + return false; + } + } + + $this->save($resultCache->getLastFullAnalysisTime(), $resultCacheName, $errorsByFile, $dependencies, $exportedNodes, $meta); + + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache is saved.'); + } + + return true; + }; + + if ($resultCache->isFullAnalysis()) { + $saved = false; + if ($save !== false) { + $saved = $doSave($freshErrorsByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), is_string($save) ? $save : null); + } else { + if ($output->isDebug()) { + $output->writeLineFormatted('Result cache was not saved because it was not requested.'); + } + } + + return new ResultCacheProcessResult($analyserResult, $saved); + } + + $errorsByFile = $this->mergeErrors($resultCache, $freshErrorsByFile); + $dependencies = $this->mergeDependencies($resultCache, $analyserResult->getDependencies()); + $exportedNodes = $this->mergeExportedNodes($resultCache, $analyserResult->getExportedNodes()); + + $saved = false; + if ($save !== false) { + $saved = $doSave($errorsByFile, $dependencies, $exportedNodes, is_string($save) ? $save : null); + } + + $flatErrors = []; + foreach ($errorsByFile as $fileErrors) { + foreach ($fileErrors as $fileError) { + $flatErrors[] = $fileError; + } + } + + return new ResultCacheProcessResult(new AnalyserResult( + $flatErrors, + $internalErrors, + $dependencies, + $exportedNodes, + $analyserResult->hasReachedInternalErrorsCountLimit() + ), $saved); + } + + /** + * @param ResultCache $resultCache + * @param array> $freshErrorsByFile + * @return array> + */ + private function mergeErrors(ResultCache $resultCache, array $freshErrorsByFile): array + { + $errorsByFile = $resultCache->getErrors(); + foreach ($resultCache->getFilesToAnalyse() as $file) { + if (!array_key_exists($file, $freshErrorsByFile)) { + unset($errorsByFile[$file]); + continue; + } + $errorsByFile[$file] = $freshErrorsByFile[$file]; + } + + return $errorsByFile; + } + + /** + * @param ResultCache $resultCache + * @param array>|null $freshDependencies + * @return array>|null + */ + private function mergeDependencies(ResultCache $resultCache, ?array $freshDependencies): ?array + { + if ($freshDependencies === null) { + return null; + } + + $cachedDependencies = []; + $resultCacheDependencies = $resultCache->getDependencies(); + $filesNoOneIsDependingOn = array_fill_keys(array_keys($resultCacheDependencies), true); + foreach ($resultCacheDependencies as $file => $filesDependingOnFile) { + foreach ($filesDependingOnFile as $fileDependingOnFile) { + $cachedDependencies[$fileDependingOnFile][] = $file; + unset($filesNoOneIsDependingOn[$fileDependingOnFile]); + } + } + + foreach (array_keys($filesNoOneIsDependingOn) as $file) { + if (array_key_exists($file, $cachedDependencies)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $cachedDependencies[$file] = []; + } + + $newDependencies = $cachedDependencies; + foreach ($resultCache->getFilesToAnalyse() as $file) { + if (!array_key_exists($file, $freshDependencies)) { + unset($newDependencies[$file]); + continue; + } + + $newDependencies[$file] = $freshDependencies[$file]; + } + + return $newDependencies; + } + + /** + * @param ResultCache $resultCache + * @param array> $freshExportedNodes + * @return array> + */ + private function mergeExportedNodes(ResultCache $resultCache, array $freshExportedNodes): array + { + $newExportedNodes = $resultCache->getExportedNodes(); + foreach ($resultCache->getFilesToAnalyse() as $file) { + if (!array_key_exists($file, $freshExportedNodes)) { + unset($newExportedNodes[$file]); + continue; + } + + $newExportedNodes[$file] = $freshExportedNodes[$file]; + } + + return $newExportedNodes; + } + + /** + * @param int $lastFullAnalysisTime + * @param string|null $resultCacheName + * @param array> $errors + * @param array> $dependencies + * @param array> $exportedNodes + * @param mixed[] $meta + */ + private function save( + int $lastFullAnalysisTime, + ?string $resultCacheName, + array $errors, + array $dependencies, + array $exportedNodes, + array $meta + ): void { + $invertedDependencies = []; + $filesNoOneIsDependingOn = array_fill_keys(array_keys($dependencies), true); + foreach ($dependencies as $file => $fileDependencies) { + foreach ($fileDependencies as $fileDep) { + if (!array_key_exists($fileDep, $invertedDependencies)) { + $invertedDependencies[$fileDep] = [ + 'fileHash' => $this->getFileHash($fileDep), + 'dependentFiles' => [], + ]; + unset($filesNoOneIsDependingOn[$fileDep]); + } + $invertedDependencies[$fileDep]['dependentFiles'][] = $file; + } + } + + foreach (array_keys($filesNoOneIsDependingOn) as $file) { + if (array_key_exists($file, $invertedDependencies)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!is_file($file)) { + continue; + } + + $invertedDependencies[$file] = [ + 'fileHash' => $this->getFileHash($file), + 'dependentFiles' => [], + ]; + } + + ksort($errors); + ksort($invertedDependencies); + + foreach ($invertedDependencies as $file => $fileData) { + $dependentFiles = $fileData['dependentFiles']; + sort($dependentFiles); + $invertedDependencies[$file]['dependentFiles'] = $dependentFiles; + } + + $template = <<<'php' cacheFilePath; - if ($resultCacheName !== null) { - $file = $this->tempResultCachePath . '/' . $resultCacheName . '.php'; - } - - $projectConfigArray = $meta['projectConfig']; - if ($projectConfigArray !== null) { - $meta['projectConfig'] = Neon::encode($projectConfigArray); - } - - FileWriter::write( - $file, - sprintf( - $template, - var_export($lastFullAnalysisTime, true), - var_export($meta, true), - var_export($this->getProjectExtensionFiles($projectConfigArray, $dependencies), true), - var_export($errors, true), - var_export($invertedDependencies, true), - var_export($exportedNodes, true) - ) - ); - } - - /** - * @param mixed[]|null $projectConfig - * @param array $dependencies - * @return array - */ - private function getProjectExtensionFiles(?array $projectConfig, array $dependencies): array - { - $this->alreadyProcessed = []; - $projectExtensionFiles = []; - if ($projectConfig !== null) { - $services = $projectConfig['services'] ?? []; - foreach ($services as $service) { - $classes = $this->getClassesFromConfigDefinition($service); - if (is_array($service)) { - foreach (['class', 'factory', 'implement'] as $key) { - if (!isset($service[$key])) { - continue; - } - - $classes = array_merge($classes, $this->getClassesFromConfigDefinition($service[$key])); - } - } - - foreach (array_unique($classes) as $class) { - if (!$this->reflectionProvider->hasClass($class)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($class); - $fileName = $classReflection->getFileName(); - if ($fileName === false) { - continue; - } - - $allServiceFiles = $this->getAllDependencies($fileName, $dependencies); - foreach ($allServiceFiles as $serviceFile) { - if (array_key_exists($serviceFile, $projectExtensionFiles)) { - continue; - } - - $projectExtensionFiles[$serviceFile] = $this->getFileHash($serviceFile); - } - } - } - } - - return $projectExtensionFiles; - } - - /** - * @param mixed $definition - * @return string[] - */ - private function getClassesFromConfigDefinition($definition): array - { - if (is_string($definition)) { - return [$definition]; - } - - if ($definition instanceof Statement) { - $entity = $definition->entity; - if (is_string($entity)) { - return [$entity]; - } elseif (is_array($entity) && isset($entity[0]) && is_string($entity[0])) { - return [$entity[0]]; - } - } - - return []; - } - - /** - * @param string $fileName - * @param array> $dependencies - * @return array - */ - private function getAllDependencies(string $fileName, array $dependencies): array - { - if (!array_key_exists($fileName, $dependencies)) { - return []; - } - - if (array_key_exists($fileName, $this->alreadyProcessed)) { - return []; - } - - $this->alreadyProcessed[$fileName] = true; - - $files = [$fileName]; - foreach ($dependencies[$fileName] as $fileDep) { - foreach ($this->getAllDependencies($fileDep, $dependencies) as $fileDep2) { - $files[] = $fileDep2; - } - } - - return $files; - } - - /** - * @param string[] $allAnalysedFiles - * @param mixed[]|null $projectConfigArray - * @return mixed[] - */ - private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): array - { - $extensions = array_values(array_filter(get_loaded_extensions(), static function (string $extension): bool { - return $extension !== 'xdebug'; - })); - sort($extensions); - - if ($projectConfigArray !== null) { - unset($projectConfigArray['parameters']['ignoreErrors']); - unset($projectConfigArray['parameters']['tipsOfTheDay']); - unset($projectConfigArray['parameters']['parallel']); - unset($projectConfigArray['parameters']['internalErrorsCountLimit']); - unset($projectConfigArray['parameters']['cache']); - unset($projectConfigArray['parameters']['reportUnmatchedIgnoredErrors']); - unset($projectConfigArray['parameters']['memoryLimitFile']); - unset($projectConfigArray['parametersSchema']); - } - - return [ - 'cacheVersion' => self::CACHE_VERSION, - 'phpstanVersion' => $this->getPhpStanVersion(), - 'phpVersion' => PHP_VERSION_ID, - 'projectConfig' => $projectConfigArray, - 'analysedPaths' => $this->analysedPaths, - 'scannedFiles' => $this->getScannedFiles($allAnalysedFiles), - 'composerLocks' => $this->getComposerLocks(), - 'composerInstalled' => $this->getComposerInstalled(), - 'executedFilesHashes' => $this->getExecutedFileHashes(), - 'phpExtensions' => $extensions, - 'stubFiles' => $this->getStubFiles(), - 'level' => $this->usedLevel, - ]; - } - - private function getFileHash(string $path): string - { - if (array_key_exists($path, $this->fileReplacements)) { - $path = $this->fileReplacements[$path]; - } - if (array_key_exists($path, $this->fileHashes)) { - return $this->fileHashes[$path]; - } - - $contents = FileReader::read($path); - $contents = str_replace("\r\n", "\n", $contents); - - $hash = sha1($contents); - $this->fileHashes[$path] = $hash; - - return $hash; - } - - /** - * @param string[] $allAnalysedFiles - * @return array - */ - private function getScannedFiles(array $allAnalysedFiles): array - { - $scannedFiles = $this->scanFiles; - foreach ($this->scanFileFinder->findFiles($this->scanDirectories)->getFiles() as $file) { - $scannedFiles[] = $file; - } - - $scannedFiles = array_unique($scannedFiles); - - $hashes = []; - foreach (array_diff($scannedFiles, $allAnalysedFiles) as $file) { - $hashes[$file] = $this->getFileHash($file); - } - - ksort($hashes); - - return $hashes; - } - - /** - * @return array - */ - private function getExecutedFileHashes(): array - { - $hashes = []; - if ($this->cliAutoloadFile !== null) { - $hashes[$this->cliAutoloadFile] = $this->getFileHash($this->cliAutoloadFile); - } - - foreach ($this->bootstrapFiles as $bootstrapFile) { - $hashes[$bootstrapFile] = $this->getFileHash($bootstrapFile); - } - - ksort($hashes); - - return $hashes; - } - - private function getPhpStanVersion(): string - { - try { - return \Jean85\PrettyVersions::getVersion('phpstan/phpstan')->getPrettyVersion(); - } catch (\OutOfBoundsException $e) { - return 'Version unknown'; - } - } - - /** - * @return array - */ - private function getComposerLocks(): array - { - $locks = []; - foreach ($this->composerAutoloaderProjectPaths as $autoloadPath) { - $lockPath = $autoloadPath . '/composer.lock'; - if (!is_file($lockPath)) { - continue; - } - - $locks[$lockPath] = $this->getFileHash($lockPath); - } - - return $locks; - } - - /** - * @return array - */ - private function getComposerInstalled(): array - { - $data = []; - foreach ($this->composerAutoloaderProjectPaths as $autoloadPath) { - $filePath = $autoloadPath . '/vendor/composer/installed.php'; - if (!is_file($filePath)) { - continue; - } - - $installed = require $filePath; - $rootName = $installed['root']['name']; - unset($installed['root']); - unset($installed['versions'][$rootName]); - - $data[$filePath] = $installed; - } - - return $data; - } - - /** - * @return array - */ - private function getStubFiles(): array - { - $stubFiles = []; - foreach ($this->stubFiles as $stubFile) { - $stubFiles[$stubFile] = $this->getFileHash($stubFile); - } - - ksort($stubFiles); - - return $stubFiles; - } - + ksort($exportedNodes); + + $file = $this->cacheFilePath; + if ($resultCacheName !== null) { + $file = $this->tempResultCachePath . '/' . $resultCacheName . '.php'; + } + + $projectConfigArray = $meta['projectConfig']; + if ($projectConfigArray !== null) { + $meta['projectConfig'] = Neon::encode($projectConfigArray); + } + + FileWriter::write( + $file, + sprintf( + $template, + var_export($lastFullAnalysisTime, true), + var_export($meta, true), + var_export($this->getProjectExtensionFiles($projectConfigArray, $dependencies), true), + var_export($errors, true), + var_export($invertedDependencies, true), + var_export($exportedNodes, true) + ) + ); + } + + /** + * @param mixed[]|null $projectConfig + * @param array $dependencies + * @return array + */ + private function getProjectExtensionFiles(?array $projectConfig, array $dependencies): array + { + $this->alreadyProcessed = []; + $projectExtensionFiles = []; + if ($projectConfig !== null) { + $services = $projectConfig['services'] ?? []; + foreach ($services as $service) { + $classes = $this->getClassesFromConfigDefinition($service); + if (is_array($service)) { + foreach (['class', 'factory', 'implement'] as $key) { + if (!isset($service[$key])) { + continue; + } + + $classes = array_merge($classes, $this->getClassesFromConfigDefinition($service[$key])); + } + } + + foreach (array_unique($classes) as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($class); + $fileName = $classReflection->getFileName(); + if ($fileName === false) { + continue; + } + + $allServiceFiles = $this->getAllDependencies($fileName, $dependencies); + foreach ($allServiceFiles as $serviceFile) { + if (array_key_exists($serviceFile, $projectExtensionFiles)) { + continue; + } + + $projectExtensionFiles[$serviceFile] = $this->getFileHash($serviceFile); + } + } + } + } + + return $projectExtensionFiles; + } + + /** + * @param mixed $definition + * @return string[] + */ + private function getClassesFromConfigDefinition($definition): array + { + if (is_string($definition)) { + return [$definition]; + } + + if ($definition instanceof Statement) { + $entity = $definition->entity; + if (is_string($entity)) { + return [$entity]; + } elseif (is_array($entity) && isset($entity[0]) && is_string($entity[0])) { + return [$entity[0]]; + } + } + + return []; + } + + /** + * @param string $fileName + * @param array> $dependencies + * @return array + */ + private function getAllDependencies(string $fileName, array $dependencies): array + { + if (!array_key_exists($fileName, $dependencies)) { + return []; + } + + if (array_key_exists($fileName, $this->alreadyProcessed)) { + return []; + } + + $this->alreadyProcessed[$fileName] = true; + + $files = [$fileName]; + foreach ($dependencies[$fileName] as $fileDep) { + foreach ($this->getAllDependencies($fileDep, $dependencies) as $fileDep2) { + $files[] = $fileDep2; + } + } + + return $files; + } + + /** + * @param string[] $allAnalysedFiles + * @param mixed[]|null $projectConfigArray + * @return mixed[] + */ + private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): array + { + $extensions = array_values(array_filter(get_loaded_extensions(), static function (string $extension): bool { + return $extension !== 'xdebug'; + })); + sort($extensions); + + if ($projectConfigArray !== null) { + unset($projectConfigArray['parameters']['ignoreErrors']); + unset($projectConfigArray['parameters']['tipsOfTheDay']); + unset($projectConfigArray['parameters']['parallel']); + unset($projectConfigArray['parameters']['internalErrorsCountLimit']); + unset($projectConfigArray['parameters']['cache']); + unset($projectConfigArray['parameters']['reportUnmatchedIgnoredErrors']); + unset($projectConfigArray['parameters']['memoryLimitFile']); + unset($projectConfigArray['parametersSchema']); + } + + return [ + 'cacheVersion' => self::CACHE_VERSION, + 'phpstanVersion' => $this->getPhpStanVersion(), + 'phpVersion' => PHP_VERSION_ID, + 'projectConfig' => $projectConfigArray, + 'analysedPaths' => $this->analysedPaths, + 'scannedFiles' => $this->getScannedFiles($allAnalysedFiles), + 'composerLocks' => $this->getComposerLocks(), + 'composerInstalled' => $this->getComposerInstalled(), + 'executedFilesHashes' => $this->getExecutedFileHashes(), + 'phpExtensions' => $extensions, + 'stubFiles' => $this->getStubFiles(), + 'level' => $this->usedLevel, + ]; + } + + private function getFileHash(string $path): string + { + if (array_key_exists($path, $this->fileReplacements)) { + $path = $this->fileReplacements[$path]; + } + if (array_key_exists($path, $this->fileHashes)) { + return $this->fileHashes[$path]; + } + + $contents = FileReader::read($path); + $contents = str_replace("\r\n", "\n", $contents); + + $hash = sha1($contents); + $this->fileHashes[$path] = $hash; + + return $hash; + } + + /** + * @param string[] $allAnalysedFiles + * @return array + */ + private function getScannedFiles(array $allAnalysedFiles): array + { + $scannedFiles = $this->scanFiles; + foreach ($this->scanFileFinder->findFiles($this->scanDirectories)->getFiles() as $file) { + $scannedFiles[] = $file; + } + + $scannedFiles = array_unique($scannedFiles); + + $hashes = []; + foreach (array_diff($scannedFiles, $allAnalysedFiles) as $file) { + $hashes[$file] = $this->getFileHash($file); + } + + ksort($hashes); + + return $hashes; + } + + /** + * @return array + */ + private function getExecutedFileHashes(): array + { + $hashes = []; + if ($this->cliAutoloadFile !== null) { + $hashes[$this->cliAutoloadFile] = $this->getFileHash($this->cliAutoloadFile); + } + + foreach ($this->bootstrapFiles as $bootstrapFile) { + $hashes[$bootstrapFile] = $this->getFileHash($bootstrapFile); + } + + ksort($hashes); + + return $hashes; + } + + private function getPhpStanVersion(): string + { + try { + return \Jean85\PrettyVersions::getVersion('phpstan/phpstan')->getPrettyVersion(); + } catch (\OutOfBoundsException $e) { + return 'Version unknown'; + } + } + + /** + * @return array + */ + private function getComposerLocks(): array + { + $locks = []; + foreach ($this->composerAutoloaderProjectPaths as $autoloadPath) { + $lockPath = $autoloadPath . '/composer.lock'; + if (!is_file($lockPath)) { + continue; + } + + $locks[$lockPath] = $this->getFileHash($lockPath); + } + + return $locks; + } + + /** + * @return array + */ + private function getComposerInstalled(): array + { + $data = []; + foreach ($this->composerAutoloaderProjectPaths as $autoloadPath) { + $filePath = $autoloadPath . '/vendor/composer/installed.php'; + if (!is_file($filePath)) { + continue; + } + + $installed = require $filePath; + $rootName = $installed['root']['name']; + unset($installed['root']); + unset($installed['versions'][$rootName]); + + $data[$filePath] = $installed; + } + + return $data; + } + + /** + * @return array + */ + private function getStubFiles(): array + { + $stubFiles = []; + foreach ($this->stubFiles as $stubFile) { + $stubFiles[$stubFile] = $this->getFileHash($stubFile); + } + + ksort($stubFiles); + + return $stubFiles; + } } diff --git a/src/Analyser/ResultCache/ResultCacheManagerFactory.php b/src/Analyser/ResultCache/ResultCacheManagerFactory.php index c2bdadefaa..db40c4c9a2 100644 --- a/src/Analyser/ResultCache/ResultCacheManagerFactory.php +++ b/src/Analyser/ResultCache/ResultCacheManagerFactory.php @@ -1,14 +1,14 @@ - $fileReplacements - * @return ResultCacheManager - */ - public function create(array $fileReplacements): ResultCacheManager; - + /** + * @param array $fileReplacements + * @return ResultCacheManager + */ + public function create(array $fileReplacements): ResultCacheManager; } diff --git a/src/Analyser/ResultCache/ResultCacheProcessResult.php b/src/Analyser/ResultCache/ResultCacheProcessResult.php index b3230ca968..d12dad1c98 100644 --- a/src/Analyser/ResultCache/ResultCacheProcessResult.php +++ b/src/Analyser/ResultCache/ResultCacheProcessResult.php @@ -1,4 +1,6 @@ -analyserResult = $analyserResult; - $this->saved = $saved; - } + private bool $saved; - public function getAnalyserResult(): AnalyserResult - { - return $this->analyserResult; - } + public function __construct(AnalyserResult $analyserResult, bool $saved) + { + $this->analyserResult = $analyserResult; + $this->saved = $saved; + } - public function isSaved(): bool - { - return $this->saved; - } + public function getAnalyserResult(): AnalyserResult + { + return $this->analyserResult; + } + public function isSaved(): bool + { + return $this->saved; + } } diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index fa6e9200a0..72d134d72c 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -1,4 +1,6 @@ - - */ - public function getDefinedVariables(): array; + public function canAnyVariableExist(): bool; - public function hasConstant(Name $name): bool; + /** + * @return array + */ + public function getDefinedVariables(): array; - public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection; + public function hasConstant(Name $name): bool; - public function getMethodReflection(Type $typeWithMethod, string $methodName): ?MethodReflection; + public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection; - public function isInAnonymousFunction(): bool; + public function getMethodReflection(Type $typeWithMethod, string $methodName): ?MethodReflection; - public function getAnonymousFunctionReflection(): ?ParametersAcceptor; + public function isInAnonymousFunction(): bool; - public function getAnonymousFunctionReturnType(): ?\PHPStan\Type\Type; + public function getAnonymousFunctionReflection(): ?ParametersAcceptor; - public function getType(Expr $node): Type; + public function getAnonymousFunctionReturnType(): ?\PHPStan\Type\Type; - /** - * Gets type of an expression with no regards to phpDocs. - * Works for function/method parameters only. - * - * @internal - * @param Expr $expr - * @return Type - */ - public function getNativeType(Expr $expr): Type; + public function getType(Expr $node): Type; - public function doNotTreatPhpDocTypesAsCertain(): self; + /** + * Gets type of an expression with no regards to phpDocs. + * Works for function/method parameters only. + * + * @internal + * @param Expr $expr + * @return Type + */ + public function getNativeType(Expr $expr): Type; - public function resolveName(Name $name): string; + public function doNotTreatPhpDocTypesAsCertain(): self; - public function resolveTypeByName(Name $name): TypeWithClassName; + public function resolveName(Name $name): string; - /** - * @param mixed $value - */ - public function getTypeFromValue($value): Type; + public function resolveTypeByName(Name $name): TypeWithClassName; - public function isSpecified(Expr $node): bool; + /** + * @param mixed $value + */ + public function getTypeFromValue($value): Type; - public function isInClassExists(string $className): bool; + public function isSpecified(Expr $node): bool; - public function isInClosureBind(): bool; + public function isInClassExists(string $className): bool; - public function isParameterValueNullable(Param $parameter): bool; + public function isInClosureBind(): bool; - /** - * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type - * @param bool $isNullable - * @param bool $isVariadic - * @return Type - */ - public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type; + public function isParameterValueNullable(Param $parameter): bool; - public function isInExpressionAssign(Expr $expr): bool; + /** + * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type + * @param bool $isNullable + * @param bool $isVariadic + * @return Type + */ + public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type; - public function filterByTruthyValue(Expr $expr): self; + public function isInExpressionAssign(Expr $expr): bool; - public function filterByFalseyValue(Expr $expr): self; + public function filterByTruthyValue(Expr $expr): self; - public function isInFirstLevelStatement(): bool; + public function filterByFalseyValue(Expr $expr): self; + public function isInFirstLevelStatement(): bool; } diff --git a/src/Analyser/ScopeContext.php b/src/Analyser/ScopeContext.php index c2ff4dab33..e640be48ea 100644 --- a/src/Analyser/ScopeContext.php +++ b/src/Analyser/ScopeContext.php @@ -1,4 +1,6 @@ -file = $file; - $this->classReflection = $classReflection; - $this->traitReflection = $traitReflection; - } - - public static function create(string $file): self - { - return new self($file, null, null); - } - - public function beginFile(): self - { - return new self($this->file, null, null); - } - - public function enterClass(ClassReflection $classReflection): self - { - if ($this->classReflection !== null && !$classReflection->isAnonymous()) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($classReflection->isTrait()) { - throw new \PHPStan\ShouldNotHappenException(); - } - return new self($this->file, $classReflection, null); - } - - public function enterTrait(ClassReflection $traitReflection): self - { - if ($this->classReflection === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - if (!$traitReflection->isTrait()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return new self($this->file, $this->classReflection, $traitReflection); - } - - public function equals(self $otherContext): bool - { - if ($this->file !== $otherContext->file) { - return false; - } - - if ($this->getClassReflection() === null) { - return $otherContext->getClassReflection() === null; - } elseif ($otherContext->getClassReflection() === null) { - return false; - } - - $isSameClass = $this->getClassReflection()->getName() === $otherContext->getClassReflection()->getName(); - - if ($this->getTraitReflection() === null) { - return $otherContext->getTraitReflection() === null && $isSameClass; - } elseif ($otherContext->getTraitReflection() === null) { - return false; - } - - $isSameTrait = $this->getTraitReflection()->getName() === $otherContext->getTraitReflection()->getName(); - - return $isSameClass && $isSameTrait; - } - - public function getFile(): string - { - return $this->file; - } - - public function getClassReflection(): ?ClassReflection - { - return $this->classReflection; - } - - public function getTraitReflection(): ?ClassReflection - { - return $this->traitReflection; - } - + private string $file; + + private ?ClassReflection $classReflection; + + private ?ClassReflection $traitReflection; + + private function __construct( + string $file, + ?ClassReflection $classReflection, + ?ClassReflection $traitReflection + ) { + $this->file = $file; + $this->classReflection = $classReflection; + $this->traitReflection = $traitReflection; + } + + public static function create(string $file): self + { + return new self($file, null, null); + } + + public function beginFile(): self + { + return new self($this->file, null, null); + } + + public function enterClass(ClassReflection $classReflection): self + { + if ($this->classReflection !== null && !$classReflection->isAnonymous()) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($classReflection->isTrait()) { + throw new \PHPStan\ShouldNotHappenException(); + } + return new self($this->file, $classReflection, null); + } + + public function enterTrait(ClassReflection $traitReflection): self + { + if ($this->classReflection === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!$traitReflection->isTrait()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return new self($this->file, $this->classReflection, $traitReflection); + } + + public function equals(self $otherContext): bool + { + if ($this->file !== $otherContext->file) { + return false; + } + + if ($this->getClassReflection() === null) { + return $otherContext->getClassReflection() === null; + } elseif ($otherContext->getClassReflection() === null) { + return false; + } + + $isSameClass = $this->getClassReflection()->getName() === $otherContext->getClassReflection()->getName(); + + if ($this->getTraitReflection() === null) { + return $otherContext->getTraitReflection() === null && $isSameClass; + } elseif ($otherContext->getTraitReflection() === null) { + return false; + } + + $isSameTrait = $this->getTraitReflection()->getName() === $otherContext->getTraitReflection()->getName(); + + return $isSameClass && $isSameTrait; + } + + public function getFile(): string + { + return $this->file; + } + + public function getClassReflection(): ?ClassReflection + { + return $this->classReflection; + } + + public function getTraitReflection(): ?ClassReflection + { + return $this->traitReflection; + } } diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index 06bd2dcc07..a7b984b9a5 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -1,4 +1,6 @@ - $constantTypes - * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function - * @param string|null $namespace - * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes - * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes - * @param array $conditionalExpressions - * @param string|null $inClosureBindScopeClass - * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection - * @param bool $inFirstLevelStatement - * @param array $currentlyAssignedExpressions - * @param array $nativeExpressionTypes - * @param array $inFunctionCallsStack - * @param bool $afterExtractCall - * @param Scope|null $parentScope - * - * @return MutatingScope - */ - public function create( - ScopeContext $context, - bool $declareStrictTypes = false, - array $constantTypes = [], - $function = null, - ?string $namespace = null, - array $variablesTypes = [], - array $moreSpecificTypes = [], - array $conditionalExpressions = [], - ?string $inClosureBindScopeClass = null, - ?ParametersAcceptor $anonymousFunctionReflection = null, - bool $inFirstLevelStatement = true, - array $currentlyAssignedExpressions = [], - array $nativeExpressionTypes = [], - array $inFunctionCallsStack = [], - bool $afterExtractCall = false, - ?Scope $parentScope = null - ): MutatingScope; - + /** + * @param \PHPStan\Analyser\ScopeContext $context + * @param bool $declareStrictTypes + * @param array $constantTypes + * @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function + * @param string|null $namespace + * @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes + * @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes + * @param array $conditionalExpressions + * @param string|null $inClosureBindScopeClass + * @param \PHPStan\Reflection\ParametersAcceptor|null $anonymousFunctionReflection + * @param bool $inFirstLevelStatement + * @param array $currentlyAssignedExpressions + * @param array $nativeExpressionTypes + * @param array $inFunctionCallsStack + * @param bool $afterExtractCall + * @param Scope|null $parentScope + * + * @return MutatingScope + */ + public function create( + ScopeContext $context, + bool $declareStrictTypes = false, + array $constantTypes = [], + $function = null, + ?string $namespace = null, + array $variablesTypes = [], + array $moreSpecificTypes = [], + array $conditionalExpressions = [], + ?string $inClosureBindScopeClass = null, + ?ParametersAcceptor $anonymousFunctionReflection = null, + bool $inFirstLevelStatement = true, + array $currentlyAssignedExpressions = [], + array $nativeExpressionTypes = [], + array $inFunctionCallsStack = [], + bool $afterExtractCall = false, + ?Scope $parentScope = null + ): MutatingScope; } diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index 0b90bd5f1f..52009f424f 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -1,4 +1,6 @@ - */ - private array $newConditionalExpressionHolders; - - /** - * @param mixed[] $sureTypes - * @param mixed[] $sureNotTypes - * @param bool $overwrite - * @param array $newConditionalExpressionHolders - */ - public function __construct( - array $sureTypes = [], - array $sureNotTypes = [], - bool $overwrite = false, - array $newConditionalExpressionHolders = [] - ) - { - $this->sureTypes = $sureTypes; - $this->sureNotTypes = $sureNotTypes; - $this->overwrite = $overwrite; - $this->newConditionalExpressionHolders = $newConditionalExpressionHolders; - } - - /** - * @return mixed[] - */ - public function getSureTypes(): array - { - return $this->sureTypes; - } - - /** - * @return mixed[] - */ - public function getSureNotTypes(): array - { - return $this->sureNotTypes; - } - - public function shouldOverwrite(): bool - { - return $this->overwrite; - } - - /** - * @return array - */ - public function getNewConditionalExpressionHolders(): array - { - return $this->newConditionalExpressionHolders; - } - - public function intersectWith(SpecifiedTypes $other): self - { - $sureTypeUnion = []; - $sureNotTypeUnion = []; - - foreach ($this->sureTypes as $exprString => [$exprNode, $type]) { - if (!isset($other->sureTypes[$exprString])) { - continue; - } - - $sureTypeUnion[$exprString] = [ - $exprNode, - TypeCombinator::union($type, $other->sureTypes[$exprString][1]), - ]; - } - - foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) { - if (!isset($other->sureNotTypes[$exprString])) { - continue; - } - - $sureNotTypeUnion[$exprString] = [ - $exprNode, - TypeCombinator::intersect($type, $other->sureNotTypes[$exprString][1]), - ]; - } - - return new self($sureTypeUnion, $sureNotTypeUnion); - } - - public function unionWith(SpecifiedTypes $other): self - { - $sureTypeUnion = $this->sureTypes + $other->sureTypes; - $sureNotTypeUnion = $this->sureNotTypes + $other->sureNotTypes; - - foreach ($this->sureTypes as $exprString => [$exprNode, $type]) { - if (!isset($other->sureTypes[$exprString])) { - continue; - } - - $sureTypeUnion[$exprString] = [ - $exprNode, - TypeCombinator::intersect($type, $other->sureTypes[$exprString][1]), - ]; - } - - foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) { - if (!isset($other->sureNotTypes[$exprString])) { - continue; - } - - $sureNotTypeUnion[$exprString] = [ - $exprNode, - TypeCombinator::union($type, $other->sureNotTypes[$exprString][1]), - ]; - } - - return new self($sureTypeUnion, $sureNotTypeUnion); - } - + /** @var mixed[] */ + private array $sureTypes; + + /** @var mixed[] */ + private array $sureNotTypes; + + private bool $overwrite; + + /** @var array */ + private array $newConditionalExpressionHolders; + + /** + * @param mixed[] $sureTypes + * @param mixed[] $sureNotTypes + * @param bool $overwrite + * @param array $newConditionalExpressionHolders + */ + public function __construct( + array $sureTypes = [], + array $sureNotTypes = [], + bool $overwrite = false, + array $newConditionalExpressionHolders = [] + ) { + $this->sureTypes = $sureTypes; + $this->sureNotTypes = $sureNotTypes; + $this->overwrite = $overwrite; + $this->newConditionalExpressionHolders = $newConditionalExpressionHolders; + } + + /** + * @return mixed[] + */ + public function getSureTypes(): array + { + return $this->sureTypes; + } + + /** + * @return mixed[] + */ + public function getSureNotTypes(): array + { + return $this->sureNotTypes; + } + + public function shouldOverwrite(): bool + { + return $this->overwrite; + } + + /** + * @return array + */ + public function getNewConditionalExpressionHolders(): array + { + return $this->newConditionalExpressionHolders; + } + + public function intersectWith(SpecifiedTypes $other): self + { + $sureTypeUnion = []; + $sureNotTypeUnion = []; + + foreach ($this->sureTypes as $exprString => [$exprNode, $type]) { + if (!isset($other->sureTypes[$exprString])) { + continue; + } + + $sureTypeUnion[$exprString] = [ + $exprNode, + TypeCombinator::union($type, $other->sureTypes[$exprString][1]), + ]; + } + + foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) { + if (!isset($other->sureNotTypes[$exprString])) { + continue; + } + + $sureNotTypeUnion[$exprString] = [ + $exprNode, + TypeCombinator::intersect($type, $other->sureNotTypes[$exprString][1]), + ]; + } + + return new self($sureTypeUnion, $sureNotTypeUnion); + } + + public function unionWith(SpecifiedTypes $other): self + { + $sureTypeUnion = $this->sureTypes + $other->sureTypes; + $sureNotTypeUnion = $this->sureNotTypes + $other->sureNotTypes; + + foreach ($this->sureTypes as $exprString => [$exprNode, $type]) { + if (!isset($other->sureTypes[$exprString])) { + continue; + } + + $sureTypeUnion[$exprString] = [ + $exprNode, + TypeCombinator::intersect($type, $other->sureTypes[$exprString][1]), + ]; + } + + foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) { + if (!isset($other->sureNotTypes[$exprString])) { + continue; + } + + $sureNotTypeUnion[$exprString] = [ + $exprNode, + TypeCombinator::union($type, $other->sureNotTypes[$exprString][1]), + ]; + } + + return new self($sureTypeUnion, $sureNotTypeUnion); + } } diff --git a/src/Analyser/StatementExitPoint.php b/src/Analyser/StatementExitPoint.php index f5f6874438..062735d2fa 100644 --- a/src/Analyser/StatementExitPoint.php +++ b/src/Analyser/StatementExitPoint.php @@ -1,4 +1,6 @@ -statement = $statement; - $this->scope = $scope; - } + private MutatingScope $scope; - public function getStatement(): Stmt - { - return $this->statement; - } + public function __construct(Stmt $statement, MutatingScope $scope) + { + $this->statement = $statement; + $this->scope = $scope; + } - public function getScope(): MutatingScope - { - return $this->scope; - } + public function getStatement(): Stmt + { + return $this->statement; + } + public function getScope(): MutatingScope + { + return $this->scope; + } } diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index ed4a85465f..141602bc75 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -1,4 +1,6 @@ -scope = $scope; - $this->hasYield = $hasYield; - $this->isAlwaysTerminating = $isAlwaysTerminating; - $this->exitPoints = $exitPoints; - $this->throwPoints = $throwPoints; - } - - public function getScope(): MutatingScope - { - return $this->scope; - } - - public function hasYield(): bool - { - return $this->hasYield; - } - - public function isAlwaysTerminating(): bool - { - return $this->isAlwaysTerminating; - } - - public function filterOutLoopExitPoints(): self - { - if (!$this->isAlwaysTerminating) { - return $this; - } - - foreach ($this->exitPoints as $exitPoint) { - $statement = $exitPoint->getStatement(); - if (!$statement instanceof Stmt\Break_ && !$statement instanceof Stmt\Continue_) { - continue; - } - - $num = $statement->num; - if (!$num instanceof LNumber) { - return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints); - } - - if ($num->value !== 1) { - continue; - } - - return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints); - } - - return $this; - } - - /** - * @return StatementExitPoint[] - */ - public function getExitPoints(): array - { - return $this->exitPoints; - } - - /** - * @param class-string|class-string $stmtClass - * @return StatementExitPoint[] - */ - public function getExitPointsByType(string $stmtClass): array - { - $exitPoints = []; - foreach ($this->exitPoints as $exitPoint) { - $statement = $exitPoint->getStatement(); - if (!$statement instanceof $stmtClass) { - continue; - } - - $value = $statement->num; - if ($value === null) { - $exitPoints[] = $exitPoint; - continue; - } - - if (!$value instanceof LNumber) { - $exitPoints[] = $exitPoint; - continue; - } - - $value = $value->value; - if ($value !== 1) { - continue; - } - - $exitPoints[] = $exitPoint; - } - - return $exitPoints; - } - - /** - * @return StatementExitPoint[] - */ - public function getExitPointsForOuterLoop(): array - { - $exitPoints = []; - foreach ($this->exitPoints as $exitPoint) { - $statement = $exitPoint->getStatement(); - if (!$statement instanceof Stmt\Continue_ && !$statement instanceof Stmt\Break_) { - continue; - } - if ($statement->num === null) { - continue; - } - if (!$statement->num instanceof LNumber) { - continue; - } - $value = $statement->num->value; - if ($value === 1) { - continue; - } - - $newNode = null; - if ($value > 2) { - $newNode = new LNumber($value - 1); - } - if ($statement instanceof Stmt\Continue_) { - $newStatement = new Stmt\Continue_($newNode); - } else { - $newStatement = new Stmt\Break_($newNode); - } - - $exitPoints[] = new StatementExitPoint($newStatement, $exitPoint->getScope()); - } - - return $exitPoints; - } - - /** - * @return ThrowPoint[] - */ - public function getThrowPoints(): array - { - return $this->throwPoints; - } - + private MutatingScope $scope; + + private bool $hasYield; + + private bool $isAlwaysTerminating; + + /** @var StatementExitPoint[] */ + private array $exitPoints; + + /** @var ThrowPoint[] */ + private array $throwPoints; + + /** + * @param MutatingScope $scope + * @param bool $hasYield + * @param bool $isAlwaysTerminating + * @param StatementExitPoint[] $exitPoints + * @param ThrowPoint[] $throwPoints + */ + public function __construct( + MutatingScope $scope, + bool $hasYield, + bool $isAlwaysTerminating, + array $exitPoints, + array $throwPoints + ) { + $this->scope = $scope; + $this->hasYield = $hasYield; + $this->isAlwaysTerminating = $isAlwaysTerminating; + $this->exitPoints = $exitPoints; + $this->throwPoints = $throwPoints; + } + + public function getScope(): MutatingScope + { + return $this->scope; + } + + public function hasYield(): bool + { + return $this->hasYield; + } + + public function isAlwaysTerminating(): bool + { + return $this->isAlwaysTerminating; + } + + public function filterOutLoopExitPoints(): self + { + if (!$this->isAlwaysTerminating) { + return $this; + } + + foreach ($this->exitPoints as $exitPoint) { + $statement = $exitPoint->getStatement(); + if (!$statement instanceof Stmt\Break_ && !$statement instanceof Stmt\Continue_) { + continue; + } + + $num = $statement->num; + if (!$num instanceof LNumber) { + return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints); + } + + if ($num->value !== 1) { + continue; + } + + return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints); + } + + return $this; + } + + /** + * @return StatementExitPoint[] + */ + public function getExitPoints(): array + { + return $this->exitPoints; + } + + /** + * @param class-string|class-string $stmtClass + * @return StatementExitPoint[] + */ + public function getExitPointsByType(string $stmtClass): array + { + $exitPoints = []; + foreach ($this->exitPoints as $exitPoint) { + $statement = $exitPoint->getStatement(); + if (!$statement instanceof $stmtClass) { + continue; + } + + $value = $statement->num; + if ($value === null) { + $exitPoints[] = $exitPoint; + continue; + } + + if (!$value instanceof LNumber) { + $exitPoints[] = $exitPoint; + continue; + } + + $value = $value->value; + if ($value !== 1) { + continue; + } + + $exitPoints[] = $exitPoint; + } + + return $exitPoints; + } + + /** + * @return StatementExitPoint[] + */ + public function getExitPointsForOuterLoop(): array + { + $exitPoints = []; + foreach ($this->exitPoints as $exitPoint) { + $statement = $exitPoint->getStatement(); + if (!$statement instanceof Stmt\Continue_ && !$statement instanceof Stmt\Break_) { + continue; + } + if ($statement->num === null) { + continue; + } + if (!$statement->num instanceof LNumber) { + continue; + } + $value = $statement->num->value; + if ($value === 1) { + continue; + } + + $newNode = null; + if ($value > 2) { + $newNode = new LNumber($value - 1); + } + if ($statement instanceof Stmt\Continue_) { + $newStatement = new Stmt\Continue_($newNode); + } else { + $newStatement = new Stmt\Break_($newNode); + } + + $exitPoints[] = new StatementExitPoint($newStatement, $exitPoint->getScope()); + } + + return $exitPoints; + } + + /** + * @return ThrowPoint[] + */ + public function getThrowPoints(): array + { + return $this->throwPoints; + } } diff --git a/src/Analyser/ThrowPoint.php b/src/Analyser/ThrowPoint.php index 4a63f3dcce..0aa298d79a 100644 --- a/src/Analyser/ThrowPoint.php +++ b/src/Analyser/ThrowPoint.php @@ -1,4 +1,6 @@ -scope = $scope; - $this->type = $type; - $this->node = $node; - $this->explicit = $explicit; - $this->canContainAnyThrowable = $canContainAnyThrowable; - } - - /** - * @param MutatingScope $scope - * @param Type $type - * @param Node\Expr|Node\Stmt $node - * @param bool $canContainAnyThrowable - * @return self - */ - public static function createExplicit(MutatingScope $scope, Type $type, Node $node, bool $canContainAnyThrowable): self - { - return new self($scope, $type, $node, true, $canContainAnyThrowable); - } - - /** - * @param MutatingScope $scope - * @param Node\Expr|Node\Stmt $node - * @return self - */ - public static function createImplicit(MutatingScope $scope, Node $node): self - { - return new self($scope, new ObjectType(\Throwable::class), $node, false, true); - } - - public function getScope(): MutatingScope - { - return $this->scope; - } - - public function getType(): Type - { - return $this->type; - } - - /** - * @return Node\Expr|Node\Stmt - */ - public function getNode() - { - return $this->node; - } - - public function isExplicit(): bool - { - return $this->explicit; - } - - public function canContainAnyThrowable(): bool - { - return $this->canContainAnyThrowable; - } - - public function subtractCatchType(Type $catchType): self - { - return new self($this->scope, TypeCombinator::remove($this->type, $catchType), $this->node, $this->explicit, $this->canContainAnyThrowable); - } - + private MutatingScope $scope; + + private Type $type; + + /** @var Node\Expr|Node\Stmt */ + private Node $node; + + private bool $explicit; + + private bool $canContainAnyThrowable; + + /** + * @param MutatingScope $scope + * @param Type $type + * @param Node\Expr|Node\Stmt $node + * @param bool $explicit + * @param bool $canContainAnyThrowable + */ + private function __construct( + MutatingScope $scope, + Type $type, + Node $node, + bool $explicit, + bool $canContainAnyThrowable + ) { + $this->scope = $scope; + $this->type = $type; + $this->node = $node; + $this->explicit = $explicit; + $this->canContainAnyThrowable = $canContainAnyThrowable; + } + + /** + * @param MutatingScope $scope + * @param Type $type + * @param Node\Expr|Node\Stmt $node + * @param bool $canContainAnyThrowable + * @return self + */ + public static function createExplicit(MutatingScope $scope, Type $type, Node $node, bool $canContainAnyThrowable): self + { + return new self($scope, $type, $node, true, $canContainAnyThrowable); + } + + /** + * @param MutatingScope $scope + * @param Node\Expr|Node\Stmt $node + * @return self + */ + public static function createImplicit(MutatingScope $scope, Node $node): self + { + return new self($scope, new ObjectType(\Throwable::class), $node, false, true); + } + + public function getScope(): MutatingScope + { + return $this->scope; + } + + public function getType(): Type + { + return $this->type; + } + + /** + * @return Node\Expr|Node\Stmt + */ + public function getNode() + { + return $this->node; + } + + public function isExplicit(): bool + { + return $this->explicit; + } + + public function canContainAnyThrowable(): bool + { + return $this->canContainAnyThrowable; + } + + public function subtractCatchType(Type $catchType): self + { + return new self($this->scope, TypeCombinator::remove($this->type, $catchType), $this->node, $this->explicit, $this->canContainAnyThrowable); + } } diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 98ccefd7a0..f3f61ab632 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1,4 +1,6 @@ -printer = $printer; - $this->reflectionProvider = $reflectionProvider; - $this->rememberFunctionValues = $rememberFunctionValues; - - foreach (array_merge($functionTypeSpecifyingExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions) as $extension) { - if (!($extension instanceof TypeSpecifierAwareExtension)) { - continue; - } - - $extension->setTypeSpecifier($this); - } - - $this->functionTypeSpecifyingExtensions = $functionTypeSpecifyingExtensions; - $this->methodTypeSpecifyingExtensions = $methodTypeSpecifyingExtensions; - $this->staticMethodTypeSpecifyingExtensions = $staticMethodTypeSpecifyingExtensions; - } - - public function specifyTypesInCondition( - Scope $scope, - Expr $expr, - TypeSpecifierContext $context - ): SpecifiedTypes - { - if ($expr instanceof Instanceof_) { - $exprNode = $expr->expr; - if ($exprNode instanceof Expr\Assign) { - $exprNode = $exprNode->var; - } - if ($expr->class instanceof Name) { - $className = (string) $expr->class; - $lowercasedClassName = strtolower($className); - if ($lowercasedClassName === 'self' && $scope->isInClass()) { - $type = new ObjectType($scope->getClassReflection()->getName()); - } elseif ($lowercasedClassName === 'static' && $scope->isInClass()) { - $type = new StaticType($scope->getClassReflection()); - } elseif ($lowercasedClassName === 'parent') { - if ( - $scope->isInClass() - && $scope->getClassReflection()->getParentClass() !== false - ) { - $type = new ObjectType($scope->getClassReflection()->getParentClass()->getName()); - } else { - $type = new NonexistentParentClassType(); - } - } else { - $type = new ObjectType($className); - } - return $this->create($exprNode, $type, $context, false, $scope); - } - - $classType = $scope->getType($expr->class); - $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } - if ($type instanceof TypeWithClassName) { - return $type; - } - if ($type instanceof GenericClassStringType) { - return $type->getGenericType(); - } - if ($type instanceof ConstantStringType) { - return new ObjectType($type->getValue()); - } - return new MixedType(); - }); - - if (!$type->isSuperTypeOf(new MixedType())->yes()) { - if ($context->true()) { - $type = TypeCombinator::intersect( - $type, - new ObjectWithoutClassType() - ); - return $this->create($exprNode, $type, $context, false, $scope); - } elseif ($context->false()) { - $exprType = $scope->getType($expr->expr); - if (!$type->isSuperTypeOf($exprType)->yes()) { - return $this->create($exprNode, $type, $context, false, $scope); - } - } - } - if ($context->true()) { - return $this->create($exprNode, new ObjectWithoutClassType(), $context, false, $scope); - } - } elseif ($expr instanceof Node\Expr\BinaryOp\Identical) { - $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); - if ($expressions !== null) { - /** @var Expr $exprNode */ - $exprNode = $expressions[0]; - if ($exprNode instanceof Expr\Assign) { - $exprNode = $exprNode->var; - } - /** @var \PHPStan\Type\ConstantScalarType $constantType */ - $constantType = $expressions[1]; - if ($constantType->getValue() === false) { - $types = $this->create($exprNode, $constantType, $context, false, $scope); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate() - )); - } - - if ($constantType->getValue() === true) { - $types = $this->create($exprNode, $constantType, $context, false, $scope); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate() - )); - } - - if ($constantType->getValue() === null) { - return $this->create($exprNode, $constantType, $context, false, $scope); - } - - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->args) === 1 - && $exprNode->name instanceof Name - && strtolower((string) $exprNode->name) === 'count' - && $constantType instanceof ConstantIntegerType - ) { - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - $argType = $scope->getType($exprNode->args[0]->value); - if ($argType->isArray()->yes()) { - return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); - } - } - } - } - - if ($context->true()) { - $type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left)); - $leftTypes = $this->create($expr->left, $type, $context, false, $scope); - $rightTypes = $this->create($expr->right, $type, $context, false, $scope); - return $leftTypes->unionWith($rightTypes); - - } elseif ($context->false()) { - $identicalType = $scope->getType($expr); - if ($identicalType instanceof ConstantBooleanType) { - $never = new NeverType(); - $contextForTypes = $identicalType->getValue() ? $context->negate() : $context; - $leftTypes = $this->create($expr->left, $never, $contextForTypes, false, $scope); - $rightTypes = $this->create($expr->right, $never, $contextForTypes, false, $scope); - return $leftTypes->unionWith($rightTypes); - } - - $exprLeftType = $scope->getType($expr->left); - $exprRightType = $scope->getType($expr->right); - - $types = null; - - if ( - $exprLeftType instanceof ConstantType - && !$expr->right instanceof Node\Scalar - ) { - $types = $this->create( - $expr->right, - $exprLeftType, - $context, - false, - $scope - ); - } - if ( - $exprRightType instanceof ConstantType - && !$expr->left instanceof Node\Scalar - ) { - $leftType = $this->create( - $expr->left, - $exprRightType, - $context, - false, - $scope - ); - if ($types !== null) { - $types = $types->unionWith($leftType); - } else { - $types = $leftType; - } - } - - if ($types !== null) { - return $types; - } - } - - } elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) { - return $this->specifyTypesInCondition( - $scope, - new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Identical($expr->left, $expr->right)), - $context - ); - } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { - $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); - if ($expressions !== null) { - /** @var Expr $exprNode */ - $exprNode = $expressions[0]; - /** @var \PHPStan\Type\ConstantScalarType $constantType */ - $constantType = $expressions[1]; - if ($constantType->getValue() === false || $constantType->getValue() === null) { - return $this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createFalsey()->negate() - ); - } - - if ($constantType->getValue() === true) { - return $this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate() - ); - } - } - - $leftType = $scope->getType($expr->left); - $leftBooleanType = $leftType->toBoolean(); - $rightType = $scope->getType($expr->right); - if ($leftBooleanType instanceof ConstantBooleanType && $rightType instanceof BooleanType) { - return $this->specifyTypesInCondition( - $scope, - new Expr\BinaryOp\Identical( - new ConstFetch(new Name($leftBooleanType->getValue() ? 'true' : 'false')), - $expr->right - ), - $context - ); - } - - $rightBooleanType = $rightType->toBoolean(); - if ($rightBooleanType instanceof ConstantBooleanType && $leftType instanceof BooleanType) { - return $this->specifyTypesInCondition( - $scope, - new Expr\BinaryOp\Identical( - $expr->left, - new ConstFetch(new Name($rightBooleanType->getValue() ? 'true' : 'false')) - ), - $context - ); - } - - if ( - $expr->left instanceof FuncCall - && $expr->left->name instanceof Name - && strtolower($expr->left->name->toString()) === 'get_class' - && isset($expr->left->args[0]) - && $rightType instanceof ConstantStringType - ) { - return $this->specifyTypesInCondition( - $scope, - new Instanceof_( - $expr->left->args[0]->value, - new Name($rightType->getValue()) - ), - $context - ); - } - - if ( - $expr->right instanceof FuncCall - && $expr->right->name instanceof Name - && strtolower($expr->right->name->toString()) === 'get_class' - && isset($expr->right->args[0]) - && $leftType instanceof ConstantStringType - ) { - return $this->specifyTypesInCondition( - $scope, - new Instanceof_( - $expr->right->args[0]->value, - new Name($leftType->getValue()) - ), - $context - ); - } - } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { - return $this->specifyTypesInCondition( - $scope, - new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Equal($expr->left, $expr->right)), - $context - ); - - } elseif ($expr instanceof Node\Expr\BinaryOp\Smaller || $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual) { - $orEqual = $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual; - $offset = $orEqual ? 0 : 1; - $leftType = $scope->getType($expr->left); - $rightType = $scope->getType($expr->right); - - if ( - $expr->left instanceof FuncCall - && count($expr->left->args) === 1 - && $expr->left->name instanceof Name - && strtolower((string) $expr->left->name) === 'count' - && ( - !$expr->right instanceof FuncCall - || !$expr->right->name instanceof Name - || strtolower((string) $expr->right->name) !== 'count' - ) - ) { - $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller - ? new Node\Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left) - : new Node\Expr\BinaryOp\Smaller($expr->right, $expr->left); - - return $this->specifyTypesInCondition( - $scope, - new Node\Expr\BooleanNot($inverseOperator), - $context - ); - } - - $result = new SpecifiedTypes(); - - if ( - !$context->null() - && $expr->right instanceof FuncCall - && count($expr->right->args) === 1 - && $expr->right->name instanceof Name - && strtolower((string) $expr->right->name) === 'count' - && (new IntegerType())->isSuperTypeOf($leftType)->yes() - ) { - if ( - $context->truthy() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) - || ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes()) - ) { - $argType = $scope->getType($expr->right->args[0]->value); - if ($argType->isArray()->yes()) { - $result = $result->unionWith($this->create($expr->right->args[0]->value, new NonEmptyArrayType(), $context, false, $scope)); - } - } - } - - if ($leftType instanceof ConstantIntegerType) { - if ($expr->right instanceof Expr\PostInc) { - $result = $result->unionWith($this->createRangeTypes( - $expr->right->var, - IntegerRangeType::fromInterval($leftType->getValue(), null, $offset + 1), - $context - )); - } elseif ($expr->right instanceof Expr\PostDec) { - $result = $result->unionWith($this->createRangeTypes( - $expr->right->var, - IntegerRangeType::fromInterval($leftType->getValue(), null, $offset - 1), - $context - )); - } elseif ($expr->right instanceof Expr\PreInc || $expr->right instanceof Expr\PreDec) { - $result = $result->unionWith($this->createRangeTypes( - $expr->right->var, - IntegerRangeType::fromInterval($leftType->getValue(), null, $offset), - $context - )); - } - } - - if ($rightType instanceof ConstantIntegerType) { - if ($expr->left instanceof Expr\PostInc) { - $result = $result->unionWith($this->createRangeTypes( - $expr->left->var, - IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset + 1), - $context - )); - } elseif ($expr->left instanceof Expr\PostDec) { - $result = $result->unionWith($this->createRangeTypes( - $expr->left->var, - IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset - 1), - $context - )); - } elseif ($expr->left instanceof Expr\PreInc || $expr->left instanceof Expr\PreDec) { - $result = $result->unionWith($this->createRangeTypes( - $expr->left->var, - IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset), - $context - )); - } - } - - if ($context->truthy()) { - if (!$expr->left instanceof Node\Scalar) { - $result = $result->unionWith( - $this->create( - $expr->left, - $orEqual ? $rightType->getSmallerOrEqualType() : $rightType->getSmallerType(), - TypeSpecifierContext::createTruthy(), - false, - $scope - ) - ); - } - if (!$expr->right instanceof Node\Scalar) { - $result = $result->unionWith( - $this->create( - $expr->right, - $orEqual ? $leftType->getGreaterOrEqualType() : $leftType->getGreaterType(), - TypeSpecifierContext::createTruthy(), - false, - $scope - ) - ); - } - } elseif ($context->falsey()) { - if (!$expr->left instanceof Node\Scalar) { - $result = $result->unionWith( - $this->create( - $expr->left, - $orEqual ? $rightType->getGreaterType() : $rightType->getGreaterOrEqualType(), - TypeSpecifierContext::createTruthy(), - false, - $scope - ) - ); - } - if (!$expr->right instanceof Node\Scalar) { - $result = $result->unionWith( - $this->create( - $expr->right, - $orEqual ? $leftType->getSmallerType() : $leftType->getSmallerOrEqualType(), - TypeSpecifierContext::createTruthy(), - false, - $scope - ) - ); - } - } - - return $result; - - } elseif ($expr instanceof Node\Expr\BinaryOp\Greater) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Smaller($expr->right, $expr->left), $context); - - } elseif ($expr instanceof Node\Expr\BinaryOp\GreaterOrEqual) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left), $context); - - } elseif ($expr instanceof FuncCall && $expr->name instanceof Name) { - if ($this->reflectionProvider->hasFunction($expr->name, $scope)) { - $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); - foreach ($this->getFunctionTypeSpecifyingExtensions() as $extension) { - if (!$extension->isFunctionSupported($functionReflection, $expr, $context)) { - continue; - } - - return $extension->specifyTypes($functionReflection, $expr, $scope, $context); - } - } - - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } - } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { - $methodCalledOnType = $scope->getType($expr->var); - $referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType); - if ( - count($referencedClasses) === 1 - && $this->reflectionProvider->hasClass($referencedClasses[0]) - ) { - $methodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); - if ($methodClassReflection->hasMethod($expr->name->name)) { - $methodReflection = $methodClassReflection->getMethod($expr->name->name, $scope); - foreach ($this->getMethodTypeSpecifyingExtensionsForClass($methodClassReflection->getName()) as $extension) { - if (!$extension->isMethodSupported($methodReflection, $expr, $context)) { - continue; - } - - return $extension->specifyTypes($methodReflection, $expr, $scope, $context); - } - } - } - - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } - } elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) { - if ($expr->class instanceof Name) { - $calleeType = $scope->resolveTypeByName($expr->class); - } else { - $calleeType = $scope->getType($expr->class); - } - - $staticMethodReflection = $scope->getMethodReflection($calleeType, $expr->name->name); - if ($staticMethodReflection !== null) { - $referencedClasses = TypeUtils::getDirectClassNames($calleeType); - if ( - count($referencedClasses) === 1 - && $this->reflectionProvider->hasClass($referencedClasses[0]) - ) { - $staticMethodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); - foreach ($this->getStaticMethodTypeSpecifyingExtensionsForClass($staticMethodClassReflection->getName()) as $extension) { - if (!$extension->isStaticMethodSupported($staticMethodReflection, $expr, $context)) { - continue; - } - - return $extension->specifyTypes($staticMethodReflection, $expr, $scope, $context); - } - } - } - - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } - } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { - $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); - $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); - $types = $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->intersectWith($rightTypes); - if ($context->false()) { - return new SpecifiedTypes( - $types->getSureTypes(), - $types->getSureNotTypes(), - false, - array_merge( - $this->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes) - ) - ); - } - - return $types; - } elseif ($expr instanceof BooleanOr || $expr instanceof LogicalOr) { - $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); - $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); - $types = $context->true() ? $leftTypes->intersectWith($rightTypes) : $leftTypes->unionWith($rightTypes); - if ($context->true()) { - return new SpecifiedTypes( - $types->getSureTypes(), - $types->getSureNotTypes(), - false, - array_merge( - $this->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes) - ) - ); - } - - return $types; - } elseif ($expr instanceof Node\Expr\BooleanNot && !$context->null()) { - return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate()); - } elseif ($expr instanceof Node\Expr\Assign) { - if (!$scope instanceof MutatingScope) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($context->null()) { - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context); - } - - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context); - } elseif ( - ( - $expr instanceof Expr\Isset_ - && count($expr->vars) > 0 - && $context->true() - ) - || ($expr instanceof Expr\Empty_ && $context->false()) - ) { - $vars = []; - if ($expr instanceof Expr\Isset_) { - $varsToIterate = $expr->vars; - } else { - $varsToIterate = [$expr->expr]; - } - foreach ($varsToIterate as $var) { - $tmpVars = [$var]; - - while ( - $var instanceof ArrayDimFetch - || $var instanceof PropertyFetch - || ( - $var instanceof StaticPropertyFetch - && $var->class instanceof Expr - ) - ) { - if ($var instanceof StaticPropertyFetch) { - /** @var Expr $var */ - $var = $var->class; - } else { - $var = $var->var; - } - $tmpVars[] = $var; - } - - $vars = array_merge($vars, array_reverse($tmpVars)); - } - - if (count($vars) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $types = null; - foreach ($vars as $var) { - if ($var instanceof Expr\Variable && is_string($var->name)) { - if ($scope->hasVariableType($var->name)->no()) { - return new SpecifiedTypes([], []); - } - } - if ($expr instanceof Expr\Isset_) { - if ( - $var instanceof ArrayDimFetch - && $var->dim !== null - && !$scope->getType($var->var) instanceof MixedType - ) { - $type = $this->create( - $var->var, - new HasOffsetType($scope->getType($var->dim)), - $context, - false, - $scope - )->unionWith( - $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) - ); - } else { - $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); - } - } else { - $type = $this->create( - $var, - new UnionType([ - new NullType(), - new ConstantBooleanType(false), - ]), - TypeSpecifierContext::createFalse(), - false, - $scope - ); - } - - if ( - $var instanceof PropertyFetch - && $var->name instanceof Node\Identifier - ) { - $type = $type->unionWith($this->create($var->var, new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope)); - } elseif ( - $var instanceof StaticPropertyFetch - && $var->class instanceof Expr - && $var->name instanceof Node\VarLikeIdentifier - ) { - $type = $type->unionWith($this->create($var->class, new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope)); - } - - if ($types === null) { - $types = $type; - } else { - $types = $types->unionWith($type); - } - } - - if ( - $expr instanceof Expr\Empty_ - && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes()) { - $types = $types->unionWith( - $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope) - ); - } - - return $types; - } elseif ( - $expr instanceof Expr\BinaryOp\Coalesce - && $context->true() - && ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->right))->yes()) - ) { - return $this->create( - $expr->left, - new NullType(), - TypeSpecifierContext::createFalse(), - false, - $scope - ); - } elseif ( - $expr instanceof Expr\Empty_ && $context->truthy() - && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes() - ) { - return $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope); - } elseif ($expr instanceof Expr\ErrorSuppress) { - return $this->specifyTypesInCondition($scope, $expr->expr, $context); - } elseif ( - $expr instanceof Expr\Ternary - && !$context->null() - && ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->else))->yes()) - ) { - $conditionExpr = $expr->cond; - if ($expr->if !== null) { - $conditionExpr = new BooleanAnd($conditionExpr, $expr->if); - } - - return $this->specifyTypesInCondition($scope, $conditionExpr, $context); - - } elseif ($expr instanceof Expr\NullsafePropertyFetch && !$context->null()) { - $types = $this->specifyTypesInCondition( - $scope, - new BooleanAnd( - new Expr\BinaryOp\NotIdentical($expr->var, new ConstFetch(new Name('null'))), - new PropertyFetch($expr->var, $expr->name) - ), - $context - ); - - $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - return $context->true() ? $types->unionWith($nullSafeTypes) : $types->intersectWith($nullSafeTypes); - } elseif ($expr instanceof Expr\NullsafeMethodCall && !$context->null()) { - $types = $this->specifyTypesInCondition( - $scope, - new BooleanAnd( - new Expr\BinaryOp\NotIdentical($expr->var, new ConstFetch(new Name('null'))), - new MethodCall($expr->var, $expr->name, $expr->args) - ), - $context - ); - - $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - return $context->true() ? $types->unionWith($nullSafeTypes) : $types->intersectWith($nullSafeTypes); - } elseif (!$context->null()) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } - - return new SpecifiedTypes(); - } - - private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, Expr $expr, Scope $scope): SpecifiedTypes - { - if ($context->null()) { - return new SpecifiedTypes(); - } - if (!$context->truthy()) { - $type = StaticTypeFactory::truthy(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope); - } elseif (!$context->falsey()) { - $type = StaticTypeFactory::falsey(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope); - } - - return new SpecifiedTypes(); - } - - /** - * @param Scope $scope - * @param SpecifiedTypes $leftTypes - * @param SpecifiedTypes $rightTypes - * @return array - */ - private function processBooleanConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes): array - { - $conditionExpressionTypes = []; - foreach ($leftTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } - - $conditionExpressionTypes[$exprString] = TypeCombinator::intersect($scope->getType($expr), $type); - } - - if (count($conditionExpressionTypes) > 0) { - $holders = []; - foreach ($rightTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } - - if (!isset($holders[$exprString])) { - $holders[$exprString] = []; - } - - $holders[$exprString][] = new ConditionalExpressionHolder( - $conditionExpressionTypes, - new VariableTypeHolder(TypeCombinator::remove($scope->getType($expr), $type), TrinaryLogic::createYes()) - ); - } - - return $holders; - } - - return []; - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node\Expr\BinaryOp $binaryOperation - * @return (Expr|\PHPStan\Type\ConstantScalarType)[]|null - */ - private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array - { - $leftType = $scope->getType($binaryOperation->left); - $rightType = $scope->getType($binaryOperation->right); - if ( - $leftType instanceof \PHPStan\Type\ConstantScalarType - && !$binaryOperation->right instanceof ConstFetch - && !$binaryOperation->right instanceof Expr\ClassConstFetch - ) { - return [$binaryOperation->right, $leftType]; - } elseif ( - $rightType instanceof \PHPStan\Type\ConstantScalarType - && !$binaryOperation->left instanceof ConstFetch - && !$binaryOperation->left instanceof Expr\ClassConstFetch - ) { - return [$binaryOperation->left, $rightType]; - } - - return null; - } - - public function create( - Expr $expr, - Type $type, - TypeSpecifierContext $context, - bool $overwrite = false, - ?Scope $scope = null - ): SpecifiedTypes - { - if ($expr instanceof New_ || $expr instanceof Instanceof_) { - return new SpecifiedTypes(); - } - - if ($scope !== null) { - if ($context->true()) { - $resultType = TypeCombinator::intersect($scope->getType($expr), $type); - } elseif ($context->false()) { - $resultType = TypeCombinator::remove($scope->getType($expr), $type); - } - } - - $originalExpr = $expr; - if (isset($resultType) && !TypeCombinator::containsNull($resultType)) { - $expr = NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($expr); - } - - if ( - $expr instanceof FuncCall - && $expr->name instanceof Name - ) { - $has = $this->reflectionProvider->hasFunction($expr->name, $scope); - if (!$has) { - // backwards compatibility with previous behaviour - return new SpecifiedTypes(); - } - - $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); - if ($functionReflection->hasSideEffects()->yes()) { - return new SpecifiedTypes(); - } - } - - if ( - $expr instanceof MethodCall - && $expr->name instanceof Node\Identifier - && $scope !== null - ) { - $methodName = $expr->name->toString(); - $calledOnType = $scope->getType($expr->var); - $methodReflection = $scope->getMethodReflection($calledOnType, $methodName); - if ($methodReflection === null || $methodReflection->hasSideEffects()->yes()) { - if (isset($resultType) && !TypeCombinator::containsNull($resultType)) { - return $this->createNullsafeTypes($originalExpr, $scope, $context, $type); - } - - return new SpecifiedTypes(); - } - } - - $sureTypes = []; - $sureNotTypes = []; - $exprString = $this->printer->prettyPrintExpr($expr); - if ($context->false()) { - $sureNotTypes[$exprString] = [$expr, $type]; - } elseif ($context->true()) { - $sureTypes[$exprString] = [$expr, $type]; - } - - $types = new SpecifiedTypes($sureTypes, $sureNotTypes, $overwrite); - if ($scope !== null && isset($resultType) && !TypeCombinator::containsNull($resultType)) { - return $this->createNullsafeTypes($originalExpr, $scope, $context, $type)->unionWith($types); - } - - return $types; - } - - private function createNullsafeTypes(Expr $expr, Scope $scope, TypeSpecifierContext $context, ?Type $type): SpecifiedTypes - { - if ($expr instanceof Expr\NullsafePropertyFetch) { - if ($type !== null) { - $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), $type, $context, false, $scope); - } else { - $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), new NullType(), TypeSpecifierContext::createFalse(), false, $scope); - } - - return $propertyFetchTypes->unionWith( - $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) - ); - } - - if ($expr instanceof Expr\NullsafeMethodCall) { - if ($type !== null) { - $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), $type, $context, false, $scope); - } else { - $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), new NullType(), TypeSpecifierContext::createFalse(), false, $scope); - } - - return $methodCallTypes->unionWith( - $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) - ); - } - - if ($expr instanceof Expr\PropertyFetch) { - return $this->createNullsafeTypes($expr->var, $scope, $context, null); - } - - if ($expr instanceof Expr\MethodCall) { - return $this->createNullsafeTypes($expr->var, $scope, $context, null); - } - - if ($expr instanceof Expr\ArrayDimFetch) { - return $this->createNullsafeTypes($expr->var, $scope, $context, null); - } - - if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->createNullsafeTypes($expr->class, $scope, $context, null); - } - - if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { - return $this->createNullsafeTypes($expr->class, $scope, $context, null); - } - - return new SpecifiedTypes(); - } - - private function createRangeTypes(Expr $expr, Type $type, TypeSpecifierContext $context): SpecifiedTypes - { - $sureNotTypes = []; - - if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) { - $exprString = $this->printer->prettyPrintExpr($expr); - if ($context->false()) { - $sureNotTypes[$exprString] = [$expr, $type]; - } elseif ($context->true()) { - $inverted = TypeCombinator::remove(new IntegerType(), $type); - $sureNotTypes[$exprString] = [$expr, $inverted]; - } - } - - return new SpecifiedTypes([], $sureNotTypes); - } - - /** - * @return \PHPStan\Type\FunctionTypeSpecifyingExtension[] - */ - private function getFunctionTypeSpecifyingExtensions(): array - { - return $this->functionTypeSpecifyingExtensions; - } - - /** - * @param string $className - * @return \PHPStan\Type\MethodTypeSpecifyingExtension[] - */ - private function getMethodTypeSpecifyingExtensionsForClass(string $className): array - { - if ($this->methodTypeSpecifyingExtensionsByClass === null) { - $byClass = []; - foreach ($this->methodTypeSpecifyingExtensions as $extension) { - $byClass[$extension->getClass()][] = $extension; - } - - $this->methodTypeSpecifyingExtensionsByClass = $byClass; - } - return $this->getTypeSpecifyingExtensionsForType($this->methodTypeSpecifyingExtensionsByClass, $className); - } - - /** - * @param string $className - * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] - */ - private function getStaticMethodTypeSpecifyingExtensionsForClass(string $className): array - { - if ($this->staticMethodTypeSpecifyingExtensionsByClass === null) { - $byClass = []; - foreach ($this->staticMethodTypeSpecifyingExtensions as $extension) { - $byClass[$extension->getClass()][] = $extension; - } - - $this->staticMethodTypeSpecifyingExtensionsByClass = $byClass; - } - return $this->getTypeSpecifyingExtensionsForType($this->staticMethodTypeSpecifyingExtensionsByClass, $className); - } - - /** - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[][]|\PHPStan\Type\StaticMethodTypeSpecifyingExtension[][] $extensions - * @param string $className - * @return mixed[] - */ - private function getTypeSpecifyingExtensionsForType(array $extensions, string $className): array - { - $extensionsForClass = [[]]; - $class = $this->reflectionProvider->getClass($className); - foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { - if (!isset($extensions[$extensionClassName])) { - continue; - } - - $extensionsForClass[] = $extensions[$extensionClassName]; - } - - return array_merge(...$extensionsForClass); - } - + private \PhpParser\PrettyPrinter\Standard $printer; + + private ReflectionProvider $reflectionProvider; + + private bool $rememberFunctionValues; + + /** @var \PHPStan\Type\FunctionTypeSpecifyingExtension[] */ + private array $functionTypeSpecifyingExtensions; + + /** @var \PHPStan\Type\MethodTypeSpecifyingExtension[] */ + private array $methodTypeSpecifyingExtensions; + + /** @var \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] */ + private array $staticMethodTypeSpecifyingExtensions; + + /** @var \PHPStan\Type\MethodTypeSpecifyingExtension[][]|null */ + private ?array $methodTypeSpecifyingExtensionsByClass = null; + + /** @var \PHPStan\Type\StaticMethodTypeSpecifyingExtension[][]|null */ + private ?array $staticMethodTypeSpecifyingExtensionsByClass = null; + + /** + * @param \PhpParser\PrettyPrinter\Standard $printer + * @param ReflectionProvider $reflectionProvider + * @param \PHPStan\Type\FunctionTypeSpecifyingExtension[] $functionTypeSpecifyingExtensions + * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions + * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions + */ + public function __construct( + \PhpParser\PrettyPrinter\Standard $printer, + ReflectionProvider $reflectionProvider, + bool $rememberFunctionValues, + array $functionTypeSpecifyingExtensions, + array $methodTypeSpecifyingExtensions, + array $staticMethodTypeSpecifyingExtensions + ) { + $this->printer = $printer; + $this->reflectionProvider = $reflectionProvider; + $this->rememberFunctionValues = $rememberFunctionValues; + + foreach (array_merge($functionTypeSpecifyingExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions) as $extension) { + if (!($extension instanceof TypeSpecifierAwareExtension)) { + continue; + } + + $extension->setTypeSpecifier($this); + } + + $this->functionTypeSpecifyingExtensions = $functionTypeSpecifyingExtensions; + $this->methodTypeSpecifyingExtensions = $methodTypeSpecifyingExtensions; + $this->staticMethodTypeSpecifyingExtensions = $staticMethodTypeSpecifyingExtensions; + } + + public function specifyTypesInCondition( + Scope $scope, + Expr $expr, + TypeSpecifierContext $context + ): SpecifiedTypes { + if ($expr instanceof Instanceof_) { + $exprNode = $expr->expr; + if ($exprNode instanceof Expr\Assign) { + $exprNode = $exprNode->var; + } + if ($expr->class instanceof Name) { + $className = (string) $expr->class; + $lowercasedClassName = strtolower($className); + if ($lowercasedClassName === 'self' && $scope->isInClass()) { + $type = new ObjectType($scope->getClassReflection()->getName()); + } elseif ($lowercasedClassName === 'static' && $scope->isInClass()) { + $type = new StaticType($scope->getClassReflection()); + } elseif ($lowercasedClassName === 'parent') { + if ( + $scope->isInClass() + && $scope->getClassReflection()->getParentClass() !== false + ) { + $type = new ObjectType($scope->getClassReflection()->getParentClass()->getName()); + } else { + $type = new NonexistentParentClassType(); + } + } else { + $type = new ObjectType($className); + } + return $this->create($exprNode, $type, $context, false, $scope); + } + + $classType = $scope->getType($expr->class); + $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + if ($type instanceof TypeWithClassName) { + return $type; + } + if ($type instanceof GenericClassStringType) { + return $type->getGenericType(); + } + if ($type instanceof ConstantStringType) { + return new ObjectType($type->getValue()); + } + return new MixedType(); + }); + + if (!$type->isSuperTypeOf(new MixedType())->yes()) { + if ($context->true()) { + $type = TypeCombinator::intersect( + $type, + new ObjectWithoutClassType() + ); + return $this->create($exprNode, $type, $context, false, $scope); + } elseif ($context->false()) { + $exprType = $scope->getType($expr->expr); + if (!$type->isSuperTypeOf($exprType)->yes()) { + return $this->create($exprNode, $type, $context, false, $scope); + } + } + } + if ($context->true()) { + return $this->create($exprNode, new ObjectWithoutClassType(), $context, false, $scope); + } + } elseif ($expr instanceof Node\Expr\BinaryOp\Identical) { + $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); + if ($expressions !== null) { + /** @var Expr $exprNode */ + $exprNode = $expressions[0]; + if ($exprNode instanceof Expr\Assign) { + $exprNode = $exprNode->var; + } + /** @var \PHPStan\Type\ConstantScalarType $constantType */ + $constantType = $expressions[1]; + if ($constantType->getValue() === false) { + $types = $this->create($exprNode, $constantType, $context, false, $scope); + return $types->unionWith($this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate() + )); + } + + if ($constantType->getValue() === true) { + $types = $this->create($exprNode, $constantType, $context, false, $scope); + return $types->unionWith($this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate() + )); + } + + if ($constantType->getValue() === null) { + return $this->create($exprNode, $constantType, $context, false, $scope); + } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && count($exprNode->args) === 1 + && $exprNode->name instanceof Name + && strtolower((string) $exprNode->name) === 'count' + && $constantType instanceof ConstantIntegerType + ) { + if ($context->truthy() || $constantType->getValue() === 0) { + $newContext = $context; + if ($constantType->getValue() === 0) { + $newContext = $newContext->negate(); + } + $argType = $scope->getType($exprNode->args[0]->value); + if ($argType->isArray()->yes()) { + return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); + } + } + } + } + + if ($context->true()) { + $type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left)); + $leftTypes = $this->create($expr->left, $type, $context, false, $scope); + $rightTypes = $this->create($expr->right, $type, $context, false, $scope); + return $leftTypes->unionWith($rightTypes); + } elseif ($context->false()) { + $identicalType = $scope->getType($expr); + if ($identicalType instanceof ConstantBooleanType) { + $never = new NeverType(); + $contextForTypes = $identicalType->getValue() ? $context->negate() : $context; + $leftTypes = $this->create($expr->left, $never, $contextForTypes, false, $scope); + $rightTypes = $this->create($expr->right, $never, $contextForTypes, false, $scope); + return $leftTypes->unionWith($rightTypes); + } + + $exprLeftType = $scope->getType($expr->left); + $exprRightType = $scope->getType($expr->right); + + $types = null; + + if ( + $exprLeftType instanceof ConstantType + && !$expr->right instanceof Node\Scalar + ) { + $types = $this->create( + $expr->right, + $exprLeftType, + $context, + false, + $scope + ); + } + if ( + $exprRightType instanceof ConstantType + && !$expr->left instanceof Node\Scalar + ) { + $leftType = $this->create( + $expr->left, + $exprRightType, + $context, + false, + $scope + ); + if ($types !== null) { + $types = $types->unionWith($leftType); + } else { + $types = $leftType; + } + } + + if ($types !== null) { + return $types; + } + } + } elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Identical($expr->left, $expr->right)), + $context + ); + } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { + $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); + if ($expressions !== null) { + /** @var Expr $exprNode */ + $exprNode = $expressions[0]; + /** @var \PHPStan\Type\ConstantScalarType $constantType */ + $constantType = $expressions[1]; + if ($constantType->getValue() === false || $constantType->getValue() === null) { + return $this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createFalsey()->negate() + ); + } + + if ($constantType->getValue() === true) { + return $this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate() + ); + } + } + + $leftType = $scope->getType($expr->left); + $leftBooleanType = $leftType->toBoolean(); + $rightType = $scope->getType($expr->right); + if ($leftBooleanType instanceof ConstantBooleanType && $rightType instanceof BooleanType) { + return $this->specifyTypesInCondition( + $scope, + new Expr\BinaryOp\Identical( + new ConstFetch(new Name($leftBooleanType->getValue() ? 'true' : 'false')), + $expr->right + ), + $context + ); + } + + $rightBooleanType = $rightType->toBoolean(); + if ($rightBooleanType instanceof ConstantBooleanType && $leftType instanceof BooleanType) { + return $this->specifyTypesInCondition( + $scope, + new Expr\BinaryOp\Identical( + $expr->left, + new ConstFetch(new Name($rightBooleanType->getValue() ? 'true' : 'false')) + ), + $context + ); + } + + if ( + $expr->left instanceof FuncCall + && $expr->left->name instanceof Name + && strtolower($expr->left->name->toString()) === 'get_class' + && isset($expr->left->args[0]) + && $rightType instanceof ConstantStringType + ) { + return $this->specifyTypesInCondition( + $scope, + new Instanceof_( + $expr->left->args[0]->value, + new Name($rightType->getValue()) + ), + $context + ); + } + + if ( + $expr->right instanceof FuncCall + && $expr->right->name instanceof Name + && strtolower($expr->right->name->toString()) === 'get_class' + && isset($expr->right->args[0]) + && $leftType instanceof ConstantStringType + ) { + return $this->specifyTypesInCondition( + $scope, + new Instanceof_( + $expr->right->args[0]->value, + new Name($leftType->getValue()) + ), + $context + ); + } + } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Equal($expr->left, $expr->right)), + $context + ); + } elseif ($expr instanceof Node\Expr\BinaryOp\Smaller || $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual) { + $orEqual = $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual; + $offset = $orEqual ? 0 : 1; + $leftType = $scope->getType($expr->left); + $rightType = $scope->getType($expr->right); + + if ( + $expr->left instanceof FuncCall + && count($expr->left->args) === 1 + && $expr->left->name instanceof Name + && strtolower((string) $expr->left->name) === 'count' + && ( + !$expr->right instanceof FuncCall + || !$expr->right->name instanceof Name + || strtolower((string) $expr->right->name) !== 'count' + ) + ) { + $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller + ? new Node\Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left) + : new Node\Expr\BinaryOp\Smaller($expr->right, $expr->left); + + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BooleanNot($inverseOperator), + $context + ); + } + + $result = new SpecifiedTypes(); + + if ( + !$context->null() + && $expr->right instanceof FuncCall + && count($expr->right->args) === 1 + && $expr->right->name instanceof Name + && strtolower((string) $expr->right->name) === 'count' + && (new IntegerType())->isSuperTypeOf($leftType)->yes() + ) { + if ( + $context->truthy() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) + || ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes()) + ) { + $argType = $scope->getType($expr->right->args[0]->value); + if ($argType->isArray()->yes()) { + $result = $result->unionWith($this->create($expr->right->args[0]->value, new NonEmptyArrayType(), $context, false, $scope)); + } + } + } + + if ($leftType instanceof ConstantIntegerType) { + if ($expr->right instanceof Expr\PostInc) { + $result = $result->unionWith($this->createRangeTypes( + $expr->right->var, + IntegerRangeType::fromInterval($leftType->getValue(), null, $offset + 1), + $context + )); + } elseif ($expr->right instanceof Expr\PostDec) { + $result = $result->unionWith($this->createRangeTypes( + $expr->right->var, + IntegerRangeType::fromInterval($leftType->getValue(), null, $offset - 1), + $context + )); + } elseif ($expr->right instanceof Expr\PreInc || $expr->right instanceof Expr\PreDec) { + $result = $result->unionWith($this->createRangeTypes( + $expr->right->var, + IntegerRangeType::fromInterval($leftType->getValue(), null, $offset), + $context + )); + } + } + + if ($rightType instanceof ConstantIntegerType) { + if ($expr->left instanceof Expr\PostInc) { + $result = $result->unionWith($this->createRangeTypes( + $expr->left->var, + IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset + 1), + $context + )); + } elseif ($expr->left instanceof Expr\PostDec) { + $result = $result->unionWith($this->createRangeTypes( + $expr->left->var, + IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset - 1), + $context + )); + } elseif ($expr->left instanceof Expr\PreInc || $expr->left instanceof Expr\PreDec) { + $result = $result->unionWith($this->createRangeTypes( + $expr->left->var, + IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset), + $context + )); + } + } + + if ($context->truthy()) { + if (!$expr->left instanceof Node\Scalar) { + $result = $result->unionWith( + $this->create( + $expr->left, + $orEqual ? $rightType->getSmallerOrEqualType() : $rightType->getSmallerType(), + TypeSpecifierContext::createTruthy(), + false, + $scope + ) + ); + } + if (!$expr->right instanceof Node\Scalar) { + $result = $result->unionWith( + $this->create( + $expr->right, + $orEqual ? $leftType->getGreaterOrEqualType() : $leftType->getGreaterType(), + TypeSpecifierContext::createTruthy(), + false, + $scope + ) + ); + } + } elseif ($context->falsey()) { + if (!$expr->left instanceof Node\Scalar) { + $result = $result->unionWith( + $this->create( + $expr->left, + $orEqual ? $rightType->getGreaterType() : $rightType->getGreaterOrEqualType(), + TypeSpecifierContext::createTruthy(), + false, + $scope + ) + ); + } + if (!$expr->right instanceof Node\Scalar) { + $result = $result->unionWith( + $this->create( + $expr->right, + $orEqual ? $leftType->getSmallerType() : $leftType->getSmallerOrEqualType(), + TypeSpecifierContext::createTruthy(), + false, + $scope + ) + ); + } + } + + return $result; + } elseif ($expr instanceof Node\Expr\BinaryOp\Greater) { + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Smaller($expr->right, $expr->left), $context); + } elseif ($expr instanceof Node\Expr\BinaryOp\GreaterOrEqual) { + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left), $context); + } elseif ($expr instanceof FuncCall && $expr->name instanceof Name) { + if ($this->reflectionProvider->hasFunction($expr->name, $scope)) { + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + foreach ($this->getFunctionTypeSpecifyingExtensions() as $extension) { + if (!$extension->isFunctionSupported($functionReflection, $expr, $context)) { + continue; + } + + return $extension->specifyTypes($functionReflection, $expr, $scope, $context); + } + } + + if ($this->rememberFunctionValues) { + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + } + } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { + $methodCalledOnType = $scope->getType($expr->var); + $referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType); + if ( + count($referencedClasses) === 1 + && $this->reflectionProvider->hasClass($referencedClasses[0]) + ) { + $methodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); + if ($methodClassReflection->hasMethod($expr->name->name)) { + $methodReflection = $methodClassReflection->getMethod($expr->name->name, $scope); + foreach ($this->getMethodTypeSpecifyingExtensionsForClass($methodClassReflection->getName()) as $extension) { + if (!$extension->isMethodSupported($methodReflection, $expr, $context)) { + continue; + } + + return $extension->specifyTypes($methodReflection, $expr, $scope, $context); + } + } + } + + if ($this->rememberFunctionValues) { + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + } + } elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) { + if ($expr->class instanceof Name) { + $calleeType = $scope->resolveTypeByName($expr->class); + } else { + $calleeType = $scope->getType($expr->class); + } + + $staticMethodReflection = $scope->getMethodReflection($calleeType, $expr->name->name); + if ($staticMethodReflection !== null) { + $referencedClasses = TypeUtils::getDirectClassNames($calleeType); + if ( + count($referencedClasses) === 1 + && $this->reflectionProvider->hasClass($referencedClasses[0]) + ) { + $staticMethodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); + foreach ($this->getStaticMethodTypeSpecifyingExtensionsForClass($staticMethodClassReflection->getName()) as $extension) { + if (!$extension->isStaticMethodSupported($staticMethodReflection, $expr, $context)) { + continue; + } + + return $extension->specifyTypes($staticMethodReflection, $expr, $scope, $context); + } + } + } + + if ($this->rememberFunctionValues) { + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + } + } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { + $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); + $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); + $types = $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->intersectWith($rightTypes); + if ($context->false()) { + return new SpecifiedTypes( + $types->getSureTypes(), + $types->getSureNotTypes(), + false, + array_merge( + $this->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes) + ) + ); + } + + return $types; + } elseif ($expr instanceof BooleanOr || $expr instanceof LogicalOr) { + $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); + $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); + $types = $context->true() ? $leftTypes->intersectWith($rightTypes) : $leftTypes->unionWith($rightTypes); + if ($context->true()) { + return new SpecifiedTypes( + $types->getSureTypes(), + $types->getSureNotTypes(), + false, + array_merge( + $this->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes) + ) + ); + } + + return $types; + } elseif ($expr instanceof Node\Expr\BooleanNot && !$context->null()) { + return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate()); + } elseif ($expr instanceof Node\Expr\Assign) { + if (!$scope instanceof MutatingScope) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($context->null()) { + return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context); + } + + return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context); + } elseif ( + ( + $expr instanceof Expr\Isset_ + && count($expr->vars) > 0 + && $context->true() + ) + || ($expr instanceof Expr\Empty_ && $context->false()) + ) { + $vars = []; + if ($expr instanceof Expr\Isset_) { + $varsToIterate = $expr->vars; + } else { + $varsToIterate = [$expr->expr]; + } + foreach ($varsToIterate as $var) { + $tmpVars = [$var]; + + while ( + $var instanceof ArrayDimFetch + || $var instanceof PropertyFetch + || ( + $var instanceof StaticPropertyFetch + && $var->class instanceof Expr + ) + ) { + if ($var instanceof StaticPropertyFetch) { + /** @var Expr $var */ + $var = $var->class; + } else { + $var = $var->var; + } + $tmpVars[] = $var; + } + + $vars = array_merge($vars, array_reverse($tmpVars)); + } + + if (count($vars) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $types = null; + foreach ($vars as $var) { + if ($var instanceof Expr\Variable && is_string($var->name)) { + if ($scope->hasVariableType($var->name)->no()) { + return new SpecifiedTypes([], []); + } + } + if ($expr instanceof Expr\Isset_) { + if ( + $var instanceof ArrayDimFetch + && $var->dim !== null + && !$scope->getType($var->var) instanceof MixedType + ) { + $type = $this->create( + $var->var, + new HasOffsetType($scope->getType($var->dim)), + $context, + false, + $scope + )->unionWith( + $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) + ); + } else { + $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); + } + } else { + $type = $this->create( + $var, + new UnionType([ + new NullType(), + new ConstantBooleanType(false), + ]), + TypeSpecifierContext::createFalse(), + false, + $scope + ); + } + + if ( + $var instanceof PropertyFetch + && $var->name instanceof Node\Identifier + ) { + $type = $type->unionWith($this->create($var->var, new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType($var->name->toString()), + ]), TypeSpecifierContext::createTruthy(), false, $scope)); + } elseif ( + $var instanceof StaticPropertyFetch + && $var->class instanceof Expr + && $var->name instanceof Node\VarLikeIdentifier + ) { + $type = $type->unionWith($this->create($var->class, new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType($var->name->toString()), + ]), TypeSpecifierContext::createTruthy(), false, $scope)); + } + + if ($types === null) { + $types = $type; + } else { + $types = $types->unionWith($type); + } + } + + if ( + $expr instanceof Expr\Empty_ + && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes()) { + $types = $types->unionWith( + $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope) + ); + } + + return $types; + } elseif ( + $expr instanceof Expr\BinaryOp\Coalesce + && $context->true() + && ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->right))->yes()) + ) { + return $this->create( + $expr->left, + new NullType(), + TypeSpecifierContext::createFalse(), + false, + $scope + ); + } elseif ( + $expr instanceof Expr\Empty_ && $context->truthy() + && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes() + ) { + return $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope); + } elseif ($expr instanceof Expr\ErrorSuppress) { + return $this->specifyTypesInCondition($scope, $expr->expr, $context); + } elseif ( + $expr instanceof Expr\Ternary + && !$context->null() + && ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->else))->yes()) + ) { + $conditionExpr = $expr->cond; + if ($expr->if !== null) { + $conditionExpr = new BooleanAnd($conditionExpr, $expr->if); + } + + return $this->specifyTypesInCondition($scope, $conditionExpr, $context); + } elseif ($expr instanceof Expr\NullsafePropertyFetch && !$context->null()) { + $types = $this->specifyTypesInCondition( + $scope, + new BooleanAnd( + new Expr\BinaryOp\NotIdentical($expr->var, new ConstFetch(new Name('null'))), + new PropertyFetch($expr->var, $expr->name) + ), + $context + ); + + $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + return $context->true() ? $types->unionWith($nullSafeTypes) : $types->intersectWith($nullSafeTypes); + } elseif ($expr instanceof Expr\NullsafeMethodCall && !$context->null()) { + $types = $this->specifyTypesInCondition( + $scope, + new BooleanAnd( + new Expr\BinaryOp\NotIdentical($expr->var, new ConstFetch(new Name('null'))), + new MethodCall($expr->var, $expr->name, $expr->args) + ), + $context + ); + + $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + return $context->true() ? $types->unionWith($nullSafeTypes) : $types->intersectWith($nullSafeTypes); + } elseif (!$context->null()) { + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); + } + + return new SpecifiedTypes(); + } + + private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, Expr $expr, Scope $scope): SpecifiedTypes + { + if ($context->null()) { + return new SpecifiedTypes(); + } + if (!$context->truthy()) { + $type = StaticTypeFactory::truthy(); + return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope); + } elseif (!$context->falsey()) { + $type = StaticTypeFactory::falsey(); + return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope); + } + + return new SpecifiedTypes(); + } + + /** + * @param Scope $scope + * @param SpecifiedTypes $leftTypes + * @param SpecifiedTypes $rightTypes + * @return array + */ + private function processBooleanConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes): array + { + $conditionExpressionTypes = []; + foreach ($leftTypes->getSureNotTypes() as $exprString => [$expr, $type]) { + if (!$expr instanceof Expr\Variable) { + continue; + } + if (!is_string($expr->name)) { + continue; + } + + $conditionExpressionTypes[$exprString] = TypeCombinator::intersect($scope->getType($expr), $type); + } + + if (count($conditionExpressionTypes) > 0) { + $holders = []; + foreach ($rightTypes->getSureNotTypes() as $exprString => [$expr, $type]) { + if (!$expr instanceof Expr\Variable) { + continue; + } + if (!is_string($expr->name)) { + continue; + } + + if (!isset($holders[$exprString])) { + $holders[$exprString] = []; + } + + $holders[$exprString][] = new ConditionalExpressionHolder( + $conditionExpressionTypes, + new VariableTypeHolder(TypeCombinator::remove($scope->getType($expr), $type), TrinaryLogic::createYes()) + ); + } + + return $holders; + } + + return []; + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node\Expr\BinaryOp $binaryOperation + * @return (Expr|\PHPStan\Type\ConstantScalarType)[]|null + */ + private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array + { + $leftType = $scope->getType($binaryOperation->left); + $rightType = $scope->getType($binaryOperation->right); + if ( + $leftType instanceof \PHPStan\Type\ConstantScalarType + && !$binaryOperation->right instanceof ConstFetch + && !$binaryOperation->right instanceof Expr\ClassConstFetch + ) { + return [$binaryOperation->right, $leftType]; + } elseif ( + $rightType instanceof \PHPStan\Type\ConstantScalarType + && !$binaryOperation->left instanceof ConstFetch + && !$binaryOperation->left instanceof Expr\ClassConstFetch + ) { + return [$binaryOperation->left, $rightType]; + } + + return null; + } + + public function create( + Expr $expr, + Type $type, + TypeSpecifierContext $context, + bool $overwrite = false, + ?Scope $scope = null + ): SpecifiedTypes { + if ($expr instanceof New_ || $expr instanceof Instanceof_) { + return new SpecifiedTypes(); + } + + if ($scope !== null) { + if ($context->true()) { + $resultType = TypeCombinator::intersect($scope->getType($expr), $type); + } elseif ($context->false()) { + $resultType = TypeCombinator::remove($scope->getType($expr), $type); + } + } + + $originalExpr = $expr; + if (isset($resultType) && !TypeCombinator::containsNull($resultType)) { + $expr = NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($expr); + } + + if ( + $expr instanceof FuncCall + && $expr->name instanceof Name + ) { + $has = $this->reflectionProvider->hasFunction($expr->name, $scope); + if (!$has) { + // backwards compatibility with previous behaviour + return new SpecifiedTypes(); + } + + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + if ($functionReflection->hasSideEffects()->yes()) { + return new SpecifiedTypes(); + } + } + + if ( + $expr instanceof MethodCall + && $expr->name instanceof Node\Identifier + && $scope !== null + ) { + $methodName = $expr->name->toString(); + $calledOnType = $scope->getType($expr->var); + $methodReflection = $scope->getMethodReflection($calledOnType, $methodName); + if ($methodReflection === null || $methodReflection->hasSideEffects()->yes()) { + if (isset($resultType) && !TypeCombinator::containsNull($resultType)) { + return $this->createNullsafeTypes($originalExpr, $scope, $context, $type); + } + + return new SpecifiedTypes(); + } + } + + $sureTypes = []; + $sureNotTypes = []; + $exprString = $this->printer->prettyPrintExpr($expr); + if ($context->false()) { + $sureNotTypes[$exprString] = [$expr, $type]; + } elseif ($context->true()) { + $sureTypes[$exprString] = [$expr, $type]; + } + + $types = new SpecifiedTypes($sureTypes, $sureNotTypes, $overwrite); + if ($scope !== null && isset($resultType) && !TypeCombinator::containsNull($resultType)) { + return $this->createNullsafeTypes($originalExpr, $scope, $context, $type)->unionWith($types); + } + + return $types; + } + + private function createNullsafeTypes(Expr $expr, Scope $scope, TypeSpecifierContext $context, ?Type $type): SpecifiedTypes + { + if ($expr instanceof Expr\NullsafePropertyFetch) { + if ($type !== null) { + $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), $type, $context, false, $scope); + } else { + $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), new NullType(), TypeSpecifierContext::createFalse(), false, $scope); + } + + return $propertyFetchTypes->unionWith( + $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) + ); + } + + if ($expr instanceof Expr\NullsafeMethodCall) { + if ($type !== null) { + $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), $type, $context, false, $scope); + } else { + $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), new NullType(), TypeSpecifierContext::createFalse(), false, $scope); + } + + return $methodCallTypes->unionWith( + $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) + ); + } + + if ($expr instanceof Expr\PropertyFetch) { + return $this->createNullsafeTypes($expr->var, $scope, $context, null); + } + + if ($expr instanceof Expr\MethodCall) { + return $this->createNullsafeTypes($expr->var, $scope, $context, null); + } + + if ($expr instanceof Expr\ArrayDimFetch) { + return $this->createNullsafeTypes($expr->var, $scope, $context, null); + } + + if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { + return $this->createNullsafeTypes($expr->class, $scope, $context, null); + } + + if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { + return $this->createNullsafeTypes($expr->class, $scope, $context, null); + } + + return new SpecifiedTypes(); + } + + private function createRangeTypes(Expr $expr, Type $type, TypeSpecifierContext $context): SpecifiedTypes + { + $sureNotTypes = []; + + if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) { + $exprString = $this->printer->prettyPrintExpr($expr); + if ($context->false()) { + $sureNotTypes[$exprString] = [$expr, $type]; + } elseif ($context->true()) { + $inverted = TypeCombinator::remove(new IntegerType(), $type); + $sureNotTypes[$exprString] = [$expr, $inverted]; + } + } + + return new SpecifiedTypes([], $sureNotTypes); + } + + /** + * @return \PHPStan\Type\FunctionTypeSpecifyingExtension[] + */ + private function getFunctionTypeSpecifyingExtensions(): array + { + return $this->functionTypeSpecifyingExtensions; + } + + /** + * @param string $className + * @return \PHPStan\Type\MethodTypeSpecifyingExtension[] + */ + private function getMethodTypeSpecifyingExtensionsForClass(string $className): array + { + if ($this->methodTypeSpecifyingExtensionsByClass === null) { + $byClass = []; + foreach ($this->methodTypeSpecifyingExtensions as $extension) { + $byClass[$extension->getClass()][] = $extension; + } + + $this->methodTypeSpecifyingExtensionsByClass = $byClass; + } + return $this->getTypeSpecifyingExtensionsForType($this->methodTypeSpecifyingExtensionsByClass, $className); + } + + /** + * @param string $className + * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] + */ + private function getStaticMethodTypeSpecifyingExtensionsForClass(string $className): array + { + if ($this->staticMethodTypeSpecifyingExtensionsByClass === null) { + $byClass = []; + foreach ($this->staticMethodTypeSpecifyingExtensions as $extension) { + $byClass[$extension->getClass()][] = $extension; + } + + $this->staticMethodTypeSpecifyingExtensionsByClass = $byClass; + } + return $this->getTypeSpecifyingExtensionsForType($this->staticMethodTypeSpecifyingExtensionsByClass, $className); + } + + /** + * @param \PHPStan\Type\MethodTypeSpecifyingExtension[][]|\PHPStan\Type\StaticMethodTypeSpecifyingExtension[][] $extensions + * @param string $className + * @return mixed[] + */ + private function getTypeSpecifyingExtensionsForType(array $extensions, string $className): array + { + $extensionsForClass = [[]]; + $class = $this->reflectionProvider->getClass($className); + foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { + if (!isset($extensions[$extensionClassName])) { + continue; + } + + $extensionsForClass[] = $extensions[$extensionClassName]; + } + + return array_merge(...$extensionsForClass); + } } diff --git a/src/Analyser/TypeSpecifierAwareExtension.php b/src/Analyser/TypeSpecifierAwareExtension.php index 88fc2e4243..d5a081ff01 100644 --- a/src/Analyser/TypeSpecifierAwareExtension.php +++ b/src/Analyser/TypeSpecifierAwareExtension.php @@ -1,10 +1,10 @@ -value = $value; - } - - private static function create(?int $value): self - { - self::$registry[$value] = self::$registry[$value] ?? new self($value); - return self::$registry[$value]; - } - - public static function createTrue(): self - { - return self::create(self::CONTEXT_TRUE); - } - - public static function createTruthy(): self - { - return self::create(self::CONTEXT_TRUTHY); - } - - public static function createFalse(): self - { - return self::create(self::CONTEXT_FALSE); - } - - public static function createFalsey(): self - { - return self::create(self::CONTEXT_FALSEY); - } - - public static function createNull(): self - { - return self::create(null); - } - - public function negate(): self - { - if ($this->value === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - return self::create(~$this->value); - } - - public function true(): bool - { - return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUE); - } - - public function truthy(): bool - { - return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUTHY); - } - - public function false(): bool - { - return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSE); - } - - public function falsey(): bool - { - return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSEY); - } - - public function null(): bool - { - return $this->value === null; - } - + public const CONTEXT_TRUE = 0b0001; + public const CONTEXT_TRUTHY_BUT_NOT_TRUE = 0b0010; + public const CONTEXT_TRUTHY = self::CONTEXT_TRUE | self::CONTEXT_TRUTHY_BUT_NOT_TRUE; + public const CONTEXT_FALSE = 0b0100; + public const CONTEXT_FALSEY_BUT_NOT_FALSE = 0b1000; + public const CONTEXT_FALSEY = self::CONTEXT_FALSE | self::CONTEXT_FALSEY_BUT_NOT_FALSE; + + private ?int $value; + + /** @var self[] */ + private static array $registry; + + private function __construct(?int $value) + { + $this->value = $value; + } + + private static function create(?int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + public static function createTrue(): self + { + return self::create(self::CONTEXT_TRUE); + } + + public static function createTruthy(): self + { + return self::create(self::CONTEXT_TRUTHY); + } + + public static function createFalse(): self + { + return self::create(self::CONTEXT_FALSE); + } + + public static function createFalsey(): self + { + return self::create(self::CONTEXT_FALSEY); + } + + public static function createNull(): self + { + return self::create(null); + } + + public function negate(): self + { + if ($this->value === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + return self::create(~$this->value); + } + + public function true(): bool + { + return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUE); + } + + public function truthy(): bool + { + return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUTHY); + } + + public function false(): bool + { + return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSE); + } + + public function falsey(): bool + { + return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSEY); + } + + public function null(): bool + { + return $this->value === null; + } } diff --git a/src/Analyser/TypeSpecifierFactory.php b/src/Analyser/TypeSpecifierFactory.php index dc022b914d..73aea7da27 100644 --- a/src/Analyser/TypeSpecifierFactory.php +++ b/src/Analyser/TypeSpecifierFactory.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function create(): TypeSpecifier - { - $typeSpecifier = new TypeSpecifier( - $this->container->getByType(Standard::class), - $this->container->getByType(ReflectionProvider::class), - $this->container->getParameter('featureToggles')['rememberFunctionValues'], - $this->container->getServicesByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), - $this->container->getServicesByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG), - $this->container->getServicesByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG) - ); - - foreach (array_merge( - $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) - ) as $extension) { - if (!($extension instanceof TypeSpecifierAwareExtension)) { - continue; - } - - $extension->setTypeSpecifier($typeSpecifier); - } - - return $typeSpecifier; - } - + public const FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.functionTypeSpecifyingExtension'; + public const METHOD_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.methodTypeSpecifyingExtension'; + public const STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension'; + + private \PHPStan\DependencyInjection\Container $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + public function create(): TypeSpecifier + { + $typeSpecifier = new TypeSpecifier( + $this->container->getByType(Standard::class), + $this->container->getByType(ReflectionProvider::class), + $this->container->getParameter('featureToggles')['rememberFunctionValues'], + $this->container->getServicesByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), + $this->container->getServicesByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG), + $this->container->getServicesByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG) + ); + + foreach (array_merge( + $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) + ) as $extension) { + if (!($extension instanceof TypeSpecifierAwareExtension)) { + continue; + } + + $extension->setTypeSpecifier($typeSpecifier); + } + + return $typeSpecifier; + } } diff --git a/src/Analyser/UndefinedVariableException.php b/src/Analyser/UndefinedVariableException.php index 55be8974c0..83c0a37a46 100644 --- a/src/Analyser/UndefinedVariableException.php +++ b/src/Analyser/UndefinedVariableException.php @@ -1,34 +1,34 @@ -scope = $scope; - $this->variableName = $variableName; - } - - public function getScope(): Scope - { - return $this->scope; - } - - public function getVariableName(): string - { - return $this->variableName; - } - - public function getTip(): ?string - { - return null; - } - + private \PHPStan\Analyser\Scope $scope; + + private string $variableName; + + public function __construct(Scope $scope, string $variableName) + { + parent::__construct(sprintf('Undefined variable: $%s', $variableName)); + $this->scope = $scope; + $this->variableName = $variableName; + } + + public function getScope(): Scope + { + return $this->scope; + } + + public function getVariableName(): string + { + return $this->variableName; + } + + public function getTip(): ?string + { + return null; + } } diff --git a/src/Analyser/VariableTypeHolder.php b/src/Analyser/VariableTypeHolder.php index aba3d385d7..d0c5773e87 100644 --- a/src/Analyser/VariableTypeHolder.php +++ b/src/Analyser/VariableTypeHolder.php @@ -1,4 +1,6 @@ -type = $type; - $this->certainty = $certainty; - } + private \PHPStan\TrinaryLogic $certainty; - public static function createYes(Type $type): self - { - return new self($type, TrinaryLogic::createYes()); - } + public function __construct(Type $type, TrinaryLogic $certainty) + { + $this->type = $type; + $this->certainty = $certainty; + } - public static function createMaybe(Type $type): self - { - return new self($type, TrinaryLogic::createMaybe()); - } + public static function createYes(Type $type): self + { + return new self($type, TrinaryLogic::createYes()); + } - public function equals(self $other): bool - { - if (!$this->certainty->equals($other->certainty)) { - return false; - } + public static function createMaybe(Type $type): self + { + return new self($type, TrinaryLogic::createMaybe()); + } - return $this->type->equals($other->type); - } + public function equals(self $other): bool + { + if (!$this->certainty->equals($other->certainty)) { + return false; + } - public function and(self $other): self - { - if ($this->getType()->equals($other->getType())) { - $type = $this->getType(); - } else { - $type = TypeCombinator::union($this->getType(), $other->getType()); - } - return new self( - $type, - $this->getCertainty()->and($other->getCertainty()) - ); - } + return $this->type->equals($other->type); + } - public function getType(): Type - { - return $this->type; - } + public function and(self $other): self + { + if ($this->getType()->equals($other->getType())) { + $type = $this->getType(); + } else { + $type = TypeCombinator::union($this->getType(), $other->getType()); + } + return new self( + $type, + $this->getCertainty()->and($other->getCertainty()) + ); + } - public function getCertainty(): TrinaryLogic - { - return $this->certainty; - } + public function getType(): Type + { + return $this->type; + } + public function getCertainty(): TrinaryLogic + { + return $this->certainty; + } } diff --git a/src/Broker/AnonymousClassNameHelper.php b/src/Broker/AnonymousClassNameHelper.php index b7040689a2..4ee9c17ae2 100644 --- a/src/Broker/AnonymousClassNameHelper.php +++ b/src/Broker/AnonymousClassNameHelper.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->relativePathHelper = $relativePathHelper; - } - - public function getAnonymousClassName( - \PhpParser\Node\Stmt\Class_ $classNode, - string $filename - ): string - { - if (isset($classNode->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $filename = $this->relativePathHelper->getRelativePath( - $this->fileHelper->normalizePath($filename, '/') - ); - - return sprintf( - 'AnonymousClass%s', - md5(sprintf('%s:%s', $filename, $classNode->getLine())) - ); - } - + private FileHelper $fileHelper; + + private RelativePathHelper $relativePathHelper; + + public function __construct( + FileHelper $fileHelper, + RelativePathHelper $relativePathHelper + ) { + $this->fileHelper = $fileHelper; + $this->relativePathHelper = $relativePathHelper; + } + + public function getAnonymousClassName( + \PhpParser\Node\Stmt\Class_ $classNode, + string $filename + ): string { + if (isset($classNode->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $filename = $this->relativePathHelper->getRelativePath( + $this->fileHelper->normalizePath($filename, '/') + ); + + return sprintf( + 'AnonymousClass%s', + md5(sprintf('%s:%s', $filename, $classNode->getLine())) + ); + } } diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 1eae982839..ecbdcc8d7c 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->dynamicReturnTypeExtensionRegistryProvider = $dynamicReturnTypeExtensionRegistryProvider; - $this->operatorTypeSpecifyingExtensionRegistryProvider = $operatorTypeSpecifyingExtensionRegistryProvider; - $this->universalObjectCratesClasses = $universalObjectCratesClasses; - } - - public static function registerInstance(Broker $reflectionProvider): void - { - self::$instance = $reflectionProvider; - } - - public static function getInstance(): Broker - { - if (self::$instance === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - return self::$instance; - } - - public function hasClass(string $className): bool - { - return $this->reflectionProvider->hasClass($className); - } - - public function getClass(string $className): ClassReflection - { - return $this->reflectionProvider->getClass($className); - } - - public function getClassName(string $className): string - { - return $this->reflectionProvider->getClassName($className); - } - - public function supportsAnonymousClasses(): bool - { - return $this->reflectionProvider->supportsAnonymousClasses(); - } - - public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - return $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope); - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->reflectionProvider->hasFunction($nameNode, $scope); - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection - { - return $this->reflectionProvider->getFunction($nameNode, $scope); - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->reflectionProvider->resolveFunctionName($nameNode, $scope); - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->reflectionProvider->hasConstant($nameNode, $scope); - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - return $this->reflectionProvider->getConstant($nameNode, $scope); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->reflectionProvider->resolveConstantName($nameNode, $scope); - } - - /** - * @return string[] - */ - public function getUniversalObjectCratesClasses(): array - { - return $this->universalObjectCratesClasses; - } - - /** - * @param string $className - * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] - */ - public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicMethodReturnTypeExtensionsForClass($className); - } - - /** - * @param string $className - * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] - */ - public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicStaticMethodReturnTypeExtensionsForClass($className); - } - - /** - * @return OperatorTypeSpecifyingExtension[] - */ - public function getOperatorTypeSpecifyingExtensions(string $operator, Type $leftType, Type $rightType): array - { - return $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->getOperatorTypeSpecifyingExtensions($operator, $leftType, $rightType); - } - - /** - * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicFunctionReturnTypeExtensions(); - } - - /** - * @internal - * @return DynamicReturnTypeExtensionRegistryProvider - */ - public function getDynamicReturnTypeExtensionRegistryProvider(): DynamicReturnTypeExtensionRegistryProvider - { - return $this->dynamicReturnTypeExtensionRegistryProvider; - } - - /** - * @internal - * @return \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider - */ - public function getOperatorTypeSpecifyingExtensionRegistryProvider(): OperatorTypeSpecifyingExtensionRegistryProvider - { - return $this->operatorTypeSpecifyingExtensionRegistryProvider; - } - + private ReflectionProvider $reflectionProvider; + + private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider; + + private \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider; + + /** @var string[] */ + private array $universalObjectCratesClasses; + + private static ?\PHPStan\Broker\Broker $instance = null; + + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param \PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider + * @param \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider + * @param string[] $universalObjectCratesClasses + */ + public function __construct( + ReflectionProvider $reflectionProvider, + DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, + OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider, + array $universalObjectCratesClasses + ) { + $this->reflectionProvider = $reflectionProvider; + $this->dynamicReturnTypeExtensionRegistryProvider = $dynamicReturnTypeExtensionRegistryProvider; + $this->operatorTypeSpecifyingExtensionRegistryProvider = $operatorTypeSpecifyingExtensionRegistryProvider; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; + } + + public static function registerInstance(Broker $reflectionProvider): void + { + self::$instance = $reflectionProvider; + } + + public static function getInstance(): Broker + { + if (self::$instance === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + return self::$instance; + } + + public function hasClass(string $className): bool + { + return $this->reflectionProvider->hasClass($className); + } + + public function getClass(string $className): ClassReflection + { + return $this->reflectionProvider->getClass($className); + } + + public function getClassName(string $className): string + { + return $this->reflectionProvider->getClassName($className); + } + + public function supportsAnonymousClasses(): bool + { + return $this->reflectionProvider->supportsAnonymousClasses(); + } + + public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection + { + return $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope); + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->reflectionProvider->hasFunction($nameNode, $scope); + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection + { + return $this->reflectionProvider->getFunction($nameNode, $scope); + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->reflectionProvider->resolveFunctionName($nameNode, $scope); + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->reflectionProvider->hasConstant($nameNode, $scope); + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + return $this->reflectionProvider->getConstant($nameNode, $scope); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->reflectionProvider->resolveConstantName($nameNode, $scope); + } + + /** + * @return string[] + */ + public function getUniversalObjectCratesClasses(): array + { + return $this->universalObjectCratesClasses; + } + + /** + * @param string $className + * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] + */ + public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array + { + return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicMethodReturnTypeExtensionsForClass($className); + } + + /** + * @param string $className + * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] + */ + public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array + { + return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicStaticMethodReturnTypeExtensionsForClass($className); + } + + /** + * @return OperatorTypeSpecifyingExtension[] + */ + public function getOperatorTypeSpecifyingExtensions(string $operator, Type $leftType, Type $rightType): array + { + return $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->getOperatorTypeSpecifyingExtensions($operator, $leftType, $rightType); + } + + /** + * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] + */ + public function getDynamicFunctionReturnTypeExtensions(): array + { + return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicFunctionReturnTypeExtensions(); + } + + /** + * @internal + * @return DynamicReturnTypeExtensionRegistryProvider + */ + public function getDynamicReturnTypeExtensionRegistryProvider(): DynamicReturnTypeExtensionRegistryProvider + { + return $this->dynamicReturnTypeExtensionRegistryProvider; + } + + /** + * @internal + * @return \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider + */ + public function getOperatorTypeSpecifyingExtensionRegistryProvider(): OperatorTypeSpecifyingExtensionRegistryProvider + { + return $this->operatorTypeSpecifyingExtensionRegistryProvider; + } } diff --git a/src/Broker/BrokerFactory.php b/src/Broker/BrokerFactory.php index 18eba1875c..6c50c90401 100644 --- a/src/Broker/BrokerFactory.php +++ b/src/Broker/BrokerFactory.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function create(): Broker - { - return new Broker( - $this->container->getByType(ReflectionProvider::class), - $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class), - $this->container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), - $this->container->getParameter('universalObjectCratesClasses') - ); - } - + public const PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.propertiesClassReflectionExtension'; + public const METHODS_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.methodsClassReflectionExtension'; + public const DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicMethodReturnTypeExtension'; + public const DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicStaticMethodReturnTypeExtension'; + public const DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicFunctionReturnTypeExtension'; + public const OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.broker.operatorTypeSpecifyingExtension'; + + private \PHPStan\DependencyInjection\Container $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + public function create(): Broker + { + return new Broker( + $this->container->getByType(ReflectionProvider::class), + $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class), + $this->container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), + $this->container->getParameter('universalObjectCratesClasses') + ); + } } diff --git a/src/Broker/ClassAutoloadingException.php b/src/Broker/ClassAutoloadingException.php index 9898953138..a3c7a514bc 100644 --- a/src/Broker/ClassAutoloadingException.php +++ b/src/Broker/ClassAutoloadingException.php @@ -1,42 +1,41 @@ -getMessage(), - $functionName - ), 0, $previous); - } else { - parent::__construct(sprintf( - 'Class %s not found.', - $functionName - ), 0); - } - - $this->className = $functionName; - } - - public function getClassName(): string - { - return $this->className; - } - - public function getTip(): ?string - { - return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; - } - + private string $className; + + public function __construct( + string $functionName, + ?\Throwable $previous = null + ) { + if ($previous !== null) { + parent::__construct(sprintf( + '%s (%s) thrown while looking for class %s.', + get_class($previous), + $previous->getMessage(), + $functionName + ), 0, $previous); + } else { + parent::__construct(sprintf( + 'Class %s not found.', + $functionName + ), 0); + } + + $this->className = $functionName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getTip(): ?string + { + return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + } } diff --git a/src/Broker/ClassNotFoundException.php b/src/Broker/ClassNotFoundException.php index 1afb8d7458..09ab429cee 100644 --- a/src/Broker/ClassNotFoundException.php +++ b/src/Broker/ClassNotFoundException.php @@ -1,26 +1,26 @@ -className = $functionName; - } - - public function getClassName(): string - { - return $this->className; - } + public function __construct(string $functionName) + { + parent::__construct(sprintf('Class %s was not found while trying to analyse it - discovering symbols is probably not configured properly.', $functionName)); + $this->className = $functionName; + } - public function getTip(): ?string - { - return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; - } + public function getClassName(): string + { + return $this->className; + } + public function getTip(): ?string + { + return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + } } diff --git a/src/Broker/ConstantNotFoundException.php b/src/Broker/ConstantNotFoundException.php index 25a3f7b775..610970b968 100644 --- a/src/Broker/ConstantNotFoundException.php +++ b/src/Broker/ConstantNotFoundException.php @@ -1,26 +1,26 @@ -constantName = $constantName; - } - - public function getConstantName(): string - { - return $this->constantName; - } + public function __construct(string $constantName) + { + parent::__construct(sprintf('Constant %s not found.', $constantName)); + $this->constantName = $constantName; + } - public function getTip(): ?string - { - return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; - } + public function getConstantName(): string + { + return $this->constantName; + } + public function getTip(): ?string + { + return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + } } diff --git a/src/Broker/FunctionNotFoundException.php b/src/Broker/FunctionNotFoundException.php index f3966b1ef8..7062a72a69 100644 --- a/src/Broker/FunctionNotFoundException.php +++ b/src/Broker/FunctionNotFoundException.php @@ -1,26 +1,26 @@ -functionName = $functionName; - } - - public function getFunctionName(): string - { - return $this->functionName; - } + public function __construct(string $functionName) + { + parent::__construct(sprintf('Function %s not found while trying to analyse it - discovering symbols is probably not configured properly.', $functionName)); + $this->functionName = $functionName; + } - public function getTip(): ?string - { - return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; - } + public function getFunctionName(): string + { + return $this->functionName; + } + public function getTip(): ?string + { + return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + } } diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index 1447662c58..23998179c8 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -1,35 +1,35 @@ -storage = $storage; - } - - /** - * @param string $key - * @return mixed|null - */ - public function load(string $key, string $variableKey) - { - return $this->storage->load($key, $variableKey); - } + public function __construct(CacheStorage $storage) + { + $this->storage = $storage; + } - /** - * @param string $key - * @param string $variableKey - * @param mixed $data - * @return void - */ - public function save(string $key, string $variableKey, $data): void - { - $this->storage->save($key, $variableKey, $data); - } + /** + * @param string $key + * @return mixed|null + */ + public function load(string $key, string $variableKey) + { + return $this->storage->load($key, $variableKey); + } + /** + * @param string $key + * @param string $variableKey + * @param mixed $data + * @return void + */ + public function save(string $key, string $variableKey, $data): void + { + $this->storage->save($key, $variableKey, $data); + } } diff --git a/src/Cache/CacheItem.php b/src/Cache/CacheItem.php index 17114f5fc5..a1d2dd3a9c 100644 --- a/src/Cache/CacheItem.php +++ b/src/Cache/CacheItem.php @@ -1,45 +1,45 @@ -variableKey = $variableKey; - $this->data = $data; - } - - public function isVariableKeyValid(string $variableKey): bool - { - return $this->variableKey === $variableKey; - } - - /** - * @return mixed - */ - public function getData() - { - return $this->data; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self($properties['variableKey'], $properties['data']); - } - + private string $variableKey; + + /** @var mixed */ + private $data; + + /** + * @param string $variableKey + * @param mixed $data + */ + public function __construct(string $variableKey, $data) + { + $this->variableKey = $variableKey; + $this->data = $data; + } + + public function isVariableKeyValid(string $variableKey): bool + { + return $this->variableKey === $variableKey; + } + + /** + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self($properties['variableKey'], $properties['data']); + } } diff --git a/src/Cache/CacheStorage.php b/src/Cache/CacheStorage.php index a9227b0eca..e418fb52ed 100644 --- a/src/Cache/CacheStorage.php +++ b/src/Cache/CacheStorage.php @@ -1,23 +1,23 @@ -directory = $directory; - } - - private function makeDir(string $directory): void - { - if (is_dir($directory)) { - return; - } - - $result = @mkdir($directory, 0777); - if ($result === false) { - clearstatcache(); - if (is_dir($directory)) { - return; - } - - $error = error_get_last(); - throw new \InvalidArgumentException(sprintf('Failed to create directory "%s" (%s).', $this->directory, $error !== null ? $error['message'] : 'unknown cause')); - } - } - - /** - * @param string $key - * @param string $variableKey - * @return mixed|null - */ - public function load(string $key, string $variableKey) - { - return (function (string $key, string $variableKey) { - [,, $filePath] = $this->getFilePaths($key); - if (!is_file($filePath)) { - return null; - } - - $cacheItem = require $filePath; - if (!$cacheItem instanceof CacheItem) { - return null; - } - if (!$cacheItem->isVariableKeyValid($variableKey)) { - return null; - } - - return $cacheItem->getData(); - })($key, $variableKey); - } - - /** - * @param string $key - * @param string $variableKey - * @param mixed $data - * @return void - */ - public function save(string $key, string $variableKey, $data): void - { - [$firstDirectory, $secondDirectory, $path] = $this->getFilePaths($key); - $this->makeDir($this->directory); - $this->makeDir($firstDirectory); - $this->makeDir($secondDirectory); - - $tmpPath = sprintf('%s/%s.tmp', $this->directory, Random::generate()); - $errorBefore = error_get_last(); - $exported = @var_export(new CacheItem($variableKey, $data), true); - $errorAfter = error_get_last(); - if ($errorAfter !== null && $errorBefore !== $errorAfter) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Error occurred while saving item %s (%s) to cache: %s', $key, $variableKey, $errorAfter['message'])); - } - FileWriter::write( - $tmpPath, - sprintf( - "directory, substr($keyHash, 0, 2)); - $secondDirectory = sprintf('%s/%s', $firstDirectory, substr($keyHash, 2, 2)); - $filePath = sprintf('%s/%s.php', $secondDirectory, $keyHash); - - return [ - $firstDirectory, - $secondDirectory, - $filePath, - ]; - } - + private string $directory; + + public function __construct(string $directory) + { + $this->directory = $directory; + } + + private function makeDir(string $directory): void + { + if (is_dir($directory)) { + return; + } + + $result = @mkdir($directory, 0777); + if ($result === false) { + clearstatcache(); + if (is_dir($directory)) { + return; + } + + $error = error_get_last(); + throw new \InvalidArgumentException(sprintf('Failed to create directory "%s" (%s).', $this->directory, $error !== null ? $error['message'] : 'unknown cause')); + } + } + + /** + * @param string $key + * @param string $variableKey + * @return mixed|null + */ + public function load(string $key, string $variableKey) + { + return (function (string $key, string $variableKey) { + [,, $filePath] = $this->getFilePaths($key); + if (!is_file($filePath)) { + return null; + } + + $cacheItem = require $filePath; + if (!$cacheItem instanceof CacheItem) { + return null; + } + if (!$cacheItem->isVariableKeyValid($variableKey)) { + return null; + } + + return $cacheItem->getData(); + })($key, $variableKey); + } + + /** + * @param string $key + * @param string $variableKey + * @param mixed $data + * @return void + */ + public function save(string $key, string $variableKey, $data): void + { + [$firstDirectory, $secondDirectory, $path] = $this->getFilePaths($key); + $this->makeDir($this->directory); + $this->makeDir($firstDirectory); + $this->makeDir($secondDirectory); + + $tmpPath = sprintf('%s/%s.tmp', $this->directory, Random::generate()); + $errorBefore = error_get_last(); + $exported = @var_export(new CacheItem($variableKey, $data), true); + $errorAfter = error_get_last(); + if ($errorAfter !== null && $errorBefore !== $errorAfter) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Error occurred while saving item %s (%s) to cache: %s', $key, $variableKey, $errorAfter['message'])); + } + FileWriter::write( + $tmpPath, + sprintf( + "directory, substr($keyHash, 0, 2)); + $secondDirectory = sprintf('%s/%s', $firstDirectory, substr($keyHash, 2, 2)); + $filePath = sprintf('%s/%s.php', $secondDirectory, $keyHash); + + return [ + $firstDirectory, + $secondDirectory, + $filePath, + ]; + } } diff --git a/src/Cache/MemoryCacheStorage.php b/src/Cache/MemoryCacheStorage.php index 1723cd1f74..ffaac90027 100644 --- a/src/Cache/MemoryCacheStorage.php +++ b/src/Cache/MemoryCacheStorage.php @@ -1,41 +1,41 @@ - */ - private array $storage = []; - - /** - * @param string $key - * @param string $variableKey - * @return mixed|null - */ - public function load(string $key, string $variableKey) - { - if (!isset($this->storage[$key])) { - return null; - } - - $item = $this->storage[$key]; - if (!$item->isVariableKeyValid($variableKey)) { - return null; - } - - return $item->getData(); - } - - /** - * @param string $key - * @param string $variableKey - * @param mixed $data - * @return void - */ - public function save(string $key, string $variableKey, $data): void - { - $this->storage[$key] = new CacheItem($variableKey, $data); - } - + /** @var array */ + private array $storage = []; + + /** + * @param string $key + * @param string $variableKey + * @return mixed|null + */ + public function load(string $key, string $variableKey) + { + if (!isset($this->storage[$key])) { + return null; + } + + $item = $this->storage[$key]; + if (!$item->isVariableKeyValid($variableKey)) { + return null; + } + + return $item->getData(); + } + + /** + * @param string $key + * @param string $variableKey + * @param mixed $data + * @return void + */ + public function save(string $key, string $variableKey, $data): void + { + $this->storage[$key] = new CacheItem($variableKey, $data); + } } diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 4d1656b0c6..376c7a3c30 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -1,4 +1,6 @@ -analyserRunner = $analyserRunner; - $this->stubValidator = $stubValidator; - $this->resultCacheManagerFactory = $resultCacheManagerFactory; - $this->ignoredErrorHelper = $ignoredErrorHelper; - $this->memoryLimitFile = $memoryLimitFile; - $this->internalErrorsCountLimit = $internalErrorsCountLimit; - } - - /** - * @param string[] $files - * @param bool $onlyFiles - * @param \PHPStan\Command\Output $stdOutput - * @param \PHPStan\Command\Output $errorOutput - * @param bool $defaultLevelUsed - * @param bool $debug - * @param string|null $projectConfigFile - * @param mixed[]|null $projectConfigArray - * @return AnalysisResult - */ - public function analyse( - array $files, - bool $onlyFiles, - Output $stdOutput, - Output $errorOutput, - bool $defaultLevelUsed, - bool $debug, - ?string $projectConfigFile, - ?array $projectConfigArray, - InputInterface $input - ): AnalysisResult - { - $this->updateMemoryLimitFile(); - $projectStubFiles = []; - if ($projectConfigArray !== null) { - $projectStubFiles = $projectConfigArray['parameters']['stubFiles'] ?? []; - } - $stubErrors = $this->stubValidator->validate($projectStubFiles, $debug); - - register_shutdown_function(function (): void { - $error = error_get_last(); - if ($error === null) { - return; - } - if ($error['type'] !== E_ERROR) { - return; - } - - if (strpos($error['message'], 'Allowed memory size') !== false) { - return; - } - - @unlink($this->memoryLimitFile); - }); - - $resultCacheManager = $this->resultCacheManagerFactory->create([]); - - $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); - if (count($ignoredErrorHelperResult->getErrors()) > 0) { - $errors = $ignoredErrorHelperResult->getErrors(); - $warnings = []; - $internalErrors = []; - $savedResultCache = false; - if ($errorOutput->isDebug()) { - $errorOutput->writeLineFormatted('Result cache was not saved because of ignoredErrorHelperResult errors.'); - } - } else { - $resultCache = $resultCacheManager->restore($files, $debug, $onlyFiles, $projectConfigArray, $errorOutput); - $intermediateAnalyserResult = $this->runAnalyser( - $resultCache->getFilesToAnalyse(), - $files, - $debug, - $projectConfigFile, - $stdOutput, - $errorOutput, - $input - ); - $resultCacheResult = $resultCacheManager->process($intermediateAnalyserResult, $resultCache, $errorOutput, $onlyFiles, true); - $analyserResult = $resultCacheResult->getAnalyserResult(); - $internalErrors = $analyserResult->getInternalErrors(); - $errors = $ignoredErrorHelperResult->process($analyserResult->getErrors(), $onlyFiles, $files, count($internalErrors) > 0 || $analyserResult->hasReachedInternalErrorsCountLimit()); - $warnings = $ignoredErrorHelperResult->getWarnings(); - $savedResultCache = $resultCacheResult->isSaved(); - if ($analyserResult->hasReachedInternalErrorsCountLimit()) { - $errors[] = sprintf('Reached internal errors count limit of %d, exiting...', $this->internalErrorsCountLimit); - } - $errors = array_merge($errors, $internalErrors); - } - - $errors = array_merge($stubErrors, $errors); - - $fileSpecificErrors = []; - $notFileSpecificErrors = []; - foreach ($errors as $error) { - if (is_string($error)) { - $notFileSpecificErrors[] = $error; - continue; - } - - $fileSpecificErrors[] = $error; - } - - return new AnalysisResult( - $fileSpecificErrors, - $notFileSpecificErrors, - $internalErrors, - $warnings, - $defaultLevelUsed, - $projectConfigFile, - $savedResultCache - ); - } - - /** - * @param string[] $files - * @param string[] $allAnalysedFiles - */ - private function runAnalyser( - array $files, - array $allAnalysedFiles, - bool $debug, - ?string $projectConfigFile, - Output $stdOutput, - Output $errorOutput, - InputInterface $input - ): AnalyserResult - { - $filesCount = count($files); - $allAnalysedFilesCount = count($allAnalysedFiles); - if ($filesCount === 0) { - $errorOutput->getStyle()->progressStart($allAnalysedFilesCount); - $errorOutput->getStyle()->progressAdvance($allAnalysedFilesCount); - $errorOutput->getStyle()->progressFinish(); - return new AnalyserResult([], [], [], [], false); - } - - if (!$debug) { - $progressStarted = false; - $fileOrder = 0; - $preFileCallback = null; - $postFileCallback = function (int $step) use ($errorOutput, &$progressStarted, $allAnalysedFilesCount, $filesCount, &$fileOrder): void { - if (!$progressStarted) { - $errorOutput->getStyle()->progressStart($allAnalysedFilesCount); - $errorOutput->getStyle()->progressAdvance($allAnalysedFilesCount - $filesCount); - $progressStarted = true; - } - $errorOutput->getStyle()->progressAdvance($step); - - if ($fileOrder >= 100) { - $this->updateMemoryLimitFile(); - $fileOrder = 0; - } - $fileOrder += $step; - }; - } else { - $preFileCallback = static function (string $file) use ($stdOutput): void { - $stdOutput->writeLineFormatted($file); - }; - $postFileCallback = null; - if ($stdOutput->isDebug()) { - $previousMemory = memory_get_peak_usage(true); - $postFileCallback = static function () use ($stdOutput, &$previousMemory): void { - $currentTotalMemory = memory_get_peak_usage(true); - $stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory))); - $previousMemory = $currentTotalMemory; - }; - } - } - - $analyserResult = $this->analyserRunner->runAnalyser($files, $allAnalysedFiles, $preFileCallback, $postFileCallback, $debug, true, $projectConfigFile, null, null, $input); - - if (isset($progressStarted) && $progressStarted) { - $errorOutput->getStyle()->progressFinish(); - } - - return $analyserResult; - } - - private function updateMemoryLimitFile(): void - { - $bytes = memory_get_peak_usage(true); - $megabytes = ceil($bytes / 1024 / 1024); - file_put_contents($this->memoryLimitFile, sprintf('%d MB', $megabytes)); - } - + private AnalyserRunner $analyserRunner; + + private \PHPStan\PhpDoc\StubValidator $stubValidator; + + private \PHPStan\Analyser\ResultCache\ResultCacheManagerFactory $resultCacheManagerFactory; + + private IgnoredErrorHelper $ignoredErrorHelper; + + private string $memoryLimitFile; + + private int $internalErrorsCountLimit; + + public function __construct( + AnalyserRunner $analyserRunner, + StubValidator $stubValidator, + ResultCacheManagerFactory $resultCacheManagerFactory, + IgnoredErrorHelper $ignoredErrorHelper, + string $memoryLimitFile, + int $internalErrorsCountLimit + ) { + $this->analyserRunner = $analyserRunner; + $this->stubValidator = $stubValidator; + $this->resultCacheManagerFactory = $resultCacheManagerFactory; + $this->ignoredErrorHelper = $ignoredErrorHelper; + $this->memoryLimitFile = $memoryLimitFile; + $this->internalErrorsCountLimit = $internalErrorsCountLimit; + } + + /** + * @param string[] $files + * @param bool $onlyFiles + * @param \PHPStan\Command\Output $stdOutput + * @param \PHPStan\Command\Output $errorOutput + * @param bool $defaultLevelUsed + * @param bool $debug + * @param string|null $projectConfigFile + * @param mixed[]|null $projectConfigArray + * @return AnalysisResult + */ + public function analyse( + array $files, + bool $onlyFiles, + Output $stdOutput, + Output $errorOutput, + bool $defaultLevelUsed, + bool $debug, + ?string $projectConfigFile, + ?array $projectConfigArray, + InputInterface $input + ): AnalysisResult { + $this->updateMemoryLimitFile(); + $projectStubFiles = []; + if ($projectConfigArray !== null) { + $projectStubFiles = $projectConfigArray['parameters']['stubFiles'] ?? []; + } + $stubErrors = $this->stubValidator->validate($projectStubFiles, $debug); + + register_shutdown_function(function (): void { + $error = error_get_last(); + if ($error === null) { + return; + } + if ($error['type'] !== E_ERROR) { + return; + } + + if (strpos($error['message'], 'Allowed memory size') !== false) { + return; + } + + @unlink($this->memoryLimitFile); + }); + + $resultCacheManager = $this->resultCacheManagerFactory->create([]); + + $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); + if (count($ignoredErrorHelperResult->getErrors()) > 0) { + $errors = $ignoredErrorHelperResult->getErrors(); + $warnings = []; + $internalErrors = []; + $savedResultCache = false; + if ($errorOutput->isDebug()) { + $errorOutput->writeLineFormatted('Result cache was not saved because of ignoredErrorHelperResult errors.'); + } + } else { + $resultCache = $resultCacheManager->restore($files, $debug, $onlyFiles, $projectConfigArray, $errorOutput); + $intermediateAnalyserResult = $this->runAnalyser( + $resultCache->getFilesToAnalyse(), + $files, + $debug, + $projectConfigFile, + $stdOutput, + $errorOutput, + $input + ); + $resultCacheResult = $resultCacheManager->process($intermediateAnalyserResult, $resultCache, $errorOutput, $onlyFiles, true); + $analyserResult = $resultCacheResult->getAnalyserResult(); + $internalErrors = $analyserResult->getInternalErrors(); + $errors = $ignoredErrorHelperResult->process($analyserResult->getErrors(), $onlyFiles, $files, count($internalErrors) > 0 || $analyserResult->hasReachedInternalErrorsCountLimit()); + $warnings = $ignoredErrorHelperResult->getWarnings(); + $savedResultCache = $resultCacheResult->isSaved(); + if ($analyserResult->hasReachedInternalErrorsCountLimit()) { + $errors[] = sprintf('Reached internal errors count limit of %d, exiting...', $this->internalErrorsCountLimit); + } + $errors = array_merge($errors, $internalErrors); + } + + $errors = array_merge($stubErrors, $errors); + + $fileSpecificErrors = []; + $notFileSpecificErrors = []; + foreach ($errors as $error) { + if (is_string($error)) { + $notFileSpecificErrors[] = $error; + continue; + } + + $fileSpecificErrors[] = $error; + } + + return new AnalysisResult( + $fileSpecificErrors, + $notFileSpecificErrors, + $internalErrors, + $warnings, + $defaultLevelUsed, + $projectConfigFile, + $savedResultCache + ); + } + + /** + * @param string[] $files + * @param string[] $allAnalysedFiles + */ + private function runAnalyser( + array $files, + array $allAnalysedFiles, + bool $debug, + ?string $projectConfigFile, + Output $stdOutput, + Output $errorOutput, + InputInterface $input + ): AnalyserResult { + $filesCount = count($files); + $allAnalysedFilesCount = count($allAnalysedFiles); + if ($filesCount === 0) { + $errorOutput->getStyle()->progressStart($allAnalysedFilesCount); + $errorOutput->getStyle()->progressAdvance($allAnalysedFilesCount); + $errorOutput->getStyle()->progressFinish(); + return new AnalyserResult([], [], [], [], false); + } + + if (!$debug) { + $progressStarted = false; + $fileOrder = 0; + $preFileCallback = null; + $postFileCallback = function (int $step) use ($errorOutput, &$progressStarted, $allAnalysedFilesCount, $filesCount, &$fileOrder): void { + if (!$progressStarted) { + $errorOutput->getStyle()->progressStart($allAnalysedFilesCount); + $errorOutput->getStyle()->progressAdvance($allAnalysedFilesCount - $filesCount); + $progressStarted = true; + } + $errorOutput->getStyle()->progressAdvance($step); + + if ($fileOrder >= 100) { + $this->updateMemoryLimitFile(); + $fileOrder = 0; + } + $fileOrder += $step; + }; + } else { + $preFileCallback = static function (string $file) use ($stdOutput): void { + $stdOutput->writeLineFormatted($file); + }; + $postFileCallback = null; + if ($stdOutput->isDebug()) { + $previousMemory = memory_get_peak_usage(true); + $postFileCallback = static function () use ($stdOutput, &$previousMemory): void { + $currentTotalMemory = memory_get_peak_usage(true); + $stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory))); + $previousMemory = $currentTotalMemory; + }; + } + } + + $analyserResult = $this->analyserRunner->runAnalyser($files, $allAnalysedFiles, $preFileCallback, $postFileCallback, $debug, true, $projectConfigFile, null, null, $input); + + if (isset($progressStarted) && $progressStarted) { + $errorOutput->getStyle()->progressFinish(); + } + + return $analyserResult; + } + + private function updateMemoryLimitFile(): void + { + $bytes = memory_get_peak_usage(true); + $megabytes = ceil($bytes / 1024 / 1024); + file_put_contents($this->memoryLimitFile, sprintf('%d MB', $megabytes)); + } } diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 4a110d3bdc..3d51cf57f1 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -1,4 +1,6 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } - - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('Analyses source code') - ->setDefinition([ - new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption(self::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), - new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), - new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', null), - new InputOption('generate-baseline', null, InputOption::VALUE_OPTIONAL, 'Path to a file where the baseline should be saved', false), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), - new InputOption('fix', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), - new InputOption('watch', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), - new InputOption('pro', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), - ]); - } - - /** - * @return string[] - */ - public function getAliases(): array - { - return ['analyze']; - } - - protected function initialize(InputInterface $input, OutputInterface $output): void - { - if ((bool) $input->getOption('debug')) { - $application = $this->getApplication(); - if ($application === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - $application->setCatchExceptions(false); - return; - } - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $paths = $input->getArgument('paths'); - $memoryLimit = $input->getOption('memory-limit'); - $autoloadFile = $input->getOption('autoload-file'); - $configuration = $input->getOption('configuration'); - $level = $input->getOption(self::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); - $allowXdebug = $input->getOption('xdebug'); - $debugEnabled = (bool) $input->getOption('debug'); - $fix = (bool) $input->getOption('fix') || (bool) $input->getOption('watch') || (bool) $input->getOption('pro'); - - /** @var string|false|null $generateBaselineFile */ - $generateBaselineFile = $input->getOption('generate-baseline'); - if ($generateBaselineFile === false) { - $generateBaselineFile = null; - } elseif ($generateBaselineFile === null) { - $generateBaselineFile = 'phpstan-baseline.neon'; - } - - if ( - !is_array($paths) - || (!is_string($memoryLimit) && $memoryLimit !== null) - || (!is_string($autoloadFile) && $autoloadFile !== null) - || (!is_string($configuration) && $configuration !== null) - || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) - || (!is_bool($allowXdebug)) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } - - try { - $inceptionResult = CommandHelper::begin( - $input, - $output, - $paths, - $pathsFile, - $memoryLimit, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configuration, - $generateBaselineFile, - $level, - $allowXdebug, - true, - $debugEnabled - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } - - $errorOutput = $inceptionResult->getErrorOutput(); - $obsoleteDockerImage = $_SERVER['PHPSTAN_OBSOLETE_DOCKER_IMAGE'] ?? 'false'; - if ($obsoleteDockerImage === 'true') { - $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete PHPStan Docker image. ⚠️️'); - $errorOutput->writeLineFormatted(' You can obtain the current one from ghcr.io/phpstan/phpstan.'); - $errorOutput->writeLineFormatted(' Read more about it here:'); - $errorOutput->writeLineFormatted(' https://phpstan.org/user-guide/docker'); - $errorOutput->writeLineFormatted(''); - } - - $errorFormat = $input->getOption('error-format'); - - if (!is_string($errorFormat) && $errorFormat !== null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($errorFormat === null) { - $errorFormat = 'table'; - $ciDetector = new CiDetector(); - - try { - $ci = $ciDetector->detect(); - if ($ci->getCiName() === CiDetector::CI_GITHUB_ACTIONS) { - $errorFormat = 'github'; - } elseif ($ci->getCiName() === CiDetector::CI_TEAMCITY) { - $errorFormat = 'teamcity'; - } - } catch (\OndraM\CiDetector\Exception\CiNotDetectedException $e) { - // pass - } - } - - $container = $inceptionResult->getContainer(); - $errorFormatterServiceName = sprintf('errorFormatter.%s', $errorFormat); - if (!$container->hasService($errorFormatterServiceName)) { - $errorOutput->writeLineFormatted(sprintf( - 'Error formatter "%s" not found. Available error formatters are: %s', - $errorFormat, - implode(', ', array_map(static function (string $name): string { - return substr($name, strlen('errorFormatter.')); - }, $container->findServiceNamesByType(ErrorFormatter::class))) - )); - return 1; - } - - if ($errorFormat === 'baselineNeon') { - $errorOutput = $inceptionResult->getErrorOutput(); - $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete option --error-format baselineNeon. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(' There\'s a new and much better option --generate-baseline. Here are the advantages:'); - $errorOutput->writeLineFormatted(' 1) The current baseline file does not have to be commented-out'); - $errorOutput->writeLineFormatted(' nor emptied when generating the new baseline. It\'s excluded automatically.'); - $errorOutput->writeLineFormatted(' 2) Output no longer has to be redirected to a file, PHPStan saves the baseline'); - $errorOutput->writeLineFormatted(' to a specified path (defaults to phpstan-baseline.neon).'); - $errorOutput->writeLineFormatted(' 3) Baseline contains correct relative paths if saved to a subdirectory.'); - $errorOutput->writeLineFormatted(''); - } - - $generateBaselineFile = $inceptionResult->getGenerateBaselineFile(); - if ($generateBaselineFile !== null) { - $baselineExtension = pathinfo($generateBaselineFile, PATHINFO_EXTENSION); - if ($baselineExtension === '') { - $inceptionResult->getStdOutput()->getStyle()->error(sprintf('Baseline filename must have an extension, %s provided instead.', pathinfo($generateBaselineFile, PATHINFO_BASENAME))); - return $inceptionResult->handleReturn(1); - } - - if ($baselineExtension !== 'neon') { - $inceptionResult->getStdOutput()->getStyle()->error(sprintf('Baseline filename extension must be .neon, .%s was used instead.', $baselineExtension)); - - return $inceptionResult->handleReturn(1); - } - } - - try { - [$files, $onlyFiles] = $inceptionResult->getFiles(); - } catch (\PHPStan\File\PathNotFoundException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } - - /** @var AnalyseApplication $application */ - $application = $container->getByType(AnalyseApplication::class); - - $debug = $input->getOption('debug'); - if (!is_bool($debug)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $analysisResult = $application->analyse( - $files, - $onlyFiles, - $inceptionResult->getStdOutput(), - $inceptionResult->getErrorOutput(), - $inceptionResult->isDefaultLevelUsed(), - $debug, - $inceptionResult->getProjectConfigFile(), - $inceptionResult->getProjectConfigArray(), - $input - ); - - if ($generateBaselineFile !== null) { - if (!$analysisResult->hasErrors()) { - $inceptionResult->getStdOutput()->getStyle()->error('No errors were found during the analysis. Baseline could not be generated.'); - - return $inceptionResult->handleReturn(1); - } - if ($analysisResult->hasInternalErrors()) { - $inceptionResult->getStdOutput()->getStyle()->error('An internal error occurred. Baseline could not be generated. Re-run PHPStan without --generate-baseline to see what\'s going on.'); - - return $inceptionResult->handleReturn(1); - } - - $baselineFileDirectory = dirname($generateBaselineFile); - $baselineErrorFormatter = new BaselineNeonErrorFormatter(new ParentDirectoryRelativePathHelper($baselineFileDirectory)); - - $streamOutput = $this->createStreamOutput(); - $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $streamOutput); - $baselineOutput = new SymfonyOutput($streamOutput, new SymfonyStyle($errorConsoleStyle)); - $baselineErrorFormatter->formatErrors($analysisResult, $baselineOutput); - - $stream = $streamOutput->getStream(); - rewind($stream); - $baselineContents = stream_get_contents($stream); - if ($baselineContents === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!is_dir($baselineFileDirectory)) { - $mkdirResult = @mkdir($baselineFileDirectory, 0644, true); - if ($mkdirResult === false) { - $inceptionResult->getStdOutput()->writeLineFormatted(sprintf('Failed to create directory "%s".', $baselineFileDirectory)); - - return $inceptionResult->handleReturn(1); - } - } - - try { - FileWriter::write($generateBaselineFile, $baselineContents); - } catch (\PHPStan\File\CouldNotWriteFileException $e) { - $inceptionResult->getStdOutput()->writeLineFormatted($e->getMessage()); - - return $inceptionResult->handleReturn(1); - } - - $errorsCount = 0; - $unignorableCount = 0; - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - if (!$fileSpecificError->canBeIgnored()) { - $unignorableCount++; - if ($output->isVeryVerbose()) { - $inceptionResult->getStdOutput()->writeLineFormatted('Unignorable could not be added to the baseline:'); - $inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getMessage()); - $inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getFile()); - $inceptionResult->getStdOutput()->writeLineFormatted(''); - } - continue; - } - - $errorsCount++; - } - - $message = sprintf('Baseline generated with %d %s.', $errorsCount, $errorsCount === 1 ? 'error' : 'errors'); - - if ( - $unignorableCount === 0 - && count($analysisResult->getNotFileSpecificErrors()) === 0 - ) { - $inceptionResult->getStdOutput()->getStyle()->success($message); - } else { - $inceptionResult->getStdOutput()->getStyle()->warning($message . "\nSome errors could not be put into baseline. Re-run PHPStan and fix them."); - } - - return $inceptionResult->handleReturn(0); - } - - if ($fix) { - $ciDetector = new CiDetector(); - if ($ciDetector->isCiDetected()) { - $inceptionResult->getStdOutput()->writeLineFormatted('PHPStan Pro can\'t run in CI environment yet. Stay tuned!'); - - return $inceptionResult->handleReturn(1); - } - $container->getByType(ResultCacheClearer::class)->clearTemporaryCaches(); - $hasInternalErrors = $analysisResult->hasInternalErrors(); - $nonIgnorableErrorsByException = []; - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - if (!$fileSpecificError->hasNonIgnorableException()) { - continue; - } - - $nonIgnorableErrorsByException[] = $fileSpecificError; - } - - if ($hasInternalErrors || count($nonIgnorableErrorsByException) > 0) { - $fixerAnalysisResult = new AnalysisResult( - $nonIgnorableErrorsByException, - $analysisResult->getInternalErrors(), - $analysisResult->getInternalErrors(), - [], - $analysisResult->isDefaultLevelUsed(), - $analysisResult->getProjectConfigFile(), - $analysisResult->isResultCacheSaved() - ); - - $stdOutput = $inceptionResult->getStdOutput(); - $stdOutput->getStyle()->error('PHPStan Pro can\'t be launched because of these errors:'); - - /** @var TableErrorFormatter $tableErrorFormatter */ - $tableErrorFormatter = $container->getService('errorFormatter.table'); - $tableErrorFormatter->formatErrors($fixerAnalysisResult, $stdOutput); - - $stdOutput->writeLineFormatted('Please fix them first and then re-run PHPStan.'); - - if ($stdOutput->isDebug()) { - $stdOutput->writeLineFormatted(sprintf('hasInternalErrors: %s', $hasInternalErrors ? 'true' : 'false')); - $stdOutput->writeLineFormatted(sprintf('nonIgnorableErrorsByExceptionCount: %d', count($nonIgnorableErrorsByException))); - } - - return $inceptionResult->handleReturn(1); - } - - if (!$analysisResult->isResultCacheSaved() && !$onlyFiles) { - // this can happen only if there are some regex-related errors in ignoreErrors configuration - $stdOutput = $inceptionResult->getStdOutput(); - if (count($analysisResult->getFileSpecificErrors()) > 0) { - $stdOutput->getStyle()->error('Unknown error. Please report this as a bug.'); - return $inceptionResult->handleReturn(1); - } - - $stdOutput->getStyle()->error('PHPStan Pro can\'t be launched because of these errors:'); - - /** @var TableErrorFormatter $tableErrorFormatter */ - $tableErrorFormatter = $container->getService('errorFormatter.table'); - $tableErrorFormatter->formatErrors($analysisResult, $stdOutput); - - $stdOutput->writeLineFormatted('Please fix them first and then re-run PHPStan.'); - - if ($stdOutput->isDebug()) { - $stdOutput->writeLineFormatted('Result cache was not saved.'); - } - - return $inceptionResult->handleReturn(1); - } - - $inceptionResult->handleReturn(0); // delete memory limit file - - /** @var FixerApplication $fixerApplication */ - $fixerApplication = $container->getByType(FixerApplication::class); - - return $fixerApplication->run( - $inceptionResult->getProjectConfigFile(), - $inceptionResult, - $input, - $output, - $analysisResult->getFileSpecificErrors(), - $analysisResult->getNotFileSpecificErrors(), - count($files), - $_SERVER['argv'][0] - ); - } - - /** @var ErrorFormatter $errorFormatter */ - $errorFormatter = $container->getService($errorFormatterServiceName); - - return $inceptionResult->handleReturn( - $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()) - ); - } - - private function createStreamOutput(): StreamOutput - { - $resource = fopen('php://memory', 'w', false); - if ($resource === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - return new StreamOutput($resource); - } - + private const NAME = 'analyse'; + + public const OPTION_LEVEL = 'level'; + + public const DEFAULT_LEVEL = CommandHelper::DEFAULT_LEVEL; + + /** @var string[] */ + private array $composerAutoloaderProjectPaths; + + /** + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + array $composerAutoloaderProjectPaths + ) { + parent::__construct(); + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } + + protected function configure(): void + { + $this->setName(self::NAME) + ->setDescription('Analyses source code') + ->setDefinition([ + new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), + new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), + new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), + new InputOption(self::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), + new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), + new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'), + new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), + new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', null), + new InputOption('generate-baseline', null, InputOption::VALUE_OPTIONAL, 'Path to a file where the baseline should be saved', false), + new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), + new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), + new InputOption('fix', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), + new InputOption('watch', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), + new InputOption('pro', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), + ]); + } + + /** + * @return string[] + */ + public function getAliases(): array + { + return ['analyze']; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + if ((bool) $input->getOption('debug')) { + $application = $this->getApplication(); + if ($application === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + $application->setCatchExceptions(false); + return; + } + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $paths = $input->getArgument('paths'); + $memoryLimit = $input->getOption('memory-limit'); + $autoloadFile = $input->getOption('autoload-file'); + $configuration = $input->getOption('configuration'); + $level = $input->getOption(self::OPTION_LEVEL); + $pathsFile = $input->getOption('paths-file'); + $allowXdebug = $input->getOption('xdebug'); + $debugEnabled = (bool) $input->getOption('debug'); + $fix = (bool) $input->getOption('fix') || (bool) $input->getOption('watch') || (bool) $input->getOption('pro'); + + /** @var string|false|null $generateBaselineFile */ + $generateBaselineFile = $input->getOption('generate-baseline'); + if ($generateBaselineFile === false) { + $generateBaselineFile = null; + } elseif ($generateBaselineFile === null) { + $generateBaselineFile = 'phpstan-baseline.neon'; + } + + if ( + !is_array($paths) + || (!is_string($memoryLimit) && $memoryLimit !== null) + || (!is_string($autoloadFile) && $autoloadFile !== null) + || (!is_string($configuration) && $configuration !== null) + || (!is_string($level) && $level !== null) + || (!is_string($pathsFile) && $pathsFile !== null) + || (!is_bool($allowXdebug)) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } + + try { + $inceptionResult = CommandHelper::begin( + $input, + $output, + $paths, + $pathsFile, + $memoryLimit, + $autoloadFile, + $this->composerAutoloaderProjectPaths, + $configuration, + $generateBaselineFile, + $level, + $allowXdebug, + true, + $debugEnabled + ); + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + return 1; + } + + $errorOutput = $inceptionResult->getErrorOutput(); + $obsoleteDockerImage = $_SERVER['PHPSTAN_OBSOLETE_DOCKER_IMAGE'] ?? 'false'; + if ($obsoleteDockerImage === 'true') { + $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete PHPStan Docker image. ⚠️️'); + $errorOutput->writeLineFormatted(' You can obtain the current one from ghcr.io/phpstan/phpstan.'); + $errorOutput->writeLineFormatted(' Read more about it here:'); + $errorOutput->writeLineFormatted(' https://phpstan.org/user-guide/docker'); + $errorOutput->writeLineFormatted(''); + } + + $errorFormat = $input->getOption('error-format'); + + if (!is_string($errorFormat) && $errorFormat !== null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($errorFormat === null) { + $errorFormat = 'table'; + $ciDetector = new CiDetector(); + + try { + $ci = $ciDetector->detect(); + if ($ci->getCiName() === CiDetector::CI_GITHUB_ACTIONS) { + $errorFormat = 'github'; + } elseif ($ci->getCiName() === CiDetector::CI_TEAMCITY) { + $errorFormat = 'teamcity'; + } + } catch (\OndraM\CiDetector\Exception\CiNotDetectedException $e) { + // pass + } + } + + $container = $inceptionResult->getContainer(); + $errorFormatterServiceName = sprintf('errorFormatter.%s', $errorFormat); + if (!$container->hasService($errorFormatterServiceName)) { + $errorOutput->writeLineFormatted(sprintf( + 'Error formatter "%s" not found. Available error formatters are: %s', + $errorFormat, + implode(', ', array_map(static function (string $name): string { + return substr($name, strlen('errorFormatter.')); + }, $container->findServiceNamesByType(ErrorFormatter::class))) + )); + return 1; + } + + if ($errorFormat === 'baselineNeon') { + $errorOutput = $inceptionResult->getErrorOutput(); + $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete option --error-format baselineNeon. ⚠️️'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(' There\'s a new and much better option --generate-baseline. Here are the advantages:'); + $errorOutput->writeLineFormatted(' 1) The current baseline file does not have to be commented-out'); + $errorOutput->writeLineFormatted(' nor emptied when generating the new baseline. It\'s excluded automatically.'); + $errorOutput->writeLineFormatted(' 2) Output no longer has to be redirected to a file, PHPStan saves the baseline'); + $errorOutput->writeLineFormatted(' to a specified path (defaults to phpstan-baseline.neon).'); + $errorOutput->writeLineFormatted(' 3) Baseline contains correct relative paths if saved to a subdirectory.'); + $errorOutput->writeLineFormatted(''); + } + + $generateBaselineFile = $inceptionResult->getGenerateBaselineFile(); + if ($generateBaselineFile !== null) { + $baselineExtension = pathinfo($generateBaselineFile, PATHINFO_EXTENSION); + if ($baselineExtension === '') { + $inceptionResult->getStdOutput()->getStyle()->error(sprintf('Baseline filename must have an extension, %s provided instead.', pathinfo($generateBaselineFile, PATHINFO_BASENAME))); + return $inceptionResult->handleReturn(1); + } + + if ($baselineExtension !== 'neon') { + $inceptionResult->getStdOutput()->getStyle()->error(sprintf('Baseline filename extension must be .neon, .%s was used instead.', $baselineExtension)); + + return $inceptionResult->handleReturn(1); + } + } + + try { + [$files, $onlyFiles] = $inceptionResult->getFiles(); + } catch (\PHPStan\File\PathNotFoundException $e) { + $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); + return 1; + } + + /** @var AnalyseApplication $application */ + $application = $container->getByType(AnalyseApplication::class); + + $debug = $input->getOption('debug'); + if (!is_bool($debug)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $analysisResult = $application->analyse( + $files, + $onlyFiles, + $inceptionResult->getStdOutput(), + $inceptionResult->getErrorOutput(), + $inceptionResult->isDefaultLevelUsed(), + $debug, + $inceptionResult->getProjectConfigFile(), + $inceptionResult->getProjectConfigArray(), + $input + ); + + if ($generateBaselineFile !== null) { + if (!$analysisResult->hasErrors()) { + $inceptionResult->getStdOutput()->getStyle()->error('No errors were found during the analysis. Baseline could not be generated.'); + + return $inceptionResult->handleReturn(1); + } + if ($analysisResult->hasInternalErrors()) { + $inceptionResult->getStdOutput()->getStyle()->error('An internal error occurred. Baseline could not be generated. Re-run PHPStan without --generate-baseline to see what\'s going on.'); + + return $inceptionResult->handleReturn(1); + } + + $baselineFileDirectory = dirname($generateBaselineFile); + $baselineErrorFormatter = new BaselineNeonErrorFormatter(new ParentDirectoryRelativePathHelper($baselineFileDirectory)); + + $streamOutput = $this->createStreamOutput(); + $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $streamOutput); + $baselineOutput = new SymfonyOutput($streamOutput, new SymfonyStyle($errorConsoleStyle)); + $baselineErrorFormatter->formatErrors($analysisResult, $baselineOutput); + + $stream = $streamOutput->getStream(); + rewind($stream); + $baselineContents = stream_get_contents($stream); + if ($baselineContents === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!is_dir($baselineFileDirectory)) { + $mkdirResult = @mkdir($baselineFileDirectory, 0644, true); + if ($mkdirResult === false) { + $inceptionResult->getStdOutput()->writeLineFormatted(sprintf('Failed to create directory "%s".', $baselineFileDirectory)); + + return $inceptionResult->handleReturn(1); + } + } + + try { + FileWriter::write($generateBaselineFile, $baselineContents); + } catch (\PHPStan\File\CouldNotWriteFileException $e) { + $inceptionResult->getStdOutput()->writeLineFormatted($e->getMessage()); + + return $inceptionResult->handleReturn(1); + } + + $errorsCount = 0; + $unignorableCount = 0; + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + if (!$fileSpecificError->canBeIgnored()) { + $unignorableCount++; + if ($output->isVeryVerbose()) { + $inceptionResult->getStdOutput()->writeLineFormatted('Unignorable could not be added to the baseline:'); + $inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getMessage()); + $inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getFile()); + $inceptionResult->getStdOutput()->writeLineFormatted(''); + } + continue; + } + + $errorsCount++; + } + + $message = sprintf('Baseline generated with %d %s.', $errorsCount, $errorsCount === 1 ? 'error' : 'errors'); + + if ( + $unignorableCount === 0 + && count($analysisResult->getNotFileSpecificErrors()) === 0 + ) { + $inceptionResult->getStdOutput()->getStyle()->success($message); + } else { + $inceptionResult->getStdOutput()->getStyle()->warning($message . "\nSome errors could not be put into baseline. Re-run PHPStan and fix them."); + } + + return $inceptionResult->handleReturn(0); + } + + if ($fix) { + $ciDetector = new CiDetector(); + if ($ciDetector->isCiDetected()) { + $inceptionResult->getStdOutput()->writeLineFormatted('PHPStan Pro can\'t run in CI environment yet. Stay tuned!'); + + return $inceptionResult->handleReturn(1); + } + $container->getByType(ResultCacheClearer::class)->clearTemporaryCaches(); + $hasInternalErrors = $analysisResult->hasInternalErrors(); + $nonIgnorableErrorsByException = []; + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + if (!$fileSpecificError->hasNonIgnorableException()) { + continue; + } + + $nonIgnorableErrorsByException[] = $fileSpecificError; + } + + if ($hasInternalErrors || count($nonIgnorableErrorsByException) > 0) { + $fixerAnalysisResult = new AnalysisResult( + $nonIgnorableErrorsByException, + $analysisResult->getInternalErrors(), + $analysisResult->getInternalErrors(), + [], + $analysisResult->isDefaultLevelUsed(), + $analysisResult->getProjectConfigFile(), + $analysisResult->isResultCacheSaved() + ); + + $stdOutput = $inceptionResult->getStdOutput(); + $stdOutput->getStyle()->error('PHPStan Pro can\'t be launched because of these errors:'); + + /** @var TableErrorFormatter $tableErrorFormatter */ + $tableErrorFormatter = $container->getService('errorFormatter.table'); + $tableErrorFormatter->formatErrors($fixerAnalysisResult, $stdOutput); + + $stdOutput->writeLineFormatted('Please fix them first and then re-run PHPStan.'); + + if ($stdOutput->isDebug()) { + $stdOutput->writeLineFormatted(sprintf('hasInternalErrors: %s', $hasInternalErrors ? 'true' : 'false')); + $stdOutput->writeLineFormatted(sprintf('nonIgnorableErrorsByExceptionCount: %d', count($nonIgnorableErrorsByException))); + } + + return $inceptionResult->handleReturn(1); + } + + if (!$analysisResult->isResultCacheSaved() && !$onlyFiles) { + // this can happen only if there are some regex-related errors in ignoreErrors configuration + $stdOutput = $inceptionResult->getStdOutput(); + if (count($analysisResult->getFileSpecificErrors()) > 0) { + $stdOutput->getStyle()->error('Unknown error. Please report this as a bug.'); + return $inceptionResult->handleReturn(1); + } + + $stdOutput->getStyle()->error('PHPStan Pro can\'t be launched because of these errors:'); + + /** @var TableErrorFormatter $tableErrorFormatter */ + $tableErrorFormatter = $container->getService('errorFormatter.table'); + $tableErrorFormatter->formatErrors($analysisResult, $stdOutput); + + $stdOutput->writeLineFormatted('Please fix them first and then re-run PHPStan.'); + + if ($stdOutput->isDebug()) { + $stdOutput->writeLineFormatted('Result cache was not saved.'); + } + + return $inceptionResult->handleReturn(1); + } + + $inceptionResult->handleReturn(0); // delete memory limit file + + /** @var FixerApplication $fixerApplication */ + $fixerApplication = $container->getByType(FixerApplication::class); + + return $fixerApplication->run( + $inceptionResult->getProjectConfigFile(), + $inceptionResult, + $input, + $output, + $analysisResult->getFileSpecificErrors(), + $analysisResult->getNotFileSpecificErrors(), + count($files), + $_SERVER['argv'][0] + ); + } + + /** @var ErrorFormatter $errorFormatter */ + $errorFormatter = $container->getService($errorFormatterServiceName); + + return $inceptionResult->handleReturn( + $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()) + ); + } + + private function createStreamOutput(): StreamOutput + { + $resource = fopen('php://memory', 'w', false); + if ($resource === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + return new StreamOutput($resource); + } } diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index e52904d869..9837348ec4 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -1,4 +1,6 @@ -scheduler = $scheduler; - $this->analyser = $analyser; - $this->parallelAnalyser = $parallelAnalyser; - $this->cpuCoreCounter = $cpuCoreCounter; - } + private CpuCoreCounter $cpuCoreCounter; - /** - * @param string[] $files - * @param string[] $allAnalysedFiles - * @param (\Closure(string $file): void)|null $preFileCallback - * @param (\Closure(int): void)|null $postFileCallback - * @param bool $debug - * @param bool $allowParallel - * @param string|null $projectConfigFile - * @param string|null $tmpFile - * @param string|null $insteadOfFile - * @param InputInterface $input - * @return AnalyserResult - */ - public function runAnalyser( - array $files, - array $allAnalysedFiles, - ?\Closure $preFileCallback, - ?\Closure $postFileCallback, - bool $debug, - bool $allowParallel, - ?string $projectConfigFile, - ?string $tmpFile, - ?string $insteadOfFile, - InputInterface $input - ): AnalyserResult - { - $filesCount = count($files); - if ($filesCount === 0) { - return new AnalyserResult([], [], [], [], false); - } + public function __construct( + Scheduler $scheduler, + Analyser $analyser, + ParallelAnalyser $parallelAnalyser, + CpuCoreCounter $cpuCoreCounter + ) { + $this->scheduler = $scheduler; + $this->analyser = $analyser; + $this->parallelAnalyser = $parallelAnalyser; + $this->cpuCoreCounter = $cpuCoreCounter; + } - $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files); - $mainScript = null; - if (isset($_SERVER['argv'][0]) && file_exists($_SERVER['argv'][0])) { - $mainScript = $_SERVER['argv'][0]; - } + /** + * @param string[] $files + * @param string[] $allAnalysedFiles + * @param (\Closure(string $file): void)|null $preFileCallback + * @param (\Closure(int): void)|null $postFileCallback + * @param bool $debug + * @param bool $allowParallel + * @param string|null $projectConfigFile + * @param string|null $tmpFile + * @param string|null $insteadOfFile + * @param InputInterface $input + * @return AnalyserResult + */ + public function runAnalyser( + array $files, + array $allAnalysedFiles, + ?\Closure $preFileCallback, + ?\Closure $postFileCallback, + bool $debug, + bool $allowParallel, + ?string $projectConfigFile, + ?string $tmpFile, + ?string $insteadOfFile, + InputInterface $input + ): AnalyserResult { + $filesCount = count($files); + if ($filesCount === 0) { + return new AnalyserResult([], [], [], [], false); + } - if ( - !$debug - && $allowParallel - && $mainScript !== null - && $schedule->getNumberOfProcesses() > 1 - ) { - return $this->parallelAnalyser->analyse($schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input); - } + $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files); + $mainScript = null; + if (isset($_SERVER['argv'][0]) && file_exists($_SERVER['argv'][0])) { + $mainScript = $_SERVER['argv'][0]; + } - return $this->analyser->analyse( - $this->switchTmpFile($files, $insteadOfFile, $tmpFile), - $preFileCallback, - $postFileCallback, - $debug, - $this->switchTmpFile($allAnalysedFiles, $insteadOfFile, $tmpFile) - ); - } + if ( + !$debug + && $allowParallel + && $mainScript !== null + && $schedule->getNumberOfProcesses() > 1 + ) { + return $this->parallelAnalyser->analyse($schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input); + } - /** - * @param string[] $analysedFiles - * @param string|null $insteadOfFile - * @param string|null $tmpFile - * @return string[] - */ - private function switchTmpFile( - array $analysedFiles, - ?string $insteadOfFile, - ?string $tmpFile - ): array - { - $analysedFiles = array_values(array_filter($analysedFiles, static function (string $file) use ($insteadOfFile): bool { - if ($insteadOfFile === null) { - return true; - } - return $file !== $insteadOfFile; - })); - if ($tmpFile !== null) { - $analysedFiles[] = $tmpFile; - } + return $this->analyser->analyse( + $this->switchTmpFile($files, $insteadOfFile, $tmpFile), + $preFileCallback, + $postFileCallback, + $debug, + $this->switchTmpFile($allAnalysedFiles, $insteadOfFile, $tmpFile) + ); + } - return $analysedFiles; - } + /** + * @param string[] $analysedFiles + * @param string|null $insteadOfFile + * @param string|null $tmpFile + * @return string[] + */ + private function switchTmpFile( + array $analysedFiles, + ?string $insteadOfFile, + ?string $tmpFile + ): array { + $analysedFiles = array_values(array_filter($analysedFiles, static function (string $file) use ($insteadOfFile): bool { + if ($insteadOfFile === null) { + return true; + } + return $file !== $insteadOfFile; + })); + if ($tmpFile !== null) { + $analysedFiles[] = $tmpFile; + } + return $analysedFiles; + } } diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index 2398b0833c..800efedb66 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -1,4 +1,6 @@ -getFile(), - $a->getLine(), - $a->getMessage(), - ] <=> [ - $b->getFile(), - $b->getLine(), - $b->getMessage(), - ]; - } - ); - - $this->fileSpecificErrors = $fileSpecificErrors; - $this->notFileSpecificErrors = $notFileSpecificErrors; - $this->internalErrors = $internalErrors; - $this->warnings = $warnings; - $this->defaultLevelUsed = $defaultLevelUsed; - $this->projectConfigFile = $projectConfigFile; - $this->savedResultCache = $savedResultCache; - } - - public function hasErrors(): bool - { - return $this->getTotalErrorsCount() > 0; - } - - public function getTotalErrorsCount(): int - { - return count($this->fileSpecificErrors) + count($this->notFileSpecificErrors); - } - - /** - * @return \PHPStan\Analyser\Error[] sorted by their file name, line number and message - */ - public function getFileSpecificErrors(): array - { - return $this->fileSpecificErrors; - } - - /** - * @return string[] - */ - public function getNotFileSpecificErrors(): array - { - return $this->notFileSpecificErrors; - } - - /** - * @return string[] - */ - public function getInternalErrors(): array - { - return $this->internalErrors; - } - - /** - * @return string[] - */ - public function getWarnings(): array - { - return $this->warnings; - } - - public function hasWarnings(): bool - { - return count($this->warnings) > 0; - } - - public function isDefaultLevelUsed(): bool - { - return $this->defaultLevelUsed; - } - - public function getProjectConfigFile(): ?string - { - return $this->projectConfigFile; - } - - public function hasInternalErrors(): bool - { - return count($this->internalErrors) > 0; - } - - public function isResultCacheSaved(): bool - { - return $this->savedResultCache; - } - + /** @var \PHPStan\Analyser\Error[] sorted by their file name, line number and message */ + private array $fileSpecificErrors; + + /** @var string[] */ + private array $notFileSpecificErrors; + + /** @var string[] */ + private array $internalErrors; + + /** @var string[] */ + private array $warnings; + + private bool $defaultLevelUsed; + + private ?string $projectConfigFile; + + private bool $savedResultCache; + + /** + * @param \PHPStan\Analyser\Error[] $fileSpecificErrors + * @param string[] $notFileSpecificErrors + * @param string[] $internalErrors + * @param string[] $warnings + * @param bool $defaultLevelUsed + * @param string|null $projectConfigFile + * @param bool $savedResultCache + */ + public function __construct( + array $fileSpecificErrors, + array $notFileSpecificErrors, + array $internalErrors, + array $warnings, + bool $defaultLevelUsed, + ?string $projectConfigFile, + bool $savedResultCache + ) { + usort( + $fileSpecificErrors, + static function (Error $a, Error $b): int { + return [ + $a->getFile(), + $a->getLine(), + $a->getMessage(), + ] <=> [ + $b->getFile(), + $b->getLine(), + $b->getMessage(), + ]; + } + ); + + $this->fileSpecificErrors = $fileSpecificErrors; + $this->notFileSpecificErrors = $notFileSpecificErrors; + $this->internalErrors = $internalErrors; + $this->warnings = $warnings; + $this->defaultLevelUsed = $defaultLevelUsed; + $this->projectConfigFile = $projectConfigFile; + $this->savedResultCache = $savedResultCache; + } + + public function hasErrors(): bool + { + return $this->getTotalErrorsCount() > 0; + } + + public function getTotalErrorsCount(): int + { + return count($this->fileSpecificErrors) + count($this->notFileSpecificErrors); + } + + /** + * @return \PHPStan\Analyser\Error[] sorted by their file name, line number and message + */ + public function getFileSpecificErrors(): array + { + return $this->fileSpecificErrors; + } + + /** + * @return string[] + */ + public function getNotFileSpecificErrors(): array + { + return $this->notFileSpecificErrors; + } + + /** + * @return string[] + */ + public function getInternalErrors(): array + { + return $this->internalErrors; + } + + /** + * @return string[] + */ + public function getWarnings(): array + { + return $this->warnings; + } + + public function hasWarnings(): bool + { + return count($this->warnings) > 0; + } + + public function isDefaultLevelUsed(): bool + { + return $this->defaultLevelUsed; + } + + public function getProjectConfigFile(): ?string + { + return $this->projectConfigFile; + } + + public function hasInternalErrors(): bool + { + return count($this->internalErrors) > 0; + } + + public function isResultCacheSaved(): bool + { + return $this->savedResultCache; + } } diff --git a/src/Command/ClearResultCacheCommand.php b/src/Command/ClearResultCacheCommand.php index 6b5198c4dc..1dff91e150 100644 --- a/src/Command/ClearResultCacheCommand.php +++ b/src/Command/ClearResultCacheCommand.php @@ -1,4 +1,6 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } + /** @var string[] */ + private array $composerAutoloaderProjectPaths; - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('Clears the result cache.') - ->setDefinition([ - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - ]); - } + /** + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + array $composerAutoloaderProjectPaths + ) { + parent::__construct(); + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } - protected function execute(InputInterface $input, OutputInterface $output): int - { - $autoloadFile = $input->getOption('autoload-file'); - $configuration = $input->getOption('configuration'); + protected function configure(): void + { + $this->setName(self::NAME) + ->setDescription('Clears the result cache.') + ->setDefinition([ + new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), + new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), + ]); + } - if ( - (!is_string($autoloadFile) && $autoloadFile !== null) - || (!is_string($configuration) && $configuration !== null) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } + protected function execute(InputInterface $input, OutputInterface $output): int + { + $autoloadFile = $input->getOption('autoload-file'); + $configuration = $input->getOption('configuration'); - try { - $inceptionResult = CommandHelper::begin( - $input, - $output, - ['.'], - null, - null, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configuration, - null, - '0', - false, - false - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } + if ( + (!is_string($autoloadFile) && $autoloadFile !== null) + || (!is_string($configuration) && $configuration !== null) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } - $container = $inceptionResult->getContainer(); + try { + $inceptionResult = CommandHelper::begin( + $input, + $output, + ['.'], + null, + null, + $autoloadFile, + $this->composerAutoloaderProjectPaths, + $configuration, + null, + '0', + false, + false + ); + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + return 1; + } - /** @var ResultCacheClearer $resultCacheClearer */ - $resultCacheClearer = $container->getByType(ResultCacheClearer::class); - $path = $resultCacheClearer->clear(); + $container = $inceptionResult->getContainer(); - $output->writeln('Result cache cleared from directory:'); - $output->writeln($path); + /** @var ResultCacheClearer $resultCacheClearer */ + $resultCacheClearer = $container->getByType(ResultCacheClearer::class); + $path = $resultCacheClearer->clear(); - return 0; - } + $output->writeln('Result cache cleared from directory:'); + $output->writeln($path); + return 0; + } } diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 02258c5b74..b47ca9f25b 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -1,4 +1,6 @@ -check(); - unset($xdebug); - } - $stdOutput = new SymfonyOutput($output, new SymfonyStyle(new ErrorsConsoleStyle($input, $output))); - - /** @var \PHPStan\Command\Output $errorOutput */ - $errorOutput = (static function () use ($input, $output): Output { - $symfonyErrorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; - return new SymfonyOutput($symfonyErrorOutput, new SymfonyStyle(new ErrorsConsoleStyle($input, $symfonyErrorOutput))); - })(); - if ($memoryLimit !== null) { - if (\Nette\Utils\Strings::match($memoryLimit, '#^-?\d+[kMG]?$#i') === null) { - $errorOutput->writeLineFormatted(sprintf('Invalid memory limit format "%s".', $memoryLimit)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - if (ini_set('memory_limit', $memoryLimit) === false) { - $errorOutput->writeLineFormatted(sprintf('Memory limit "%s" cannot be set.', $memoryLimit)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - } - - $currentWorkingDirectory = getcwd(); - if ($currentWorkingDirectory === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $currentWorkingDirectoryFileHelper = new FileHelper($currentWorkingDirectory); - $currentWorkingDirectory = $currentWorkingDirectoryFileHelper->getWorkingDirectory(); - if ($autoloadFile !== null) { - $autoloadFile = $currentWorkingDirectoryFileHelper->absolutizePath($autoloadFile); - if (!is_file($autoloadFile)) { - $errorOutput->writeLineFormatted(sprintf('Autoload file "%s" not found.', $autoloadFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - (static function (string $file): void { - require_once $file; - })($autoloadFile); - } - if ($projectConfigFile === null) { - foreach (['phpstan.neon', 'phpstan.neon.dist'] as $discoverableConfigName) { - $discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName; - if (is_file($discoverableConfigFile)) { - $projectConfigFile = $discoverableConfigFile; - $errorOutput->writeLineFormatted(sprintf('Note: Using configuration file %s.', $projectConfigFile)); - break; - } - } - } else { - $projectConfigFile = $currentWorkingDirectoryFileHelper->absolutizePath($projectConfigFile); - } - - if ($generateBaselineFile !== null) { - $generateBaselineFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($generateBaselineFile)); - } - - $defaultLevelUsed = false; - if ($projectConfigFile === null && $level === null) { - $level = self::DEFAULT_LEVEL; - $defaultLevelUsed = true; - } - - $paths = array_map(static function (string $path) use ($currentWorkingDirectoryFileHelper): string { - return $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($path)); - }, $paths); - - if (count($paths) === 0 && $pathsFile !== null) { - $pathsFile = $currentWorkingDirectoryFileHelper->absolutizePath($pathsFile); - if (!file_exists($pathsFile)) { - $errorOutput->writeLineFormatted(sprintf('Paths file %s does not exist.', $pathsFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - try { - $pathsString = FileReader::read($pathsFile); - } catch (\PHPStan\File\CouldNotReadFileException $e) { - $errorOutput->writeLineFormatted($e->getMessage()); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $paths = array_values(array_filter(explode("\n", $pathsString), static function (string $path): bool { - return trim($path) !== ''; - })); - - $pathsFileFileHelper = new FileHelper(dirname($pathsFile)); - $paths = array_map(static function (string $path) use ($pathsFileFileHelper): string { - return $pathsFileFileHelper->normalizePath($pathsFileFileHelper->absolutizePath($path)); - }, $paths); - } - - $analysedPathsFromConfig = []; - $containerFactory = new ContainerFactory($currentWorkingDirectory); - $projectConfig = null; - if ($projectConfigFile !== null) { - if (!is_file($projectConfigFile)) { - $errorOutput->writeLineFormatted(sprintf('Project config file at path %s does not exist.', $projectConfigFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $loader = (new LoaderFactory( - $currentWorkingDirectoryFileHelper, - $containerFactory->getRootDirectory(), - $containerFactory->getCurrentWorkingDirectory(), - $generateBaselineFile - ))->createLoader(); - - try { - $projectConfig = $loader->load($projectConfigFile, null); - } catch (\Nette\InvalidStateException | \Nette\FileNotFoundException $e) { - $errorOutput->writeLineFormatted($e->getMessage()); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - $defaultParameters = [ - 'rootDir' => $containerFactory->getRootDirectory(), - 'currentWorkingDirectory' => $containerFactory->getCurrentWorkingDirectory(), - ]; - - if (isset($projectConfig['parameters']['tmpDir'])) { - $tmpDir = Helpers::expand($projectConfig['parameters']['tmpDir'], $defaultParameters); - } - if ($level === null && isset($projectConfig['parameters']['level'])) { - $level = (string) $projectConfig['parameters']['level']; - } - if (count($paths) === 0 && isset($projectConfig['parameters']['paths'])) { - $analysedPathsFromConfig = Helpers::expand($projectConfig['parameters']['paths'], $defaultParameters); - $paths = $analysedPathsFromConfig; - } - } - - $additionalConfigFiles = []; - if ($level !== null) { - $levelConfigFile = sprintf('%s/config.level%s.neon', $containerFactory->getConfigDirectory(), $level); - if (!is_file($levelConfigFile)) { - $errorOutput->writeLineFormatted(sprintf('Level config file %s was not found.', $levelConfigFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $additionalConfigFiles[] = $levelConfigFile; - } - - if (class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) { - $generatedConfigReflection = new \ReflectionClass('PHPStan\ExtensionInstaller\GeneratedConfig'); - $generatedConfigDirectory = dirname($generatedConfigReflection->getFileName()); - foreach (\PHPStan\ExtensionInstaller\GeneratedConfig::EXTENSIONS as $name => $extensionConfig) { - foreach ($extensionConfig['extra']['includes'] ?? [] as $includedFile) { - if (!is_string($includedFile)) { - $errorOutput->writeLineFormatted(sprintf('Cannot include config from package %s, expecting string file path but got %s', $name, gettype($includedFile))); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - $includedFilePath = null; - if (isset($extensionConfig['relative_install_path'])) { - $includedFilePath = sprintf('%s/%s/%s', $generatedConfigDirectory, $extensionConfig['relative_install_path'], $includedFile); - if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { - $includedFilePath = null; - } - } - - if ($includedFilePath === null) { - $includedFilePath = sprintf('%s/%s', $extensionConfig['install_path'], $includedFile); - } - if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { - $errorOutput->writeLineFormatted(sprintf('Config file %s does not exist or isn\'t readable', $includedFilePath)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - $additionalConfigFiles[] = $includedFilePath; - } - } - } - - if ($projectConfigFile !== null) { - $additionalConfigFiles[] = $projectConfigFile; - } - - $loaderParameters = [ - 'rootDir' => $containerFactory->getRootDirectory(), - 'currentWorkingDirectory' => $containerFactory->getCurrentWorkingDirectory(), - ]; - - self::detectDuplicateIncludedFiles( - $errorOutput, - $currentWorkingDirectoryFileHelper, - $additionalConfigFiles, - $loaderParameters - ); - - $createDir = static function (string $path) use ($errorOutput): void { - if (!@mkdir($path, 0777) && !is_dir($path)) { - $errorOutput->writeLineFormatted(sprintf('Cannot create a temp directory %s', $path)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - }; - - if (!isset($tmpDir)) { - $tmpDir = sys_get_temp_dir() . '/phpstan'; - $createDir($tmpDir); - } - - try { - $container = $containerFactory->create($tmpDir, $additionalConfigFiles, $paths, $composerAutoloaderProjectPaths, $analysedPathsFromConfig, $level ?? self::DEFAULT_LEVEL, $generateBaselineFile, $autoloadFile, $singleReflectionFile, $singleReflectionInsteadOfFile); - } catch (\Nette\DI\InvalidConfigurationException | \Nette\Utils\AssertionException $e) { - $errorOutput->writeLineFormatted('Invalid configuration:'); - $errorOutput->writeLineFormatted($e->getMessage()); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $containerFactory->clearOldContainers($tmpDir); - - if (count($paths) === 0) { - $errorOutput->writeLineFormatted('At least one path must be specified to analyse.'); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $memoryLimitFile = $container->getParameter('memoryLimitFile'); - if ($manageMemoryLimitFile && file_exists($memoryLimitFile)) { - $memoryLimitFileContents = FileReader::read($memoryLimitFile); - $errorOutput->writeLineFormatted('PHPStan crashed in the previous run probably because of excessive memory consumption.'); - $errorOutput->writeLineFormatted(sprintf('It consumed around %s of memory.', $memoryLimitFileContents)); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('To avoid this issue, allow to use more memory with the --memory-limit option.'); - @unlink($memoryLimitFile); - } - - self::setUpSignalHandler($errorOutput, $manageMemoryLimitFile ? $memoryLimitFile : null); - if (!$container->hasParameter('customRulesetUsed')) { - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('No rules detected'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('You have the following choices:'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('* while running the analyse option, use the --level option to adjust your rule level - the higher the stricter'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(sprintf('* create your own custom ruleset by selecting which rules you want to check by copying the service definitions from the built-in config level files in %s.', $currentWorkingDirectoryFileHelper->normalizePath(__DIR__ . '/../../conf'))); - $errorOutput->writeLineFormatted(' * in this case, don\'t forget to define parameter customRulesetUsed in your config file.'); - $errorOutput->writeLineFormatted(''); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } elseif ((bool) $container->getParameter('customRulesetUsed')) { - $defaultLevelUsed = false; - } - - $schema = $container->getParameter('__parametersSchema'); - $processor = new Processor(); - $processor->onNewContext[] = static function (SchemaContext $context): void { - $context->path = ['parameters']; - }; - - try { - $processor->process($schema, $container->getParameters()); - } catch (\Nette\Schema\ValidationException $e) { - foreach ($e->getMessages() as $message) { - $errorOutput->writeLineFormatted('Invalid configuration:'); - $errorOutput->writeLineFormatted($message); - } - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $autoloadFiles = $container->getParameter('autoload_files'); - if ($manageMemoryLimitFile && count($autoloadFiles) > 0) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_files. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); - $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('If the analysis fails, there are now two distinct options'); - $errorOutput->writeLineFormatted('to choose from to replace autoload_files:'); - $errorOutput->writeLineFormatted('1) scanFiles - PHPStan will scan those for classes and functions'); - $errorOutput->writeLineFormatted(' definitions. PHPStan will not execute those files.'); - $errorOutput->writeLineFormatted('2) bootstrapFiles - PHPStan will execute these files to prepare'); - $errorOutput->writeLineFormatted(' the PHP runtime environment for the analysis.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); - $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); - $errorOutput->writeLineFormatted(''); - } - - $autoloadDirectories = $container->getParameter('autoload_directories'); - if (count($autoloadDirectories) > 0 && $manageMemoryLimitFile) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_directories. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); - $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('If the analysis fails, replace it with scanDirectories.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); - $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); - $errorOutput->writeLineFormatted(''); - } - - foreach ($autoloadFiles as $parameterAutoloadFile) { - if (!file_exists($parameterAutoloadFile)) { - $errorOutput->writeLineFormatted(sprintf('Autoload file %s does not exist.', $parameterAutoloadFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - (static function (string $file) use ($container): void { - require_once $file; - })($parameterAutoloadFile); - } - - $bootstrapFile = $container->getParameter('bootstrap'); - if ($bootstrapFile !== null) { - if ($manageMemoryLimitFile) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option bootstrap. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('This option has been replaced with bootstrapFiles which accepts a list of files'); - $errorOutput->writeLineFormatted('to execute before the analysis.'); - $errorOutput->writeLineFormatted(''); - } - - self::executeBootstrapFile($bootstrapFile, $container, $errorOutput, $debugEnabled); - } - - foreach ($container->getParameter('bootstrapFiles') as $bootstrapFileFromArray) { - self::executeBootstrapFile($bootstrapFileFromArray, $container, $errorOutput, $debugEnabled); - } - - foreach ($container->getParameter('scanFiles') as $scannedFile) { - if (is_file($scannedFile)) { - continue; - } - - $errorOutput->writeLineFormatted(sprintf('Scanned file %s does not exist.', $scannedFile)); - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - foreach ($container->getParameter('scanDirectories') as $scannedDirectory) { - if (is_dir($scannedDirectory)) { - continue; - } - - $errorOutput->writeLineFormatted(sprintf('Scanned directory %s does not exist.', $scannedDirectory)); - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $alreadyAddedStubFiles = []; - foreach ($container->getParameter('stubFiles') as $stubFile) { - if ( - $container->getParameter('featureToggles')['detectDuplicateStubFiles'] - && array_key_exists($stubFile, $alreadyAddedStubFiles) - ) { - $errorOutput->writeLineFormatted(sprintf('Stub file %s is added multiple times.', $stubFile)); - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $alreadyAddedStubFiles[$stubFile] = true; - - if (is_file($stubFile)) { - continue; - } - - $errorOutput->writeLineFormatted(sprintf('Stub file %s does not exist.', $stubFile)); - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $excludesAnalyse = $container->getParameter('excludes_analyse'); - $excludePaths = $container->getParameter('excludePaths'); - if (count($excludesAnalyse) > 0 && $excludePaths !== null) { - $errorOutput->writeLineFormatted(sprintf('Configuration parameters excludes_analyse and excludePaths cannot be used at the same time.')); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(sprintf('Parameter excludes_analyse has been deprecated so use excludePaths only from now on.')); - $errorOutput->writeLineFormatted(''); - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $tempResultCachePath = $container->getParameter('tempResultCachePath'); - $createDir($tempResultCachePath); - - /** @var FileFinder $fileFinder */ - $fileFinder = $container->getService('fileFinderAnalyse'); - - /** @var \Closure(): (array{string[], bool}) $filesCallback */ - $filesCallback = static function () use ($fileFinder, $paths): array { - $fileFinderResult = $fileFinder->findFiles($paths); - - return [$fileFinderResult->getFiles(), $fileFinderResult->isOnlyFiles()]; - }; - - return new InceptionResult( - $filesCallback, - $stdOutput, - $errorOutput, - $container, - $defaultLevelUsed, - $memoryLimitFile, - $projectConfigFile, - $projectConfig, - $generateBaselineFile - ); - } - - /** - * @throws InceptionNotSuccessfulException - */ - private static function executeBootstrapFile( - string $file, - Container $container, - Output $errorOutput, - bool $debugEnabled - ): void - { - if (!is_file($file)) { - $errorOutput->writeLineFormatted(sprintf('Bootstrap file %s does not exist.', $file)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - try { - (static function (string $file) use ($container): void { - require_once $file; - })($file); - } catch (\Throwable $e) { - $errorOutput->writeLineFormatted(sprintf('%s thrown in %s on line %d while loading bootstrap file %s: %s', get_class($e), $e->getFile(), $e->getLine(), $file, $e->getMessage())); - - if ($debugEnabled) { - $errorOutput->writeLineFormatted($e->getTraceAsString()); - } - - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - } - - private static function setUpSignalHandler(Output $output, ?string $memoryLimitFile): void - { - if (!function_exists('pcntl_signal')) { - return; - } - - pcntl_async_signals(true); - pcntl_signal(SIGINT, static function () use ($output, $memoryLimitFile): void { - if ($memoryLimitFile !== null && file_exists($memoryLimitFile)) { - @unlink($memoryLimitFile); - } - $output->writeLineFormatted(''); - exit(1); - }); - } - - /** - * @param \PHPStan\Command\Output $output - * @param \PHPStan\File\FileHelper $fileHelper - * @param string[] $configFiles - * @param array $loaderParameters - * @throws \PHPStan\Command\InceptionNotSuccessfulException - */ - private static function detectDuplicateIncludedFiles( - Output $output, - FileHelper $fileHelper, - array $configFiles, - array $loaderParameters - ): void - { - $neonAdapter = new NeonAdapter(); - $phpAdapter = new PhpAdapter(); - $allConfigFiles = []; - foreach ($configFiles as $configFile) { - $allConfigFiles = array_merge($allConfigFiles, self::getConfigFiles($fileHelper, $neonAdapter, $phpAdapter, $configFile, $loaderParameters, null)); - } - - $normalized = array_map(static function (string $file) use ($fileHelper): string { - return $fileHelper->normalizePath($file); - }, $allConfigFiles); - - $deduplicated = array_unique($normalized); - if (count($normalized) <= count($deduplicated)) { - return; - } - - $duplicateFiles = array_unique(array_diff_key($normalized, $deduplicated)); - - $format = "These files are included multiple times:\n- %s"; - if (count($duplicateFiles) === 1) { - $format = "This file is included multiple times:\n- %s"; - } - $output->writeLineFormatted(sprintf($format, implode("\n- ", $duplicateFiles))); - - if (class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) { - $output->writeLineFormatted(''); - $output->writeLineFormatted('It can lead to unexpected results. If you\'re using phpstan/extension-installer, make sure you have removed corresponding neon files from your project config file.'); - } - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - /** - * @param \PHPStan\DependencyInjection\NeonAdapter $neonAdapter - * @param \Nette\DI\Config\Adapters\PhpAdapter $phpAdapter - * @param string $configFile - * @param array $loaderParameters - * @param string|null $generateBaselineFile - * @return string[] - */ - private static function getConfigFiles( - FileHelper $fileHelper, - NeonAdapter $neonAdapter, - PhpAdapter $phpAdapter, - string $configFile, - array $loaderParameters, - ?string $generateBaselineFile - ): array - { - if ($generateBaselineFile === $fileHelper->normalizePath($configFile)) { - return []; - } - if (!is_file($configFile) || !is_readable($configFile)) { - return []; - } - - if (Strings::endsWith($configFile, '.php')) { - $data = $phpAdapter->load($configFile); - } else { - $data = $neonAdapter->load($configFile); - } - $allConfigFiles = [$configFile]; - if (isset($data['includes'])) { - Validators::assert($data['includes'], 'list', sprintf("section 'includes' in file '%s'", $configFile)); - $includes = Helpers::expand($data['includes'], $loaderParameters); - foreach ($includes as $include) { - $include = self::expandIncludedFile($include, $configFile); - $allConfigFiles = array_merge($allConfigFiles, self::getConfigFiles($fileHelper, $neonAdapter, $phpAdapter, $include, $loaderParameters, $generateBaselineFile)); - } - } - - return $allConfigFiles; - } - - private static function expandIncludedFile(string $includedFile, string $mainFile): string - { - return Strings::match($includedFile, '#([a-z]+:)?[/\\\\]#Ai') !== null // is absolute - ? $includedFile - : dirname($mainFile) . '/' . $includedFile; - } - + public const DEFAULT_LEVEL = '0'; + + /** + * @param string[] $paths + * @param string[] $composerAutoloaderProjectPaths + * + * @throws \PHPStan\Command\InceptionNotSuccessfulException + */ + public static function begin( + InputInterface $input, + OutputInterface $output, + array $paths, + ?string $pathsFile, + ?string $memoryLimit, + ?string $autoloadFile, + array $composerAutoloaderProjectPaths, + ?string $projectConfigFile, + ?string $generateBaselineFile, + ?string $level, + bool $allowXdebug, + bool $manageMemoryLimitFile = true, + bool $debugEnabled = false, + ?string $singleReflectionFile = null, + ?string $singleReflectionInsteadOfFile = null + ): InceptionResult { + if (!$allowXdebug) { + $xdebug = new XdebugHandler('phpstan', '--ansi'); + $xdebug->check(); + unset($xdebug); + } + $stdOutput = new SymfonyOutput($output, new SymfonyStyle(new ErrorsConsoleStyle($input, $output))); + + /** @var \PHPStan\Command\Output $errorOutput */ + $errorOutput = (static function () use ($input, $output): Output { + $symfonyErrorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + return new SymfonyOutput($symfonyErrorOutput, new SymfonyStyle(new ErrorsConsoleStyle($input, $symfonyErrorOutput))); + })(); + if ($memoryLimit !== null) { + if (\Nette\Utils\Strings::match($memoryLimit, '#^-?\d+[kMG]?$#i') === null) { + $errorOutput->writeLineFormatted(sprintf('Invalid memory limit format "%s".', $memoryLimit)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + if (ini_set('memory_limit', $memoryLimit) === false) { + $errorOutput->writeLineFormatted(sprintf('Memory limit "%s" cannot be set.', $memoryLimit)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + } + + $currentWorkingDirectory = getcwd(); + if ($currentWorkingDirectory === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $currentWorkingDirectoryFileHelper = new FileHelper($currentWorkingDirectory); + $currentWorkingDirectory = $currentWorkingDirectoryFileHelper->getWorkingDirectory(); + if ($autoloadFile !== null) { + $autoloadFile = $currentWorkingDirectoryFileHelper->absolutizePath($autoloadFile); + if (!is_file($autoloadFile)) { + $errorOutput->writeLineFormatted(sprintf('Autoload file "%s" not found.', $autoloadFile)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + (static function (string $file): void { + require_once $file; + })($autoloadFile); + } + if ($projectConfigFile === null) { + foreach (['phpstan.neon', 'phpstan.neon.dist'] as $discoverableConfigName) { + $discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName; + if (is_file($discoverableConfigFile)) { + $projectConfigFile = $discoverableConfigFile; + $errorOutput->writeLineFormatted(sprintf('Note: Using configuration file %s.', $projectConfigFile)); + break; + } + } + } else { + $projectConfigFile = $currentWorkingDirectoryFileHelper->absolutizePath($projectConfigFile); + } + + if ($generateBaselineFile !== null) { + $generateBaselineFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($generateBaselineFile)); + } + + $defaultLevelUsed = false; + if ($projectConfigFile === null && $level === null) { + $level = self::DEFAULT_LEVEL; + $defaultLevelUsed = true; + } + + $paths = array_map(static function (string $path) use ($currentWorkingDirectoryFileHelper): string { + return $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($path)); + }, $paths); + + if (count($paths) === 0 && $pathsFile !== null) { + $pathsFile = $currentWorkingDirectoryFileHelper->absolutizePath($pathsFile); + if (!file_exists($pathsFile)) { + $errorOutput->writeLineFormatted(sprintf('Paths file %s does not exist.', $pathsFile)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + try { + $pathsString = FileReader::read($pathsFile); + } catch (\PHPStan\File\CouldNotReadFileException $e) { + $errorOutput->writeLineFormatted($e->getMessage()); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $paths = array_values(array_filter(explode("\n", $pathsString), static function (string $path): bool { + return trim($path) !== ''; + })); + + $pathsFileFileHelper = new FileHelper(dirname($pathsFile)); + $paths = array_map(static function (string $path) use ($pathsFileFileHelper): string { + return $pathsFileFileHelper->normalizePath($pathsFileFileHelper->absolutizePath($path)); + }, $paths); + } + + $analysedPathsFromConfig = []; + $containerFactory = new ContainerFactory($currentWorkingDirectory); + $projectConfig = null; + if ($projectConfigFile !== null) { + if (!is_file($projectConfigFile)) { + $errorOutput->writeLineFormatted(sprintf('Project config file at path %s does not exist.', $projectConfigFile)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $loader = (new LoaderFactory( + $currentWorkingDirectoryFileHelper, + $containerFactory->getRootDirectory(), + $containerFactory->getCurrentWorkingDirectory(), + $generateBaselineFile + ))->createLoader(); + + try { + $projectConfig = $loader->load($projectConfigFile, null); + } catch (\Nette\InvalidStateException | \Nette\FileNotFoundException $e) { + $errorOutput->writeLineFormatted($e->getMessage()); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + $defaultParameters = [ + 'rootDir' => $containerFactory->getRootDirectory(), + 'currentWorkingDirectory' => $containerFactory->getCurrentWorkingDirectory(), + ]; + + if (isset($projectConfig['parameters']['tmpDir'])) { + $tmpDir = Helpers::expand($projectConfig['parameters']['tmpDir'], $defaultParameters); + } + if ($level === null && isset($projectConfig['parameters']['level'])) { + $level = (string) $projectConfig['parameters']['level']; + } + if (count($paths) === 0 && isset($projectConfig['parameters']['paths'])) { + $analysedPathsFromConfig = Helpers::expand($projectConfig['parameters']['paths'], $defaultParameters); + $paths = $analysedPathsFromConfig; + } + } + + $additionalConfigFiles = []; + if ($level !== null) { + $levelConfigFile = sprintf('%s/config.level%s.neon', $containerFactory->getConfigDirectory(), $level); + if (!is_file($levelConfigFile)) { + $errorOutput->writeLineFormatted(sprintf('Level config file %s was not found.', $levelConfigFile)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $additionalConfigFiles[] = $levelConfigFile; + } + + if (class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) { + $generatedConfigReflection = new \ReflectionClass('PHPStan\ExtensionInstaller\GeneratedConfig'); + $generatedConfigDirectory = dirname($generatedConfigReflection->getFileName()); + foreach (\PHPStan\ExtensionInstaller\GeneratedConfig::EXTENSIONS as $name => $extensionConfig) { + foreach ($extensionConfig['extra']['includes'] ?? [] as $includedFile) { + if (!is_string($includedFile)) { + $errorOutput->writeLineFormatted(sprintf('Cannot include config from package %s, expecting string file path but got %s', $name, gettype($includedFile))); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + $includedFilePath = null; + if (isset($extensionConfig['relative_install_path'])) { + $includedFilePath = sprintf('%s/%s/%s', $generatedConfigDirectory, $extensionConfig['relative_install_path'], $includedFile); + if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { + $includedFilePath = null; + } + } + + if ($includedFilePath === null) { + $includedFilePath = sprintf('%s/%s', $extensionConfig['install_path'], $includedFile); + } + if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { + $errorOutput->writeLineFormatted(sprintf('Config file %s does not exist or isn\'t readable', $includedFilePath)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + $additionalConfigFiles[] = $includedFilePath; + } + } + } + + if ($projectConfigFile !== null) { + $additionalConfigFiles[] = $projectConfigFile; + } + + $loaderParameters = [ + 'rootDir' => $containerFactory->getRootDirectory(), + 'currentWorkingDirectory' => $containerFactory->getCurrentWorkingDirectory(), + ]; + + self::detectDuplicateIncludedFiles( + $errorOutput, + $currentWorkingDirectoryFileHelper, + $additionalConfigFiles, + $loaderParameters + ); + + $createDir = static function (string $path) use ($errorOutput): void { + if (!@mkdir($path, 0777) && !is_dir($path)) { + $errorOutput->writeLineFormatted(sprintf('Cannot create a temp directory %s', $path)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + }; + + if (!isset($tmpDir)) { + $tmpDir = sys_get_temp_dir() . '/phpstan'; + $createDir($tmpDir); + } + + try { + $container = $containerFactory->create($tmpDir, $additionalConfigFiles, $paths, $composerAutoloaderProjectPaths, $analysedPathsFromConfig, $level ?? self::DEFAULT_LEVEL, $generateBaselineFile, $autoloadFile, $singleReflectionFile, $singleReflectionInsteadOfFile); + } catch (\Nette\DI\InvalidConfigurationException | \Nette\Utils\AssertionException $e) { + $errorOutput->writeLineFormatted('Invalid configuration:'); + $errorOutput->writeLineFormatted($e->getMessage()); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $containerFactory->clearOldContainers($tmpDir); + + if (count($paths) === 0) { + $errorOutput->writeLineFormatted('At least one path must be specified to analyse.'); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $memoryLimitFile = $container->getParameter('memoryLimitFile'); + if ($manageMemoryLimitFile && file_exists($memoryLimitFile)) { + $memoryLimitFileContents = FileReader::read($memoryLimitFile); + $errorOutput->writeLineFormatted('PHPStan crashed in the previous run probably because of excessive memory consumption.'); + $errorOutput->writeLineFormatted(sprintf('It consumed around %s of memory.', $memoryLimitFileContents)); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('To avoid this issue, allow to use more memory with the --memory-limit option.'); + @unlink($memoryLimitFile); + } + + self::setUpSignalHandler($errorOutput, $manageMemoryLimitFile ? $memoryLimitFile : null); + if (!$container->hasParameter('customRulesetUsed')) { + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('No rules detected'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('You have the following choices:'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('* while running the analyse option, use the --level option to adjust your rule level - the higher the stricter'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(sprintf('* create your own custom ruleset by selecting which rules you want to check by copying the service definitions from the built-in config level files in %s.', $currentWorkingDirectoryFileHelper->normalizePath(__DIR__ . '/../../conf'))); + $errorOutput->writeLineFormatted(' * in this case, don\'t forget to define parameter customRulesetUsed in your config file.'); + $errorOutput->writeLineFormatted(''); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } elseif ((bool) $container->getParameter('customRulesetUsed')) { + $defaultLevelUsed = false; + } + + $schema = $container->getParameter('__parametersSchema'); + $processor = new Processor(); + $processor->onNewContext[] = static function (SchemaContext $context): void { + $context->path = ['parameters']; + }; + + try { + $processor->process($schema, $container->getParameters()); + } catch (\Nette\Schema\ValidationException $e) { + foreach ($e->getMessages() as $message) { + $errorOutput->writeLineFormatted('Invalid configuration:'); + $errorOutput->writeLineFormatted($message); + } + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $autoloadFiles = $container->getParameter('autoload_files'); + if ($manageMemoryLimitFile && count($autoloadFiles) > 0) { + $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_files. ⚠️️'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); + $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('If the analysis fails, there are now two distinct options'); + $errorOutput->writeLineFormatted('to choose from to replace autoload_files:'); + $errorOutput->writeLineFormatted('1) scanFiles - PHPStan will scan those for classes and functions'); + $errorOutput->writeLineFormatted(' definitions. PHPStan will not execute those files.'); + $errorOutput->writeLineFormatted('2) bootstrapFiles - PHPStan will execute these files to prepare'); + $errorOutput->writeLineFormatted(' the PHP runtime environment for the analysis.'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); + $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); + $errorOutput->writeLineFormatted(''); + } + + $autoloadDirectories = $container->getParameter('autoload_directories'); + if (count($autoloadDirectories) > 0 && $manageMemoryLimitFile) { + $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_directories. ⚠️️'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); + $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('If the analysis fails, replace it with scanDirectories.'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); + $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); + $errorOutput->writeLineFormatted(''); + } + + foreach ($autoloadFiles as $parameterAutoloadFile) { + if (!file_exists($parameterAutoloadFile)) { + $errorOutput->writeLineFormatted(sprintf('Autoload file %s does not exist.', $parameterAutoloadFile)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + (static function (string $file) use ($container): void { + require_once $file; + })($parameterAutoloadFile); + } + + $bootstrapFile = $container->getParameter('bootstrap'); + if ($bootstrapFile !== null) { + if ($manageMemoryLimitFile) { + $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option bootstrap. ⚠️️'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('This option has been replaced with bootstrapFiles which accepts a list of files'); + $errorOutput->writeLineFormatted('to execute before the analysis.'); + $errorOutput->writeLineFormatted(''); + } + + self::executeBootstrapFile($bootstrapFile, $container, $errorOutput, $debugEnabled); + } + + foreach ($container->getParameter('bootstrapFiles') as $bootstrapFileFromArray) { + self::executeBootstrapFile($bootstrapFileFromArray, $container, $errorOutput, $debugEnabled); + } + + foreach ($container->getParameter('scanFiles') as $scannedFile) { + if (is_file($scannedFile)) { + continue; + } + + $errorOutput->writeLineFormatted(sprintf('Scanned file %s does not exist.', $scannedFile)); + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + foreach ($container->getParameter('scanDirectories') as $scannedDirectory) { + if (is_dir($scannedDirectory)) { + continue; + } + + $errorOutput->writeLineFormatted(sprintf('Scanned directory %s does not exist.', $scannedDirectory)); + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $alreadyAddedStubFiles = []; + foreach ($container->getParameter('stubFiles') as $stubFile) { + if ( + $container->getParameter('featureToggles')['detectDuplicateStubFiles'] + && array_key_exists($stubFile, $alreadyAddedStubFiles) + ) { + $errorOutput->writeLineFormatted(sprintf('Stub file %s is added multiple times.', $stubFile)); + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $alreadyAddedStubFiles[$stubFile] = true; + + if (is_file($stubFile)) { + continue; + } + + $errorOutput->writeLineFormatted(sprintf('Stub file %s does not exist.', $stubFile)); + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $excludesAnalyse = $container->getParameter('excludes_analyse'); + $excludePaths = $container->getParameter('excludePaths'); + if (count($excludesAnalyse) > 0 && $excludePaths !== null) { + $errorOutput->writeLineFormatted(sprintf('Configuration parameters excludes_analyse and excludePaths cannot be used at the same time.')); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(sprintf('Parameter excludes_analyse has been deprecated so use excludePaths only from now on.')); + $errorOutput->writeLineFormatted(''); + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + $tempResultCachePath = $container->getParameter('tempResultCachePath'); + $createDir($tempResultCachePath); + + /** @var FileFinder $fileFinder */ + $fileFinder = $container->getService('fileFinderAnalyse'); + + /** @var \Closure(): (array{string[], bool}) $filesCallback */ + $filesCallback = static function () use ($fileFinder, $paths): array { + $fileFinderResult = $fileFinder->findFiles($paths); + + return [$fileFinderResult->getFiles(), $fileFinderResult->isOnlyFiles()]; + }; + + return new InceptionResult( + $filesCallback, + $stdOutput, + $errorOutput, + $container, + $defaultLevelUsed, + $memoryLimitFile, + $projectConfigFile, + $projectConfig, + $generateBaselineFile + ); + } + + /** + * @throws InceptionNotSuccessfulException + */ + private static function executeBootstrapFile( + string $file, + Container $container, + Output $errorOutput, + bool $debugEnabled + ): void { + if (!is_file($file)) { + $errorOutput->writeLineFormatted(sprintf('Bootstrap file %s does not exist.', $file)); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + try { + (static function (string $file) use ($container): void { + require_once $file; + })($file); + } catch (\Throwable $e) { + $errorOutput->writeLineFormatted(sprintf('%s thrown in %s on line %d while loading bootstrap file %s: %s', get_class($e), $e->getFile(), $e->getLine(), $file, $e->getMessage())); + + if ($debugEnabled) { + $errorOutput->writeLineFormatted($e->getTraceAsString()); + } + + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + } + + private static function setUpSignalHandler(Output $output, ?string $memoryLimitFile): void + { + if (!function_exists('pcntl_signal')) { + return; + } + + pcntl_async_signals(true); + pcntl_signal(SIGINT, static function () use ($output, $memoryLimitFile): void { + if ($memoryLimitFile !== null && file_exists($memoryLimitFile)) { + @unlink($memoryLimitFile); + } + $output->writeLineFormatted(''); + exit(1); + }); + } + + /** + * @param \PHPStan\Command\Output $output + * @param \PHPStan\File\FileHelper $fileHelper + * @param string[] $configFiles + * @param array $loaderParameters + * @throws \PHPStan\Command\InceptionNotSuccessfulException + */ + private static function detectDuplicateIncludedFiles( + Output $output, + FileHelper $fileHelper, + array $configFiles, + array $loaderParameters + ): void { + $neonAdapter = new NeonAdapter(); + $phpAdapter = new PhpAdapter(); + $allConfigFiles = []; + foreach ($configFiles as $configFile) { + $allConfigFiles = array_merge($allConfigFiles, self::getConfigFiles($fileHelper, $neonAdapter, $phpAdapter, $configFile, $loaderParameters, null)); + } + + $normalized = array_map(static function (string $file) use ($fileHelper): string { + return $fileHelper->normalizePath($file); + }, $allConfigFiles); + + $deduplicated = array_unique($normalized); + if (count($normalized) <= count($deduplicated)) { + return; + } + + $duplicateFiles = array_unique(array_diff_key($normalized, $deduplicated)); + + $format = "These files are included multiple times:\n- %s"; + if (count($duplicateFiles) === 1) { + $format = "This file is included multiple times:\n- %s"; + } + $output->writeLineFormatted(sprintf($format, implode("\n- ", $duplicateFiles))); + + if (class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) { + $output->writeLineFormatted(''); + $output->writeLineFormatted('It can lead to unexpected results. If you\'re using phpstan/extension-installer, make sure you have removed corresponding neon files from your project config file.'); + } + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } + + /** + * @param \PHPStan\DependencyInjection\NeonAdapter $neonAdapter + * @param \Nette\DI\Config\Adapters\PhpAdapter $phpAdapter + * @param string $configFile + * @param array $loaderParameters + * @param string|null $generateBaselineFile + * @return string[] + */ + private static function getConfigFiles( + FileHelper $fileHelper, + NeonAdapter $neonAdapter, + PhpAdapter $phpAdapter, + string $configFile, + array $loaderParameters, + ?string $generateBaselineFile + ): array { + if ($generateBaselineFile === $fileHelper->normalizePath($configFile)) { + return []; + } + if (!is_file($configFile) || !is_readable($configFile)) { + return []; + } + + if (Strings::endsWith($configFile, '.php')) { + $data = $phpAdapter->load($configFile); + } else { + $data = $neonAdapter->load($configFile); + } + $allConfigFiles = [$configFile]; + if (isset($data['includes'])) { + Validators::assert($data['includes'], 'list', sprintf("section 'includes' in file '%s'", $configFile)); + $includes = Helpers::expand($data['includes'], $loaderParameters); + foreach ($includes as $include) { + $include = self::expandIncludedFile($include, $configFile); + $allConfigFiles = array_merge($allConfigFiles, self::getConfigFiles($fileHelper, $neonAdapter, $phpAdapter, $include, $loaderParameters, $generateBaselineFile)); + } + } + + return $allConfigFiles; + } + + private static function expandIncludedFile(string $includedFile, string $mainFile): string + { + return Strings::match($includedFile, '#([a-z]+:)?[/\\\\]#Ai') !== null // is absolute + ? $includedFile + : dirname($mainFile) . '/' . $includedFile; + } } diff --git a/src/Command/DumpDependenciesCommand.php b/src/Command/DumpDependenciesCommand.php index aa69b5900d..14d6fc4102 100644 --- a/src/Command/DumpDependenciesCommand.php +++ b/src/Command/DumpDependenciesCommand.php @@ -1,4 +1,6 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } - - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('Dumps files dependency tree') - ->setDefinition([ - new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run dump on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for the run'), - new InputOption('analysed-paths', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Project-scope paths'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), - ]); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - try { - /** @var string[] $paths */ - $paths = $input->getArgument('paths'); - - /** @var string|null $memoryLimit */ - $memoryLimit = $input->getOption('memory-limit'); - - /** @var string|null $autoloadFile */ - $autoloadFile = $input->getOption('autoload-file'); - - /** @var string|null $configurationFile */ - $configurationFile = $input->getOption('configuration'); - - /** @var string|null $pathsFile */ - $pathsFile = $input->getOption('paths-file'); - - /** @var bool $allowXdebug */ - $allowXdebug = $input->getOption('xdebug'); - - $inceptionResult = CommandHelper::begin( - $input, - $output, - $paths, - $pathsFile, - $memoryLimit, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configurationFile, - null, - '0', // irrelevant but prevents an error when a config file is passed - $allowXdebug, - true - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } - - try { - [$files] = $inceptionResult->getFiles(); - } catch (\PHPStan\File\PathNotFoundException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } - - $stdOutput = $inceptionResult->getStdOutput(); - $stdOutputStyole = $stdOutput->getStyle(); - - /** @var DependencyDumper $dependencyDumper */ - $dependencyDumper = $inceptionResult->getContainer()->getByType(DependencyDumper::class); - - /** @var FileHelper $fileHelper */ - $fileHelper = $inceptionResult->getContainer()->getByType(FileHelper::class); - - /** @var string[] $analysedPaths */ - $analysedPaths = $input->getOption('analysed-paths'); - $analysedPaths = array_map(static function (string $path) use ($fileHelper): string { - return $fileHelper->absolutizePath($path); - }, $analysedPaths); - $dependencies = $dependencyDumper->dumpDependencies( - $files, - static function (int $count) use ($stdOutputStyole): void { - $stdOutputStyole->progressStart($count); - }, - static function () use ($stdOutputStyole): void { - $stdOutputStyole->progressAdvance(); - }, - count($analysedPaths) > 0 ? $analysedPaths : null - ); - $stdOutputStyole->progressFinish(); - - try { - $stdOutput->writeLineFormatted(Json::encode($dependencies, Json::PRETTY)); - } catch (\Nette\Utils\JsonException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } - - return $inceptionResult->handleReturn(0); - } - + private const NAME = 'dump-deps'; + + /** @var string[] */ + private array $composerAutoloaderProjectPaths; + + /** + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + array $composerAutoloaderProjectPaths + ) { + parent::__construct(); + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } + + protected function configure(): void + { + $this->setName(self::NAME) + ->setDescription('Dumps files dependency tree') + ->setDefinition([ + new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run dump on'), + new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), + new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), + new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), + new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), + new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for the run'), + new InputOption('analysed-paths', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Project-scope paths'), + new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), + ]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + /** @var string[] $paths */ + $paths = $input->getArgument('paths'); + + /** @var string|null $memoryLimit */ + $memoryLimit = $input->getOption('memory-limit'); + + /** @var string|null $autoloadFile */ + $autoloadFile = $input->getOption('autoload-file'); + + /** @var string|null $configurationFile */ + $configurationFile = $input->getOption('configuration'); + + /** @var string|null $pathsFile */ + $pathsFile = $input->getOption('paths-file'); + + /** @var bool $allowXdebug */ + $allowXdebug = $input->getOption('xdebug'); + + $inceptionResult = CommandHelper::begin( + $input, + $output, + $paths, + $pathsFile, + $memoryLimit, + $autoloadFile, + $this->composerAutoloaderProjectPaths, + $configurationFile, + null, + '0', // irrelevant but prevents an error when a config file is passed + $allowXdebug, + true + ); + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + return 1; + } + + try { + [$files] = $inceptionResult->getFiles(); + } catch (\PHPStan\File\PathNotFoundException $e) { + $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); + return 1; + } + + $stdOutput = $inceptionResult->getStdOutput(); + $stdOutputStyole = $stdOutput->getStyle(); + + /** @var DependencyDumper $dependencyDumper */ + $dependencyDumper = $inceptionResult->getContainer()->getByType(DependencyDumper::class); + + /** @var FileHelper $fileHelper */ + $fileHelper = $inceptionResult->getContainer()->getByType(FileHelper::class); + + /** @var string[] $analysedPaths */ + $analysedPaths = $input->getOption('analysed-paths'); + $analysedPaths = array_map(static function (string $path) use ($fileHelper): string { + return $fileHelper->absolutizePath($path); + }, $analysedPaths); + $dependencies = $dependencyDumper->dumpDependencies( + $files, + static function (int $count) use ($stdOutputStyole): void { + $stdOutputStyole->progressStart($count); + }, + static function () use ($stdOutputStyole): void { + $stdOutputStyole->progressAdvance(); + }, + count($analysedPaths) > 0 ? $analysedPaths : null + ); + $stdOutputStyole->progressFinish(); + + try { + $stdOutput->writeLineFormatted(Json::encode($dependencies, Json::PRETTY)); + } catch (\Nette\Utils\JsonException $e) { + $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); + return 1; + } + + return $inceptionResult->handleReturn(0); + } } diff --git a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php index f819dd664f..0f4b356f4c 100644 --- a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - } - - public function formatErrors( - AnalysisResult $analysisResult, - Output $output - ): int - { - if (!$analysisResult->hasErrors()) { - $output->writeRaw(Neon::encode([ - 'parameters' => [ - 'ignoreErrors' => [], - ], - ], Neon::BLOCK)); - return 0; - } + public function __construct(RelativePathHelper $relativePathHelper) + { + $this->relativePathHelper = $relativePathHelper; + } - $fileErrors = []; - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - if (!$fileSpecificError->canBeIgnored()) { - continue; - } - $fileErrors[$fileSpecificError->getFilePath()][] = $fileSpecificError->getMessage(); - } - ksort($fileErrors, SORT_STRING); + public function formatErrors( + AnalysisResult $analysisResult, + Output $output + ): int { + if (!$analysisResult->hasErrors()) { + $output->writeRaw(Neon::encode([ + 'parameters' => [ + 'ignoreErrors' => [], + ], + ], Neon::BLOCK)); + return 0; + } - $errorsToOutput = []; - foreach ($fileErrors as $file => $errorMessages) { - $fileErrorsCounts = []; - foreach ($errorMessages as $errorMessage) { - if (!isset($fileErrorsCounts[$errorMessage])) { - $fileErrorsCounts[$errorMessage] = 1; - continue; - } + $fileErrors = []; + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + if (!$fileSpecificError->canBeIgnored()) { + continue; + } + $fileErrors[$fileSpecificError->getFilePath()][] = $fileSpecificError->getMessage(); + } + ksort($fileErrors, SORT_STRING); - $fileErrorsCounts[$errorMessage]++; - } - ksort($fileErrorsCounts, SORT_STRING); + $errorsToOutput = []; + foreach ($fileErrors as $file => $errorMessages) { + $fileErrorsCounts = []; + foreach ($errorMessages as $errorMessage) { + if (!isset($fileErrorsCounts[$errorMessage])) { + $fileErrorsCounts[$errorMessage] = 1; + continue; + } - foreach ($fileErrorsCounts as $message => $count) { - $errorsToOutput[] = [ - 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), - 'count' => $count, - 'path' => Helpers::escape($this->relativePathHelper->getRelativePath($file)), - ]; - } - } + $fileErrorsCounts[$errorMessage]++; + } + ksort($fileErrorsCounts, SORT_STRING); - $output->writeRaw(Neon::encode([ - 'parameters' => [ - 'ignoreErrors' => $errorsToOutput, - ], - ], Neon::BLOCK)); + foreach ($fileErrorsCounts as $message => $count) { + $errorsToOutput[] = [ + 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), + 'count' => $count, + 'path' => Helpers::escape($this->relativePathHelper->getRelativePath($file)), + ]; + } + } - return 1; - } + $output->writeRaw(Neon::encode([ + 'parameters' => [ + 'ignoreErrors' => $errorsToOutput, + ], + ], Neon::BLOCK)); + return 1; + } } diff --git a/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php b/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php index 47d8da120a..037f7992f8 100644 --- a/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php +++ b/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - } - - public function formatErrors( - AnalysisResult $analysisResult, - Output $output - ): int - { - $output->writeRaw(''); - $output->writeLineFormatted(''); - $output->writeRaw(''); - $output->writeLineFormatted(''); - - foreach ($this->groupByFile($analysisResult) as $relativeFilePath => $errors) { - $output->writeRaw(sprintf( - '', - $this->escape($relativeFilePath) - )); - $output->writeLineFormatted(''); - - foreach ($errors as $error) { - $output->writeRaw(sprintf( - ' ', - $this->escape((string) $error->getLine()), - $this->escape((string) $error->getMessage()) - )); - $output->writeLineFormatted(''); - } - $output->writeRaw(''); - $output->writeLineFormatted(''); - } - - $notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors(); - - if (count($notFileSpecificErrors) > 0) { - $output->writeRaw(''); - $output->writeLineFormatted(''); - - foreach ($notFileSpecificErrors as $error) { - $output->writeRaw(sprintf(' ', $this->escape($error))); - $output->writeLineFormatted(''); - } - - $output->writeRaw(''); - $output->writeLineFormatted(''); - } - - if ($analysisResult->hasWarnings()) { - $output->writeRaw(''); - $output->writeLineFormatted(''); - - foreach ($analysisResult->getWarnings() as $warning) { - $output->writeRaw(sprintf(' ', $this->escape($warning))); - $output->writeLineFormatted(''); - } - - $output->writeRaw(''); - $output->writeLineFormatted(''); - } - - $output->writeRaw(''); - $output->writeLineFormatted(''); - - return $analysisResult->hasErrors() ? 1 : 0; - } - - /** - * Escapes values for using in XML - * - * @param string $string - * @return string - */ - private function escape(string $string): string - { - return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); - } - - /** - * Group errors by file - * - * @param AnalysisResult $analysisResult - * @return array Array that have as key the relative path of file - * and as value an array with occurred errors. - */ - private function groupByFile(AnalysisResult $analysisResult): array - { - $files = []; - - /** @var \PHPStan\Analyser\Error $fileSpecificError */ - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $absolutePath = $fileSpecificError->getFilePath(); - if ($fileSpecificError->getTraitFilePath() !== null) { - $absolutePath = $fileSpecificError->getTraitFilePath(); - } - $relativeFilePath = $this->relativePathHelper->getRelativePath( - $absolutePath - ); - - $files[$relativeFilePath][] = $fileSpecificError; - } - - return $files; - } - + private RelativePathHelper $relativePathHelper; + + public function __construct(RelativePathHelper $relativePathHelper) + { + $this->relativePathHelper = $relativePathHelper; + } + + public function formatErrors( + AnalysisResult $analysisResult, + Output $output + ): int { + $output->writeRaw(''); + $output->writeLineFormatted(''); + $output->writeRaw(''); + $output->writeLineFormatted(''); + + foreach ($this->groupByFile($analysisResult) as $relativeFilePath => $errors) { + $output->writeRaw(sprintf( + '', + $this->escape($relativeFilePath) + )); + $output->writeLineFormatted(''); + + foreach ($errors as $error) { + $output->writeRaw(sprintf( + ' ', + $this->escape((string) $error->getLine()), + $this->escape((string) $error->getMessage()) + )); + $output->writeLineFormatted(''); + } + $output->writeRaw(''); + $output->writeLineFormatted(''); + } + + $notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors(); + + if (count($notFileSpecificErrors) > 0) { + $output->writeRaw(''); + $output->writeLineFormatted(''); + + foreach ($notFileSpecificErrors as $error) { + $output->writeRaw(sprintf(' ', $this->escape($error))); + $output->writeLineFormatted(''); + } + + $output->writeRaw(''); + $output->writeLineFormatted(''); + } + + if ($analysisResult->hasWarnings()) { + $output->writeRaw(''); + $output->writeLineFormatted(''); + + foreach ($analysisResult->getWarnings() as $warning) { + $output->writeRaw(sprintf(' ', $this->escape($warning))); + $output->writeLineFormatted(''); + } + + $output->writeRaw(''); + $output->writeLineFormatted(''); + } + + $output->writeRaw(''); + $output->writeLineFormatted(''); + + return $analysisResult->hasErrors() ? 1 : 0; + } + + /** + * Escapes values for using in XML + * + * @param string $string + * @return string + */ + private function escape(string $string): string + { + return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); + } + + /** + * Group errors by file + * + * @param AnalysisResult $analysisResult + * @return array Array that have as key the relative path of file + * and as value an array with occurred errors. + */ + private function groupByFile(AnalysisResult $analysisResult): array + { + $files = []; + + /** @var \PHPStan\Analyser\Error $fileSpecificError */ + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $absolutePath = $fileSpecificError->getFilePath(); + if ($fileSpecificError->getTraitFilePath() !== null) { + $absolutePath = $fileSpecificError->getTraitFilePath(); + } + $relativeFilePath = $this->relativePathHelper->getRelativePath( + $absolutePath + ); + + $files[$relativeFilePath][] = $fileSpecificError; + } + + return $files; + } } diff --git a/src/Command/ErrorFormatter/ErrorFormatter.php b/src/Command/ErrorFormatter/ErrorFormatter.php index ab3831c733..893bc09551 100644 --- a/src/Command/ErrorFormatter/ErrorFormatter.php +++ b/src/Command/ErrorFormatter/ErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - $this->tableErrorformatter = $tableErrorformatter; - } - - public function formatErrors(AnalysisResult $analysisResult, Output $output): int - { - $this->tableErrorformatter->formatErrors($analysisResult, $output); - - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $metas = [ - 'file' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), - 'line' => $fileSpecificError->getLine(), - 'col' => 0, - ]; - array_walk($metas, static function (&$value, string $key): void { - $value = sprintf('%s=%s', $key, (string) $value); - }); - - $message = $fileSpecificError->getMessage(); - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 - $message = str_replace("\n", '%0A', $message); - - $line = sprintf('::error %s::%s', implode(',', $metas), $message); - - $output->writeRaw($line); - $output->writeLineFormatted(''); - } - - foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 - $notFileSpecificError = str_replace("\n", '%0A', $notFileSpecificError); - - $line = sprintf('::error ::%s', $notFileSpecificError); - - $output->writeRaw($line); - $output->writeLineFormatted(''); - } - - foreach ($analysisResult->getWarnings() as $warning) { - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 - $warning = str_replace("\n", '%0A', $warning); - - $line = sprintf('::warning ::%s', $warning); - - $output->writeRaw($line); - $output->writeLineFormatted(''); - } - - return $analysisResult->hasErrors() ? 1 : 0; - } - + private RelativePathHelper $relativePathHelper; + + private TableErrorFormatter $tableErrorformatter; + + public function __construct( + RelativePathHelper $relativePathHelper, + TableErrorFormatter $tableErrorformatter + ) { + $this->relativePathHelper = $relativePathHelper; + $this->tableErrorformatter = $tableErrorformatter; + } + + public function formatErrors(AnalysisResult $analysisResult, Output $output): int + { + $this->tableErrorformatter->formatErrors($analysisResult, $output); + + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $metas = [ + 'file' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), + 'line' => $fileSpecificError->getLine(), + 'col' => 0, + ]; + array_walk($metas, static function (&$value, string $key): void { + $value = sprintf('%s=%s', $key, (string) $value); + }); + + $message = $fileSpecificError->getMessage(); + // newlines need to be encoded + // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $message = str_replace("\n", '%0A', $message); + + $line = sprintf('::error %s::%s', implode(',', $metas), $message); + + $output->writeRaw($line); + $output->writeLineFormatted(''); + } + + foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { + // newlines need to be encoded + // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $notFileSpecificError = str_replace("\n", '%0A', $notFileSpecificError); + + $line = sprintf('::error ::%s', $notFileSpecificError); + + $output->writeRaw($line); + $output->writeLineFormatted(''); + } + + foreach ($analysisResult->getWarnings() as $warning) { + // newlines need to be encoded + // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $warning = str_replace("\n", '%0A', $warning); + + $line = sprintf('::warning ::%s', $warning); + + $output->writeRaw($line); + $output->writeLineFormatted(''); + } + + return $analysisResult->hasErrors() ? 1 : 0; + } } diff --git a/src/Command/ErrorFormatter/GitlabErrorFormatter.php b/src/Command/ErrorFormatter/GitlabErrorFormatter.php index 16713dd779..6b50d81698 100644 --- a/src/Command/ErrorFormatter/GitlabErrorFormatter.php +++ b/src/Command/ErrorFormatter/GitlabErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - } - - public function formatErrors(AnalysisResult $analysisResult, Output $output): int - { - $errorsArray = []; + public function __construct(RelativePathHelper $relativePathHelper) + { + $this->relativePathHelper = $relativePathHelper; + } - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $error = [ - 'description' => $fileSpecificError->getMessage(), - 'fingerprint' => hash( - 'sha256', - implode( - [ - $fileSpecificError->getFile(), - $fileSpecificError->getLine(), - $fileSpecificError->getMessage(), - ] - ) - ), - 'severity' => $fileSpecificError->canBeIgnored() ? 'major' : 'blocker', - 'location' => [ - 'path' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), - 'lines' => [ - 'begin' => $fileSpecificError->getLine(), - ], - ], - ]; + public function formatErrors(AnalysisResult $analysisResult, Output $output): int + { + $errorsArray = []; - $errorsArray[] = $error; - } + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $error = [ + 'description' => $fileSpecificError->getMessage(), + 'fingerprint' => hash( + 'sha256', + implode( + [ + $fileSpecificError->getFile(), + $fileSpecificError->getLine(), + $fileSpecificError->getMessage(), + ] + ) + ), + 'severity' => $fileSpecificError->canBeIgnored() ? 'major' : 'blocker', + 'location' => [ + 'path' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), + 'lines' => [ + 'begin' => $fileSpecificError->getLine(), + ], + ], + ]; - foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { - $errorsArray[] = [ - 'description' => $notFileSpecificError, - 'fingerprint' => hash('sha256', $notFileSpecificError), - 'severity' => 'major', - 'location' => [ - 'path' => '', - 'lines' => [ - 'begin' => 0, - ], - ], - ]; - } + $errorsArray[] = $error; + } - $json = Json::encode($errorsArray, Json::PRETTY); + foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { + $errorsArray[] = [ + 'description' => $notFileSpecificError, + 'fingerprint' => hash('sha256', $notFileSpecificError), + 'severity' => 'major', + 'location' => [ + 'path' => '', + 'lines' => [ + 'begin' => 0, + ], + ], + ]; + } - $output->writeRaw($json); + $json = Json::encode($errorsArray, Json::PRETTY); - return $analysisResult->hasErrors() ? 1 : 0; - } + $output->writeRaw($json); + return $analysisResult->hasErrors() ? 1 : 0; + } } diff --git a/src/Command/ErrorFormatter/JsonErrorFormatter.php b/src/Command/ErrorFormatter/JsonErrorFormatter.php index c2ea2f6f56..6c782a86cf 100644 --- a/src/Command/ErrorFormatter/JsonErrorFormatter.php +++ b/src/Command/ErrorFormatter/JsonErrorFormatter.php @@ -1,4 +1,6 @@ -pretty = $pretty; - } - - public function formatErrors(AnalysisResult $analysisResult, Output $output): int - { - $errorsArray = [ - 'totals' => [ - 'errors' => count($analysisResult->getNotFileSpecificErrors()), - 'file_errors' => count($analysisResult->getFileSpecificErrors()), - ], - 'files' => [], - 'errors' => [], - ]; - - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $file = $fileSpecificError->getFile(); - if (!array_key_exists($file, $errorsArray['files'])) { - $errorsArray['files'][$file] = [ - 'errors' => 0, - 'messages' => [], - ]; - } - $errorsArray['files'][$file]['errors']++; - - $errorsArray['files'][$file]['messages'][] = [ - 'message' => $fileSpecificError->getMessage(), - 'line' => $fileSpecificError->getLine(), - 'ignorable' => $fileSpecificError->canBeIgnored(), - ]; - } - - foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { - $errorsArray['errors'][] = $notFileSpecificError; - } - - $json = Json::encode($errorsArray, $this->pretty ? Json::PRETTY : 0); - - $output->writeRaw($json); - - return $analysisResult->hasErrors() ? 1 : 0; - } - + private bool $pretty; + + public function __construct(bool $pretty) + { + $this->pretty = $pretty; + } + + public function formatErrors(AnalysisResult $analysisResult, Output $output): int + { + $errorsArray = [ + 'totals' => [ + 'errors' => count($analysisResult->getNotFileSpecificErrors()), + 'file_errors' => count($analysisResult->getFileSpecificErrors()), + ], + 'files' => [], + 'errors' => [], + ]; + + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $file = $fileSpecificError->getFile(); + if (!array_key_exists($file, $errorsArray['files'])) { + $errorsArray['files'][$file] = [ + 'errors' => 0, + 'messages' => [], + ]; + } + $errorsArray['files'][$file]['errors']++; + + $errorsArray['files'][$file]['messages'][] = [ + 'message' => $fileSpecificError->getMessage(), + 'line' => $fileSpecificError->getLine(), + 'ignorable' => $fileSpecificError->canBeIgnored(), + ]; + } + + foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { + $errorsArray['errors'][] = $notFileSpecificError; + } + + $json = Json::encode($errorsArray, $this->pretty ? Json::PRETTY : 0); + + $output->writeRaw($json); + + return $analysisResult->hasErrors() ? 1 : 0; + } } diff --git a/src/Command/ErrorFormatter/JunitErrorFormatter.php b/src/Command/ErrorFormatter/JunitErrorFormatter.php index 3476afed65..30a27599fe 100644 --- a/src/Command/ErrorFormatter/JunitErrorFormatter.php +++ b/src/Command/ErrorFormatter/JunitErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - } - - public function formatErrors( - AnalysisResult $analysisResult, - Output $output - ): int - { - $result = ''; - $result .= sprintf( - '', - $analysisResult->getTotalErrorsCount(), - $analysisResult->getTotalErrorsCount() - ); - - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $fileName = $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()); - $result .= $this->createTestCase( - sprintf('%s:%s', $fileName, (string) $fileSpecificError->getLine()), - 'ERROR', - $this->escape($fileSpecificError->getMessage()) - ); - } - - foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { - $result .= $this->createTestCase('General error', 'ERROR', $this->escape($notFileSpecificError)); - } - - foreach ($analysisResult->getWarnings() as $warning) { - $result .= $this->createTestCase('Warning', 'WARNING', $this->escape($warning)); - } - - if (!$analysisResult->hasErrors()) { - $result .= $this->createTestCase('phpstan', ''); - } - - $result .= ''; - - $output->writeRaw($result); - - return $analysisResult->hasErrors() ? 1 : 0; - } - - /** - * Format a single test case - * - * @param string $reference - * @param string|null $message - * - * @return string - */ - private function createTestCase(string $reference, string $type, ?string $message = null): string - { - $result = sprintf('', $this->escape($reference)); - - if ($message !== null) { - $result .= sprintf('', $this->escape($type), $this->escape($message)); - } - - $result .= ''; - - return $result; - } - - /** - * Escapes values for using in XML - * - * @param string $string - * @return string - */ - private function escape(string $string): string - { - return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); - } - + private \PHPStan\File\RelativePathHelper $relativePathHelper; + + public function __construct(RelativePathHelper $relativePathHelper) + { + $this->relativePathHelper = $relativePathHelper; + } + + public function formatErrors( + AnalysisResult $analysisResult, + Output $output + ): int { + $result = ''; + $result .= sprintf( + '', + $analysisResult->getTotalErrorsCount(), + $analysisResult->getTotalErrorsCount() + ); + + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $fileName = $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()); + $result .= $this->createTestCase( + sprintf('%s:%s', $fileName, (string) $fileSpecificError->getLine()), + 'ERROR', + $this->escape($fileSpecificError->getMessage()) + ); + } + + foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { + $result .= $this->createTestCase('General error', 'ERROR', $this->escape($notFileSpecificError)); + } + + foreach ($analysisResult->getWarnings() as $warning) { + $result .= $this->createTestCase('Warning', 'WARNING', $this->escape($warning)); + } + + if (!$analysisResult->hasErrors()) { + $result .= $this->createTestCase('phpstan', ''); + } + + $result .= ''; + + $output->writeRaw($result); + + return $analysisResult->hasErrors() ? 1 : 0; + } + + /** + * Format a single test case + * + * @param string $reference + * @param string|null $message + * + * @return string + */ + private function createTestCase(string $reference, string $type, ?string $message = null): string + { + $result = sprintf('', $this->escape($reference)); + + if ($message !== null) { + $result .= sprintf('', $this->escape($type), $this->escape($message)); + } + + $result .= ''; + + return $result; + } + + /** + * Escapes values for using in XML + * + * @param string $string + * @return string + */ + private function escape(string $string): string + { + return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); + } } diff --git a/src/Command/ErrorFormatter/RawErrorFormatter.php b/src/Command/ErrorFormatter/RawErrorFormatter.php index 7d926c3b7c..e522f4888f 100644 --- a/src/Command/ErrorFormatter/RawErrorFormatter.php +++ b/src/Command/ErrorFormatter/RawErrorFormatter.php @@ -1,4 +1,6 @@ -getNotFileSpecificErrors() as $notFileSpecificError) { - $output->writeRaw(sprintf('?:?:%s', $notFileSpecificError)); - $output->writeLineFormatted(''); - } - - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - $output->writeRaw( - sprintf( - '%s:%d:%s', - $fileSpecificError->getFile(), - $fileSpecificError->getLine() ?? '?', - $fileSpecificError->getMessage() - ) - ); - $output->writeLineFormatted(''); - } - - foreach ($analysisResult->getWarnings() as $warning) { - $output->writeRaw(sprintf('?:?:%s', $warning)); - $output->writeLineFormatted(''); - } - - return $analysisResult->hasErrors() ? 1 : 0; - } - + public function formatErrors( + AnalysisResult $analysisResult, + Output $output + ): int { + foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { + $output->writeRaw(sprintf('?:?:%s', $notFileSpecificError)); + $output->writeLineFormatted(''); + } + + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + $output->writeRaw( + sprintf( + '%s:%d:%s', + $fileSpecificError->getFile(), + $fileSpecificError->getLine() ?? '?', + $fileSpecificError->getMessage() + ) + ); + $output->writeLineFormatted(''); + } + + foreach ($analysisResult->getWarnings() as $warning) { + $output->writeRaw(sprintf('?:?:%s', $warning)); + $output->writeLineFormatted(''); + } + + return $analysisResult->hasErrors() ? 1 : 0; + } } diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index a02a7aa435..8861f85c9c 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - $this->showTipsOfTheDay = $showTipsOfTheDay; - } - - public function formatErrors( - AnalysisResult $analysisResult, - Output $output - ): int - { - $projectConfigFile = 'phpstan.neon'; - if ($analysisResult->getProjectConfigFile() !== null) { - $projectConfigFile = $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()); - } - - $style = $output->getStyle(); - - if (!$analysisResult->hasErrors() && !$analysisResult->hasWarnings()) { - $style->success('No errors'); - if ($this->showTipsOfTheDay) { - if ($analysisResult->isDefaultLevelUsed()) { - $output->writeLineFormatted('💡 Tip of the Day:'); - $output->writeLineFormatted(sprintf( - "PHPStan is performing only the most basic checks.\nYou can pass a higher rule level through the --%s option\n(the default and current level is %d) to analyse code more thoroughly.", - AnalyseCommand::OPTION_LEVEL, - AnalyseCommand::DEFAULT_LEVEL - )); - $output->writeLineFormatted(''); - } - } - - return 0; - } - - /** @var array $fileErrors */ - $fileErrors = []; - foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { - if (!isset($fileErrors[$fileSpecificError->getFile()])) { - $fileErrors[$fileSpecificError->getFile()] = []; - } - - $fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError; - } - - foreach ($fileErrors as $file => $errors) { - $rows = []; - foreach ($errors as $error) { - $message = $error->getMessage(); - if ($error->getTip() !== null) { - $tip = $error->getTip(); - $tip = str_replace('%configurationFile%', $projectConfigFile, $tip); - $message .= "\n💡 " . $tip; - } - $rows[] = [ - (string) $error->getLine(), - $message, - ]; - } - - $relativeFilePath = $this->relativePathHelper->getRelativePath($file); - - $style->table(['Line', $relativeFilePath], $rows); - } - - if (count($analysisResult->getNotFileSpecificErrors()) > 0) { - $style->table(['', 'Error'], array_map(static function (string $error): array { - return ['', $error]; - }, $analysisResult->getNotFileSpecificErrors())); - } - - $warningsCount = count($analysisResult->getWarnings()); - if ($warningsCount > 0) { - $style->table(['', 'Warning'], array_map(static function (string $warning): array { - return ['', $warning]; - }, $analysisResult->getWarnings())); - } - - $finalMessage = sprintf($analysisResult->getTotalErrorsCount() === 1 ? 'Found %d error' : 'Found %d errors', $analysisResult->getTotalErrorsCount()); - if ($warningsCount > 0) { - $finalMessage .= sprintf($warningsCount === 1 ? ' and %d warning' : ' and %d warnings', $warningsCount); - } - - if ($analysisResult->getTotalErrorsCount() > 0) { - $style->error($finalMessage); - } else { - $style->warning($finalMessage); - } - - return $analysisResult->getTotalErrorsCount() > 0 ? 1 : 0; - } - + private RelativePathHelper $relativePathHelper; + + private bool $showTipsOfTheDay; + + public function __construct( + RelativePathHelper $relativePathHelper, + bool $showTipsOfTheDay + ) { + $this->relativePathHelper = $relativePathHelper; + $this->showTipsOfTheDay = $showTipsOfTheDay; + } + + public function formatErrors( + AnalysisResult $analysisResult, + Output $output + ): int { + $projectConfigFile = 'phpstan.neon'; + if ($analysisResult->getProjectConfigFile() !== null) { + $projectConfigFile = $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()); + } + + $style = $output->getStyle(); + + if (!$analysisResult->hasErrors() && !$analysisResult->hasWarnings()) { + $style->success('No errors'); + if ($this->showTipsOfTheDay) { + if ($analysisResult->isDefaultLevelUsed()) { + $output->writeLineFormatted('💡 Tip of the Day:'); + $output->writeLineFormatted(sprintf( + "PHPStan is performing only the most basic checks.\nYou can pass a higher rule level through the --%s option\n(the default and current level is %d) to analyse code more thoroughly.", + AnalyseCommand::OPTION_LEVEL, + AnalyseCommand::DEFAULT_LEVEL + )); + $output->writeLineFormatted(''); + } + } + + return 0; + } + + /** @var array $fileErrors */ + $fileErrors = []; + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + if (!isset($fileErrors[$fileSpecificError->getFile()])) { + $fileErrors[$fileSpecificError->getFile()] = []; + } + + $fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError; + } + + foreach ($fileErrors as $file => $errors) { + $rows = []; + foreach ($errors as $error) { + $message = $error->getMessage(); + if ($error->getTip() !== null) { + $tip = $error->getTip(); + $tip = str_replace('%configurationFile%', $projectConfigFile, $tip); + $message .= "\n💡 " . $tip; + } + $rows[] = [ + (string) $error->getLine(), + $message, + ]; + } + + $relativeFilePath = $this->relativePathHelper->getRelativePath($file); + + $style->table(['Line', $relativeFilePath], $rows); + } + + if (count($analysisResult->getNotFileSpecificErrors()) > 0) { + $style->table(['', 'Error'], array_map(static function (string $error): array { + return ['', $error]; + }, $analysisResult->getNotFileSpecificErrors())); + } + + $warningsCount = count($analysisResult->getWarnings()); + if ($warningsCount > 0) { + $style->table(['', 'Warning'], array_map(static function (string $warning): array { + return ['', $warning]; + }, $analysisResult->getWarnings())); + } + + $finalMessage = sprintf($analysisResult->getTotalErrorsCount() === 1 ? 'Found %d error' : 'Found %d errors', $analysisResult->getTotalErrorsCount()); + if ($warningsCount > 0) { + $finalMessage .= sprintf($warningsCount === 1 ? ' and %d warning' : ' and %d warnings', $warningsCount); + } + + if ($analysisResult->getTotalErrorsCount() > 0) { + $style->error($finalMessage); + } else { + $style->warning($finalMessage); + } + + return $analysisResult->getTotalErrorsCount() > 0 ? 1 : 0; + } } diff --git a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php index 17bbb080af..175d2389c2 100644 --- a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php +++ b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php @@ -1,4 +1,6 @@ -relativePathHelper = $relativePathHelper; - } - - public function formatErrors(AnalysisResult $analysisResult, Output $output): int - { - $result = ''; - $fileSpecificErrors = $analysisResult->getFileSpecificErrors(); - $notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors(); - $warnings = $analysisResult->getWarnings(); - - if (count($fileSpecificErrors) === 0 && count($notFileSpecificErrors) === 0 && count($warnings) === 0) { - return 0; - } - - $result .= $this->createTeamcityLine('inspectionType', [ - 'id' => 'phpstan', - 'name' => 'phpstan', - 'category' => 'phpstan', - 'description' => 'phpstan Inspection', - ]); - - foreach ($fileSpecificErrors as $fileSpecificError) { - $result .= $this->createTeamcityLine('inspection', [ - 'typeId' => 'phpstan', - 'message' => $fileSpecificError->getMessage(), - 'file' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), - 'line' => $fileSpecificError->getLine(), - // additional attributes - 'SEVERITY' => 'ERROR', - 'ignorable' => $fileSpecificError->canBeIgnored(), - 'tip' => $fileSpecificError->getTip(), - ]); - } - - foreach ($notFileSpecificErrors as $notFileSpecificError) { - $result .= $this->createTeamcityLine('inspection', [ - 'typeId' => 'phpstan', - 'message' => $notFileSpecificError, - // the file is required - 'file' => $analysisResult->getProjectConfigFile() !== null ? $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()) : '.', - 'SEVERITY' => 'ERROR', - ]); - } - - foreach ($warnings as $warning) { - $result .= $this->createTeamcityLine('inspection', [ - 'typeId' => 'phpstan', - 'message' => $warning, - // the file is required - 'file' => $analysisResult->getProjectConfigFile() !== null ? $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()) : '.', - 'SEVERITY' => 'WARNING', - ]); - } - - $output->writeRaw($result); - - return $analysisResult->hasErrors() ? 1 : 0; - } - - /** - * Creates a Teamcity report line - * - * @param string $messageName The message name - * @param mixed[] $keyValuePairs The key=>value pairs - * @return string The Teamcity report line - */ - private function createTeamcityLine(string $messageName, array $keyValuePairs): string - { - $string = '##teamcity[' . $messageName; - foreach ($keyValuePairs as $key => $value) { - if (is_string($value)) { - $value = $this->escape($value); - } - $string .= ' ' . $key . '=\'' . $value . '\''; - } - return $string . ']' . PHP_EOL; - } - - /** - * Escapes the given string for Teamcity output - * - * @param string $string The string to escape - * @return string The escaped string - */ - private function escape(string $string): string - { - $replacements = [ - '~\n~' => '|n', - '~\r~' => '|r', - '~([\'\|\[\]])~' => '|$1', - ]; - return (string) preg_replace(array_keys($replacements), array_values($replacements), $string); - } - + private RelativePathHelper $relativePathHelper; + + public function __construct(RelativePathHelper $relativePathHelper) + { + $this->relativePathHelper = $relativePathHelper; + } + + public function formatErrors(AnalysisResult $analysisResult, Output $output): int + { + $result = ''; + $fileSpecificErrors = $analysisResult->getFileSpecificErrors(); + $notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors(); + $warnings = $analysisResult->getWarnings(); + + if (count($fileSpecificErrors) === 0 && count($notFileSpecificErrors) === 0 && count($warnings) === 0) { + return 0; + } + + $result .= $this->createTeamcityLine('inspectionType', [ + 'id' => 'phpstan', + 'name' => 'phpstan', + 'category' => 'phpstan', + 'description' => 'phpstan Inspection', + ]); + + foreach ($fileSpecificErrors as $fileSpecificError) { + $result .= $this->createTeamcityLine('inspection', [ + 'typeId' => 'phpstan', + 'message' => $fileSpecificError->getMessage(), + 'file' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), + 'line' => $fileSpecificError->getLine(), + // additional attributes + 'SEVERITY' => 'ERROR', + 'ignorable' => $fileSpecificError->canBeIgnored(), + 'tip' => $fileSpecificError->getTip(), + ]); + } + + foreach ($notFileSpecificErrors as $notFileSpecificError) { + $result .= $this->createTeamcityLine('inspection', [ + 'typeId' => 'phpstan', + 'message' => $notFileSpecificError, + // the file is required + 'file' => $analysisResult->getProjectConfigFile() !== null ? $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()) : '.', + 'SEVERITY' => 'ERROR', + ]); + } + + foreach ($warnings as $warning) { + $result .= $this->createTeamcityLine('inspection', [ + 'typeId' => 'phpstan', + 'message' => $warning, + // the file is required + 'file' => $analysisResult->getProjectConfigFile() !== null ? $this->relativePathHelper->getRelativePath($analysisResult->getProjectConfigFile()) : '.', + 'SEVERITY' => 'WARNING', + ]); + } + + $output->writeRaw($result); + + return $analysisResult->hasErrors() ? 1 : 0; + } + + /** + * Creates a Teamcity report line + * + * @param string $messageName The message name + * @param mixed[] $keyValuePairs The key=>value pairs + * @return string The Teamcity report line + */ + private function createTeamcityLine(string $messageName, array $keyValuePairs): string + { + $string = '##teamcity[' . $messageName; + foreach ($keyValuePairs as $key => $value) { + if (is_string($value)) { + $value = $this->escape($value); + } + $string .= ' ' . $key . '=\'' . $value . '\''; + } + return $string . ']' . PHP_EOL; + } + + /** + * Escapes the given string for Teamcity output + * + * @param string $string The string to escape + * @return string The escaped string + */ + private function escape(string $string): string + { + $replacements = [ + '~\n~' => '|n', + '~\r~' => '|r', + '~([\'\|\[\]])~' => '|$1', + ]; + return (string) preg_replace(array_keys($replacements), array_values($replacements), $string); + } } diff --git a/src/Command/ErrorsConsoleStyle.php b/src/Command/ErrorsConsoleStyle.php index f106573a69..8286e90c9b 100644 --- a/src/Command/ErrorsConsoleStyle.php +++ b/src/Command/ErrorsConsoleStyle.php @@ -1,4 +1,6 @@ -showProgress = $input->hasOption(self::OPTION_NO_PROGRESS) && !(bool) $input->getOption(self::OPTION_NO_PROGRESS); - } - - private function isCiDetected(): bool - { - if ($this->isCiDetected === null) { - $ciDetector = new CiDetector(); - $this->isCiDetected = $ciDetector->isCiDetected(); - } - - return $this->isCiDetected; - } - - /** - * @param string[] $headers - * @param string[][] $rows - */ - public function table(array $headers, array $rows): void - { - /** @var int $terminalWidth */ - $terminalWidth = (new \Symfony\Component\Console\Terminal())->getWidth() - 2; - $maxHeaderWidth = strlen($headers[0]); - foreach ($rows as $row) { - $length = strlen($row[0]); - if ($maxHeaderWidth !== 0 && $length <= $maxHeaderWidth) { - continue; - } - - $maxHeaderWidth = $length; - } - - $wrap = static function ($rows) use ($terminalWidth, $maxHeaderWidth): array { - return array_map(static function ($row) use ($terminalWidth, $maxHeaderWidth): array { - return array_map(static function ($s) use ($terminalWidth, $maxHeaderWidth) { - if ($terminalWidth > $maxHeaderWidth + 5) { - return wordwrap( - $s, - $terminalWidth - $maxHeaderWidth - 5, - "\n", - true - ); - } - - return $s; - }, $row); - }, $rows); - }; - - parent::table($headers, $wrap($rows)); - } - - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - * @param int $max - */ - public function createProgressBar($max = 0): ProgressBar - { - $this->progressBar = parent::createProgressBar($max); - $this->progressBar->setOverwrite(!$this->isCiDetected()); - return $this->progressBar; - } - - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - * @param int $max - */ - public function progressStart($max = 0): void - { - if (!$this->showProgress) { - return; - } - parent::progressStart($max); - } - - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - * @param int $step - */ - public function progressAdvance($step = 1): void - { - if (!$this->showProgress) { - return; - } - - if (!$this->isCiDetected() && $step > 0) { - $stepTime = (time() - $this->progressBar->getStartTime()) / $step; - if ($stepTime > 0 && $stepTime < 1) { - $this->progressBar->setRedrawFrequency((int) (1 / $stepTime)); - } else { - $this->progressBar->setRedrawFrequency(1); - } - } - - parent::progressAdvance($step); - } - - public function progressFinish(): void - { - if (!$this->showProgress) { - return; - } - parent::progressFinish(); - } - + public const OPTION_NO_PROGRESS = 'no-progress'; + + private bool $showProgress; + + private \Symfony\Component\Console\Helper\ProgressBar $progressBar; + + private ?bool $isCiDetected = null; + + public function __construct(InputInterface $input, OutputInterface $output) + { + parent::__construct($input, $output); + $this->showProgress = $input->hasOption(self::OPTION_NO_PROGRESS) && !(bool) $input->getOption(self::OPTION_NO_PROGRESS); + } + + private function isCiDetected(): bool + { + if ($this->isCiDetected === null) { + $ciDetector = new CiDetector(); + $this->isCiDetected = $ciDetector->isCiDetected(); + } + + return $this->isCiDetected; + } + + /** + * @param string[] $headers + * @param string[][] $rows + */ + public function table(array $headers, array $rows): void + { + /** @var int $terminalWidth */ + $terminalWidth = (new \Symfony\Component\Console\Terminal())->getWidth() - 2; + $maxHeaderWidth = strlen($headers[0]); + foreach ($rows as $row) { + $length = strlen($row[0]); + if ($maxHeaderWidth !== 0 && $length <= $maxHeaderWidth) { + continue; + } + + $maxHeaderWidth = $length; + } + + $wrap = static function ($rows) use ($terminalWidth, $maxHeaderWidth): array { + return array_map(static function ($row) use ($terminalWidth, $maxHeaderWidth): array { + return array_map(static function ($s) use ($terminalWidth, $maxHeaderWidth) { + if ($terminalWidth > $maxHeaderWidth + 5) { + return wordwrap( + $s, + $terminalWidth - $maxHeaderWidth - 5, + "\n", + true + ); + } + + return $s; + }, $row); + }, $rows); + }; + + parent::table($headers, $wrap($rows)); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param int $max + */ + public function createProgressBar($max = 0): ProgressBar + { + $this->progressBar = parent::createProgressBar($max); + $this->progressBar->setOverwrite(!$this->isCiDetected()); + return $this->progressBar; + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param int $max + */ + public function progressStart($max = 0): void + { + if (!$this->showProgress) { + return; + } + parent::progressStart($max); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param int $step + */ + public function progressAdvance($step = 1): void + { + if (!$this->showProgress) { + return; + } + + if (!$this->isCiDetected() && $step > 0) { + $stepTime = (time() - $this->progressBar->getStartTime()) / $step; + if ($stepTime > 0 && $stepTime < 1) { + $this->progressBar->setRedrawFrequency((int) (1 / $stepTime)); + } else { + $this->progressBar->setRedrawFrequency(1); + } + } + + parent::progressAdvance($step); + } + + public function progressFinish(): void + { + if (!$this->showProgress) { + return; + } + parent::progressFinish(); + } } diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 957dc82bb0..cea3536969 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -1,4 +1,6 @@ -fileMonitor = $fileMonitor; - $this->resultCacheManagerFactory = $resultCacheManagerFactory; - $this->resultCacheClearer = $resultCacheClearer; - $this->ignoredErrorHelper = $ignoredErrorHelper; - $this->cpuCoreCounter = $cpuCoreCounter; - $this->scheduler = $scheduler; - $this->analysedPaths = $analysedPaths; - $this->currentWorkingDirectory = $currentWorkingDirectory; - $this->fixerTmpDir = $fixerTmpDir; - $this->maximumNumberOfProcesses = $maximumNumberOfProcesses; - } - - /** - * @param \Symfony\Component\Console\Output\OutputInterface $output - * @param \PHPStan\Analyser\Error[] $fileSpecificErrors - * @param string[] $notFileSpecificErrors - * @return int - */ - public function run( - ?string $projectConfigFile, - InceptionResult $inceptionResult, - InputInterface $input, - OutputInterface $output, - array $fileSpecificErrors, - array $notFileSpecificErrors, - int $filesCount, - string $mainScript - ): int - { - $loop = new StreamSelectLoop(); - $server = new \React\Socket\TcpServer('127.0.0.1:0', $loop); - /** @var string $serverAddress */ - $serverAddress = $server->getAddress(); - - /** @var int $serverPort */ - $serverPort = parse_url($serverAddress, PHP_URL_PORT); - - $reanalyseProcessQueue = new RunnableQueue( - new class () implements RunnableQueueLogger { - - public function log(string $message): void - { - } - - }, - min($this->cpuCoreCounter->getNumberOfCpuCores(), $this->maximumNumberOfProcesses) - ); - - $server->on('connection', function (ConnectionInterface $connection) use ($loop, $projectConfigFile, $input, $output, $fileSpecificErrors, $notFileSpecificErrors, $mainScript, $filesCount, $reanalyseProcessQueue, $inceptionResult): void { - $decoder = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, 128 * 1024 * 1024); - $encoder = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); - $encoder->write(['action' => 'initialData', 'data' => [ - 'fileSpecificErrors' => $fileSpecificErrors, - 'notFileSpecificErrors' => $notFileSpecificErrors, - 'currentWorkingDirectory' => $this->currentWorkingDirectory, - 'analysedPaths' => $this->analysedPaths, - 'projectConfigFile' => $projectConfigFile, - 'filesCount' => $filesCount, - 'phpstanVersion' => $this->getPhpstanVersion(), - ]]); - $decoder->on('data', function (array $data) use ( - $loop, - $encoder, - $projectConfigFile, - $input, - $output, - $mainScript, - $reanalyseProcessQueue, - $inceptionResult - ): void { - if ($data['action'] === 'webPort') { - $output->writeln(sprintf('Open your web browser at: http://127.0.0.1:%d', $data['data']['port'])); - $output->writeln('Press [Ctrl-C] to quit.'); - return; - } - if ($data['action'] === 'restoreResultCache') { - $this->fixerSuggestionId = $data['data']['fixerSuggestionId']; - } - if ($data['action'] !== 'reanalyse') { - return; - } - - $id = $data['id']; - - $this->reanalyseWithTmpFile( - $loop, - $inceptionResult, - $mainScript, - $reanalyseProcessQueue, - $projectConfigFile, - $data['data']['tmpFile'], - $data['data']['insteadOfFile'], - $data['data']['fixerSuggestionId'], - $input - )->done(static function (string $output) use ($encoder, $id): void { - $encoder->write(['id' => $id, 'response' => Json::decode($output, Json::FORCE_ARRAY)]); - }, static function (\Throwable $e) use ($encoder, $id, $output): void { - if ($e instanceof \PHPStan\Process\ProcessCrashedException) { - $output->writeln('Worker process exited: ' . $e->getMessage() . ''); - $encoder->write(['id' => $id, 'error' => $e->getMessage()]); - return; - } - if ($e instanceof \PHPStan\Process\ProcessCanceledException) { - $encoder->write(['id' => $id, 'error' => $e->getMessage()]); - return; - } - - $output->writeln('Unexpected error: ' . $e->getMessage() . ''); - $encoder->write(['id' => $id, 'error' => $e->getMessage()]); - }); - }); - - $this->fileMonitor->initialize($this->analysedPaths); - $this->monitorFileChanges($loop, function (FileMonitorResult $changes) use ($loop, $mainScript, $projectConfigFile, $input, $encoder, $output, $reanalyseProcessQueue, $inceptionResult): void { - $reanalyseProcessQueue->cancelAll(); - if ($this->processInProgress !== null) { - $this->processInProgress->cancel(); - $this->processInProgress = null; - } else { - $encoder->write(['action' => 'analysisStart']); - } - - $this->reanalyseAfterFileChanges( - $loop, - $inceptionResult, - $mainScript, - $projectConfigFile, - $this->fixerSuggestionId, - $input - )->done(function (array $json) use ($encoder, $changes): void { - $this->processInProgress = null; - $this->fixerSuggestionId = null; - $encoder->write(['action' => 'analysisEnd', 'data' => [ - 'fileSpecificErrors' => $json['fileSpecificErrors'], - 'notFileSpecificErrors' => $json['notFileSpecificErrors'], - 'filesCount' => $changes->getTotalFilesCount(), - ]]); - $this->resultCacheClearer->clearTemporaryCaches(); - }, function (\Throwable $e) use ($encoder, $output): void { - $this->processInProgress = null; - $this->fixerSuggestionId = null; - $output->writeln('Worker process exited: ' . $e->getMessage() . ''); - $encoder->write(['action' => 'analysisCrash', 'data' => [ - 'error' => $e->getMessage(), - ]]); - }); - }); - }); - - try { - $fixerProcess = $this->getFixerProcess($output, $serverPort); - } catch (\PHPStan\Command\FixerProcessException $e) { - return 1; - } - - $fixerProcess->start($loop); - $fixerProcess->on('exit', static function ($exitCode) use ($output, $loop): void { - $loop->stop(); - if ($exitCode === null) { - return; - } - if ($exitCode === 0) { - return; - } - $output->writeln(sprintf('PHPStan Pro process exited with code %d.', $exitCode)); - }); - - $loop->run(); - - return 0; - } - - /** - * @throws FixerProcessException - */ - private function getFixerProcess(OutputInterface $output, int $serverPort): Process - { - if (!@mkdir($this->fixerTmpDir, 0777) && !is_dir($this->fixerTmpDir)) { - $output->writeln(sprintf('Cannot create a temp directory %s', $this->fixerTmpDir)); - throw new \PHPStan\Command\FixerProcessException(); - } - - $pharPath = $this->fixerTmpDir . '/phpstan-fixer.phar'; - $infoPath = $this->fixerTmpDir . '/phar-info.json'; - - try { - $this->downloadPhar($output, $pharPath, $infoPath); - } catch (\RuntimeException $e) { - if (!file_exists($pharPath)) { - $output->writeln('Could not download the PHPStan Pro executable.'); - $output->writeln($e->getMessage()); - - throw new \PHPStan\Command\FixerProcessException(); - } - } - - $pubKeyPath = $pharPath . '.pubkey'; - FileWriter::write($pubKeyPath, FileReader::read(__DIR__ . '/fixer-phar.pubkey')); - - try { - $phar = new Phar($pharPath); - } catch (\Throwable $e) { - @unlink($pharPath); - @unlink($infoPath); - $output->writeln('PHPStan Pro PHAR signature is corrupted.'); - - throw new \PHPStan\Command\FixerProcessException(); - } - - if ($phar->getSignature()['hash_type'] !== 'OpenSSL') { - @unlink($pharPath); - @unlink($infoPath); - $output->writeln('PHPStan Pro PHAR signature is corrupted.'); - - throw new \PHPStan\Command\FixerProcessException(); - } - - $env = null; - $forcedPort = $_SERVER['PHPSTAN_PRO_WEB_PORT'] ?? null; - if ($forcedPort !== null) { - $env['PHPSTAN_PRO_WEB_PORT'] = $_SERVER['PHPSTAN_PRO_WEB_PORT']; - $isDocker = $this->isDockerRunning(); - if ($isDocker) { - $output->writeln('Running in Docker? Don\'t forget to do these steps:'); - - $output->writeln('1) Publish this port when running Docker:'); - $output->writeln(sprintf(' -p 127.0.0.1:%d:%d', $_SERVER['PHPSTAN_PRO_WEB_PORT'], $_SERVER['PHPSTAN_PRO_WEB_PORT'])); - $output->writeln('2) Map the temp directory to a persistent volume'); - $output->writeln(' so that you don\'t have to log in every time:'); - $output->writeln(sprintf(' -v ~/.phpstan-pro:%s', $this->fixerTmpDir)); - $output->writeln(''); - } - } else { - $isDocker = $this->isDockerRunning(); - if ($isDocker) { - $output->writeln('Running in Docker? You need to do these steps in order to launch PHPStan Pro:'); - $output->writeln(''); - $output->writeln('1) Set the PHPSTAN_PRO_WEB_PORT environment variable in the Dockerfile:'); - $output->writeln(' ENV PHPSTAN_PRO_WEB_PORT=11111'); - $output->writeln('2) Expose this port in the Dockerfile:'); - $output->writeln(' EXPOSE 11111'); - $output->writeln('3) Publish this port when running Docker:'); - $output->writeln(' -p 127.0.0.1:11111:11111'); - $output->writeln('4) Map the temp directory to a persistent volume'); - $output->writeln(' so that you don\'t have to log in every time:'); - $output->writeln(sprintf(' -v ~/phpstan-pro:%s', $this->fixerTmpDir)); - $output->writeln(''); - } - } - - return new Process(sprintf('%s -d memory_limit=%s %s --port %d', PHP_BINARY, escapeshellarg(ini_get('memory_limit')), escapeshellarg($pharPath), $serverPort), null, $env, []); - } - - private function downloadPhar( - OutputInterface $output, - string $pharPath, - string $infoPath - ): void - { - $currentVersion = null; - if (file_exists($pharPath) && file_exists($infoPath)) { - /** @var array{version: string, date: string} $currentInfo */ - $currentInfo = Json::decode(FileReader::read($infoPath), Json::FORCE_ARRAY); - $currentVersion = $currentInfo['version']; - $currentDate = \DateTime::createFromFormat(\DateTime::ATOM, $currentInfo['date']); - if ($currentDate === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ((new \DateTimeImmutable('', new \DateTimeZone('UTC'))) <= $currentDate->modify('+24 hours')) { - return; - } - - $output->writeln('Checking if there\'s a new PHPStan Pro release...'); - } - - $loop = new StreamSelectLoop(); - $client = new Browser( - $loop, - new Connector( - $loop, - [ - 'timeout' => 5, - 'tls' => [ - 'cafile' => CaBundle::getBundledCaBundlePath(), - ], - 'dns' => '1.1.1.1', - ] - ) - ); - - /** @var array{url: string, version: string} $latestInfo */ - $latestInfo = Json::decode((string) await($client->get('https://fixer-download-api.phpstan.com/latest'), $loop, 5.0)->getBody(), Json::FORCE_ARRAY); // @phpstan-ignore-line - if ($currentVersion !== null && $latestInfo['version'] === $currentVersion) { - $this->writeInfoFile($infoPath, $latestInfo['version']); - $output->writeln('You\'re running the latest PHPStan Pro!'); - return; - } - - $output->writeln('Downloading the latest PHPStan Pro...'); - - $pharPathResource = fopen($pharPath, 'w'); - if ($pharPathResource === false) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Could not open file %s for writing.', $pharPath)); - } - $progressBar = new ProgressBar($output); - $client->requestStreaming('GET', $latestInfo['url'])->done(static function (ResponseInterface $response) use ($progressBar, $pharPathResource): void { - $body = $response->getBody(); - if (!$body instanceof \React\Stream\ReadableStreamInterface) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $totalSize = (int) $response->getHeaderLine('Content-Length'); - $progressBar->setFormat('file_download'); - $progressBar->setMessage(sprintf('%.2f MB', $totalSize / 1000000), 'fileSize'); - $progressBar->start($totalSize); - - $bytes = 0; - $body->on('data', static function ($chunk) use ($pharPathResource, $progressBar, &$bytes): void { - $bytes += strlen($chunk); - fwrite($pharPathResource, $chunk); - $progressBar->setProgress($bytes); - }); - }, static function (\Throwable $e) use ($output): void { - $output->writeln(sprintf('Could not download the PHPStan Pro executable: %s', $e->getMessage())); - }); - - $loop->run(); - - fclose($pharPathResource); - - $progressBar->finish(); - $output->writeln(''); - $output->writeln(''); - - $this->writeInfoFile($infoPath, $latestInfo['version']); - } - - private function writeInfoFile(string $infoPath, string $version): void - { - FileWriter::write($infoPath, Json::encode([ - 'version' => $version, - 'date' => (new \DateTimeImmutable('', new \DateTimeZone('UTC')))->format(\DateTime::ATOM), - ])); - } - - /** - * @param LoopInterface $loop - * @param callable(FileMonitorResult): void $hasChangesCallback - */ - private function monitorFileChanges(LoopInterface $loop, callable $hasChangesCallback): void - { - $callback = function () use (&$callback, $loop, $hasChangesCallback): void { - $changes = $this->fileMonitor->getChanges(); - - if ($changes->hasAnyChanges()) { - $hasChangesCallback($changes); - } - - $loop->addTimer(1.0, $callback); - }; - $loop->addTimer(1.0, $callback); - } - - private function reanalyseWithTmpFile( - LoopInterface $loop, - InceptionResult $inceptionResult, - string $mainScript, - RunnableQueue $runnableQueue, - ?string $projectConfigFile, - string $tmpFile, - string $insteadOfFile, - string $fixerSuggestionId, - InputInterface $input - ): PromiseInterface - { - $resultCacheManager = $this->resultCacheManagerFactory->create([$insteadOfFile => $tmpFile]); - [$inceptionFiles] = $inceptionResult->getFiles(); - $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $inceptionResult->getProjectConfigArray(), $inceptionResult->getErrorOutput()); - $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $resultCache->getFilesToAnalyse()); - - $process = new ProcessPromise($loop, $fixerSuggestionId, ProcessHelper::getWorkerCommand( - $mainScript, - 'fixer:worker', - $projectConfigFile, - [ - '--tmp-file', - escapeshellarg($tmpFile), - '--instead-of', - escapeshellarg($insteadOfFile), - '--save-result-cache', - escapeshellarg($fixerSuggestionId), - '--allow-parallel', - ], - $input - )); - - return $runnableQueue->queue($process, $schedule->getNumberOfProcesses()); - } - - private function reanalyseAfterFileChanges( - LoopInterface $loop, - InceptionResult $inceptionResult, - string $mainScript, - ?string $projectConfigFile, - ?string $fixerSuggestionId, - InputInterface $input - ): PromiseInterface - { - $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); - if (count($ignoredErrorHelperResult->getErrors()) > 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $projectConfigArray = $inceptionResult->getProjectConfigArray(); - - $resultCacheManager = $this->resultCacheManagerFactory->create([]); - [$inceptionFiles, $isOnlyFiles] = $inceptionResult->getFiles(); - $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $projectConfigArray, $inceptionResult->getErrorOutput(), $fixerSuggestionId); - if (count($resultCache->getFilesToAnalyse()) === 0) { - $result = $resultCacheManager->process( - new AnalyserResult([], [], [], [], false), - $resultCache, - $inceptionResult->getErrorOutput(), - false, - true - )->getAnalyserResult(); - $intermediateErrors = $ignoredErrorHelperResult->process( - $result->getErrors(), - $isOnlyFiles, - $inceptionFiles, - count($result->getInternalErrors()) > 0 || $result->hasReachedInternalErrorsCountLimit() - ); - $finalFileSpecificErrors = []; - $finalNotFileSpecificErrors = []; - foreach ($intermediateErrors as $intermediateError) { - if (is_string($intermediateError)) { - $finalNotFileSpecificErrors[] = $intermediateError; - continue; - } - - $finalFileSpecificErrors[] = $intermediateError; - } - - return resolve([ - 'fileSpecificErrors' => $finalFileSpecificErrors, - 'notFileSpecificErrors' => $finalNotFileSpecificErrors, - ]); - } - - $options = ['--save-result-cache', '--allow-parallel']; - if ($fixerSuggestionId !== null) { - $options[] = '--restore-result-cache'; - $options[] = $fixerSuggestionId; - } - $process = new ProcessPromise($loop, 'changedFileAnalysis', ProcessHelper::getWorkerCommand( - $mainScript, - 'fixer:worker', - $projectConfigFile, - $options, - $input - )); - $this->processInProgress = $process->run(); - - return $this->processInProgress->then(static function (string $output): array { - return Json::decode($output, Json::FORCE_ARRAY); - }); - } - - private function getPhpstanVersion(): string - { - try { - return \Jean85\PrettyVersions::getVersion('phpstan/phpstan')->getPrettyVersion(); - } catch (\OutOfBoundsException $e) { - return 'Version unknown'; - } - } - - private function isDockerRunning(): bool - { - if (!is_file('/proc/1/cgroup')) { - return false; - } - - try { - $contents = FileReader::read('/proc/1/cgroup'); - - return strpos($contents, 'docker') !== false; - } catch (\PHPStan\File\CouldNotReadFileException $e) { - return false; - } - } - + /** @var FileMonitor */ + private $fileMonitor; + + /** @var ResultCacheManagerFactory */ + private $resultCacheManagerFactory; + + /** @var ResultCacheClearer */ + private $resultCacheClearer; + + /** @var IgnoredErrorHelper */ + private $ignoredErrorHelper; + + /** @var CpuCoreCounter */ + private $cpuCoreCounter; + + /** @var Scheduler */ + private $scheduler; + + /** @var string[] */ + private $analysedPaths; + + /** @var (ExtendedPromiseInterface&CancellablePromiseInterface)|null */ + private $processInProgress; + + /** @var string */ + private $currentWorkingDirectory; + + /** @var string */ + private $fixerTmpDir; + + private int $maximumNumberOfProcesses; + + /** @var string|null */ + private $fixerSuggestionId; + + /** + * @param FileMonitor $fileMonitor + * @param ResultCacheManagerFactory $resultCacheManagerFactory + * @param string[] $analysedPaths + */ + public function __construct( + FileMonitor $fileMonitor, + ResultCacheManagerFactory $resultCacheManagerFactory, + ResultCacheClearer $resultCacheClearer, + IgnoredErrorHelper $ignoredErrorHelper, + CpuCoreCounter $cpuCoreCounter, + Scheduler $scheduler, + array $analysedPaths, + string $currentWorkingDirectory, + string $fixerTmpDir, + int $maximumNumberOfProcesses + ) { + $this->fileMonitor = $fileMonitor; + $this->resultCacheManagerFactory = $resultCacheManagerFactory; + $this->resultCacheClearer = $resultCacheClearer; + $this->ignoredErrorHelper = $ignoredErrorHelper; + $this->cpuCoreCounter = $cpuCoreCounter; + $this->scheduler = $scheduler; + $this->analysedPaths = $analysedPaths; + $this->currentWorkingDirectory = $currentWorkingDirectory; + $this->fixerTmpDir = $fixerTmpDir; + $this->maximumNumberOfProcesses = $maximumNumberOfProcesses; + } + + /** + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param \PHPStan\Analyser\Error[] $fileSpecificErrors + * @param string[] $notFileSpecificErrors + * @return int + */ + public function run( + ?string $projectConfigFile, + InceptionResult $inceptionResult, + InputInterface $input, + OutputInterface $output, + array $fileSpecificErrors, + array $notFileSpecificErrors, + int $filesCount, + string $mainScript + ): int { + $loop = new StreamSelectLoop(); + $server = new \React\Socket\TcpServer('127.0.0.1:0', $loop); + /** @var string $serverAddress */ + $serverAddress = $server->getAddress(); + + /** @var int $serverPort */ + $serverPort = parse_url($serverAddress, PHP_URL_PORT); + + $reanalyseProcessQueue = new RunnableQueue( + new class() implements RunnableQueueLogger { + public function log(string $message): void + { + } + }, + min($this->cpuCoreCounter->getNumberOfCpuCores(), $this->maximumNumberOfProcesses) + ); + + $server->on('connection', function (ConnectionInterface $connection) use ($loop, $projectConfigFile, $input, $output, $fileSpecificErrors, $notFileSpecificErrors, $mainScript, $filesCount, $reanalyseProcessQueue, $inceptionResult): void { + $decoder = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, 128 * 1024 * 1024); + $encoder = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); + $encoder->write(['action' => 'initialData', 'data' => [ + 'fileSpecificErrors' => $fileSpecificErrors, + 'notFileSpecificErrors' => $notFileSpecificErrors, + 'currentWorkingDirectory' => $this->currentWorkingDirectory, + 'analysedPaths' => $this->analysedPaths, + 'projectConfigFile' => $projectConfigFile, + 'filesCount' => $filesCount, + 'phpstanVersion' => $this->getPhpstanVersion(), + ]]); + $decoder->on('data', function (array $data) use ( + $loop, + $encoder, + $projectConfigFile, + $input, + $output, + $mainScript, + $reanalyseProcessQueue, + $inceptionResult + ): void { + if ($data['action'] === 'webPort') { + $output->writeln(sprintf('Open your web browser at: http://127.0.0.1:%d', $data['data']['port'])); + $output->writeln('Press [Ctrl-C] to quit.'); + return; + } + if ($data['action'] === 'restoreResultCache') { + $this->fixerSuggestionId = $data['data']['fixerSuggestionId']; + } + if ($data['action'] !== 'reanalyse') { + return; + } + + $id = $data['id']; + + $this->reanalyseWithTmpFile( + $loop, + $inceptionResult, + $mainScript, + $reanalyseProcessQueue, + $projectConfigFile, + $data['data']['tmpFile'], + $data['data']['insteadOfFile'], + $data['data']['fixerSuggestionId'], + $input + )->done(static function (string $output) use ($encoder, $id): void { + $encoder->write(['id' => $id, 'response' => Json::decode($output, Json::FORCE_ARRAY)]); + }, static function (\Throwable $e) use ($encoder, $id, $output): void { + if ($e instanceof \PHPStan\Process\ProcessCrashedException) { + $output->writeln('Worker process exited: ' . $e->getMessage() . ''); + $encoder->write(['id' => $id, 'error' => $e->getMessage()]); + return; + } + if ($e instanceof \PHPStan\Process\ProcessCanceledException) { + $encoder->write(['id' => $id, 'error' => $e->getMessage()]); + return; + } + + $output->writeln('Unexpected error: ' . $e->getMessage() . ''); + $encoder->write(['id' => $id, 'error' => $e->getMessage()]); + }); + }); + + $this->fileMonitor->initialize($this->analysedPaths); + $this->monitorFileChanges($loop, function (FileMonitorResult $changes) use ($loop, $mainScript, $projectConfigFile, $input, $encoder, $output, $reanalyseProcessQueue, $inceptionResult): void { + $reanalyseProcessQueue->cancelAll(); + if ($this->processInProgress !== null) { + $this->processInProgress->cancel(); + $this->processInProgress = null; + } else { + $encoder->write(['action' => 'analysisStart']); + } + + $this->reanalyseAfterFileChanges( + $loop, + $inceptionResult, + $mainScript, + $projectConfigFile, + $this->fixerSuggestionId, + $input + )->done(function (array $json) use ($encoder, $changes): void { + $this->processInProgress = null; + $this->fixerSuggestionId = null; + $encoder->write(['action' => 'analysisEnd', 'data' => [ + 'fileSpecificErrors' => $json['fileSpecificErrors'], + 'notFileSpecificErrors' => $json['notFileSpecificErrors'], + 'filesCount' => $changes->getTotalFilesCount(), + ]]); + $this->resultCacheClearer->clearTemporaryCaches(); + }, function (\Throwable $e) use ($encoder, $output): void { + $this->processInProgress = null; + $this->fixerSuggestionId = null; + $output->writeln('Worker process exited: ' . $e->getMessage() . ''); + $encoder->write(['action' => 'analysisCrash', 'data' => [ + 'error' => $e->getMessage(), + ]]); + }); + }); + }); + + try { + $fixerProcess = $this->getFixerProcess($output, $serverPort); + } catch (\PHPStan\Command\FixerProcessException $e) { + return 1; + } + + $fixerProcess->start($loop); + $fixerProcess->on('exit', static function ($exitCode) use ($output, $loop): void { + $loop->stop(); + if ($exitCode === null) { + return; + } + if ($exitCode === 0) { + return; + } + $output->writeln(sprintf('PHPStan Pro process exited with code %d.', $exitCode)); + }); + + $loop->run(); + + return 0; + } + + /** + * @throws FixerProcessException + */ + private function getFixerProcess(OutputInterface $output, int $serverPort): Process + { + if (!@mkdir($this->fixerTmpDir, 0777) && !is_dir($this->fixerTmpDir)) { + $output->writeln(sprintf('Cannot create a temp directory %s', $this->fixerTmpDir)); + throw new \PHPStan\Command\FixerProcessException(); + } + + $pharPath = $this->fixerTmpDir . '/phpstan-fixer.phar'; + $infoPath = $this->fixerTmpDir . '/phar-info.json'; + + try { + $this->downloadPhar($output, $pharPath, $infoPath); + } catch (\RuntimeException $e) { + if (!file_exists($pharPath)) { + $output->writeln('Could not download the PHPStan Pro executable.'); + $output->writeln($e->getMessage()); + + throw new \PHPStan\Command\FixerProcessException(); + } + } + + $pubKeyPath = $pharPath . '.pubkey'; + FileWriter::write($pubKeyPath, FileReader::read(__DIR__ . '/fixer-phar.pubkey')); + + try { + $phar = new Phar($pharPath); + } catch (\Throwable $e) { + @unlink($pharPath); + @unlink($infoPath); + $output->writeln('PHPStan Pro PHAR signature is corrupted.'); + + throw new \PHPStan\Command\FixerProcessException(); + } + + if ($phar->getSignature()['hash_type'] !== 'OpenSSL') { + @unlink($pharPath); + @unlink($infoPath); + $output->writeln('PHPStan Pro PHAR signature is corrupted.'); + + throw new \PHPStan\Command\FixerProcessException(); + } + + $env = null; + $forcedPort = $_SERVER['PHPSTAN_PRO_WEB_PORT'] ?? null; + if ($forcedPort !== null) { + $env['PHPSTAN_PRO_WEB_PORT'] = $_SERVER['PHPSTAN_PRO_WEB_PORT']; + $isDocker = $this->isDockerRunning(); + if ($isDocker) { + $output->writeln('Running in Docker? Don\'t forget to do these steps:'); + + $output->writeln('1) Publish this port when running Docker:'); + $output->writeln(sprintf(' -p 127.0.0.1:%d:%d', $_SERVER['PHPSTAN_PRO_WEB_PORT'], $_SERVER['PHPSTAN_PRO_WEB_PORT'])); + $output->writeln('2) Map the temp directory to a persistent volume'); + $output->writeln(' so that you don\'t have to log in every time:'); + $output->writeln(sprintf(' -v ~/.phpstan-pro:%s', $this->fixerTmpDir)); + $output->writeln(''); + } + } else { + $isDocker = $this->isDockerRunning(); + if ($isDocker) { + $output->writeln('Running in Docker? You need to do these steps in order to launch PHPStan Pro:'); + $output->writeln(''); + $output->writeln('1) Set the PHPSTAN_PRO_WEB_PORT environment variable in the Dockerfile:'); + $output->writeln(' ENV PHPSTAN_PRO_WEB_PORT=11111'); + $output->writeln('2) Expose this port in the Dockerfile:'); + $output->writeln(' EXPOSE 11111'); + $output->writeln('3) Publish this port when running Docker:'); + $output->writeln(' -p 127.0.0.1:11111:11111'); + $output->writeln('4) Map the temp directory to a persistent volume'); + $output->writeln(' so that you don\'t have to log in every time:'); + $output->writeln(sprintf(' -v ~/phpstan-pro:%s', $this->fixerTmpDir)); + $output->writeln(''); + } + } + + return new Process(sprintf('%s -d memory_limit=%s %s --port %d', PHP_BINARY, escapeshellarg(ini_get('memory_limit')), escapeshellarg($pharPath), $serverPort), null, $env, []); + } + + private function downloadPhar( + OutputInterface $output, + string $pharPath, + string $infoPath + ): void { + $currentVersion = null; + if (file_exists($pharPath) && file_exists($infoPath)) { + /** @var array{version: string, date: string} $currentInfo */ + $currentInfo = Json::decode(FileReader::read($infoPath), Json::FORCE_ARRAY); + $currentVersion = $currentInfo['version']; + $currentDate = \DateTime::createFromFormat(\DateTime::ATOM, $currentInfo['date']); + if ($currentDate === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ((new \DateTimeImmutable('', new \DateTimeZone('UTC'))) <= $currentDate->modify('+24 hours')) { + return; + } + + $output->writeln('Checking if there\'s a new PHPStan Pro release...'); + } + + $loop = new StreamSelectLoop(); + $client = new Browser( + $loop, + new Connector( + $loop, + [ + 'timeout' => 5, + 'tls' => [ + 'cafile' => CaBundle::getBundledCaBundlePath(), + ], + 'dns' => '1.1.1.1', + ] + ) + ); + + /** @var array{url: string, version: string} $latestInfo */ + $latestInfo = Json::decode((string) await($client->get('https://fixer-download-api.phpstan.com/latest'), $loop, 5.0)->getBody(), Json::FORCE_ARRAY); // @phpstan-ignore-line + if ($currentVersion !== null && $latestInfo['version'] === $currentVersion) { + $this->writeInfoFile($infoPath, $latestInfo['version']); + $output->writeln('You\'re running the latest PHPStan Pro!'); + return; + } + + $output->writeln('Downloading the latest PHPStan Pro...'); + + $pharPathResource = fopen($pharPath, 'w'); + if ($pharPathResource === false) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Could not open file %s for writing.', $pharPath)); + } + $progressBar = new ProgressBar($output); + $client->requestStreaming('GET', $latestInfo['url'])->done(static function (ResponseInterface $response) use ($progressBar, $pharPathResource): void { + $body = $response->getBody(); + if (!$body instanceof \React\Stream\ReadableStreamInterface) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $totalSize = (int) $response->getHeaderLine('Content-Length'); + $progressBar->setFormat('file_download'); + $progressBar->setMessage(sprintf('%.2f MB', $totalSize / 1000000), 'fileSize'); + $progressBar->start($totalSize); + + $bytes = 0; + $body->on('data', static function ($chunk) use ($pharPathResource, $progressBar, &$bytes): void { + $bytes += strlen($chunk); + fwrite($pharPathResource, $chunk); + $progressBar->setProgress($bytes); + }); + }, static function (\Throwable $e) use ($output): void { + $output->writeln(sprintf('Could not download the PHPStan Pro executable: %s', $e->getMessage())); + }); + + $loop->run(); + + fclose($pharPathResource); + + $progressBar->finish(); + $output->writeln(''); + $output->writeln(''); + + $this->writeInfoFile($infoPath, $latestInfo['version']); + } + + private function writeInfoFile(string $infoPath, string $version): void + { + FileWriter::write($infoPath, Json::encode([ + 'version' => $version, + 'date' => (new \DateTimeImmutable('', new \DateTimeZone('UTC')))->format(\DateTime::ATOM), + ])); + } + + /** + * @param LoopInterface $loop + * @param callable(FileMonitorResult): void $hasChangesCallback + */ + private function monitorFileChanges(LoopInterface $loop, callable $hasChangesCallback): void + { + $callback = function () use (&$callback, $loop, $hasChangesCallback): void { + $changes = $this->fileMonitor->getChanges(); + + if ($changes->hasAnyChanges()) { + $hasChangesCallback($changes); + } + + $loop->addTimer(1.0, $callback); + }; + $loop->addTimer(1.0, $callback); + } + + private function reanalyseWithTmpFile( + LoopInterface $loop, + InceptionResult $inceptionResult, + string $mainScript, + RunnableQueue $runnableQueue, + ?string $projectConfigFile, + string $tmpFile, + string $insteadOfFile, + string $fixerSuggestionId, + InputInterface $input + ): PromiseInterface { + $resultCacheManager = $this->resultCacheManagerFactory->create([$insteadOfFile => $tmpFile]); + [$inceptionFiles] = $inceptionResult->getFiles(); + $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $inceptionResult->getProjectConfigArray(), $inceptionResult->getErrorOutput()); + $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $resultCache->getFilesToAnalyse()); + + $process = new ProcessPromise($loop, $fixerSuggestionId, ProcessHelper::getWorkerCommand( + $mainScript, + 'fixer:worker', + $projectConfigFile, + [ + '--tmp-file', + escapeshellarg($tmpFile), + '--instead-of', + escapeshellarg($insteadOfFile), + '--save-result-cache', + escapeshellarg($fixerSuggestionId), + '--allow-parallel', + ], + $input + )); + + return $runnableQueue->queue($process, $schedule->getNumberOfProcesses()); + } + + private function reanalyseAfterFileChanges( + LoopInterface $loop, + InceptionResult $inceptionResult, + string $mainScript, + ?string $projectConfigFile, + ?string $fixerSuggestionId, + InputInterface $input + ): PromiseInterface { + $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); + if (count($ignoredErrorHelperResult->getErrors()) > 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $projectConfigArray = $inceptionResult->getProjectConfigArray(); + + $resultCacheManager = $this->resultCacheManagerFactory->create([]); + [$inceptionFiles, $isOnlyFiles] = $inceptionResult->getFiles(); + $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $projectConfigArray, $inceptionResult->getErrorOutput(), $fixerSuggestionId); + if (count($resultCache->getFilesToAnalyse()) === 0) { + $result = $resultCacheManager->process( + new AnalyserResult([], [], [], [], false), + $resultCache, + $inceptionResult->getErrorOutput(), + false, + true + )->getAnalyserResult(); + $intermediateErrors = $ignoredErrorHelperResult->process( + $result->getErrors(), + $isOnlyFiles, + $inceptionFiles, + count($result->getInternalErrors()) > 0 || $result->hasReachedInternalErrorsCountLimit() + ); + $finalFileSpecificErrors = []; + $finalNotFileSpecificErrors = []; + foreach ($intermediateErrors as $intermediateError) { + if (is_string($intermediateError)) { + $finalNotFileSpecificErrors[] = $intermediateError; + continue; + } + + $finalFileSpecificErrors[] = $intermediateError; + } + + return resolve([ + 'fileSpecificErrors' => $finalFileSpecificErrors, + 'notFileSpecificErrors' => $finalNotFileSpecificErrors, + ]); + } + + $options = ['--save-result-cache', '--allow-parallel']; + if ($fixerSuggestionId !== null) { + $options[] = '--restore-result-cache'; + $options[] = $fixerSuggestionId; + } + $process = new ProcessPromise($loop, 'changedFileAnalysis', ProcessHelper::getWorkerCommand( + $mainScript, + 'fixer:worker', + $projectConfigFile, + $options, + $input + )); + $this->processInProgress = $process->run(); + + return $this->processInProgress->then(static function (string $output): array { + return Json::decode($output, Json::FORCE_ARRAY); + }); + } + + private function getPhpstanVersion(): string + { + try { + return \Jean85\PrettyVersions::getVersion('phpstan/phpstan')->getPrettyVersion(); + } catch (\OutOfBoundsException $e) { + return 'Version unknown'; + } + } + + private function isDockerRunning(): bool + { + if (!is_file('/proc/1/cgroup')) { + return false; + } + + try { + $contents = FileReader::read('/proc/1/cgroup'); + + return strpos($contents, 'docker') !== false; + } catch (\PHPStan\File\CouldNotReadFileException $e) { + return false; + } + } } diff --git a/src/Command/FixerProcessException.php b/src/Command/FixerProcessException.php index 996000c6ad..b2420fe6a6 100644 --- a/src/Command/FixerProcessException.php +++ b/src/Command/FixerProcessException.php @@ -1,8 +1,9 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } - - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('(Internal) Support for PHPStan Pro.') - ->setDefinition([ - new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), - new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), - new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), - new InputOption('save-result-cache', null, InputOption::VALUE_OPTIONAL, '', false), - new InputOption('restore-result-cache', null, InputOption::VALUE_REQUIRED), - new InputOption('allow-parallel', null, InputOption::VALUE_NONE, 'Allow parallel analysis'), - ]); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $paths = $input->getArgument('paths'); - $memoryLimit = $input->getOption('memory-limit'); - $autoloadFile = $input->getOption('autoload-file'); - $configuration = $input->getOption('configuration'); - $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); - $allowXdebug = $input->getOption('xdebug'); - $allowParallel = $input->getOption('allow-parallel'); - - if ( - !is_array($paths) - || (!is_string($memoryLimit) && $memoryLimit !== null) - || (!is_string($autoloadFile) && $autoloadFile !== null) - || (!is_string($configuration) && $configuration !== null) - || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) - || (!is_bool($allowXdebug)) - || (!is_bool($allowParallel)) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } - - /** @var string|null $tmpFile */ - $tmpFile = $input->getOption('tmp-file'); - - /** @var string|null $insteadOfFile */ - $insteadOfFile = $input->getOption('instead-of'); - - /** @var false|string|null $saveResultCache */ - $saveResultCache = $input->getOption('save-result-cache'); - - /** @var string|null $restoreResultCache */ - $restoreResultCache = $input->getOption('restore-result-cache'); - if (is_string($tmpFile)) { - if (!is_string($insteadOfFile)) { - throw new \PHPStan\ShouldNotHappenException(); - } - } elseif (is_string($insteadOfFile)) { - throw new \PHPStan\ShouldNotHappenException(); - } elseif ($saveResultCache === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $singleReflectionFile = null; - if ($tmpFile !== null) { - $singleReflectionFile = $tmpFile; - } - - try { - $inceptionResult = CommandHelper::begin( - $input, - $output, - $paths, - $pathsFile, - $memoryLimit, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configuration, - null, - $level, - $allowXdebug, - false, - false, - $singleReflectionFile, - $insteadOfFile - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } - - $container = $inceptionResult->getContainer(); - - /** @var IgnoredErrorHelper $ignoredErrorHelper */ - $ignoredErrorHelper = $container->getByType(IgnoredErrorHelper::class); - $ignoredErrorHelperResult = $ignoredErrorHelper->initialize(); - if (count($ignoredErrorHelperResult->getErrors()) > 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - /** @var AnalyserRunner $analyserRunner */ - $analyserRunner = $container->getByType(AnalyserRunner::class); - - $fileReplacements = []; - if ($insteadOfFile !== null && $tmpFile !== null) { - $fileReplacements = [$insteadOfFile => $tmpFile]; - } - /** @var ResultCacheManager $resultCacheManager */ - $resultCacheManager = $container->getByType(ResultCacheManagerFactory::class)->create($fileReplacements); - $projectConfigArray = $inceptionResult->getProjectConfigArray(); - [$inceptionFiles, $isOnlyFiles] = $inceptionResult->getFiles(); - $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $projectConfigArray, $inceptionResult->getErrorOutput(), $restoreResultCache); - - $intermediateAnalyserResult = $analyserRunner->runAnalyser( - $resultCache->getFilesToAnalyse(), - $inceptionFiles, - null, - null, - false, - $allowParallel, - $configuration, - $tmpFile, - $insteadOfFile, - $input - ); - $result = $resultCacheManager->process( - $this->switchTmpFileInAnalyserResult($intermediateAnalyserResult, $tmpFile, $insteadOfFile), - $resultCache, - $inceptionResult->getErrorOutput(), - false, - is_string($saveResultCache) ? $saveResultCache : $saveResultCache === null - )->getAnalyserResult(); - - $intermediateErrors = $ignoredErrorHelperResult->process( - $result->getErrors(), - $isOnlyFiles, - $inceptionFiles, - count($result->getInternalErrors()) > 0 || $result->hasReachedInternalErrorsCountLimit() - ); - $finalFileSpecificErrors = []; - $finalNotFileSpecificErrors = []; - foreach ($intermediateErrors as $intermediateError) { - if (is_string($intermediateError)) { - $finalNotFileSpecificErrors[] = $intermediateError; - continue; - } - - $finalFileSpecificErrors[] = $intermediateError; - } - - $output->writeln(Json::encode([ - 'fileSpecificErrors' => $finalFileSpecificErrors, - 'notFileSpecificErrors' => $finalNotFileSpecificErrors, - ]), OutputInterface::OUTPUT_RAW); - - return 0; - } - - private function switchTmpFileInAnalyserResult( - AnalyserResult $analyserResult, - ?string $insteadOfFile, - ?string $tmpFile - ): AnalyserResult - { - $fileSpecificErrors = []; - foreach ($analyserResult->getErrors() as $error) { - if ( - $tmpFile !== null - && $insteadOfFile !== null - ) { - if ($error->getFilePath() === $insteadOfFile) { - $error = $error->changeFilePath($tmpFile); - } - if ($error->getTraitFilePath() === $insteadOfFile) { - $error = $error->changeTraitFilePath($tmpFile); - } - } - - $fileSpecificErrors[] = $error; - } - - $dependencies = null; - if ($analyserResult->getDependencies() !== null) { - $dependencies = []; - foreach ($analyserResult->getDependencies() as $dependencyFile => $dependentFiles) { - $new = []; - foreach ($dependentFiles as $file) { - if ($file === $insteadOfFile && $tmpFile !== null) { - $new[] = $tmpFile; - continue; - } - - $new[] = $file; - } - - $key = $dependencyFile; - if ($key === $insteadOfFile && $tmpFile !== null) { - $key = $tmpFile; - } - - $dependencies[$key] = $new; - } - } - - $exportedNodes = []; - foreach ($analyserResult->getExportedNodes() as $file => $fileExportedNodes) { - if ( - $tmpFile !== null - && $insteadOfFile !== null - && $file === $insteadOfFile - ) { - $file = $tmpFile; - } - - $exportedNodes[$file] = $fileExportedNodes; - } - - return new AnalyserResult( - $fileSpecificErrors, - $analyserResult->getInternalErrors(), - $dependencies, - $exportedNodes, - $analyserResult->hasReachedInternalErrorsCountLimit() - ); - } - + private const NAME = 'fixer:worker'; + + /** @var string[] */ + private $composerAutoloaderProjectPaths; + + /** + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + array $composerAutoloaderProjectPaths + ) { + parent::__construct(); + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } + + protected function configure(): void + { + $this->setName(self::NAME) + ->setDescription('(Internal) Support for PHPStan Pro.') + ->setDefinition([ + new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), + new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), + new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), + new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), + new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), + new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), + new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), + new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), + new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), + new InputOption('save-result-cache', null, InputOption::VALUE_OPTIONAL, '', false), + new InputOption('restore-result-cache', null, InputOption::VALUE_REQUIRED), + new InputOption('allow-parallel', null, InputOption::VALUE_NONE, 'Allow parallel analysis'), + ]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $paths = $input->getArgument('paths'); + $memoryLimit = $input->getOption('memory-limit'); + $autoloadFile = $input->getOption('autoload-file'); + $configuration = $input->getOption('configuration'); + $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); + $pathsFile = $input->getOption('paths-file'); + $allowXdebug = $input->getOption('xdebug'); + $allowParallel = $input->getOption('allow-parallel'); + + if ( + !is_array($paths) + || (!is_string($memoryLimit) && $memoryLimit !== null) + || (!is_string($autoloadFile) && $autoloadFile !== null) + || (!is_string($configuration) && $configuration !== null) + || (!is_string($level) && $level !== null) + || (!is_string($pathsFile) && $pathsFile !== null) + || (!is_bool($allowXdebug)) + || (!is_bool($allowParallel)) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } + + /** @var string|null $tmpFile */ + $tmpFile = $input->getOption('tmp-file'); + + /** @var string|null $insteadOfFile */ + $insteadOfFile = $input->getOption('instead-of'); + + /** @var false|string|null $saveResultCache */ + $saveResultCache = $input->getOption('save-result-cache'); + + /** @var string|null $restoreResultCache */ + $restoreResultCache = $input->getOption('restore-result-cache'); + if (is_string($tmpFile)) { + if (!is_string($insteadOfFile)) { + throw new \PHPStan\ShouldNotHappenException(); + } + } elseif (is_string($insteadOfFile)) { + throw new \PHPStan\ShouldNotHappenException(); + } elseif ($saveResultCache === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $singleReflectionFile = null; + if ($tmpFile !== null) { + $singleReflectionFile = $tmpFile; + } + + try { + $inceptionResult = CommandHelper::begin( + $input, + $output, + $paths, + $pathsFile, + $memoryLimit, + $autoloadFile, + $this->composerAutoloaderProjectPaths, + $configuration, + null, + $level, + $allowXdebug, + false, + false, + $singleReflectionFile, + $insteadOfFile + ); + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + return 1; + } + + $container = $inceptionResult->getContainer(); + + /** @var IgnoredErrorHelper $ignoredErrorHelper */ + $ignoredErrorHelper = $container->getByType(IgnoredErrorHelper::class); + $ignoredErrorHelperResult = $ignoredErrorHelper->initialize(); + if (count($ignoredErrorHelperResult->getErrors()) > 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + /** @var AnalyserRunner $analyserRunner */ + $analyserRunner = $container->getByType(AnalyserRunner::class); + + $fileReplacements = []; + if ($insteadOfFile !== null && $tmpFile !== null) { + $fileReplacements = [$insteadOfFile => $tmpFile]; + } + /** @var ResultCacheManager $resultCacheManager */ + $resultCacheManager = $container->getByType(ResultCacheManagerFactory::class)->create($fileReplacements); + $projectConfigArray = $inceptionResult->getProjectConfigArray(); + [$inceptionFiles, $isOnlyFiles] = $inceptionResult->getFiles(); + $resultCache = $resultCacheManager->restore($inceptionFiles, false, false, $projectConfigArray, $inceptionResult->getErrorOutput(), $restoreResultCache); + + $intermediateAnalyserResult = $analyserRunner->runAnalyser( + $resultCache->getFilesToAnalyse(), + $inceptionFiles, + null, + null, + false, + $allowParallel, + $configuration, + $tmpFile, + $insteadOfFile, + $input + ); + $result = $resultCacheManager->process( + $this->switchTmpFileInAnalyserResult($intermediateAnalyserResult, $tmpFile, $insteadOfFile), + $resultCache, + $inceptionResult->getErrorOutput(), + false, + is_string($saveResultCache) ? $saveResultCache : $saveResultCache === null + )->getAnalyserResult(); + + $intermediateErrors = $ignoredErrorHelperResult->process( + $result->getErrors(), + $isOnlyFiles, + $inceptionFiles, + count($result->getInternalErrors()) > 0 || $result->hasReachedInternalErrorsCountLimit() + ); + $finalFileSpecificErrors = []; + $finalNotFileSpecificErrors = []; + foreach ($intermediateErrors as $intermediateError) { + if (is_string($intermediateError)) { + $finalNotFileSpecificErrors[] = $intermediateError; + continue; + } + + $finalFileSpecificErrors[] = $intermediateError; + } + + $output->writeln(Json::encode([ + 'fileSpecificErrors' => $finalFileSpecificErrors, + 'notFileSpecificErrors' => $finalNotFileSpecificErrors, + ]), OutputInterface::OUTPUT_RAW); + + return 0; + } + + private function switchTmpFileInAnalyserResult( + AnalyserResult $analyserResult, + ?string $insteadOfFile, + ?string $tmpFile + ): AnalyserResult { + $fileSpecificErrors = []; + foreach ($analyserResult->getErrors() as $error) { + if ( + $tmpFile !== null + && $insteadOfFile !== null + ) { + if ($error->getFilePath() === $insteadOfFile) { + $error = $error->changeFilePath($tmpFile); + } + if ($error->getTraitFilePath() === $insteadOfFile) { + $error = $error->changeTraitFilePath($tmpFile); + } + } + + $fileSpecificErrors[] = $error; + } + + $dependencies = null; + if ($analyserResult->getDependencies() !== null) { + $dependencies = []; + foreach ($analyserResult->getDependencies() as $dependencyFile => $dependentFiles) { + $new = []; + foreach ($dependentFiles as $file) { + if ($file === $insteadOfFile && $tmpFile !== null) { + $new[] = $tmpFile; + continue; + } + + $new[] = $file; + } + + $key = $dependencyFile; + if ($key === $insteadOfFile && $tmpFile !== null) { + $key = $tmpFile; + } + + $dependencies[$key] = $new; + } + } + + $exportedNodes = []; + foreach ($analyserResult->getExportedNodes() as $file => $fileExportedNodes) { + if ( + $tmpFile !== null + && $insteadOfFile !== null + && $file === $insteadOfFile + ) { + $file = $tmpFile; + } + + $exportedNodes[$file] = $fileExportedNodes; + } + + return new AnalyserResult( + $fileSpecificErrors, + $analyserResult->getInternalErrors(), + $dependencies, + $exportedNodes, + $analyserResult->hasReachedInternalErrorsCountLimit() + ); + } } diff --git a/src/Command/IgnoredRegexValidator.php b/src/Command/IgnoredRegexValidator.php index 7e648f86e8..82f9ccc514 100644 --- a/src/Command/IgnoredRegexValidator.php +++ b/src/Command/IgnoredRegexValidator.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->typeStringResolver = $typeStringResolver; - } - - public function validate(string $regex): IgnoredRegexValidatorResult - { - $regex = $this->removeDelimiters($regex); - - try { - /** @var TreeNode $ast */ - $ast = $this->parser->parse($regex); - } catch (\Hoa\Exception\Exception $e) { - if (strpos($e->getMessage(), 'Unexpected token "|" (alternation) at line 1') === 0) { - return new IgnoredRegexValidatorResult([], false, true, '||', '\|\|'); - } - if ( - strpos($regex, '()') !== false - && strpos($e->getMessage(), 'Unexpected token ")" (_capturing) at line 1') === 0 - ) { - return new IgnoredRegexValidatorResult([], false, true, '()', '\(\)'); - } - return new IgnoredRegexValidatorResult([], false, false); - } - - return new IgnoredRegexValidatorResult( - $this->getIgnoredTypes($ast), - $this->hasAnchorsInTheMiddle($ast), - false - ); - } - - /** - * @param TreeNode $ast - * @return array - */ - private function getIgnoredTypes(TreeNode $ast): array - { - /** @var TreeNode|null $alternation */ - $alternation = $ast->getChild(0); - if ($alternation === null) { - return []; - } - - if ($alternation->getId() !== '#alternation') { - return []; - } - - $types = []; - foreach ($alternation->getChildren() as $child) { - $text = $this->getText($child); - if ($text === null) { - continue; - } - - $matches = Strings::match($text, '#^([a-zA-Z0-9]+)[,]?\s*#'); - if ($matches === null) { - continue; - } - - try { - $type = $this->typeStringResolver->resolve($matches[1], null); - } catch (\PHPStan\PhpDocParser\Parser\ParserException $e) { - continue; - } - - if ($type->describe(VerbosityLevel::typeOnly()) !== $matches[1]) { - continue; - } - - if ($type instanceof ObjectType) { - continue; - } - - $types[$type->describe(VerbosityLevel::typeOnly())] = $text; - } - - return $types; - } - - private function removeDelimiters(string $regex): string - { - $delimiter = substr($regex, 0, 1); - $endDelimiterPosition = strrpos($regex, $delimiter); - if ($endDelimiterPosition === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return substr($regex, 1, $endDelimiterPosition - 1); - } - - private function getText(TreeNode $treeNode): ?string - { - if ($treeNode->getId() === 'token') { - return $treeNode->getValueValue(); - } - - if ($treeNode->getId() === '#concatenation') { - $fullText = ''; - foreach ($treeNode->getChildren() as $child) { - $text = $this->getText($child); - if ($text === null) { - continue; - } - - $fullText .= $text; - } - - if ($fullText === '') { - return null; - } - - return $fullText; - } - - return null; - } - - private function hasAnchorsInTheMiddle(TreeNode $ast): bool - { - if ($ast->getId() === 'token') { - $valueArray = $ast->getValue(); - - return $valueArray['token'] === 'anchor' && $valueArray['value'] === '$'; - } - $childrenCount = count($ast->getChildren()); - foreach ($ast->getChildren() as $i => $child) { - $has = $this->hasAnchorsInTheMiddle($child); - if ( - $has - && ($ast->getId() !== '#concatenation' || $i !== $childrenCount - 1) - ) { - return true; - } - } - - return false; - } - + private Parser $parser; + + private \PHPStan\PhpDoc\TypeStringResolver $typeStringResolver; + + public function __construct( + Parser $parser, + TypeStringResolver $typeStringResolver + ) { + $this->parser = $parser; + $this->typeStringResolver = $typeStringResolver; + } + + public function validate(string $regex): IgnoredRegexValidatorResult + { + $regex = $this->removeDelimiters($regex); + + try { + /** @var TreeNode $ast */ + $ast = $this->parser->parse($regex); + } catch (\Hoa\Exception\Exception $e) { + if (strpos($e->getMessage(), 'Unexpected token "|" (alternation) at line 1') === 0) { + return new IgnoredRegexValidatorResult([], false, true, '||', '\|\|'); + } + if ( + strpos($regex, '()') !== false + && strpos($e->getMessage(), 'Unexpected token ")" (_capturing) at line 1') === 0 + ) { + return new IgnoredRegexValidatorResult([], false, true, '()', '\(\)'); + } + return new IgnoredRegexValidatorResult([], false, false); + } + + return new IgnoredRegexValidatorResult( + $this->getIgnoredTypes($ast), + $this->hasAnchorsInTheMiddle($ast), + false + ); + } + + /** + * @param TreeNode $ast + * @return array + */ + private function getIgnoredTypes(TreeNode $ast): array + { + /** @var TreeNode|null $alternation */ + $alternation = $ast->getChild(0); + if ($alternation === null) { + return []; + } + + if ($alternation->getId() !== '#alternation') { + return []; + } + + $types = []; + foreach ($alternation->getChildren() as $child) { + $text = $this->getText($child); + if ($text === null) { + continue; + } + + $matches = Strings::match($text, '#^([a-zA-Z0-9]+)[,]?\s*#'); + if ($matches === null) { + continue; + } + + try { + $type = $this->typeStringResolver->resolve($matches[1], null); + } catch (\PHPStan\PhpDocParser\Parser\ParserException $e) { + continue; + } + + if ($type->describe(VerbosityLevel::typeOnly()) !== $matches[1]) { + continue; + } + + if ($type instanceof ObjectType) { + continue; + } + + $types[$type->describe(VerbosityLevel::typeOnly())] = $text; + } + + return $types; + } + + private function removeDelimiters(string $regex): string + { + $delimiter = substr($regex, 0, 1); + $endDelimiterPosition = strrpos($regex, $delimiter); + if ($endDelimiterPosition === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return substr($regex, 1, $endDelimiterPosition - 1); + } + + private function getText(TreeNode $treeNode): ?string + { + if ($treeNode->getId() === 'token') { + return $treeNode->getValueValue(); + } + + if ($treeNode->getId() === '#concatenation') { + $fullText = ''; + foreach ($treeNode->getChildren() as $child) { + $text = $this->getText($child); + if ($text === null) { + continue; + } + + $fullText .= $text; + } + + if ($fullText === '') { + return null; + } + + return $fullText; + } + + return null; + } + + private function hasAnchorsInTheMiddle(TreeNode $ast): bool + { + if ($ast->getId() === 'token') { + $valueArray = $ast->getValue(); + + return $valueArray['token'] === 'anchor' && $valueArray['value'] === '$'; + } + $childrenCount = count($ast->getChildren()); + foreach ($ast->getChildren() as $i => $child) { + $has = $this->hasAnchorsInTheMiddle($child); + if ( + $has + && ($ast->getId() !== '#concatenation' || $i !== $childrenCount - 1) + ) { + return true; + } + } + + return false; + } } diff --git a/src/Command/IgnoredRegexValidatorResult.php b/src/Command/IgnoredRegexValidatorResult.php index 0acda5dd11..37dbc51924 100644 --- a/src/Command/IgnoredRegexValidatorResult.php +++ b/src/Command/IgnoredRegexValidatorResult.php @@ -1,67 +1,66 @@ - */ + private array $ignoredTypes; - /** @var array */ - private array $ignoredTypes; - - private bool $anchorsInTheMiddle; - - private bool $allErrorsIgnored; + private bool $anchorsInTheMiddle; - private ?string $wrongSequence; + private bool $allErrorsIgnored; - private ?string $escapedWrongSequence; + private ?string $wrongSequence; - /** - * @param array $ignoredTypes - * @param bool $anchorsInTheMiddle - * @param bool $allErrorsIgnored - */ - public function __construct( - array $ignoredTypes, - bool $anchorsInTheMiddle, - bool $allErrorsIgnored, - ?string $wrongSequence = null, - ?string $escapedWrongSequence = null - ) - { - $this->ignoredTypes = $ignoredTypes; - $this->anchorsInTheMiddle = $anchorsInTheMiddle; - $this->allErrorsIgnored = $allErrorsIgnored; - $this->wrongSequence = $wrongSequence; - $this->escapedWrongSequence = $escapedWrongSequence; - } + private ?string $escapedWrongSequence; - /** - * @return array - */ - public function getIgnoredTypes(): array - { - return $this->ignoredTypes; - } + /** + * @param array $ignoredTypes + * @param bool $anchorsInTheMiddle + * @param bool $allErrorsIgnored + */ + public function __construct( + array $ignoredTypes, + bool $anchorsInTheMiddle, + bool $allErrorsIgnored, + ?string $wrongSequence = null, + ?string $escapedWrongSequence = null + ) { + $this->ignoredTypes = $ignoredTypes; + $this->anchorsInTheMiddle = $anchorsInTheMiddle; + $this->allErrorsIgnored = $allErrorsIgnored; + $this->wrongSequence = $wrongSequence; + $this->escapedWrongSequence = $escapedWrongSequence; + } - public function hasAnchorsInTheMiddle(): bool - { - return $this->anchorsInTheMiddle; - } + /** + * @return array + */ + public function getIgnoredTypes(): array + { + return $this->ignoredTypes; + } - public function areAllErrorsIgnored(): bool - { - return $this->allErrorsIgnored; - } + public function hasAnchorsInTheMiddle(): bool + { + return $this->anchorsInTheMiddle; + } - public function getWrongSequence(): ?string - { - return $this->wrongSequence; - } + public function areAllErrorsIgnored(): bool + { + return $this->allErrorsIgnored; + } - public function getEscapedWrongSequence(): ?string - { - return $this->escapedWrongSequence; - } + public function getWrongSequence(): ?string + { + return $this->wrongSequence; + } + public function getEscapedWrongSequence(): ?string + { + return $this->escapedWrongSequence; + } } diff --git a/src/Command/InceptionNotSuccessfulException.php b/src/Command/InceptionNotSuccessfulException.php index 872b71cfaa..b1da889111 100644 --- a/src/Command/InceptionNotSuccessfulException.php +++ b/src/Command/InceptionNotSuccessfulException.php @@ -1,8 +1,9 @@ -filesCallback = $filesCallback; - $this->stdOutput = $stdOutput; - $this->errorOutput = $errorOutput; - $this->container = $container; - $this->isDefaultLevelUsed = $isDefaultLevelUsed; - $this->memoryLimitFile = $memoryLimitFile; - $this->projectConfigFile = $projectConfigFile; - $this->projectConfigArray = $projectConfigArray; - $this->generateBaselineFile = $generateBaselineFile; - } - - /** - * @return array{string[], bool} - */ - public function getFiles(): array - { - $callback = $this->filesCallback; - - return $callback(); - } - - public function getStdOutput(): Output - { - return $this->stdOutput; - } - - public function getErrorOutput(): Output - { - return $this->errorOutput; - } - - public function getContainer(): Container - { - return $this->container; - } - - public function isDefaultLevelUsed(): bool - { - return $this->isDefaultLevelUsed; - } - - public function getProjectConfigFile(): ?string - { - return $this->projectConfigFile; - } - - /** - * @return mixed[]|null - */ - public function getProjectConfigArray(): ?array - { - return $this->projectConfigArray; - } - - public function getGenerateBaselineFile(): ?string - { - return $this->generateBaselineFile; - } - - public function handleReturn(int $exitCode): int - { - if ($this->getErrorOutput()->isVerbose()) { - $this->getErrorOutput()->writeLineFormatted(sprintf('Used memory: %s', BytesHelper::bytes(memory_get_peak_usage(true)))); - } - - @unlink($this->memoryLimitFile); - return $exitCode; - } - + /** @var callable(): (array{string[], bool}) */ + private $filesCallback; + + private Output $stdOutput; + + private Output $errorOutput; + + private \PHPStan\DependencyInjection\Container $container; + + private bool $isDefaultLevelUsed; + + private string $memoryLimitFile; + + private ?string $projectConfigFile; + + /** @var mixed[]|null */ + private ?array $projectConfigArray; + + private ?string $generateBaselineFile; + + /** + * @param callable(): (array{string[], bool}) $filesCallback + * @param Output $stdOutput + * @param Output $errorOutput + * @param \PHPStan\DependencyInjection\Container $container + * @param bool $isDefaultLevelUsed + * @param string $memoryLimitFile + * @param string|null $projectConfigFile + * @param mixed[] $projectConfigArray + * @param string|null $generateBaselineFile + */ + public function __construct( + callable $filesCallback, + Output $stdOutput, + Output $errorOutput, + Container $container, + bool $isDefaultLevelUsed, + string $memoryLimitFile, + ?string $projectConfigFile, + ?array $projectConfigArray, + ?string $generateBaselineFile + ) { + $this->filesCallback = $filesCallback; + $this->stdOutput = $stdOutput; + $this->errorOutput = $errorOutput; + $this->container = $container; + $this->isDefaultLevelUsed = $isDefaultLevelUsed; + $this->memoryLimitFile = $memoryLimitFile; + $this->projectConfigFile = $projectConfigFile; + $this->projectConfigArray = $projectConfigArray; + $this->generateBaselineFile = $generateBaselineFile; + } + + /** + * @return array{string[], bool} + */ + public function getFiles(): array + { + $callback = $this->filesCallback; + + return $callback(); + } + + public function getStdOutput(): Output + { + return $this->stdOutput; + } + + public function getErrorOutput(): Output + { + return $this->errorOutput; + } + + public function getContainer(): Container + { + return $this->container; + } + + public function isDefaultLevelUsed(): bool + { + return $this->isDefaultLevelUsed; + } + + public function getProjectConfigFile(): ?string + { + return $this->projectConfigFile; + } + + /** + * @return mixed[]|null + */ + public function getProjectConfigArray(): ?array + { + return $this->projectConfigArray; + } + + public function getGenerateBaselineFile(): ?string + { + return $this->generateBaselineFile; + } + + public function handleReturn(int $exitCode): int + { + if ($this->getErrorOutput()->isVerbose()) { + $this->getErrorOutput()->writeLineFormatted(sprintf('Used memory: %s', BytesHelper::bytes(memory_get_peak_usage(true)))); + } + + @unlink($this->memoryLimitFile); + return $exitCode; + } } diff --git a/src/Command/Output.php b/src/Command/Output.php index d5fe874a51..e4941e2ee8 100644 --- a/src/Command/Output.php +++ b/src/Command/Output.php @@ -1,20 +1,20 @@ -symfonyOutput = $symfonyOutput; - $this->style = $style; - } - - public function writeFormatted(string $message): void - { - $this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_NORMAL); - } - - public function writeLineFormatted(string $message): void - { - $this->symfonyOutput->writeln($message, OutputInterface::OUTPUT_NORMAL); - } - - public function writeRaw(string $message): void - { - $this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_RAW); - } - - public function getStyle(): OutputStyle - { - return $this->style; - } - - public function isVerbose(): bool - { - return $this->symfonyOutput->isVerbose(); - } - - public function isDebug(): bool - { - return $this->symfonyOutput->isDebug(); - } - + private \Symfony\Component\Console\Output\OutputInterface $symfonyOutput; + + private OutputStyle $style; + + public function __construct( + OutputInterface $symfonyOutput, + OutputStyle $style + ) { + $this->symfonyOutput = $symfonyOutput; + $this->style = $style; + } + + public function writeFormatted(string $message): void + { + $this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_NORMAL); + } + + public function writeLineFormatted(string $message): void + { + $this->symfonyOutput->writeln($message, OutputInterface::OUTPUT_NORMAL); + } + + public function writeRaw(string $message): void + { + $this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_RAW); + } + + public function getStyle(): OutputStyle + { + return $this->style; + } + + public function isVerbose(): bool + { + return $this->symfonyOutput->isVerbose(); + } + + public function isDebug(): bool + { + return $this->symfonyOutput->isDebug(); + } } diff --git a/src/Command/Symfony/SymfonyStyle.php b/src/Command/Symfony/SymfonyStyle.php index ba2f25ee16..50800ad319 100644 --- a/src/Command/Symfony/SymfonyStyle.php +++ b/src/Command/Symfony/SymfonyStyle.php @@ -1,4 +1,6 @@ -symfonyStyle = $symfonyStyle; - } - - public function getSymfonyStyle(): \Symfony\Component\Console\Style\StyleInterface - { - return $this->symfonyStyle; - } - - public function title(string $message): void - { - $this->symfonyStyle->title($message); - } - - public function section(string $message): void - { - $this->symfonyStyle->section($message); - } - - public function listing(array $elements): void - { - $this->symfonyStyle->listing($elements); - } - - public function success(string $message): void - { - $this->symfonyStyle->success($message); - } - - public function error(string $message): void - { - $this->symfonyStyle->error($message); - } - - public function warning(string $message): void - { - $this->symfonyStyle->warning($message); - } - - public function note(string $message): void - { - $this->symfonyStyle->note($message); - } - - public function caution(string $message): void - { - $this->symfonyStyle->caution($message); - } - - public function table(array $headers, array $rows): void - { - $this->symfonyStyle->table($headers, $rows); - } - - public function newLine(int $count = 1): void - { - $this->symfonyStyle->newLine($count); - } - - public function progressStart(int $max = 0): void - { - $this->symfonyStyle->progressStart($max); - } - - public function progressAdvance(int $step = 1): void - { - $this->symfonyStyle->progressAdvance($step); - } - - public function progressFinish(): void - { - $this->symfonyStyle->progressFinish(); - } - + private \Symfony\Component\Console\Style\StyleInterface $symfonyStyle; + + public function __construct(StyleInterface $symfonyStyle) + { + $this->symfonyStyle = $symfonyStyle; + } + + public function getSymfonyStyle(): \Symfony\Component\Console\Style\StyleInterface + { + return $this->symfonyStyle; + } + + public function title(string $message): void + { + $this->symfonyStyle->title($message); + } + + public function section(string $message): void + { + $this->symfonyStyle->section($message); + } + + public function listing(array $elements): void + { + $this->symfonyStyle->listing($elements); + } + + public function success(string $message): void + { + $this->symfonyStyle->success($message); + } + + public function error(string $message): void + { + $this->symfonyStyle->error($message); + } + + public function warning(string $message): void + { + $this->symfonyStyle->warning($message); + } + + public function note(string $message): void + { + $this->symfonyStyle->note($message); + } + + public function caution(string $message): void + { + $this->symfonyStyle->caution($message); + } + + public function table(array $headers, array $rows): void + { + $this->symfonyStyle->table($headers, $rows); + } + + public function newLine(int $count = 1): void + { + $this->symfonyStyle->newLine($count); + } + + public function progressStart(int $max = 0): void + { + $this->symfonyStyle->progressStart($max); + } + + public function progressAdvance(int $step = 1): void + { + $this->symfonyStyle->progressAdvance($step); + } + + public function progressFinish(): void + { + $this->symfonyStyle->progressFinish(); + } } diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index e1154d3829..2793033f53 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -1,4 +1,6 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } + private int $errorCount = 0; - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('(Internal) Support for parallel analysis.') - ->setDefinition([ - new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), - new InputOption('port', null, InputOption::VALUE_REQUIRED), - new InputOption('identifier', null, InputOption::VALUE_REQUIRED), - new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), - new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), - ]); - } + /** + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + array $composerAutoloaderProjectPaths + ) { + parent::__construct(); + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } - protected function execute(InputInterface $input, OutputInterface $output): int - { - $paths = $input->getArgument('paths'); - $memoryLimit = $input->getOption('memory-limit'); - $autoloadFile = $input->getOption('autoload-file'); - $configuration = $input->getOption('configuration'); - $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); - $allowXdebug = $input->getOption('xdebug'); - $port = $input->getOption('port'); - $identifier = $input->getOption('identifier'); + protected function configure(): void + { + $this->setName(self::NAME) + ->setDescription('(Internal) Support for parallel analysis.') + ->setDefinition([ + new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), + new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), + new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), + new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), + new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), + new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), + new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), + new InputOption('port', null, InputOption::VALUE_REQUIRED), + new InputOption('identifier', null, InputOption::VALUE_REQUIRED), + new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), + new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), + ]); + } - if ( - !is_array($paths) - || (!is_string($memoryLimit) && $memoryLimit !== null) - || (!is_string($autoloadFile) && $autoloadFile !== null) - || (!is_string($configuration) && $configuration !== null) - || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) - || (!is_bool($allowXdebug)) - || !is_string($port) - || !is_string($identifier) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } + protected function execute(InputInterface $input, OutputInterface $output): int + { + $paths = $input->getArgument('paths'); + $memoryLimit = $input->getOption('memory-limit'); + $autoloadFile = $input->getOption('autoload-file'); + $configuration = $input->getOption('configuration'); + $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); + $pathsFile = $input->getOption('paths-file'); + $allowXdebug = $input->getOption('xdebug'); + $port = $input->getOption('port'); + $identifier = $input->getOption('identifier'); - /** @var string|null $tmpFile */ - $tmpFile = $input->getOption('tmp-file'); + if ( + !is_array($paths) + || (!is_string($memoryLimit) && $memoryLimit !== null) + || (!is_string($autoloadFile) && $autoloadFile !== null) + || (!is_string($configuration) && $configuration !== null) + || (!is_string($level) && $level !== null) + || (!is_string($pathsFile) && $pathsFile !== null) + || (!is_bool($allowXdebug)) + || !is_string($port) + || !is_string($identifier) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } - /** @var string|null $insteadOfFile */ - $insteadOfFile = $input->getOption('instead-of'); + /** @var string|null $tmpFile */ + $tmpFile = $input->getOption('tmp-file'); - $singleReflectionFile = null; - if ($tmpFile !== null) { - $singleReflectionFile = $tmpFile; - } + /** @var string|null $insteadOfFile */ + $insteadOfFile = $input->getOption('instead-of'); - try { - $inceptionResult = CommandHelper::begin( - $input, - $output, - $paths, - $pathsFile, - $memoryLimit, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configuration, - null, - $level, - $allowXdebug, - false, - false, - $singleReflectionFile - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } - $loop = new StreamSelectLoop(); + $singleReflectionFile = null; + if ($tmpFile !== null) { + $singleReflectionFile = $tmpFile; + } - $container = $inceptionResult->getContainer(); + try { + $inceptionResult = CommandHelper::begin( + $input, + $output, + $paths, + $pathsFile, + $memoryLimit, + $autoloadFile, + $this->composerAutoloaderProjectPaths, + $configuration, + null, + $level, + $allowXdebug, + false, + false, + $singleReflectionFile + ); + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + return 1; + } + $loop = new StreamSelectLoop(); - try { - [$analysedFiles] = $inceptionResult->getFiles(); - $analysedFiles = $this->switchTmpFile($analysedFiles, $insteadOfFile, $tmpFile); - } catch (\PHPStan\File\PathNotFoundException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } + $container = $inceptionResult->getContainer(); - /** @var NodeScopeResolver $nodeScopeResolver */ - $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); - $nodeScopeResolver->setAnalysedFiles($analysedFiles); + try { + [$analysedFiles] = $inceptionResult->getFiles(); + $analysedFiles = $this->switchTmpFile($analysedFiles, $insteadOfFile, $tmpFile); + } catch (\PHPStan\File\PathNotFoundException $e) { + $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); + return 1; + } - $analysedFiles = array_fill_keys($analysedFiles, true); + /** @var NodeScopeResolver $nodeScopeResolver */ + $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); + $nodeScopeResolver->setAnalysedFiles($analysedFiles); - $tcpConector = new TcpConnector($loop); - $tcpConector->connect(sprintf('127.0.0.1:%d', $port))->done(function (ConnectionInterface $connection) use ($container, $identifier, $output, $analysedFiles, $tmpFile, $insteadOfFile): void { - $out = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); - $in = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, $container->getParameter('parallel')['buffer']); - $out->write(['action' => 'hello', 'identifier' => $identifier]); - $this->runWorker($container, $out, $in, $output, $analysedFiles, $tmpFile, $insteadOfFile); - }); + $analysedFiles = array_fill_keys($analysedFiles, true); - $loop->run(); + $tcpConector = new TcpConnector($loop); + $tcpConector->connect(sprintf('127.0.0.1:%d', $port))->done(function (ConnectionInterface $connection) use ($container, $identifier, $output, $analysedFiles, $tmpFile, $insteadOfFile): void { + $out = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); + $in = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, $container->getParameter('parallel')['buffer']); + $out->write(['action' => 'hello', 'identifier' => $identifier]); + $this->runWorker($container, $out, $in, $output, $analysedFiles, $tmpFile, $insteadOfFile); + }); - if ($this->errorCount > 0) { - return 1; - } + $loop->run(); - return 0; - } + if ($this->errorCount > 0) { + return 1; + } - /** - * @param Container $container - * @param WritableStreamInterface $out - * @param ReadableStreamInterface $in - * @param OutputInterface $output - * @param array $analysedFiles - * @param string|null $tmpFile - * @param string|null $insteadOfFile - */ - private function runWorker( - Container $container, - WritableStreamInterface $out, - ReadableStreamInterface $in, - OutputInterface $output, - array $analysedFiles, - ?string $tmpFile, - ?string $insteadOfFile - ): void - { - $handleError = function (\Throwable $error) use ($out, $output): void { - $this->errorCount++; - $output->writeln(sprintf('Error: %s', $error->getMessage())); - $out->write([ - 'action' => 'result', - 'result' => [ - 'errors' => [$error->getMessage()], - 'dependencies' => [], - 'filesCount' => 0, - 'internalErrorsCount' => 1, - ], - ]); - $out->end(); - }; - $out->on('error', $handleError); + return 0; + } - /** @var FileAnalyser $fileAnalyser */ - $fileAnalyser = $container->getByType(FileAnalyser::class); + /** + * @param Container $container + * @param WritableStreamInterface $out + * @param ReadableStreamInterface $in + * @param OutputInterface $output + * @param array $analysedFiles + * @param string|null $tmpFile + * @param string|null $insteadOfFile + */ + private function runWorker( + Container $container, + WritableStreamInterface $out, + ReadableStreamInterface $in, + OutputInterface $output, + array $analysedFiles, + ?string $tmpFile, + ?string $insteadOfFile + ): void { + $handleError = function (\Throwable $error) use ($out, $output): void { + $this->errorCount++; + $output->writeln(sprintf('Error: %s', $error->getMessage())); + $out->write([ + 'action' => 'result', + 'result' => [ + 'errors' => [$error->getMessage()], + 'dependencies' => [], + 'filesCount' => 0, + 'internalErrorsCount' => 1, + ], + ]); + $out->end(); + }; + $out->on('error', $handleError); - /** @var Registry $registry */ - $registry = $container->getByType(Registry::class); + /** @var FileAnalyser $fileAnalyser */ + $fileAnalyser = $container->getByType(FileAnalyser::class); - $in->on('data', function (array $json) use ($fileAnalyser, $registry, $out, $analysedFiles, $tmpFile, $insteadOfFile): void { - $action = $json['action']; - if ($action !== 'analyse') { - return; - } + /** @var Registry $registry */ + $registry = $container->getByType(Registry::class); - $internalErrorsCount = 0; - $files = $json['files']; - $errors = []; - $dependencies = []; - $exportedNodes = []; - foreach ($files as $file) { - try { - if ($file === $insteadOfFile) { - $file = $tmpFile; - } - $fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $registry, null); - $fileErrors = $fileAnalyserResult->getErrors(); - $dependencies[$file] = $fileAnalyserResult->getDependencies(); - $exportedNodes[$file] = $fileAnalyserResult->getExportedNodes(); - foreach ($fileErrors as $fileError) { - $errors[] = $fileError; - } - } catch (\Throwable $t) { - $this->errorCount++; - $internalErrorsCount++; - $internalErrorMessage = sprintf('Internal error: %s in file %s', $t->getMessage(), $file); - $internalErrorMessage .= sprintf( - '%sRun PHPStan with --debug option and post the stack trace to:%s%s', - "\n", - "\n", - 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.md' - ); - $errors[] = $internalErrorMessage; - } - } + $in->on('data', function (array $json) use ($fileAnalyser, $registry, $out, $analysedFiles, $tmpFile, $insteadOfFile): void { + $action = $json['action']; + if ($action !== 'analyse') { + return; + } - $out->write([ - 'action' => 'result', - 'result' => [ - 'errors' => $errors, - 'dependencies' => $dependencies, - 'exportedNodes' => $exportedNodes, - 'filesCount' => count($files), - 'internalErrorsCount' => $internalErrorsCount, - ]]); - }); - $in->on('error', $handleError); - } + $internalErrorsCount = 0; + $files = $json['files']; + $errors = []; + $dependencies = []; + $exportedNodes = []; + foreach ($files as $file) { + try { + if ($file === $insteadOfFile) { + $file = $tmpFile; + } + $fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $registry, null); + $fileErrors = $fileAnalyserResult->getErrors(); + $dependencies[$file] = $fileAnalyserResult->getDependencies(); + $exportedNodes[$file] = $fileAnalyserResult->getExportedNodes(); + foreach ($fileErrors as $fileError) { + $errors[] = $fileError; + } + } catch (\Throwable $t) { + $this->errorCount++; + $internalErrorsCount++; + $internalErrorMessage = sprintf('Internal error: %s in file %s', $t->getMessage(), $file); + $internalErrorMessage .= sprintf( + '%sRun PHPStan with --debug option and post the stack trace to:%s%s', + "\n", + "\n", + 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.md' + ); + $errors[] = $internalErrorMessage; + } + } - /** - * @param string[] $analysedFiles - * @param string|null $insteadOfFile - * @param string|null $tmpFile - * @return string[] - */ - private function switchTmpFile( - array $analysedFiles, - ?string $insteadOfFile, - ?string $tmpFile - ): array - { - $analysedFiles = array_values(array_filter($analysedFiles, static function (string $file) use ($insteadOfFile): bool { - if ($insteadOfFile === null) { - return true; - } - return $file !== $insteadOfFile; - })); - if ($tmpFile !== null) { - $analysedFiles[] = $tmpFile; - } + $out->write([ + 'action' => 'result', + 'result' => [ + 'errors' => $errors, + 'dependencies' => $dependencies, + 'exportedNodes' => $exportedNodes, + 'filesCount' => count($files), + 'internalErrorsCount' => $internalErrorsCount, + ]]); + }); + $in->on('error', $handleError); + } - return $analysedFiles; - } + /** + * @param string[] $analysedFiles + * @param string|null $insteadOfFile + * @param string|null $tmpFile + * @return string[] + */ + private function switchTmpFile( + array $analysedFiles, + ?string $insteadOfFile, + ?string $tmpFile + ): array { + $analysedFiles = array_values(array_filter($analysedFiles, static function (string $file) use ($insteadOfFile): bool { + if ($insteadOfFile === null) { + return true; + } + return $file !== $insteadOfFile; + })); + if ($tmpFile !== null) { + $analysedFiles[] = $tmpFile; + } + return $analysedFiles; + } } diff --git a/src/Dependency/DependencyDumper.php b/src/Dependency/DependencyDumper.php index c7480dc01a..4097de0d0a 100644 --- a/src/Dependency/DependencyDumper.php +++ b/src/Dependency/DependencyDumper.php @@ -1,4 +1,6 @@ -dependencyResolver = $dependencyResolver; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->parser = $parser; - $this->scopeFactory = $scopeFactory; - $this->fileFinder = $fileFinder; - } - - /** - * @param string[] $files - * @param callable(int $count): void $countCallback - * @param callable(): void $progressCallback - * @param string[]|null $analysedPaths - * @return string[][] - */ - public function dumpDependencies( - array $files, - callable $countCallback, - callable $progressCallback, - ?array $analysedPaths - ): array - { - $analysedFiles = $files; - if ($analysedPaths !== null) { - $analysedFiles = $this->fileFinder->findFiles($analysedPaths)->getFiles(); - } - $this->nodeScopeResolver->setAnalysedFiles($analysedFiles); - $analysedFiles = array_fill_keys($analysedFiles, true); - - $dependencies = []; - $countCallback(count($files)); - foreach ($files as $file) { - try { - $parserNodes = $this->parser->parseFile($file); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - continue; - } - - $fileDependencies = []; - try { - $this->nodeScopeResolver->processNodes( - $parserNodes, - $this->scopeFactory->create(ScopeContext::create($file)), - function (\PhpParser\Node $node, Scope $scope) use ($analysedFiles, &$fileDependencies): void { - $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); - $fileDependencies = array_merge( - $fileDependencies, - $dependencies->getFileDependencies($scope->getFile(), $analysedFiles) - ); - } - ); - } catch (\PHPStan\AnalysedCodeException $e) { - // pass - } - - foreach (array_unique($fileDependencies) as $fileDependency) { - $dependencies[$fileDependency][] = $file; - } - - $progressCallback(); - } - - return $dependencies; - } - + private DependencyResolver $dependencyResolver; + + private NodeScopeResolver $nodeScopeResolver; + + private Parser $parser; + + private ScopeFactory $scopeFactory; + + private FileFinder $fileFinder; + + public function __construct( + DependencyResolver $dependencyResolver, + NodeScopeResolver $nodeScopeResolver, + Parser $parser, + ScopeFactory $scopeFactory, + FileFinder $fileFinder + ) { + $this->dependencyResolver = $dependencyResolver; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->parser = $parser; + $this->scopeFactory = $scopeFactory; + $this->fileFinder = $fileFinder; + } + + /** + * @param string[] $files + * @param callable(int $count): void $countCallback + * @param callable(): void $progressCallback + * @param string[]|null $analysedPaths + * @return string[][] + */ + public function dumpDependencies( + array $files, + callable $countCallback, + callable $progressCallback, + ?array $analysedPaths + ): array { + $analysedFiles = $files; + if ($analysedPaths !== null) { + $analysedFiles = $this->fileFinder->findFiles($analysedPaths)->getFiles(); + } + $this->nodeScopeResolver->setAnalysedFiles($analysedFiles); + $analysedFiles = array_fill_keys($analysedFiles, true); + + $dependencies = []; + $countCallback(count($files)); + foreach ($files as $file) { + try { + $parserNodes = $this->parser->parseFile($file); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + continue; + } + + $fileDependencies = []; + try { + $this->nodeScopeResolver->processNodes( + $parserNodes, + $this->scopeFactory->create(ScopeContext::create($file)), + function (\PhpParser\Node $node, Scope $scope) use ($analysedFiles, &$fileDependencies): void { + $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); + $fileDependencies = array_merge( + $fileDependencies, + $dependencies->getFileDependencies($scope->getFile(), $analysedFiles) + ); + } + ); + } catch (\PHPStan\AnalysedCodeException $e) { + // pass + } + + foreach (array_unique($fileDependencies) as $fileDependency) { + $dependencies[$fileDependency][] = $file; + } + + $progressCallback(); + } + + return $dependencies; + } } diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 80440237e4..edda60ab09 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->reflectionProvider = $reflectionProvider; - $this->exportedNodeResolver = $exportedNodeResolver; - } + private ExportedNodeResolver $exportedNodeResolver; - public function resolveDependencies(\PhpParser\Node $node, Scope $scope): NodeDependencies - { - $dependenciesReflections = []; + public function __construct( + FileHelper $fileHelper, + ReflectionProvider $reflectionProvider, + ExportedNodeResolver $exportedNodeResolver + ) { + $this->fileHelper = $fileHelper; + $this->reflectionProvider = $reflectionProvider; + $this->exportedNodeResolver = $exportedNodeResolver; + } - if ($node instanceof \PhpParser\Node\Stmt\Class_) { - if ($node->extends !== null) { - $this->addClassToDependencies($node->extends->toString(), $dependenciesReflections); - } - foreach ($node->implements as $className) { - $this->addClassToDependencies($className->toString(), $dependenciesReflections); - } - } elseif ($node instanceof \PhpParser\Node\Stmt\Interface_) { - foreach ($node->extends as $className) { - $this->addClassToDependencies($className->toString(), $dependenciesReflections); - } - } elseif ($node instanceof InClassMethodNode) { - $nativeMethod = $scope->getFunction(); - if ($nativeMethod !== null) { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants()); - if ($parametersAcceptor instanceof \PHPStan\Reflection\ParametersAcceptorWithPhpDocs) { - $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); - } - } - } elseif ($node instanceof InFunctionNode) { - $functionReflection = $scope->getFunction(); - if ($functionReflection !== null) { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + public function resolveDependencies(\PhpParser\Node $node, Scope $scope): NodeDependencies + { + $dependenciesReflections = []; - if ($parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { - $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); - } - } - } elseif ($node instanceof Closure) { - /** @var ClosureType $closureType */ - $closureType = $scope->getType($node); - foreach ($closureType->getParameters() as $parameter) { - $referencedClasses = $parameter->getType()->getReferencedClasses(); - foreach ($referencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } + if ($node instanceof \PhpParser\Node\Stmt\Class_) { + if ($node->extends !== null) { + $this->addClassToDependencies($node->extends->toString(), $dependenciesReflections); + } + foreach ($node->implements as $className) { + $this->addClassToDependencies($className->toString(), $dependenciesReflections); + } + } elseif ($node instanceof \PhpParser\Node\Stmt\Interface_) { + foreach ($node->extends as $className) { + $this->addClassToDependencies($className->toString(), $dependenciesReflections); + } + } elseif ($node instanceof InClassMethodNode) { + $nativeMethod = $scope->getFunction(); + if ($nativeMethod !== null) { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants()); + if ($parametersAcceptor instanceof \PHPStan\Reflection\ParametersAcceptorWithPhpDocs) { + $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); + } + } + } elseif ($node instanceof InFunctionNode) { + $functionReflection = $scope->getFunction(); + if ($functionReflection !== null) { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); - $returnTypeReferencedClasses = $closureType->getReturnType()->getReferencedClasses(); - foreach ($returnTypeReferencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ($node instanceof \PhpParser\Node\Expr\FuncCall) { - $functionName = $node->name; - if ($functionName instanceof \PhpParser\Node\Name) { - try { - $dependenciesReflections[] = $this->getFunctionReflection($functionName, $scope); - } catch (\PHPStan\Broker\FunctionNotFoundException $e) { - // pass - } - } else { - $calledType = $scope->getType($functionName); - if ($calledType->isCallable()->yes()) { - $variants = $calledType->getCallableParametersAcceptors($scope); - foreach ($variants as $variant) { - $referencedClasses = $variant->getReturnType()->getReferencedClasses(); - foreach ($referencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } - } - } + if ($parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { + $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); + } + } + } elseif ($node instanceof Closure) { + /** @var ClosureType $closureType */ + $closureType = $scope->getType($node); + foreach ($closureType->getParameters() as $parameter) { + $referencedClasses = $parameter->getType()->getReferencedClasses(); + foreach ($referencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } - $returnType = $scope->getType($node); - foreach ($returnType->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ($node instanceof \PhpParser\Node\Expr\MethodCall || $node instanceof \PhpParser\Node\Expr\PropertyFetch) { - $classNames = $scope->getType($node->var)->getReferencedClasses(); - foreach ($classNames as $className) { - $this->addClassToDependencies($className, $dependenciesReflections); - } + $returnTypeReferencedClasses = $closureType->getReturnType()->getReferencedClasses(); + foreach ($returnTypeReferencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ($node instanceof \PhpParser\Node\Expr\FuncCall) { + $functionName = $node->name; + if ($functionName instanceof \PhpParser\Node\Name) { + try { + $dependenciesReflections[] = $this->getFunctionReflection($functionName, $scope); + } catch (\PHPStan\Broker\FunctionNotFoundException $e) { + // pass + } + } else { + $calledType = $scope->getType($functionName); + if ($calledType->isCallable()->yes()) { + $variants = $calledType->getCallableParametersAcceptors($scope); + foreach ($variants as $variant) { + $referencedClasses = $variant->getReturnType()->getReferencedClasses(); + foreach ($referencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } + } + } - $returnType = $scope->getType($node); - foreach ($returnType->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ( - $node instanceof \PhpParser\Node\Expr\StaticCall - || $node instanceof \PhpParser\Node\Expr\ClassConstFetch - || $node instanceof \PhpParser\Node\Expr\StaticPropertyFetch - ) { - if ($node->class instanceof \PhpParser\Node\Name) { - $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); - } else { - foreach ($scope->getType($node->class)->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } + $returnType = $scope->getType($node); + foreach ($returnType->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ($node instanceof \PhpParser\Node\Expr\MethodCall || $node instanceof \PhpParser\Node\Expr\PropertyFetch) { + $classNames = $scope->getType($node->var)->getReferencedClasses(); + foreach ($classNames as $className) { + $this->addClassToDependencies($className, $dependenciesReflections); + } - $returnType = $scope->getType($node); - foreach ($returnType->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ( - $node instanceof \PhpParser\Node\Expr\New_ - && $node->class instanceof \PhpParser\Node\Name - ) { - $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); - } elseif ($node instanceof \PhpParser\Node\Stmt\TraitUse) { - foreach ($node->traits as $traitName) { - $this->addClassToDependencies($traitName->toString(), $dependenciesReflections); - } - } elseif ($node instanceof \PhpParser\Node\Expr\Instanceof_) { - if ($node->class instanceof Name) { - $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); - } - } elseif ($node instanceof \PhpParser\Node\Stmt\Catch_) { - foreach ($node->types as $type) { - $this->addClassToDependencies($scope->resolveName($type), $dependenciesReflections); - } - } elseif ($node instanceof ArrayDimFetch && $node->dim !== null) { - $varType = $scope->getType($node->var); - $dimType = $scope->getType($node->dim); + $returnType = $scope->getType($node); + foreach ($returnType->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ( + $node instanceof \PhpParser\Node\Expr\StaticCall + || $node instanceof \PhpParser\Node\Expr\ClassConstFetch + || $node instanceof \PhpParser\Node\Expr\StaticPropertyFetch + ) { + if ($node->class instanceof \PhpParser\Node\Name) { + $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); + } else { + foreach ($scope->getType($node->class)->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } - foreach ($varType->getOffsetValueType($dimType)->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ($node instanceof Foreach_) { - $exprType = $scope->getType($node->expr); - if ($node->keyVar !== null) { - foreach ($exprType->getIterableKeyType()->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } + $returnType = $scope->getType($node); + foreach ($returnType->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ( + $node instanceof \PhpParser\Node\Expr\New_ + && $node->class instanceof \PhpParser\Node\Name + ) { + $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); + } elseif ($node instanceof \PhpParser\Node\Stmt\TraitUse) { + foreach ($node->traits as $traitName) { + $this->addClassToDependencies($traitName->toString(), $dependenciesReflections); + } + } elseif ($node instanceof \PhpParser\Node\Expr\Instanceof_) { + if ($node->class instanceof Name) { + $this->addClassToDependencies($scope->resolveName($node->class), $dependenciesReflections); + } + } elseif ($node instanceof \PhpParser\Node\Stmt\Catch_) { + foreach ($node->types as $type) { + $this->addClassToDependencies($scope->resolveName($type), $dependenciesReflections); + } + } elseif ($node instanceof ArrayDimFetch && $node->dim !== null) { + $varType = $scope->getType($node->var); + $dimType = $scope->getType($node->dim); - foreach ($exprType->getIterableValueType()->getReferencedClasses() as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } elseif ( - $node instanceof Array_ - && $this->considerArrayForCallableTest($scope, $node) - ) { - $arrayType = $scope->getType($node); - if (!$arrayType->isCallable()->no()) { - foreach ($arrayType->getCallableParametersAcceptors($scope) as $variant) { - $referencedClasses = $variant->getReturnType()->getReferencedClasses(); - foreach ($referencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } - } - } + foreach ($varType->getOffsetValueType($dimType)->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ($node instanceof Foreach_) { + $exprType = $scope->getType($node->expr); + if ($node->keyVar !== null) { + foreach ($exprType->getIterableKeyType()->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } - return new NodeDependencies($this->fileHelper, $dependenciesReflections, $this->exportedNodeResolver->resolve($scope->getFile(), $node)); - } + foreach ($exprType->getIterableValueType()->getReferencedClasses() as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } elseif ( + $node instanceof Array_ + && $this->considerArrayForCallableTest($scope, $node) + ) { + $arrayType = $scope->getType($node); + if (!$arrayType->isCallable()->no()) { + foreach ($arrayType->getCallableParametersAcceptors($scope) as $variant) { + $referencedClasses = $variant->getReturnType()->getReferencedClasses(); + foreach ($referencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } + } + } - private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): bool - { - if (!isset($arrayNode->items[0])) { - return false; - } + return new NodeDependencies($this->fileHelper, $dependenciesReflections, $this->exportedNodeResolver->resolve($scope->getFile(), $node)); + } - $itemType = $scope->getType($arrayNode->items[0]->value); - if (!$itemType instanceof ConstantStringType) { - return true; - } + private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): bool + { + if (!isset($arrayNode->items[0])) { + return false; + } - return $itemType->isClassString(); - } + $itemType = $scope->getType($arrayNode->items[0]->value); + if (!$itemType instanceof ConstantStringType) { + return true; + } - /** - * @param string $className - * @param ReflectionWithFilename[] $dependenciesReflections - */ - private function addClassToDependencies(string $className, array &$dependenciesReflections): void - { - try { - $classReflection = $this->reflectionProvider->getClass($className); - } catch (\PHPStan\Broker\ClassNotFoundException $e) { - return; - } + return $itemType->isClassString(); + } - do { - $dependenciesReflections[] = $classReflection; + /** + * @param string $className + * @param ReflectionWithFilename[] $dependenciesReflections + */ + private function addClassToDependencies(string $className, array &$dependenciesReflections): void + { + try { + $classReflection = $this->reflectionProvider->getClass($className); + } catch (\PHPStan\Broker\ClassNotFoundException $e) { + return; + } - foreach ($classReflection->getInterfaces() as $interface) { - $dependenciesReflections[] = $interface; - } + do { + $dependenciesReflections[] = $classReflection; - foreach ($classReflection->getTraits() as $trait) { - $dependenciesReflections[] = $trait; - } + foreach ($classReflection->getInterfaces() as $interface) { + $dependenciesReflections[] = $interface; + } - $classReflection = $classReflection->getParentClass(); - } while ($classReflection !== false); - } + foreach ($classReflection->getTraits() as $trait) { + $dependenciesReflections[] = $trait; + } - private function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): ReflectionWithFilename - { - $reflection = $this->reflectionProvider->getFunction($nameNode, $scope); - if (!$reflection instanceof ReflectionWithFilename) { - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } + $classReflection = $classReflection->getParentClass(); + } while ($classReflection !== false); + } - return $reflection; - } + private function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): ReflectionWithFilename + { + $reflection = $this->reflectionProvider->getFunction($nameNode, $scope); + if (!$reflection instanceof ReflectionWithFilename) { + throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); + } - /** - * @param ParametersAcceptorWithPhpDocs $parametersAcceptor - * @param ReflectionWithFilename[] $dependenciesReflections - */ - private function extractFromParametersAcceptor( - ParametersAcceptorWithPhpDocs $parametersAcceptor, - array &$dependenciesReflections - ): void - { - foreach ($parametersAcceptor->getParameters() as $parameter) { - $referencedClasses = array_merge( - $parameter->getNativeType()->getReferencedClasses(), - $parameter->getPhpDocType()->getReferencedClasses() - ); + return $reflection; + } - foreach ($referencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } + /** + * @param ParametersAcceptorWithPhpDocs $parametersAcceptor + * @param ReflectionWithFilename[] $dependenciesReflections + */ + private function extractFromParametersAcceptor( + ParametersAcceptorWithPhpDocs $parametersAcceptor, + array &$dependenciesReflections + ): void { + foreach ($parametersAcceptor->getParameters() as $parameter) { + $referencedClasses = array_merge( + $parameter->getNativeType()->getReferencedClasses(), + $parameter->getPhpDocType()->getReferencedClasses() + ); - $returnTypeReferencedClasses = array_merge( - $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), - $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() - ); - foreach ($returnTypeReferencedClasses as $referencedClass) { - $this->addClassToDependencies($referencedClass, $dependenciesReflections); - } - } + foreach ($referencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } + $returnTypeReferencedClasses = array_merge( + $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), + $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() + ); + foreach ($returnTypeReferencedClasses as $referencedClass) { + $this->addClassToDependencies($referencedClass, $dependenciesReflections); + } + } } diff --git a/src/Dependency/ExportedNode.php b/src/Dependency/ExportedNode.php index ee088cd05b..41e6e5b93a 100644 --- a/src/Dependency/ExportedNode.php +++ b/src/Dependency/ExportedNode.php @@ -1,22 +1,22 @@ -name = $name; - $this->value = $value; - $this->public = $public; - $this->private = $private; - } + private bool $private; - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } + public function __construct(string $name, string $value, bool $public, bool $private) + { + $this->name = $name; + $this->value = $value; + $this->public = $public; + $this->private = $private; + } - return $this->name === $node->name - && $this->value === $node->value - && $this->public === $node->public - && $this->private === $node->private; - } + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['value'], - $properties['public'], - $properties['private'] - ); - } + return $this->name === $node->name + && $this->value === $node->value + && $this->public === $node->public + && $this->private === $node->private; + } - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['value'], - $data['public'], - $data['private'] - ); - } + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['value'], + $properties['public'], + $properties['private'] + ); + } - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'value' => $this->value, - 'public' => $this->public, - 'private' => $this->private, - ], - ]; - } + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['value'], + $data['public'], + $data['private'] + ); + } + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'value' => $this->value, + 'public' => $this->public, + 'private' => $this->private, + ], + ]; + } } diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index 4977a47643..78dee39f92 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->phpDoc = $phpDoc; - $this->abstract = $abstract; - $this->final = $final; - $this->extends = $extends; - $this->implements = $implements; - $this->usedTraits = $usedTraits; - $this->traitUseAdaptations = $traitUseAdaptations; - } - - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } - - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } - - if (count($this->traitUseAdaptations) !== count($node->traitUseAdaptations)) { - return false; - } - - foreach ($this->traitUseAdaptations as $i => $ourTraitUseAdaptation) { - $theirTraitUseAdaptation = $node->traitUseAdaptations[$i]; - if (!$ourTraitUseAdaptation->equals($theirTraitUseAdaptation)) { - return false; - } - } - - return $this->name === $node->name - && $this->abstract === $node->abstract - && $this->final === $node->final - && $this->extends === $node->extends - && $this->implements === $node->implements - && $this->usedTraits === $node->usedTraits; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['phpDoc'], - $properties['abstract'], - $properties['final'], - $properties['extends'], - $properties['implements'], - $properties['usedTraits'], - $properties['traitUseAdaptations'] - ); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'phpDoc' => $this->phpDoc, - 'abstract' => $this->abstract, - 'final' => $this->final, - 'extends' => $this->extends, - 'implements' => $this->implements, - 'usedTraits' => $this->usedTraits, - 'traitUseAdaptations' => $this->traitUseAdaptations, - ], - ]; - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['abstract'], - $data['final'], - $data['extends'], - $data['implements'], - $data['usedTraits'], - array_map(static function (array $traitUseAdaptationData): ExportedTraitUseAdaptation { - if ($traitUseAdaptationData['type'] !== ExportedTraitUseAdaptation::class) { - throw new \PHPStan\ShouldNotHappenException(); - } - return ExportedTraitUseAdaptation::decode($traitUseAdaptationData['data']); - }, $data['traitUseAdaptations']) - ); - } - + private string $name; + + private ?ExportedPhpDocNode $phpDoc; + + private bool $abstract; + + private bool $final; + + private ?string $extends; + + /** @var string[] */ + private array $implements; + + /** @var string[] */ + private array $usedTraits; + + /** @var ExportedTraitUseAdaptation[] */ + private array $traitUseAdaptations; + + /** + * @param string $name + * @param ExportedPhpDocNode|null $phpDoc + * @param bool $abstract + * @param bool $final + * @param string|null $extends + * @param string[] $implements + * @param string[] $usedTraits + * @param ExportedTraitUseAdaptation[] $traitUseAdaptations + */ + public function __construct( + string $name, + ?ExportedPhpDocNode $phpDoc, + bool $abstract, + bool $final, + ?string $extends, + array $implements, + array $usedTraits, + array $traitUseAdaptations + ) { + $this->name = $name; + $this->phpDoc = $phpDoc; + $this->abstract = $abstract; + $this->final = $final; + $this->extends = $extends; + $this->implements = $implements; + $this->usedTraits = $usedTraits; + $this->traitUseAdaptations = $traitUseAdaptations; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + if (count($this->traitUseAdaptations) !== count($node->traitUseAdaptations)) { + return false; + } + + foreach ($this->traitUseAdaptations as $i => $ourTraitUseAdaptation) { + $theirTraitUseAdaptation = $node->traitUseAdaptations[$i]; + if (!$ourTraitUseAdaptation->equals($theirTraitUseAdaptation)) { + return false; + } + } + + return $this->name === $node->name + && $this->abstract === $node->abstract + && $this->final === $node->final + && $this->extends === $node->extends + && $this->implements === $node->implements + && $this->usedTraits === $node->usedTraits; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['abstract'], + $properties['final'], + $properties['extends'], + $properties['implements'], + $properties['usedTraits'], + $properties['traitUseAdaptations'] + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'abstract' => $this->abstract, + 'final' => $this->final, + 'extends' => $this->extends, + 'implements' => $this->implements, + 'usedTraits' => $this->usedTraits, + 'traitUseAdaptations' => $this->traitUseAdaptations, + ], + ]; + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['abstract'], + $data['final'], + $data['extends'], + $data['implements'], + $data['usedTraits'], + array_map(static function (array $traitUseAdaptationData): ExportedTraitUseAdaptation { + if ($traitUseAdaptationData['type'] !== ExportedTraitUseAdaptation::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedTraitUseAdaptation::decode($traitUseAdaptationData['data']); + }, $data['traitUseAdaptations']) + ); + } } diff --git a/src/Dependency/ExportedNode/ExportedFunctionNode.php b/src/Dependency/ExportedNode/ExportedFunctionNode.php index 6c4382b1b0..7b00e952a3 100644 --- a/src/Dependency/ExportedNode/ExportedFunctionNode.php +++ b/src/Dependency/ExportedNode/ExportedFunctionNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->phpDoc = $phpDoc; - $this->byRef = $byRef; - $this->returnType = $returnType; - $this->parameters = $parameters; - } - - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } - - if (count($this->parameters) !== count($node->parameters)) { - return false; - } - - foreach ($this->parameters as $i => $ourParameter) { - $theirParameter = $node->parameters[$i]; - if (!$ourParameter->equals($theirParameter)) { - return false; - } - } - - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } - - return $this->name === $node->name - && $this->byRef === $node->byRef - && $this->returnType === $node->returnType; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['phpDoc'], - $properties['byRef'], - $properties['returnType'], - $properties['parameters'] - ); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'phpDoc' => $this->phpDoc, - 'byRef' => $this->byRef, - 'returnType' => $this->returnType, - 'parameters' => $this->parameters, - ], - ]; - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['byRef'], - $data['returnType'], - array_map(static function (array $parameterData): ExportedParameterNode { - if ($parameterData['type'] !== ExportedParameterNode::class) { - throw new \PHPStan\ShouldNotHappenException(); - } - return ExportedParameterNode::decode($parameterData['data']); - }, $data['parameters']) - ); - } - + private string $name; + + private ?ExportedPhpDocNode $phpDoc; + + private bool $byRef; + + private ?string $returnType; + + /** @var ExportedParameterNode[] */ + private array $parameters; + + /** + * @param string $name + * @param ExportedPhpDocNode|null $phpDoc + * @param bool $byRef + * @param string|null $returnType + * @param ExportedParameterNode[] $parameters + */ + public function __construct( + string $name, + ?ExportedPhpDocNode $phpDoc, + bool $byRef, + ?string $returnType, + array $parameters + ) { + $this->name = $name; + $this->phpDoc = $phpDoc; + $this->byRef = $byRef; + $this->returnType = $returnType; + $this->parameters = $parameters; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if (count($this->parameters) !== count($node->parameters)) { + return false; + } + + foreach ($this->parameters as $i => $ourParameter) { + $theirParameter = $node->parameters[$i]; + if (!$ourParameter->equals($theirParameter)) { + return false; + } + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + return $this->name === $node->name + && $this->byRef === $node->byRef + && $this->returnType === $node->returnType; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['byRef'], + $properties['returnType'], + $properties['parameters'] + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'byRef' => $this->byRef, + 'returnType' => $this->returnType, + 'parameters' => $this->parameters, + ], + ]; + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['byRef'], + $data['returnType'], + array_map(static function (array $parameterData): ExportedParameterNode { + if ($parameterData['type'] !== ExportedParameterNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedParameterNode::decode($parameterData['data']); + }, $data['parameters']) + ); + } } diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index 0519efba99..4cc8a7660b 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->phpDoc = $phpDoc; - $this->extends = $extends; - } + /** @var string[] */ + private array $extends; - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } + /** + * @param string $name + * @param ExportedPhpDocNode|null $phpDoc + * @param string[] $extends + */ + public function __construct(string $name, ?ExportedPhpDocNode $phpDoc, array $extends) + { + $this->name = $name; + $this->phpDoc = $phpDoc; + $this->extends = $extends; + } - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } - return $this->name === $node->name - && $this->extends === $node->extends; - } + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['phpDoc'], - $properties['extends'] - ); - } + return $this->name === $node->name + && $this->extends === $node->extends; + } - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'phpDoc' => $this->phpDoc, - 'extends' => $this->extends, - ], - ]; - } + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['extends'] + ); + } - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['extends'] - ); - } + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'extends' => $this->extends, + ], + ]; + } + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['extends'] + ); + } } diff --git a/src/Dependency/ExportedNode/ExportedMethodNode.php b/src/Dependency/ExportedNode/ExportedMethodNode.php index a59a40c25a..bd0bddd832 100644 --- a/src/Dependency/ExportedNode/ExportedMethodNode.php +++ b/src/Dependency/ExportedNode/ExportedMethodNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->phpDoc = $phpDoc; - $this->byRef = $byRef; - $this->public = $public; - $this->private = $private; - $this->abstract = $abstract; - $this->final = $final; - $this->static = $static; - $this->returnType = $returnType; - $this->parameters = $parameters; - } - - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } - - if (count($this->parameters) !== count($node->parameters)) { - return false; - } - - foreach ($this->parameters as $i => $ourParameter) { - $theirParameter = $node->parameters[$i]; - if (!$ourParameter->equals($theirParameter)) { - return false; - } - } - - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } - - return $this->name === $node->name - && $this->byRef === $node->byRef - && $this->public === $node->public - && $this->private === $node->private - && $this->abstract === $node->abstract - && $this->final === $node->final - && $this->static === $node->static - && $this->returnType === $node->returnType; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['phpDoc'], - $properties['byRef'], - $properties['public'], - $properties['private'], - $properties['abstract'], - $properties['final'], - $properties['static'], - $properties['returnType'], - $properties['parameters'] - ); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'phpDoc' => $this->phpDoc, - 'byRef' => $this->byRef, - 'public' => $this->public, - 'private' => $this->private, - 'abstract' => $this->abstract, - 'final' => $this->final, - 'static' => $this->static, - 'returnType' => $this->returnType, - 'parameters' => $this->parameters, - ], - ]; - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['byRef'], - $data['public'], - $data['private'], - $data['abstract'], - $data['final'], - $data['static'], - $data['returnType'], - array_map(static function (array $parameterData): ExportedParameterNode { - if ($parameterData['type'] !== ExportedParameterNode::class) { - throw new \PHPStan\ShouldNotHappenException(); - } - return ExportedParameterNode::decode($parameterData['data']); - }, $data['parameters']) - ); - } - + private string $name; + + private ?ExportedPhpDocNode $phpDoc; + + private bool $byRef; + + private bool $public; + + private bool $private; + + private bool $abstract; + + private bool $final; + + private bool $static; + + private ?string $returnType; + + /** @var ExportedParameterNode[] */ + private array $parameters; + + /** + * @param string $name + * @param ExportedPhpDocNode|null $phpDoc + * @param bool $byRef + * @param bool $public + * @param bool $private + * @param bool $abstract + * @param bool $final + * @param bool $static + * @param string|null $returnType + * @param ExportedParameterNode[] $parameters + */ + public function __construct( + string $name, + ?ExportedPhpDocNode $phpDoc, + bool $byRef, + bool $public, + bool $private, + bool $abstract, + bool $final, + bool $static, + ?string $returnType, + array $parameters + ) { + $this->name = $name; + $this->phpDoc = $phpDoc; + $this->byRef = $byRef; + $this->public = $public; + $this->private = $private; + $this->abstract = $abstract; + $this->final = $final; + $this->static = $static; + $this->returnType = $returnType; + $this->parameters = $parameters; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if (count($this->parameters) !== count($node->parameters)) { + return false; + } + + foreach ($this->parameters as $i => $ourParameter) { + $theirParameter = $node->parameters[$i]; + if (!$ourParameter->equals($theirParameter)) { + return false; + } + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + return $this->name === $node->name + && $this->byRef === $node->byRef + && $this->public === $node->public + && $this->private === $node->private + && $this->abstract === $node->abstract + && $this->final === $node->final + && $this->static === $node->static + && $this->returnType === $node->returnType; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['byRef'], + $properties['public'], + $properties['private'], + $properties['abstract'], + $properties['final'], + $properties['static'], + $properties['returnType'], + $properties['parameters'] + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'byRef' => $this->byRef, + 'public' => $this->public, + 'private' => $this->private, + 'abstract' => $this->abstract, + 'final' => $this->final, + 'static' => $this->static, + 'returnType' => $this->returnType, + 'parameters' => $this->parameters, + ], + ]; + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['byRef'], + $data['public'], + $data['private'], + $data['abstract'], + $data['final'], + $data['static'], + $data['returnType'], + array_map(static function (array $parameterData): ExportedParameterNode { + if ($parameterData['type'] !== ExportedParameterNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedParameterNode::decode($parameterData['data']); + }, $data['parameters']) + ); + } } diff --git a/src/Dependency/ExportedNode/ExportedParameterNode.php b/src/Dependency/ExportedNode/ExportedParameterNode.php index 5f171a442f..db3967b01f 100644 --- a/src/Dependency/ExportedNode/ExportedParameterNode.php +++ b/src/Dependency/ExportedNode/ExportedParameterNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->type = $type; - $this->byRef = $byRef; - $this->variadic = $variadic; - $this->hasDefault = $hasDefault; - } + private bool $hasDefault; - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } + public function __construct( + string $name, + ?string $type, + bool $byRef, + bool $variadic, + bool $hasDefault + ) { + $this->name = $name; + $this->type = $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->hasDefault = $hasDefault; + } - return $this->name === $node->name - && $this->type === $node->type - && $this->byRef === $node->byRef - && $this->variadic === $node->variadic - && $this->hasDefault === $node->hasDefault; - } + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['type'], - $properties['byRef'], - $properties['variadic'], - $properties['hasDefault'] - ); - } + return $this->name === $node->name + && $this->type === $node->type + && $this->byRef === $node->byRef + && $this->variadic === $node->variadic + && $this->hasDefault === $node->hasDefault; + } - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'type' => $this->type, - 'byRef' => $this->byRef, - 'variadic' => $this->variadic, - 'hasDefault' => $this->hasDefault, - ], - ]; - } + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['type'], + $properties['byRef'], + $properties['variadic'], + $properties['hasDefault'] + ); + } - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['type'], - $data['byRef'], - $data['variadic'], - $data['hasDefault'] - ); - } + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'type' => $this->type, + 'byRef' => $this->byRef, + 'variadic' => $this->variadic, + 'hasDefault' => $this->hasDefault, + ], + ]; + } + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['type'], + $data['byRef'], + $data['variadic'], + $data['hasDefault'] + ); + } } diff --git a/src/Dependency/ExportedNode/ExportedPhpDocNode.php b/src/Dependency/ExportedNode/ExportedPhpDocNode.php index e271b6b752..e13cf57979 100644 --- a/src/Dependency/ExportedNode/ExportedPhpDocNode.php +++ b/src/Dependency/ExportedNode/ExportedPhpDocNode.php @@ -1,4 +1,6 @@ - alias(string) => fullName(string) */ - private array $uses; + private ?string $namespace; - /** - * @param string $phpDocString - * @param string|null $namespace - * @param array $uses - */ - public function __construct(string $phpDocString, ?string $namespace, array $uses) - { - $this->phpDocString = $phpDocString; - $this->namespace = $namespace; - $this->uses = $uses; - } + /** @var array alias(string) => fullName(string) */ + private array $uses; - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } + /** + * @param string $phpDocString + * @param string|null $namespace + * @param array $uses + */ + public function __construct(string $phpDocString, ?string $namespace, array $uses) + { + $this->phpDocString = $phpDocString; + $this->namespace = $namespace; + $this->uses = $uses; + } - return $this->phpDocString === $node->phpDocString - && $this->namespace === $node->namespace - && $this->uses === $node->uses; - } + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'phpDocString' => $this->phpDocString, - 'namespace' => $this->namespace, - 'uses' => $this->uses, - ], - ]; - } + return $this->phpDocString === $node->phpDocString + && $this->namespace === $node->namespace + && $this->uses === $node->uses; + } - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self($properties['phpDocString'], $properties['namespace'], $properties['uses']); - } + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'phpDocString' => $this->phpDocString, + 'namespace' => $this->namespace, + 'uses' => $this->uses, + ], + ]; + } - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self($data['phpDocString'], $data['namespace'], $data['uses']); - } + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self($properties['phpDocString'], $properties['namespace'], $properties['uses']); + } + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self($data['phpDocString'], $data['namespace'], $data['uses']); + } } diff --git a/src/Dependency/ExportedNode/ExportedPropertyNode.php b/src/Dependency/ExportedNode/ExportedPropertyNode.php index 37aee5a044..ce457c7b39 100644 --- a/src/Dependency/ExportedNode/ExportedPropertyNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertyNode.php @@ -1,4 +1,6 @@ -name = $name; - $this->phpDoc = $phpDoc; - $this->type = $type; - $this->public = $public; - $this->private = $private; - $this->static = $static; - } - - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } - - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } - - return $this->name === $node->name - && $this->type === $node->type - && $this->public === $node->public - && $this->private === $node->private - && $this->static === $node->static; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['name'], - $properties['phpDoc'], - $properties['type'], - $properties['public'], - $properties['private'], - $properties['static'] - ); - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['name'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['type'], - $data['public'], - $data['private'], - $data['static'] - ); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'name' => $this->name, - 'phpDoc' => $this->phpDoc, - 'type' => $this->type, - 'public' => $this->public, - 'private' => $this->private, - 'static' => $this->static, - ], - ]; - } - + private string $name; + + private ?ExportedPhpDocNode $phpDoc; + + private ?string $type; + + private bool $public; + + private bool $private; + + private bool $static; + + public function __construct( + string $name, + ?ExportedPhpDocNode $phpDoc, + ?string $type, + bool $public, + bool $private, + bool $static + ) { + $this->name = $name; + $this->phpDoc = $phpDoc; + $this->type = $type; + $this->public = $public; + $this->private = $private; + $this->static = $static; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + return $this->name === $node->name + && $this->type === $node->type + && $this->public === $node->public + && $this->private === $node->private + && $this->static === $node->static; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['type'], + $properties['public'], + $properties['private'], + $properties['static'] + ); + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['type'], + $data['public'], + $data['private'], + $data['static'] + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'type' => $this->type, + 'public' => $this->public, + 'private' => $this->private, + 'static' => $this->static, + ], + ]; + } } diff --git a/src/Dependency/ExportedNode/ExportedTraitNode.php b/src/Dependency/ExportedNode/ExportedTraitNode.php index ed8a6fa5ef..68e8726272 100644 --- a/src/Dependency/ExportedNode/ExportedTraitNode.php +++ b/src/Dependency/ExportedNode/ExportedTraitNode.php @@ -1,4 +1,6 @@ -traitName = $traitName; - } - - public function equals(ExportedNode $node): bool - { - return false; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self($properties['traitName']); - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self($data['traitName']); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'traitName' => $this->traitName, - ], - ]; - } - + private string $traitName; + + public function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + public function equals(ExportedNode $node): bool + { + return false; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self($properties['traitName']); + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self($data['traitName']); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'traitName' => $this->traitName, + ], + ]; + } } diff --git a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php index 1de62fcf0a..d173740e87 100644 --- a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php +++ b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php @@ -1,4 +1,6 @@ -traitName = $traitName; - $this->method = $method; - $this->newModifier = $newModifier; - $this->newName = $newName; - $this->insteadOfs = $insteadOfs; - } - - public static function createAlias( - ?string $traitName, - string $method, - ?int $newModifier, - ?string $newName - ): self - { - return new self($traitName, $method, $newModifier, $newName, null); - } - - /** - * @param string|null $traitName - * @param string $method - * @param string[] $insteadOfs - * @return self - */ - public static function createPrecedence( - ?string $traitName, - string $method, - array $insteadOfs - ): self - { - return new self($traitName, $method, null, null, $insteadOfs); - } - - public function equals(ExportedNode $node): bool - { - if (!$node instanceof self) { - return false; - } - - return $this->traitName === $node->traitName - && $this->method === $node->method - && $this->newModifier === $node->newModifier - && $this->newName === $node->newName - && $this->insteadOfs === $node->insteadOfs; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): ExportedNode - { - return new self( - $properties['traitName'], - $properties['method'], - $properties['newModifier'], - $properties['newName'], - $properties['insteadOfs'] - ); - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self( - $data['traitName'], - $data['method'], - $data['newModifier'], - $data['newName'], - $data['insteadOfs'] - ); - } - - /** - * @return mixed - */ - public function jsonSerialize() - { - return [ - 'type' => self::class, - 'data' => [ - 'traitName' => $this->traitName, - 'method' => $this->method, - 'newModifier' => $this->newModifier, - 'newName' => $this->newName, - 'insteadOfs' => $this->insteadOfs, - ], - ]; - } - + private ?string $traitName; + + private string $method; + + private ?int $newModifier; + + private ?string $newName; + + /** @var string[]|null */ + private ?array $insteadOfs; + + /** + * @param string|null $traitName + * @param string $method + * @param int|null $newModifier + * @param string|null $newName + * @param string[]|null $insteadOfs + */ + private function __construct( + ?string $traitName, + string $method, + ?int $newModifier, + ?string $newName, + ?array $insteadOfs + ) { + $this->traitName = $traitName; + $this->method = $method; + $this->newModifier = $newModifier; + $this->newName = $newName; + $this->insteadOfs = $insteadOfs; + } + + public static function createAlias( + ?string $traitName, + string $method, + ?int $newModifier, + ?string $newName + ): self { + return new self($traitName, $method, $newModifier, $newName, null); + } + + /** + * @param string|null $traitName + * @param string $method + * @param string[] $insteadOfs + * @return self + */ + public static function createPrecedence( + ?string $traitName, + string $method, + array $insteadOfs + ): self { + return new self($traitName, $method, null, null, $insteadOfs); + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + return $this->traitName === $node->traitName + && $this->method === $node->method + && $this->newModifier === $node->newModifier + && $this->newName === $node->newName + && $this->insteadOfs === $node->insteadOfs; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['traitName'], + $properties['method'], + $properties['newModifier'], + $properties['newName'], + $properties['insteadOfs'] + ); + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['traitName'], + $data['method'], + $data['newModifier'], + $data['newName'], + $data['insteadOfs'] + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'traitName' => $this->traitName, + 'method' => $this->method, + 'newModifier' => $this->newModifier, + 'newName' => $this->newName, + 'insteadOfs' => $this->insteadOfs, + ], + ]; + } } diff --git a/src/Dependency/ExportedNodeFetcher.php b/src/Dependency/ExportedNodeFetcher.php index 8a4c6beb79..849cde9f66 100644 --- a/src/Dependency/ExportedNodeFetcher.php +++ b/src/Dependency/ExportedNodeFetcher.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->visitor = $visitor; - } - - /** - * @param string $fileName - * @return ExportedNode[] - */ - public function fetchNodes(string $fileName): array - { - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->visitor); - - try { - /** @var \PhpParser\Node[] $ast */ - $ast = $this->parser->parseFile($fileName); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - return []; - } - $this->visitor->reset($fileName); - $nodeTraverser->traverse($ast); - - return $this->visitor->getExportedNodes(); - } - + private Parser $parser; + + private ExportedNodeVisitor $visitor; + + public function __construct( + Parser $parser, + ExportedNodeVisitor $visitor + ) { + $this->parser = $parser; + $this->visitor = $visitor; + } + + /** + * @param string $fileName + * @return ExportedNode[] + */ + public function fetchNodes(string $fileName): array + { + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->visitor); + + try { + /** @var \PhpParser\Node[] $ast */ + $ast = $this->parser->parseFile($fileName); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + return []; + } + $this->visitor->reset($fileName); + $nodeTraverser->traverse($ast); + + return $this->visitor->getExportedNodes(); + } } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 406c937d9f..5ffd3db3b0 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->printer = $printer; - } - - public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode - { - if ($node instanceof Class_ && isset($node->namespacedName)) { - $docComment = $node->getDocComment(); - $extendsName = null; - if ($node->extends !== null) { - $extendsName = $node->extends->toString(); - } - - $implementsNames = []; - foreach ($node->implements as $className) { - $implementsNames[] = $className->toString(); - } - - $usedTraits = []; - $adaptations = []; - foreach ($node->getTraitUses() as $traitUse) { - foreach ($traitUse->traits as $usedTraitName) { - $usedTraits[] = $usedTraitName->toString(); - } - foreach ($traitUse->adaptations as $adaptation) { - $adaptations[] = $adaptation; - } - } - - $className = $node->namespacedName->toString(); - - return new ExportedClassNode( - $className, - $this->exportPhpDocNode( - $fileName, - $className, - null, - $docComment !== null ? $docComment->getText() : null - ), - $node->isAbstract(), - $node->isFinal(), - $extendsName, - $implementsNames, - $usedTraits, - array_map(static function (Node\Stmt\TraitUseAdaptation $adaptation): ExportedTraitUseAdaptation { - if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { - return ExportedTraitUseAdaptation::createAlias( - $adaptation->trait !== null ? $adaptation->trait->toString() : null, - $adaptation->method->toString(), - $adaptation->newModifier, - $adaptation->newName !== null ? $adaptation->newName->toString() : null - ); - } - - if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { - return ExportedTraitUseAdaptation::createPrecedence( - $adaptation->trait !== null ? $adaptation->trait->toString() : null, - $adaptation->method->toString(), - array_map(static function (Name $name): string { - return $name->toString(); - }, $adaptation->insteadof) - ); - } - - throw new \PHPStan\ShouldNotHappenException(); - }, $adaptations) - ); - } - - if ($node instanceof \PhpParser\Node\Stmt\Interface_ && isset($node->namespacedName)) { - $extendsNames = []; - $docComment = $node->getDocComment(); - - $interfaceName = $node->namespacedName->toString(); - - return new ExportedInterfaceNode( - $interfaceName, - $this->exportPhpDocNode( - $fileName, - $interfaceName, - null, - $docComment !== null ? $docComment->getText() : null - ), - $extendsNames - ); - } - - if ($node instanceof Node\Stmt\Trait_ && isset($node->namespacedName)) { - return new ExportedTraitNode($node->namespacedName->toString()); - } - - if ($node instanceof ClassMethod) { - if ($node->isAbstract() || $node->isFinal() || !$node->isPrivate()) { - $methodName = $node->name->toString(); - $docComment = $node->getDocComment(); - $parentNode = $node->getAttribute('parent'); - $continue = ($parentNode instanceof Class_ || $parentNode instanceof Node\Stmt\Interface_) && isset($parentNode->namespacedName); - if (!$continue) { - return null; - } - - return new ExportedMethodNode( - $methodName, - $this->exportPhpDocNode( - $fileName, - $parentNode->namespacedName->toString(), - $methodName, - $docComment !== null ? $docComment->getText() : null - ), - $node->byRef, - $node->isPublic(), - $node->isPrivate(), - $node->isAbstract(), - $node->isFinal(), - $node->isStatic(), - $this->printType($node->returnType), - $this->exportParameterNodes($node->params) - ); - } - } - - if ($node instanceof Node\Stmt\PropertyProperty) { - $parentNode = $node->getAttribute('parent'); - if (!$parentNode instanceof Property) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Expected node type %s, %s occurred.', Property::class, is_object($parentNode) ? get_class($parentNode) : gettype($parentNode))); - } - if ($parentNode->isPrivate()) { - return null; - } - - $classNode = $parentNode->getAttribute('parent'); - if (!$classNode instanceof Class_ || !isset($classNode->namespacedName)) { - return null; - } - - $docComment = $parentNode->getDocComment(); - - return new ExportedPropertyNode( - $node->name->toString(), - $this->exportPhpDocNode( - $fileName, - $classNode->namespacedName->toString(), - null, - $docComment !== null ? $docComment->getText() : null - ), - $this->printType($parentNode->type), - $parentNode->isPublic(), - $parentNode->isPrivate(), - $parentNode->isStatic() - ); - } - - if ($node instanceof Node\Const_) { - $parentNode = $node->getAttribute('parent'); - if (!$parentNode instanceof Node\Stmt\ClassConst) { - return null; - } - - if ($parentNode->isPrivate()) { - return null; - } - - return new ExportedClassConstantNode( - $node->name->toString(), - $this->printer->prettyPrintExpr($node->value), - $parentNode->isPublic(), - $parentNode->isPrivate() - ); - } - - if ($node instanceof Function_) { - $functionName = $node->name->name; - if (isset($node->namespacedName)) { - $functionName = (string) $node->namespacedName; - } - - $docComment = $node->getDocComment(); - - return new ExportedFunctionNode( - $functionName, - $this->exportPhpDocNode( - $fileName, - null, - $functionName, - $docComment !== null ? $docComment->getText() : null - ), - $node->byRef, - $this->printType($node->returnType), - $this->exportParameterNodes($node->params) - ); - } - - return null; - } - - /** - * @param Node\Identifier|Node\Name|Node\NullableType|Node\UnionType|null $type - * @return string|null - */ - private function printType($type): ?string - { - if ($type === null) { - return null; - } - - if ($type instanceof Node\NullableType) { - return '?' . $this->printType($type->type); - } - - if ($type instanceof Node\UnionType) { - return implode('|', array_map(function ($innerType): string { - $printedType = $this->printType($innerType); - if ($printedType === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $printedType; - }, $type->types)); - } - - return $type->toString(); - } - - /** - * @param Node\Param[] $params - * @return ExportedParameterNode[] - */ - private function exportParameterNodes(array $params): array - { - $nodes = []; - foreach ($params as $param) { - if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $type = $param->type; - if ( - $type !== null - && $param->default instanceof Node\Expr\ConstFetch - && $param->default->name->toLowerString() === 'null' - ) { - if ($type instanceof Node\UnionType) { - $innerTypes = $type->types; - $innerTypes[] = new Name('null'); - $type = new Node\UnionType($innerTypes); - } elseif (!$type instanceof Node\NullableType) { - $type = new Node\NullableType($type); - } - } - $nodes[] = new ExportedParameterNode( - $param->var->name, - $this->printType($type), - $param->byRef, - $param->variadic, - $param->default !== null - ); - } - - return $nodes; - } - - private function exportPhpDocNode( - string $file, - ?string $className, - ?string $functionName, - ?string $text - ): ?ExportedPhpDocNode - { - if ($text === null) { - return null; - } - - $resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $className, - null, - $functionName, - $text - ); - - $nameScope = $resolvedPhpDocBlock->getNullableNameScope(); - if ($nameScope === null) { - return null; - } - - return new ExportedPhpDocNode($text, $nameScope->getNamespace(), $nameScope->getUses()); - } - + private FileTypeMapper $fileTypeMapper; + + private Standard $printer; + + public function __construct(FileTypeMapper $fileTypeMapper, Standard $printer) + { + $this->fileTypeMapper = $fileTypeMapper; + $this->printer = $printer; + } + + public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode + { + if ($node instanceof Class_ && isset($node->namespacedName)) { + $docComment = $node->getDocComment(); + $extendsName = null; + if ($node->extends !== null) { + $extendsName = $node->extends->toString(); + } + + $implementsNames = []; + foreach ($node->implements as $className) { + $implementsNames[] = $className->toString(); + } + + $usedTraits = []; + $adaptations = []; + foreach ($node->getTraitUses() as $traitUse) { + foreach ($traitUse->traits as $usedTraitName) { + $usedTraits[] = $usedTraitName->toString(); + } + foreach ($traitUse->adaptations as $adaptation) { + $adaptations[] = $adaptation; + } + } + + $className = $node->namespacedName->toString(); + + return new ExportedClassNode( + $className, + $this->exportPhpDocNode( + $fileName, + $className, + null, + $docComment !== null ? $docComment->getText() : null + ), + $node->isAbstract(), + $node->isFinal(), + $extendsName, + $implementsNames, + $usedTraits, + array_map(static function (Node\Stmt\TraitUseAdaptation $adaptation): ExportedTraitUseAdaptation { + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + return ExportedTraitUseAdaptation::createAlias( + $adaptation->trait !== null ? $adaptation->trait->toString() : null, + $adaptation->method->toString(), + $adaptation->newModifier, + $adaptation->newName !== null ? $adaptation->newName->toString() : null + ); + } + + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { + return ExportedTraitUseAdaptation::createPrecedence( + $adaptation->trait !== null ? $adaptation->trait->toString() : null, + $adaptation->method->toString(), + array_map(static function (Name $name): string { + return $name->toString(); + }, $adaptation->insteadof) + ); + } + + throw new \PHPStan\ShouldNotHappenException(); + }, $adaptations) + ); + } + + if ($node instanceof \PhpParser\Node\Stmt\Interface_ && isset($node->namespacedName)) { + $extendsNames = []; + $docComment = $node->getDocComment(); + + $interfaceName = $node->namespacedName->toString(); + + return new ExportedInterfaceNode( + $interfaceName, + $this->exportPhpDocNode( + $fileName, + $interfaceName, + null, + $docComment !== null ? $docComment->getText() : null + ), + $extendsNames + ); + } + + if ($node instanceof Node\Stmt\Trait_ && isset($node->namespacedName)) { + return new ExportedTraitNode($node->namespacedName->toString()); + } + + if ($node instanceof ClassMethod) { + if ($node->isAbstract() || $node->isFinal() || !$node->isPrivate()) { + $methodName = $node->name->toString(); + $docComment = $node->getDocComment(); + $parentNode = $node->getAttribute('parent'); + $continue = ($parentNode instanceof Class_ || $parentNode instanceof Node\Stmt\Interface_) && isset($parentNode->namespacedName); + if (!$continue) { + return null; + } + + return new ExportedMethodNode( + $methodName, + $this->exportPhpDocNode( + $fileName, + $parentNode->namespacedName->toString(), + $methodName, + $docComment !== null ? $docComment->getText() : null + ), + $node->byRef, + $node->isPublic(), + $node->isPrivate(), + $node->isAbstract(), + $node->isFinal(), + $node->isStatic(), + $this->printType($node->returnType), + $this->exportParameterNodes($node->params) + ); + } + } + + if ($node instanceof Node\Stmt\PropertyProperty) { + $parentNode = $node->getAttribute('parent'); + if (!$parentNode instanceof Property) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Expected node type %s, %s occurred.', Property::class, is_object($parentNode) ? get_class($parentNode) : gettype($parentNode))); + } + if ($parentNode->isPrivate()) { + return null; + } + + $classNode = $parentNode->getAttribute('parent'); + if (!$classNode instanceof Class_ || !isset($classNode->namespacedName)) { + return null; + } + + $docComment = $parentNode->getDocComment(); + + return new ExportedPropertyNode( + $node->name->toString(), + $this->exportPhpDocNode( + $fileName, + $classNode->namespacedName->toString(), + null, + $docComment !== null ? $docComment->getText() : null + ), + $this->printType($parentNode->type), + $parentNode->isPublic(), + $parentNode->isPrivate(), + $parentNode->isStatic() + ); + } + + if ($node instanceof Node\Const_) { + $parentNode = $node->getAttribute('parent'); + if (!$parentNode instanceof Node\Stmt\ClassConst) { + return null; + } + + if ($parentNode->isPrivate()) { + return null; + } + + return new ExportedClassConstantNode( + $node->name->toString(), + $this->printer->prettyPrintExpr($node->value), + $parentNode->isPublic(), + $parentNode->isPrivate() + ); + } + + if ($node instanceof Function_) { + $functionName = $node->name->name; + if (isset($node->namespacedName)) { + $functionName = (string) $node->namespacedName; + } + + $docComment = $node->getDocComment(); + + return new ExportedFunctionNode( + $functionName, + $this->exportPhpDocNode( + $fileName, + null, + $functionName, + $docComment !== null ? $docComment->getText() : null + ), + $node->byRef, + $this->printType($node->returnType), + $this->exportParameterNodes($node->params) + ); + } + + return null; + } + + /** + * @param Node\Identifier|Node\Name|Node\NullableType|Node\UnionType|null $type + * @return string|null + */ + private function printType($type): ?string + { + if ($type === null) { + return null; + } + + if ($type instanceof Node\NullableType) { + return '?' . $this->printType($type->type); + } + + if ($type instanceof Node\UnionType) { + return implode('|', array_map(function ($innerType): string { + $printedType = $this->printType($innerType); + if ($printedType === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $printedType; + }, $type->types)); + } + + return $type->toString(); + } + + /** + * @param Node\Param[] $params + * @return ExportedParameterNode[] + */ + private function exportParameterNodes(array $params): array + { + $nodes = []; + foreach ($params as $param) { + if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $type = $param->type; + if ( + $type !== null + && $param->default instanceof Node\Expr\ConstFetch + && $param->default->name->toLowerString() === 'null' + ) { + if ($type instanceof Node\UnionType) { + $innerTypes = $type->types; + $innerTypes[] = new Name('null'); + $type = new Node\UnionType($innerTypes); + } elseif (!$type instanceof Node\NullableType) { + $type = new Node\NullableType($type); + } + } + $nodes[] = new ExportedParameterNode( + $param->var->name, + $this->printType($type), + $param->byRef, + $param->variadic, + $param->default !== null + ); + } + + return $nodes; + } + + private function exportPhpDocNode( + string $file, + ?string $className, + ?string $functionName, + ?string $text + ): ?ExportedPhpDocNode { + if ($text === null) { + return null; + } + + $resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $className, + null, + $functionName, + $text + ); + + $nameScope = $resolvedPhpDocBlock->getNullableNameScope(); + if ($nameScope === null) { + return null; + } + + return new ExportedPhpDocNode($text, $nameScope->getNamespace(), $nameScope->getUses()); + } } diff --git a/src/Dependency/ExportedNodeVisitor.php b/src/Dependency/ExportedNodeVisitor.php index bedb18c037..f8d0222139 100644 --- a/src/Dependency/ExportedNodeVisitor.php +++ b/src/Dependency/ExportedNodeVisitor.php @@ -1,4 +1,6 @@ -exportedNodeResolver = $exportedNodeResolver; - } + /** @var ExportedNode[] */ + private array $currentNodes = []; - public function reset(string $fileName): void - { - $this->fileName = $fileName; - $this->currentNodes = []; - } + /** + * ExportedNodeVisitor constructor. + * + * @param ExportedNodeResolver $exportedNodeResolver + */ + public function __construct(ExportedNodeResolver $exportedNodeResolver) + { + $this->exportedNodeResolver = $exportedNodeResolver; + } - /** - * @return ExportedNode[] - */ - public function getExportedNodes(): array - { - return $this->currentNodes; - } + public function reset(string $fileName): void + { + $this->fileName = $fileName; + $this->currentNodes = []; + } - public function enterNode(Node $node): ?int - { - if ($this->fileName === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - $exportedNode = $this->exportedNodeResolver->resolve($this->fileName, $node); - if ($exportedNode !== null) { - $this->currentNodes[] = $exportedNode; - } + /** + * @return ExportedNode[] + */ + public function getExportedNodes(): array + { + return $this->currentNodes; + } - if ( - $node instanceof Node\Stmt\ClassMethod - || $node instanceof Node\Stmt\Function_ - || $node instanceof Node\Stmt\Trait_ - ) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } + public function enterNode(Node $node): ?int + { + if ($this->fileName === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + $exportedNode = $this->exportedNodeResolver->resolve($this->fileName, $node); + if ($exportedNode !== null) { + $this->currentNodes[] = $exportedNode; + } - return null; - } + if ( + $node instanceof Node\Stmt\ClassMethod + || $node instanceof Node\Stmt\Function_ + || $node instanceof Node\Stmt\Trait_ + ) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + return null; + } } diff --git a/src/Dependency/NodeDependencies.php b/src/Dependency/NodeDependencies.php index 7853933cb1..6fb0d9b889 100644 --- a/src/Dependency/NodeDependencies.php +++ b/src/Dependency/NodeDependencies.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->reflections = $reflections; - $this->exportedNode = $exportedNode; - } - - public function getIterator(): \Traversable - { - return new \ArrayIterator($this->reflections); - } - - /** - * @param string $currentFile - * @param array $analysedFiles - * @return string[] - */ - public function getFileDependencies(string $currentFile, array $analysedFiles): array - { - $dependencies = []; - - foreach ($this->reflections as $dependencyReflection) { - $dependencyFile = $dependencyReflection->getFileName(); - if ($dependencyFile === false) { - continue; - } - $dependencyFile = $this->fileHelper->normalizePath($dependencyFile); - - if ($currentFile === $dependencyFile) { - continue; - } - - if (!isset($analysedFiles[$dependencyFile])) { - continue; - } - - $dependencies[$dependencyFile] = $dependencyFile; - } - - return array_values($dependencies); - } - - public function getExportedNode(): ?ExportedNode - { - return $this->exportedNode; - } - + private FileHelper $fileHelper; + + /** @var ReflectionWithFilename[] */ + private array $reflections; + + private ?ExportedNode $exportedNode; + + /** + * @param FileHelper $fileHelper + * @param ReflectionWithFilename[] $reflections + */ + public function __construct( + FileHelper $fileHelper, + array $reflections, + ?ExportedNode $exportedNode + ) { + $this->fileHelper = $fileHelper; + $this->reflections = $reflections; + $this->exportedNode = $exportedNode; + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->reflections); + } + + /** + * @param string $currentFile + * @param array $analysedFiles + * @return string[] + */ + public function getFileDependencies(string $currentFile, array $analysedFiles): array + { + $dependencies = []; + + foreach ($this->reflections as $dependencyReflection) { + $dependencyFile = $dependencyReflection->getFileName(); + if ($dependencyFile === false) { + continue; + } + $dependencyFile = $this->fileHelper->normalizePath($dependencyFile); + + if ($currentFile === $dependencyFile) { + continue; + } + + if (!isset($analysedFiles[$dependencyFile])) { + continue; + } + + $dependencies[$dependencyFile] = $dependencyFile; + } + + return array_values($dependencies); + } + + public function getExportedNode(): ?ExportedNode + { + return $this->exportedNode; + } } diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 4808fcc85e..73de5c8b29 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -1,4 +1,6 @@ - $bool, + BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG => $bool, + BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, + BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, + BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG => $bool, + BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG => $bool, + RegistryFactory::RULE_TAG => $bool, + TypeNodeResolverExtension::EXTENSION_TAG => $bool, + TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG => $bool, + TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, + TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, + ])->min(1)); + } - public function getConfigSchema(): Nette\Schema\Schema - { - $bool = Expect::bool(); - return Expect::arrayOf(Expect::structure([ - BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG => $bool, - BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - RegistryFactory::RULE_TAG => $bool, - TypeNodeResolverExtension::EXTENSION_TAG => $bool, - TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - ])->min(1)); - } - - public function beforeCompile(): void - { - /** @var mixed[] $config */ - $config = $this->config; - $builder = $this->getContainerBuilder(); - - foreach ($config as $type => $tags) { - $services = $builder->findByType($type); - if (count($services) === 0) { - throw new \PHPStan\ShouldNotHappenException(sprintf('No services of type "%s" found.', $type)); - } - foreach ($services as $service) { - foreach ($tags as $tag => $parameter) { - if ((bool) $parameter) { - $service->addTag($tag); - continue; - } - } - } - } - } + public function beforeCompile(): void + { + /** @var mixed[] $config */ + $config = $this->config; + $builder = $this->getContainerBuilder(); + foreach ($config as $type => $tags) { + $services = $builder->findByType($type); + if (count($services) === 0) { + throw new \PHPStan\ShouldNotHappenException(sprintf('No services of type "%s" found.', $type)); + } + foreach ($services as $service) { + foreach ($tags as $tag => $parameter) { + if ((bool) $parameter) { + $service->addTag($tag); + continue; + } + } + } + } + } } diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 1da912305f..ce52dd6eea 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -1,4 +1,6 @@ -loaderFactory = $loaderFactory; - - parent::__construct(); - } - - protected function createLoader(): Loader - { - return $this->loaderFactory->createLoader(); - } - - /** - * @return mixed[] - */ - protected function getDefaultParameters(): array - { - return []; - } - - public function getContainerCacheDirectory(): string - { - return $this->getCacheDirectory() . '/nette.configurator'; - } - - public function loadContainer(): string - { - $loader = new ContainerLoader( - $this->getContainerCacheDirectory(), - $this->parameters['debugMode'] - ); - - return $loader->load( - [$this, 'generateContainer'], - [$this->parameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] - ); - } - + private LoaderFactory $loaderFactory; + + public function __construct(LoaderFactory $loaderFactory) + { + $this->loaderFactory = $loaderFactory; + + parent::__construct(); + } + + protected function createLoader(): Loader + { + return $this->loaderFactory->createLoader(); + } + + /** + * @return mixed[] + */ + protected function getDefaultParameters(): array + { + return []; + } + + public function getContainerCacheDirectory(): string + { + return $this->getCacheDirectory() . '/nette.configurator'; + } + + public function loadContainer(): string + { + $loader = new ContainerLoader( + $this->getContainerCacheDirectory(), + $this->parameters['debugMode'] + ); + + return $loader->load( + [$this, 'generateContainer'], + [$this->parameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] + ); + } } diff --git a/src/DependencyInjection/Container.php b/src/DependencyInjection/Container.php index 11410b5184..ef665c6ed5 100644 --- a/src/DependencyInjection/Container.php +++ b/src/DependencyInjection/Container.php @@ -1,48 +1,48 @@ -currentWorkingDirectory = $currentWorkingDirectory; - $this->fileHelper = new FileHelper($currentWorkingDirectory); - - $rootDir = __DIR__ . '/../..'; - $originalRootDir = $this->fileHelper->normalizePath($rootDir); - if (extension_loaded('phar')) { - $pharPath = Phar::running(false); - if ($pharPath !== '') { - $rootDir = dirname($pharPath); - } - } - $this->rootDirectory = $this->fileHelper->normalizePath($rootDir); - $this->configDirectory = $originalRootDir . '/conf'; - } - - /** - * @param string $tempDirectory - * @param string[] $additionalConfigFiles - * @param string[] $analysedPaths - * @param string[] $composerAutoloaderProjectPaths - * @param string[] $analysedPathsFromConfig - * @param string $usedLevel - * @param string|null $generateBaselineFile - * @param string|null $cliAutoloadFile - * @param string|null $singleReflectionFile - * @param string|null $singleReflectionInsteadOfFile - * @return \PHPStan\DependencyInjection\Container - */ - public function create( - string $tempDirectory, - array $additionalConfigFiles, - array $analysedPaths, - array $composerAutoloaderProjectPaths = [], - array $analysedPathsFromConfig = [], - string $usedLevel = CommandHelper::DEFAULT_LEVEL, - ?string $generateBaselineFile = null, - ?string $cliAutoloadFile = null, - ?string $singleReflectionFile = null, - ?string $singleReflectionInsteadOfFile = null - ): Container - { - $configurator = new Configurator(new LoaderFactory( - $this->fileHelper, - $this->rootDirectory, - $this->currentWorkingDirectory, - $generateBaselineFile - )); - $configurator->defaultExtensions = [ - 'php' => PhpExtension::class, - 'extensions' => \Nette\DI\Extensions\ExtensionsExtension::class, - ]; - $configurator->setDebugMode(true); - $configurator->setTempDirectory($tempDirectory); - $configurator->addParameters([ - 'rootDir' => $this->rootDirectory, - 'currentWorkingDirectory' => $this->currentWorkingDirectory, - 'cliArgumentsVariablesRegistered' => ini_get('register_argc_argv') === '1', - 'tmpDir' => $tempDirectory, - 'additionalConfigFiles' => $additionalConfigFiles, - 'analysedPaths' => $analysedPaths, - 'composerAutoloaderProjectPaths' => $composerAutoloaderProjectPaths, - 'analysedPathsFromConfig' => $analysedPathsFromConfig, - 'generateBaselineFile' => $generateBaselineFile, - 'usedLevel' => $usedLevel, - 'cliAutoloadFile' => $cliAutoloadFile, - 'fixerTmpDir' => sys_get_temp_dir() . '/phpstan-fixer', - ]); - $configurator->addDynamicParameters([ - 'singleReflectionFile' => $singleReflectionFile, - 'singleReflectionInsteadOfFile' => $singleReflectionInsteadOfFile, - ]); - $configurator->addConfig($this->configDirectory . '/config.neon'); - foreach ($additionalConfigFiles as $additionalConfigFile) { - $configurator->addConfig($additionalConfigFile); - } - - $container = $configurator->createContainer(); - - BetterReflection::$phpVersion = $container->getByType(PhpVersion::class)->getVersionId(); - - BetterReflection::populate( - $container->getService('betterReflectionSourceLocator'), // @phpstan-ignore-line - $container->getService('betterReflectionClassReflector'), // @phpstan-ignore-line - $container->getService('betterReflectionFunctionReflector'), // @phpstan-ignore-line - $container->getService('betterReflectionConstantReflector'), // @phpstan-ignore-line - $container->getService('phpParserDecorator'), // @phpstan-ignore-line - $container->getByType(PhpStormStubsSourceStubber::class) - ); - - /** @var Broker $broker */ - $broker = $container->getByType(Broker::class); - Broker::registerInstance($broker); - $container->getService('typeSpecifier'); - - return $container->getByType(Container::class); - } - - public function clearOldContainers(string $tempDirectory): void - { - $configurator = new Configurator(new LoaderFactory( - $this->fileHelper, - $this->rootDirectory, - $this->currentWorkingDirectory, - null - )); - $configurator->setDebugMode(true); - $configurator->setTempDirectory($tempDirectory); - - $finder = new Finder(); - $finder->name('Container_*')->in($configurator->getContainerCacheDirectory()); - $twoDaysAgo = time() - 24 * 60 * 60 * 2; - - foreach ($finder as $containerFile) { - $path = $containerFile->getRealPath(); - if ($path === false) { - continue; - } - if ($containerFile->getATime() > $twoDaysAgo) { - continue; - } - if ($containerFile->getCTime() > $twoDaysAgo) { - continue; - } - - @unlink($path); - } - } - - public function getCurrentWorkingDirectory(): string - { - return $this->currentWorkingDirectory; - } - - public function getRootDirectory(): string - { - return $this->rootDirectory; - } - - public function getConfigDirectory(): string - { - return $this->configDirectory; - } - + private string $currentWorkingDirectory; + + private FileHelper $fileHelper; + + private string $rootDirectory; + + private string $configDirectory; + + public function __construct(string $currentWorkingDirectory) + { + $this->currentWorkingDirectory = $currentWorkingDirectory; + $this->fileHelper = new FileHelper($currentWorkingDirectory); + + $rootDir = __DIR__ . '/../..'; + $originalRootDir = $this->fileHelper->normalizePath($rootDir); + if (extension_loaded('phar')) { + $pharPath = Phar::running(false); + if ($pharPath !== '') { + $rootDir = dirname($pharPath); + } + } + $this->rootDirectory = $this->fileHelper->normalizePath($rootDir); + $this->configDirectory = $originalRootDir . '/conf'; + } + + /** + * @param string $tempDirectory + * @param string[] $additionalConfigFiles + * @param string[] $analysedPaths + * @param string[] $composerAutoloaderProjectPaths + * @param string[] $analysedPathsFromConfig + * @param string $usedLevel + * @param string|null $generateBaselineFile + * @param string|null $cliAutoloadFile + * @param string|null $singleReflectionFile + * @param string|null $singleReflectionInsteadOfFile + * @return \PHPStan\DependencyInjection\Container + */ + public function create( + string $tempDirectory, + array $additionalConfigFiles, + array $analysedPaths, + array $composerAutoloaderProjectPaths = [], + array $analysedPathsFromConfig = [], + string $usedLevel = CommandHelper::DEFAULT_LEVEL, + ?string $generateBaselineFile = null, + ?string $cliAutoloadFile = null, + ?string $singleReflectionFile = null, + ?string $singleReflectionInsteadOfFile = null + ): Container { + $configurator = new Configurator(new LoaderFactory( + $this->fileHelper, + $this->rootDirectory, + $this->currentWorkingDirectory, + $generateBaselineFile + )); + $configurator->defaultExtensions = [ + 'php' => PhpExtension::class, + 'extensions' => \Nette\DI\Extensions\ExtensionsExtension::class, + ]; + $configurator->setDebugMode(true); + $configurator->setTempDirectory($tempDirectory); + $configurator->addParameters([ + 'rootDir' => $this->rootDirectory, + 'currentWorkingDirectory' => $this->currentWorkingDirectory, + 'cliArgumentsVariablesRegistered' => ini_get('register_argc_argv') === '1', + 'tmpDir' => $tempDirectory, + 'additionalConfigFiles' => $additionalConfigFiles, + 'analysedPaths' => $analysedPaths, + 'composerAutoloaderProjectPaths' => $composerAutoloaderProjectPaths, + 'analysedPathsFromConfig' => $analysedPathsFromConfig, + 'generateBaselineFile' => $generateBaselineFile, + 'usedLevel' => $usedLevel, + 'cliAutoloadFile' => $cliAutoloadFile, + 'fixerTmpDir' => sys_get_temp_dir() . '/phpstan-fixer', + ]); + $configurator->addDynamicParameters([ + 'singleReflectionFile' => $singleReflectionFile, + 'singleReflectionInsteadOfFile' => $singleReflectionInsteadOfFile, + ]); + $configurator->addConfig($this->configDirectory . '/config.neon'); + foreach ($additionalConfigFiles as $additionalConfigFile) { + $configurator->addConfig($additionalConfigFile); + } + + $container = $configurator->createContainer(); + + BetterReflection::$phpVersion = $container->getByType(PhpVersion::class)->getVersionId(); + + BetterReflection::populate( + $container->getService('betterReflectionSourceLocator'), // @phpstan-ignore-line + $container->getService('betterReflectionClassReflector'), // @phpstan-ignore-line + $container->getService('betterReflectionFunctionReflector'), // @phpstan-ignore-line + $container->getService('betterReflectionConstantReflector'), // @phpstan-ignore-line + $container->getService('phpParserDecorator'), // @phpstan-ignore-line + $container->getByType(PhpStormStubsSourceStubber::class) + ); + + /** @var Broker $broker */ + $broker = $container->getByType(Broker::class); + Broker::registerInstance($broker); + $container->getService('typeSpecifier'); + + return $container->getByType(Container::class); + } + + public function clearOldContainers(string $tempDirectory): void + { + $configurator = new Configurator(new LoaderFactory( + $this->fileHelper, + $this->rootDirectory, + $this->currentWorkingDirectory, + null + )); + $configurator->setDebugMode(true); + $configurator->setTempDirectory($tempDirectory); + + $finder = new Finder(); + $finder->name('Container_*')->in($configurator->getContainerCacheDirectory()); + $twoDaysAgo = time() - 24 * 60 * 60 * 2; + + foreach ($finder as $containerFile) { + $path = $containerFile->getRealPath(); + if ($path === false) { + continue; + } + if ($containerFile->getATime() > $twoDaysAgo) { + continue; + } + if ($containerFile->getCTime() > $twoDaysAgo) { + continue; + } + + @unlink($path); + } + } + + public function getCurrentWorkingDirectory(): string + { + return $this->currentWorkingDirectory; + } + + public function getRootDirectory(): string + { + return $this->rootDirectory; + } + + public function getConfigDirectory(): string + { + return $this->configDirectory; + } } diff --git a/src/DependencyInjection/DerivativeContainerFactory.php b/src/DependencyInjection/DerivativeContainerFactory.php index ada4f5f921..12534f580c 100644 --- a/src/DependencyInjection/DerivativeContainerFactory.php +++ b/src/DependencyInjection/DerivativeContainerFactory.php @@ -1,79 +1,78 @@ -currentWorkingDirectory = $currentWorkingDirectory; - $this->tempDirectory = $tempDirectory; - $this->additionalConfigFiles = $additionalConfigFiles; - $this->analysedPaths = $analysedPaths; - $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - $this->analysedPathsFromConfig = $analysedPathsFromConfig; - $this->usedLevel = $usedLevel; - $this->generateBaselineFile = $generateBaselineFile; - } + private ?string $generateBaselineFile; - /** - * @param string[] $additionalConfigFiles - * @return \PHPStan\DependencyInjection\Container - */ - public function create(array $additionalConfigFiles): Container - { - $containerFactory = new ContainerFactory( - $this->currentWorkingDirectory - ); + /** + * @param string $currentWorkingDirectory + * @param string $tempDirectory + * @param string[] $additionalConfigFiles + * @param string[] $analysedPaths + * @param string[] $composerAutoloaderProjectPaths + * @param string[] $analysedPathsFromConfig + * @param string $usedLevel + */ + public function __construct( + string $currentWorkingDirectory, + string $tempDirectory, + array $additionalConfigFiles, + array $analysedPaths, + array $composerAutoloaderProjectPaths, + array $analysedPathsFromConfig, + string $usedLevel, + ?string $generateBaselineFile + ) { + $this->currentWorkingDirectory = $currentWorkingDirectory; + $this->tempDirectory = $tempDirectory; + $this->additionalConfigFiles = $additionalConfigFiles; + $this->analysedPaths = $analysedPaths; + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + $this->analysedPathsFromConfig = $analysedPathsFromConfig; + $this->usedLevel = $usedLevel; + $this->generateBaselineFile = $generateBaselineFile; + } - return $containerFactory->create( - $this->tempDirectory, - array_merge($this->additionalConfigFiles, $additionalConfigFiles), - $this->analysedPaths, - $this->composerAutoloaderProjectPaths, - $this->analysedPathsFromConfig, - $this->usedLevel, - $this->generateBaselineFile - ); - } + /** + * @param string[] $additionalConfigFiles + * @return \PHPStan\DependencyInjection\Container + */ + public function create(array $additionalConfigFiles): Container + { + $containerFactory = new ContainerFactory( + $this->currentWorkingDirectory + ); + return $containerFactory->create( + $this->tempDirectory, + array_merge($this->additionalConfigFiles, $additionalConfigFiles), + $this->analysedPaths, + $this->composerAutoloaderProjectPaths, + $this->analysedPathsFromConfig, + $this->usedLevel, + $this->generateBaselineFile + ); + } } diff --git a/src/DependencyInjection/LoaderFactory.php b/src/DependencyInjection/LoaderFactory.php index eeb35a3110..3484179c98 100644 --- a/src/DependencyInjection/LoaderFactory.php +++ b/src/DependencyInjection/LoaderFactory.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->rootDir = $rootDir; - $this->currentWorkingDirectory = $currentWorkingDirectory; - $this->generateBaselineFile = $generateBaselineFile; - } - - public function createLoader(): Loader - { - $loader = new NeonLoader($this->fileHelper, $this->generateBaselineFile); - $loader->addAdapter('dist', NeonAdapter::class); - $loader->addAdapter('neon', NeonAdapter::class); - $loader->setParameters([ - 'rootDir' => $this->rootDir, - 'currentWorkingDirectory' => $this->currentWorkingDirectory, - ]); - - return $loader; - } - + private FileHelper $fileHelper; + + private string $rootDir; + + private string $currentWorkingDirectory; + + private ?string $generateBaselineFile; + + public function __construct( + FileHelper $fileHelper, + string $rootDir, + string $currentWorkingDirectory, + ?string $generateBaselineFile + ) { + $this->fileHelper = $fileHelper; + $this->rootDir = $rootDir; + $this->currentWorkingDirectory = $currentWorkingDirectory; + $this->generateBaselineFile = $generateBaselineFile; + } + + public function createLoader(): Loader + { + $loader = new NeonLoader($this->fileHelper, $this->generateBaselineFile); + $loader->addAdapter('dist', NeonAdapter::class); + $loader->addAdapter('neon', NeonAdapter::class); + $loader->setParameters([ + 'rootDir' => $this->rootDir, + 'currentWorkingDirectory' => $this->currentWorkingDirectory, + ]); + + return $loader; + } } diff --git a/src/DependencyInjection/MemoizingContainer.php b/src/DependencyInjection/MemoizingContainer.php index d43641705c..17cb18802b 100644 --- a/src/DependencyInjection/MemoizingContainer.php +++ b/src/DependencyInjection/MemoizingContainer.php @@ -1,78 +1,78 @@ - */ - private array $servicesByType = []; - - public function __construct(Container $originalContainer) - { - $this->originalContainer = $originalContainer; - } - - public function hasService(string $serviceName): bool - { - return $this->originalContainer->hasService($serviceName); - } - - /** - * @param string $serviceName - * @return mixed - */ - public function getService(string $serviceName) - { - return $this->originalContainer->getService($serviceName); - } - - /** - * @param string $className - * @return mixed - */ - public function getByType(string $className) - { - if (array_key_exists($className, $this->servicesByType)) { - return $this->servicesByType[$className]; - } - - $service = $this->originalContainer->getByType($className); - $this->servicesByType[$className] = $service; - - return $service; - } - - public function findServiceNamesByType(string $className): array - { - return $this->originalContainer->findServiceNamesByType($className); - } - - public function getServicesByTag(string $tagName): array - { - return $this->originalContainer->getServicesByTag($tagName); - } - - public function getParameters(): array - { - return $this->originalContainer->getParameters(); - } - - public function hasParameter(string $parameterName): bool - { - return $this->originalContainer->hasParameter($parameterName); - } - - /** - * @param string $parameterName - * @return mixed - * @throws ParameterNotFoundException - */ - public function getParameter(string $parameterName) - { - return $this->originalContainer->getParameter($parameterName); - } - + private Container $originalContainer; + + /** @var array */ + private array $servicesByType = []; + + public function __construct(Container $originalContainer) + { + $this->originalContainer = $originalContainer; + } + + public function hasService(string $serviceName): bool + { + return $this->originalContainer->hasService($serviceName); + } + + /** + * @param string $serviceName + * @return mixed + */ + public function getService(string $serviceName) + { + return $this->originalContainer->getService($serviceName); + } + + /** + * @param string $className + * @return mixed + */ + public function getByType(string $className) + { + if (array_key_exists($className, $this->servicesByType)) { + return $this->servicesByType[$className]; + } + + $service = $this->originalContainer->getByType($className); + $this->servicesByType[$className] = $service; + + return $service; + } + + public function findServiceNamesByType(string $className): array + { + return $this->originalContainer->findServiceNamesByType($className); + } + + public function getServicesByTag(string $tagName): array + { + return $this->originalContainer->getServicesByTag($tagName); + } + + public function getParameters(): array + { + return $this->originalContainer->getParameters(); + } + + public function hasParameter(string $parameterName): bool + { + return $this->originalContainer->hasParameter($parameterName); + } + + /** + * @param string $parameterName + * @return mixed + * @throws ParameterNotFoundException + */ + public function getParameter(string $parameterName) + { + return $this->originalContainer->getParameter($parameterName); + } } diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index 6d868d7bf2..f761576cff 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -1,4 +1,6 @@ -process((array) Neon::decode($contents), '', $file); - } catch (\Nette\Neon\Exception $e) { - throw new \Nette\Neon\Exception(sprintf('Error while loading %s: %s', $file, $e->getMessage())); - } - } - - /** - * @param mixed[] $arr - * @return mixed[] - */ - public function process(array $arr, string $fileKey, string $file): array - { - $res = []; - foreach ($arr as $key => $val) { - if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING_SUFFIX) { - if (!is_array($val) && $val !== null) { - throw new \Nette\DI\InvalidConfigurationException(sprintf('Replacing operator is available only for arrays, item \'%s\' is not array.', $key)); - } - $key = substr($key, 0, -1); - $val[Helpers::PREVENT_MERGING] = true; - } - - if (is_array($val)) { - if (!is_int($key)) { - $fileKeyToPass = $fileKey . '[' . $key . ']'; - } else { - $fileKeyToPass = $fileKey . '[]'; - } - $val = $this->process($val, $fileKeyToPass, $file); - - } elseif ($val instanceof Entity) { - if (!is_int($key)) { - $fileKeyToPass = $fileKey . '(' . $key . ')'; - } else { - $fileKeyToPass = $fileKey . '()'; - } - if ($val->value === Neon::CHAIN) { - $tmp = null; - foreach ($this->process($val->attributes, $fileKeyToPass, $file) as $st) { - $tmp = new Statement( - $tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')], - $st->arguments - ); - } - $val = $tmp; - } else { - $tmp = $this->process([$val->value], $fileKeyToPass, $file); - $val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file)); - } - } - - $keyToResolve = $fileKey; - if (is_int($key)) { - $keyToResolve .= '[]'; - } else { - $keyToResolve .= '[' . $key . ']'; - } - - if (in_array($keyToResolve, [ - '[parameters][autoload_files][]', - '[parameters][autoload_directories][]', - '[parameters][paths][]', - '[parameters][excludes_analyse][]', - '[parameters][excludePaths][]', - '[parameters][excludePaths][analyse][]', - '[parameters][excludePaths][analyseAndScan][]', - '[parameters][ignoreErrors][][paths][]', - '[parameters][ignoreErrors][][path]', - '[parameters][bootstrap]', - '[parameters][bootstrapFiles][]', - '[parameters][scanFiles][]', - '[parameters][scanDirectories][]', - '[parameters][tmpDir]', - '[parameters][memoryLimitFile]', - '[parameters][benchmarkFile]', - '[parameters][stubFiles][]', - '[parameters][symfony][console_application_loader]', - '[parameters][symfony][container_xml_path]', - '[parameters][doctrine][objectManagerLoader]', - ], true) && is_string($val) && strpos($val, '%') === false && strpos($val, '*') !== 0) { - $fileHelper = $this->createFileHelperByFile($file); - $val = $fileHelper->normalizePath($fileHelper->absolutizePath($val)); - } - - $res[$key] = $val; - } - return $res; - } - - /** - * @param mixed[] $data - * @return string - */ - public function dump(array $data): string - { - array_walk_recursive( - $data, - static function (&$val): void { - if (!($val instanceof Statement)) { - return; - } - - $val = self::statementToEntity($val); - } - ); - return "# generated by Nette\n\n" . Neon::encode($data, Neon::BLOCK); - } - - private static function statementToEntity(Statement $val): Entity - { - array_walk_recursive( - $val->arguments, - static function (&$val): void { - if ($val instanceof Statement) { - $val = self::statementToEntity($val); - } elseif ($val instanceof Reference) { - $val = '@' . $val->getValue(); - } - } - ); - - $entity = $val->getEntity(); - if ($entity instanceof Reference) { - $entity = '@' . $entity->getValue(); - } elseif (is_array($entity)) { - if ($entity[0] instanceof Statement) { - return new Entity( - Neon::CHAIN, - [ - self::statementToEntity($entity[0]), - new Entity('::' . $entity[1], $val->arguments), - ] - ); - } elseif ($entity[0] instanceof Reference) { - $entity = '@' . $entity[0]->getValue() . '::' . $entity[1]; - } elseif (is_string($entity[0])) { - $entity = $entity[0] . '::' . $entity[1]; - } - } - return new Entity($entity, $val->arguments); - } - - private function createFileHelperByFile(string $file): FileHelper - { - $dir = dirname($file); - if (!isset($this->fileHelpers[$dir])) { - $this->fileHelpers[$dir] = new FileHelper($dir); - } - - return $this->fileHelpers[$dir]; - } - + public const CACHE_KEY = 'v11-excludePaths'; + + private const PREVENT_MERGING_SUFFIX = '!'; + + /** @var FileHelper[] */ + private array $fileHelpers = []; + + /** + * @param string $file + * @return mixed[] + */ + public function load(string $file): array + { + $contents = FileReader::read($file); + try { + return $this->process((array) Neon::decode($contents), '', $file); + } catch (\Nette\Neon\Exception $e) { + throw new \Nette\Neon\Exception(sprintf('Error while loading %s: %s', $file, $e->getMessage())); + } + } + + /** + * @param mixed[] $arr + * @return mixed[] + */ + public function process(array $arr, string $fileKey, string $file): array + { + $res = []; + foreach ($arr as $key => $val) { + if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING_SUFFIX) { + if (!is_array($val) && $val !== null) { + throw new \Nette\DI\InvalidConfigurationException(sprintf('Replacing operator is available only for arrays, item \'%s\' is not array.', $key)); + } + $key = substr($key, 0, -1); + $val[Helpers::PREVENT_MERGING] = true; + } + + if (is_array($val)) { + if (!is_int($key)) { + $fileKeyToPass = $fileKey . '[' . $key . ']'; + } else { + $fileKeyToPass = $fileKey . '[]'; + } + $val = $this->process($val, $fileKeyToPass, $file); + } elseif ($val instanceof Entity) { + if (!is_int($key)) { + $fileKeyToPass = $fileKey . '(' . $key . ')'; + } else { + $fileKeyToPass = $fileKey . '()'; + } + if ($val->value === Neon::CHAIN) { + $tmp = null; + foreach ($this->process($val->attributes, $fileKeyToPass, $file) as $st) { + $tmp = new Statement( + $tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')], + $st->arguments + ); + } + $val = $tmp; + } else { + $tmp = $this->process([$val->value], $fileKeyToPass, $file); + $val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file)); + } + } + + $keyToResolve = $fileKey; + if (is_int($key)) { + $keyToResolve .= '[]'; + } else { + $keyToResolve .= '[' . $key . ']'; + } + + if (in_array($keyToResolve, [ + '[parameters][autoload_files][]', + '[parameters][autoload_directories][]', + '[parameters][paths][]', + '[parameters][excludes_analyse][]', + '[parameters][excludePaths][]', + '[parameters][excludePaths][analyse][]', + '[parameters][excludePaths][analyseAndScan][]', + '[parameters][ignoreErrors][][paths][]', + '[parameters][ignoreErrors][][path]', + '[parameters][bootstrap]', + '[parameters][bootstrapFiles][]', + '[parameters][scanFiles][]', + '[parameters][scanDirectories][]', + '[parameters][tmpDir]', + '[parameters][memoryLimitFile]', + '[parameters][benchmarkFile]', + '[parameters][stubFiles][]', + '[parameters][symfony][console_application_loader]', + '[parameters][symfony][container_xml_path]', + '[parameters][doctrine][objectManagerLoader]', + ], true) && is_string($val) && strpos($val, '%') === false && strpos($val, '*') !== 0) { + $fileHelper = $this->createFileHelperByFile($file); + $val = $fileHelper->normalizePath($fileHelper->absolutizePath($val)); + } + + $res[$key] = $val; + } + return $res; + } + + /** + * @param mixed[] $data + * @return string + */ + public function dump(array $data): string + { + array_walk_recursive( + $data, + static function (&$val): void { + if (!($val instanceof Statement)) { + return; + } + + $val = self::statementToEntity($val); + } + ); + return "# generated by Nette\n\n" . Neon::encode($data, Neon::BLOCK); + } + + private static function statementToEntity(Statement $val): Entity + { + array_walk_recursive( + $val->arguments, + static function (&$val): void { + if ($val instanceof Statement) { + $val = self::statementToEntity($val); + } elseif ($val instanceof Reference) { + $val = '@' . $val->getValue(); + } + } + ); + + $entity = $val->getEntity(); + if ($entity instanceof Reference) { + $entity = '@' . $entity->getValue(); + } elseif (is_array($entity)) { + if ($entity[0] instanceof Statement) { + return new Entity( + Neon::CHAIN, + [ + self::statementToEntity($entity[0]), + new Entity('::' . $entity[1], $val->arguments), + ] + ); + } elseif ($entity[0] instanceof Reference) { + $entity = '@' . $entity[0]->getValue() . '::' . $entity[1]; + } elseif (is_string($entity[0])) { + $entity = $entity[0] . '::' . $entity[1]; + } + } + return new Entity($entity, $val->arguments); + } + + private function createFileHelperByFile(string $file): FileHelper + { + $dir = dirname($file); + if (!isset($this->fileHelpers[$dir])) { + $this->fileHelpers[$dir] = new FileHelper($dir); + } + + return $this->fileHelpers[$dir]; + } } diff --git a/src/DependencyInjection/NeonLoader.php b/src/DependencyInjection/NeonLoader.php index cee5eb5e1e..bf4c967ae3 100644 --- a/src/DependencyInjection/NeonLoader.php +++ b/src/DependencyInjection/NeonLoader.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->generateBaselineFile = $generateBaselineFile; - } - - /** - * @param string $file - * @param bool|null $merge - * @return mixed[] - */ - public function load(string $file, ?bool $merge = true): array - { - if ($this->generateBaselineFile === null) { - return parent::load($file, $merge); - } - - $normalizedFile = $this->fileHelper->normalizePath($file); - if ($this->generateBaselineFile === $normalizedFile) { - return []; - } - - return parent::load($file, $merge); - } - + private FileHelper $fileHelper; + + private ?string $generateBaselineFile; + + public function __construct( + FileHelper $fileHelper, + ?string $generateBaselineFile + ) { + $this->fileHelper = $fileHelper; + $this->generateBaselineFile = $generateBaselineFile; + } + + /** + * @param string $file + * @param bool|null $merge + * @return mixed[] + */ + public function load(string $file, ?bool $merge = true): array + { + if ($this->generateBaselineFile === null) { + return parent::load($file, $merge); + } + + $normalizedFile = $this->fileHelper->normalizePath($file); + if ($this->generateBaselineFile === $normalizedFile) { + return []; + } + + return parent::load($file, $merge); + } } diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index 5e57025649..8dceb8a2fa 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function hasService(string $serviceName): bool - { - return $this->container->hasService($serviceName); - } - - /** - * @param string $serviceName - * @return mixed - */ - public function getService(string $serviceName) - { - return $this->container->getService($serviceName); - } - - /** - * @param string $className - * @return mixed - */ - public function getByType(string $className) - { - return $this->container->getByType($className); - } - - /** - * @param string $className - * @return string[] - */ - public function findServiceNamesByType(string $className): array - { - return $this->container->findByType($className); - } - - /** - * @param string $tagName - * @return mixed[] - */ - public function getServicesByTag(string $tagName): array - { - return $this->tagsToServices($this->container->findByTag($tagName)); - } - - /** - * @return mixed[] - */ - public function getParameters(): array - { - return $this->container->parameters; - } - - public function hasParameter(string $parameterName): bool - { - return array_key_exists($parameterName, $this->container->parameters); - } - - /** - * @param string $parameterName - * @return mixed - */ - public function getParameter(string $parameterName) - { - if (!$this->hasParameter($parameterName)) { - throw new \PHPStan\DependencyInjection\ParameterNotFoundException($parameterName); - } - - return $this->container->parameters[$parameterName]; - } - - /** - * @param mixed[] $tags - * @return mixed[] - */ - private function tagsToServices(array $tags): array - { - return array_map(function (string $serviceName) { - return $this->getService($serviceName); - }, array_keys($tags)); - } - + private \Nette\DI\Container $container; + + public function __construct(\Nette\DI\Container $container) + { + $this->container = $container; + } + + public function hasService(string $serviceName): bool + { + return $this->container->hasService($serviceName); + } + + /** + * @param string $serviceName + * @return mixed + */ + public function getService(string $serviceName) + { + return $this->container->getService($serviceName); + } + + /** + * @param string $className + * @return mixed + */ + public function getByType(string $className) + { + return $this->container->getByType($className); + } + + /** + * @param string $className + * @return string[] + */ + public function findServiceNamesByType(string $className): array + { + return $this->container->findByType($className); + } + + /** + * @param string $tagName + * @return mixed[] + */ + public function getServicesByTag(string $tagName): array + { + return $this->tagsToServices($this->container->findByTag($tagName)); + } + + /** + * @return mixed[] + */ + public function getParameters(): array + { + return $this->container->parameters; + } + + public function hasParameter(string $parameterName): bool + { + return array_key_exists($parameterName, $this->container->parameters); + } + + /** + * @param string $parameterName + * @return mixed + */ + public function getParameter(string $parameterName) + { + if (!$this->hasParameter($parameterName)) { + throw new \PHPStan\DependencyInjection\ParameterNotFoundException($parameterName); + } + + return $this->container->parameters[$parameterName]; + } + + /** + * @param mixed[] $tags + * @return mixed[] + */ + private function tagsToServices(array $tags): array + { + return array_map(function (string $serviceName) { + return $this->getService($serviceName); + }, array_keys($tags)); + } } diff --git a/src/DependencyInjection/ParameterNotFoundException.php b/src/DependencyInjection/ParameterNotFoundException.php index 393c5cd779..65cd8bbf60 100644 --- a/src/DependencyInjection/ParameterNotFoundException.php +++ b/src/DependencyInjection/ParameterNotFoundException.php @@ -1,13 +1,13 @@ -min(1); - } - - public function loadConfiguration(): void - { - /** @var mixed[] $config */ - $config = $this->config; - $config['__parametersSchema'] = new Statement(Schema::class); - $builder = $this->getContainerBuilder(); - $builder->parameters['__parametersSchema'] = $this->processArgument( - new Statement('schema', [ - new Statement('structure', [$config]), - ]) - ); - } - - /** - * @param Statement[] $statements - * @return \Nette\Schema\Schema - */ - private function processSchema(array $statements): Schema - { - if (count($statements) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $parameterSchema = null; - foreach ($statements as $statement) { - $processedArguments = array_map(function ($argument) { - return $this->processArgument($argument); - }, $statement->arguments); - if ($parameterSchema === null) { - /** @var \Nette\Schema\Elements\Type|\Nette\Schema\Elements\AnyOf|\Nette\Schema\Elements\Structure $parameterSchema */ - $parameterSchema = Expect::{$statement->getEntity()}(...$processedArguments); - } else { - $parameterSchema->{$statement->getEntity()}(...$processedArguments); - } - } - - $parameterSchema->required(); - - return $parameterSchema; - } - - /** - * @param mixed $argument - * @return mixed - */ - private function processArgument($argument) - { - if ($argument instanceof Statement) { - if ($argument->entity === 'schema') { - $arguments = []; - foreach ($argument->arguments as $schemaArgument) { - if (!$schemaArgument instanceof Statement) { - throw new \PHPStan\ShouldNotHappenException('schema() should contain another statement().'); - } - - $arguments[] = $schemaArgument; - } - - if (count($arguments) === 0) { - throw new \PHPStan\ShouldNotHappenException('schema() should have at least one argument.'); - } - - return $this->processSchema($arguments); - } - - return $this->processSchema([$argument]); - } elseif (is_array($argument)) { - $processedArray = []; - foreach ($argument as $key => $val) { - $processedArray[$key] = $this->processArgument($val); - } - - return $processedArray; - } - - return $argument; - } - + public function getConfigSchema(): \Nette\Schema\Schema + { + return Expect::arrayOf(Expect::type(Statement::class))->min(1); + } + + public function loadConfiguration(): void + { + /** @var mixed[] $config */ + $config = $this->config; + $config['__parametersSchema'] = new Statement(Schema::class); + $builder = $this->getContainerBuilder(); + $builder->parameters['__parametersSchema'] = $this->processArgument( + new Statement('schema', [ + new Statement('structure', [$config]), + ]) + ); + } + + /** + * @param Statement[] $statements + * @return \Nette\Schema\Schema + */ + private function processSchema(array $statements): Schema + { + if (count($statements) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $parameterSchema = null; + foreach ($statements as $statement) { + $processedArguments = array_map(function ($argument) { + return $this->processArgument($argument); + }, $statement->arguments); + if ($parameterSchema === null) { + /** @var \Nette\Schema\Elements\Type|\Nette\Schema\Elements\AnyOf|\Nette\Schema\Elements\Structure $parameterSchema */ + $parameterSchema = Expect::{$statement->getEntity()}(...$processedArguments); + } else { + $parameterSchema->{$statement->getEntity()}(...$processedArguments); + } + } + + $parameterSchema->required(); + + return $parameterSchema; + } + + /** + * @param mixed $argument + * @return mixed + */ + private function processArgument($argument) + { + if ($argument instanceof Statement) { + if ($argument->entity === 'schema') { + $arguments = []; + foreach ($argument->arguments as $schemaArgument) { + if (!$schemaArgument instanceof Statement) { + throw new \PHPStan\ShouldNotHappenException('schema() should contain another statement().'); + } + + $arguments[] = $schemaArgument; + } + + if (count($arguments) === 0) { + throw new \PHPStan\ShouldNotHappenException('schema() should have at least one argument.'); + } + + return $this->processSchema($arguments); + } + + return $this->processSchema([$argument]); + } elseif (is_array($argument)) { + $processedArray = []; + foreach ($argument as $key => $val) { + $processedArray[$key] = $this->processArgument($val); + } + + return $processedArray; + } + + return $argument; + } } diff --git a/src/DependencyInjection/Reflection/ClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/ClassReflectionExtensionRegistryProvider.php index eef210b319..b9e353d836 100644 --- a/src/DependencyInjection/Reflection/ClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/ClassReflectionExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; - $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function addPropertiesClassReflectionExtension(PropertiesClassReflectionExtension $extension): void - { - $this->propertiesClassReflectionExtensions[] = $extension; - } - - public function addMethodsClassReflectionExtension(MethodsClassReflectionExtension $extension): void - { - $this->methodsClassReflectionExtensions[] = $extension; - } - - public function getRegistry(): ClassReflectionExtensionRegistry - { - return new ClassReflectionExtensionRegistry( - $this->broker, - $this->propertiesClassReflectionExtensions, - $this->methodsClassReflectionExtensions - ); - } - + /** @var \PHPStan\Reflection\PropertiesClassReflectionExtension[] */ + private array $propertiesClassReflectionExtensions; + + /** @var \PHPStan\Reflection\MethodsClassReflectionExtension[] */ + private array $methodsClassReflectionExtensions; + + private Broker $broker; + + /** + * @param \PHPStan\Reflection\PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions + * @param \PHPStan\Reflection\MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + */ + public function __construct( + array $propertiesClassReflectionExtensions, + array $methodsClassReflectionExtensions + ) { + $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; + $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; + } + + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } + + public function addPropertiesClassReflectionExtension(PropertiesClassReflectionExtension $extension): void + { + $this->propertiesClassReflectionExtensions[] = $extension; + } + + public function addMethodsClassReflectionExtension(MethodsClassReflectionExtension $extension): void + { + $this->methodsClassReflectionExtensions[] = $extension; + } + + public function getRegistry(): ClassReflectionExtensionRegistry + { + return new ClassReflectionExtensionRegistry( + $this->broker, + $this->propertiesClassReflectionExtensions, + $this->methodsClassReflectionExtensions + ); + } } diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index 41c35dacdf..7282eb902f 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function getRegistry(): ClassReflectionExtensionRegistry - { - if ($this->registry === null) { - $phpClassReflectionExtension = $this->container->getByType(PhpClassReflectionExtension::class); - $annotationsMethodsClassReflectionExtension = $this->container->getByType(AnnotationsMethodsClassReflectionExtension::class); - $annotationsPropertiesClassReflectionExtension = $this->container->getByType(AnnotationsPropertiesClassReflectionExtension::class); - - $this->registry = new ClassReflectionExtensionRegistry( - $this->container->getByType(Broker::class), - array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension]), - array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension]) - ); - } - - return $this->registry; - } - + private \PHPStan\DependencyInjection\Container $container; + + private ?\PHPStan\Reflection\ClassReflectionExtensionRegistry $registry = null; + + public function __construct(\PHPStan\DependencyInjection\Container $container) + { + $this->container = $container; + } + + public function getRegistry(): ClassReflectionExtensionRegistry + { + if ($this->registry === null) { + $phpClassReflectionExtension = $this->container->getByType(PhpClassReflectionExtension::class); + $annotationsMethodsClassReflectionExtension = $this->container->getByType(AnnotationsMethodsClassReflectionExtension::class); + $annotationsPropertiesClassReflectionExtension = $this->container->getByType(AnnotationsPropertiesClassReflectionExtension::class); + + $this->registry = new ClassReflectionExtensionRegistry( + $this->container->getByType(Broker::class), + array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension]), + array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension]) + ); + } + + return $this->registry; + } } diff --git a/src/DependencyInjection/RulesExtension.php b/src/DependencyInjection/RulesExtension.php index b72f286754..1cdd4b9378 100644 --- a/src/DependencyInjection/RulesExtension.php +++ b/src/DependencyInjection/RulesExtension.php @@ -1,4 +1,6 @@ -config; - $builder = $this->getContainerBuilder(); - - foreach ($config as $key => $rule) { - $builder->addDefinition($this->prefix((string) $key)) - ->setFactory($rule) - ->setAutowired(false) - ->addTag(RegistryFactory::RULE_TAG); - } - } + public function loadConfiguration(): void + { + /** @var mixed[] $config */ + $config = $this->config; + $builder = $this->getContainerBuilder(); + foreach ($config as $key => $rule) { + $builder->addDefinition($this->prefix((string) $key)) + ->setFactory($rule) + ->setAutowired(false) + ->addTag(RegistryFactory::RULE_TAG); + } + } } diff --git a/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php b/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php index 3759ec24ed..aa9e7ebc83 100644 --- a/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions; - $this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions; - $this->dynamicFunctionReturnTypeExtensions = $dynamicFunctionReturnTypeExtensions; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function setReflectionProvider(ReflectionProvider $reflectionProvider): void - { - $this->reflectionProvider = $reflectionProvider; - } - - public function addDynamicMethodReturnTypeExtension(DynamicMethodReturnTypeExtension $extension): void - { - $this->dynamicMethodReturnTypeExtensions[] = $extension; - } - - public function addDynamicStaticMethodReturnTypeExtension(DynamicStaticMethodReturnTypeExtension $extension): void - { - $this->dynamicStaticMethodReturnTypeExtensions[] = $extension; - } - - public function addDynamicFunctionReturnTypeExtension(DynamicFunctionReturnTypeExtension $extension): void - { - $this->dynamicFunctionReturnTypeExtensions[] = $extension; - } - - public function getRegistry(): DynamicReturnTypeExtensionRegistry - { - return new DynamicReturnTypeExtensionRegistry( - $this->broker, - $this->reflectionProvider, - $this->dynamicMethodReturnTypeExtensions, - $this->dynamicStaticMethodReturnTypeExtensions, - $this->dynamicFunctionReturnTypeExtensions - ); - } - + /** @var \PHPStan\Type\DynamicMethodReturnTypeExtension[] */ + private array $dynamicMethodReturnTypeExtensions; + + /** @var \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] */ + private array $dynamicStaticMethodReturnTypeExtensions; + + /** @var \PHPStan\Type\DynamicFunctionReturnTypeExtension[] */ + private array $dynamicFunctionReturnTypeExtensions; + + private Broker $broker; + + private ReflectionProvider $reflectionProvider; + + /** + * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicFunctionReturnTypeExtension[] $dynamicFunctionReturnTypeExtensions + */ + public function __construct( + array $dynamicMethodReturnTypeExtensions, + array $dynamicStaticMethodReturnTypeExtensions, + array $dynamicFunctionReturnTypeExtensions + ) { + $this->dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions; + $this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions; + $this->dynamicFunctionReturnTypeExtensions = $dynamicFunctionReturnTypeExtensions; + } + + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } + + public function setReflectionProvider(ReflectionProvider $reflectionProvider): void + { + $this->reflectionProvider = $reflectionProvider; + } + + public function addDynamicMethodReturnTypeExtension(DynamicMethodReturnTypeExtension $extension): void + { + $this->dynamicMethodReturnTypeExtensions[] = $extension; + } + + public function addDynamicStaticMethodReturnTypeExtension(DynamicStaticMethodReturnTypeExtension $extension): void + { + $this->dynamicStaticMethodReturnTypeExtensions[] = $extension; + } + + public function addDynamicFunctionReturnTypeExtension(DynamicFunctionReturnTypeExtension $extension): void + { + $this->dynamicFunctionReturnTypeExtensions[] = $extension; + } + + public function getRegistry(): DynamicReturnTypeExtensionRegistry + { + return new DynamicReturnTypeExtensionRegistry( + $this->broker, + $this->reflectionProvider, + $this->dynamicMethodReturnTypeExtensions, + $this->dynamicStaticMethodReturnTypeExtensions, + $this->dynamicFunctionReturnTypeExtensions + ); + } } diff --git a/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php index 80f73d7b5a..e6a481b1e1 100644 --- a/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -extensions = $extensions; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry - { - return new OperatorTypeSpecifyingExtensionRegistry( - $this->broker, - $this->extensions - ); - } - + /** @var OperatorTypeSpecifyingExtension[] */ + private array $extensions; + + private Broker $broker; + + /** + * @param \PHPStan\Type\OperatorTypeSpecifyingExtension[] $extensions + */ + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } + + public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry + { + return new OperatorTypeSpecifyingExtensionRegistry( + $this->broker, + $this->extensions + ); + } } diff --git a/src/DependencyInjection/Type/DynamicReturnTypeExtensionRegistryProvider.php b/src/DependencyInjection/Type/DynamicReturnTypeExtensionRegistryProvider.php index 14fb3eccef..af0c6da165 100644 --- a/src/DependencyInjection/Type/DynamicReturnTypeExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/DynamicReturnTypeExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function getRegistry(): DynamicReturnTypeExtensionRegistry - { - if ($this->registry === null) { - $this->registry = new DynamicReturnTypeExtensionRegistry( - $this->container->getByType(Broker::class), - $this->container->getByType(ReflectionProvider::class), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - $this->container->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) - ); - } - - return $this->registry; - } - + private \PHPStan\DependencyInjection\Container $container; + + private ?\PHPStan\Type\DynamicReturnTypeExtensionRegistry $registry = null; + + public function __construct(\PHPStan\DependencyInjection\Container $container) + { + $this->container = $container; + } + + public function getRegistry(): DynamicReturnTypeExtensionRegistry + { + if ($this->registry === null) { + $this->registry = new DynamicReturnTypeExtensionRegistry( + $this->container->getByType(Broker::class), + $this->container->getByType(ReflectionProvider::class), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + $this->container->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) + ); + } + + return $this->registry; + } } diff --git a/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php b/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php index ef7885034c..b28a08bd18 100644 --- a/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php +++ b/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function getDynamicFunctionThrowTypeExtensions(): array - { - return $this->container->getServicesByTag(self::FUNCTION_TAG); - } - - public function getDynamicMethodThrowTypeExtensions(): array - { - return $this->container->getServicesByTag(self::METHOD_TAG); - } - - public function getDynamicStaticMethodThrowTypeExtensions(): array - { - return $this->container->getServicesByTag(self::STATIC_METHOD_TAG); - } - + public const FUNCTION_TAG = 'phpstan.dynamicFunctionThrowTypeExtension'; + public const METHOD_TAG = 'phpstan.dynamicMethodThrowTypeExtension'; + public const STATIC_METHOD_TAG = 'phpstan.dynamicStaticMethodThrowTypeExtension'; + + private Container $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + public function getDynamicFunctionThrowTypeExtensions(): array + { + return $this->container->getServicesByTag(self::FUNCTION_TAG); + } + + public function getDynamicMethodThrowTypeExtensions(): array + { + return $this->container->getServicesByTag(self::METHOD_TAG); + } + + public function getDynamicStaticMethodThrowTypeExtensions(): array + { + return $this->container->getServicesByTag(self::STATIC_METHOD_TAG); + } } diff --git a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php index 3f9f0ca376..47e7787c75 100644 --- a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry - { - if ($this->registry === null) { - $this->registry = new OperatorTypeSpecifyingExtensionRegistry( - $this->container->getByType(Broker::class), - $this->container->getServicesByTag(BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG) - ); - } - - return $this->registry; - } - + private \PHPStan\DependencyInjection\Container $container; + + private ?\PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry $registry = null; + + public function __construct(\PHPStan\DependencyInjection\Container $container) + { + $this->container = $container; + } + + public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry + { + if ($this->registry === null) { + $this->registry = new OperatorTypeSpecifyingExtensionRegistry( + $this->container->getByType(Broker::class), + $this->container->getServicesByTag(BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG) + ); + } + + return $this->registry; + } } diff --git a/src/DependencyInjection/Type/OperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/OperatorTypeSpecifyingExtensionRegistryProvider.php index 2a1de7beae..d56017b21d 100644 --- a/src/DependencyInjection/Type/OperatorTypeSpecifyingExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/OperatorTypeSpecifyingExtensionRegistryProvider.php @@ -1,4 +1,6 @@ -analyseExcludes = array_map(function (string $exclude) use ($fileHelper): string { - $len = strlen($exclude); - $trailingDirSeparator = ($len > 0 && in_array($exclude[$len - 1], ['\\', '/'], true)); - - $normalized = $fileHelper->normalizePath($exclude); + /** + * @param FileHelper $fileHelper + * @param string[] $analyseExcludes + * @param string[] $stubFiles + */ + public function __construct( + FileHelper $fileHelper, + array $analyseExcludes, + array $stubFiles + ) { + $this->analyseExcludes = array_map(function (string $exclude) use ($fileHelper): string { + $len = strlen($exclude); + $trailingDirSeparator = ($len > 0 && in_array($exclude[$len - 1], ['\\', '/'], true)); - if ($trailingDirSeparator) { - $normalized .= DIRECTORY_SEPARATOR; - } + $normalized = $fileHelper->normalizePath($exclude); - if ($this->isFnmatchPattern($normalized)) { - return $normalized; - } + if ($trailingDirSeparator) { + $normalized .= DIRECTORY_SEPARATOR; + } - return $fileHelper->absolutizePath($normalized); - }, array_merge($analyseExcludes, $stubFiles)); - } + if ($this->isFnmatchPattern($normalized)) { + return $normalized; + } - public function isExcludedFromAnalysing(string $file): bool - { - foreach ($this->analyseExcludes as $exclude) { - if (strpos($file, $exclude) === 0) { - return true; - } + return $fileHelper->absolutizePath($normalized); + }, array_merge($analyseExcludes, $stubFiles)); + } - $isWindows = DIRECTORY_SEPARATOR === '\\'; - if ($isWindows) { - $fnmatchFlags = FNM_NOESCAPE | FNM_CASEFOLD; - } else { - $fnmatchFlags = 0; - } + public function isExcludedFromAnalysing(string $file): bool + { + foreach ($this->analyseExcludes as $exclude) { + if (strpos($file, $exclude) === 0) { + return true; + } - if ($this->isFnmatchPattern($exclude) && fnmatch($exclude, $file, $fnmatchFlags)) { - return true; - } - } + $isWindows = DIRECTORY_SEPARATOR === '\\'; + if ($isWindows) { + $fnmatchFlags = FNM_NOESCAPE | FNM_CASEFOLD; + } else { + $fnmatchFlags = 0; + } - return false; - } + if ($this->isFnmatchPattern($exclude) && fnmatch($exclude, $file, $fnmatchFlags)) { + return true; + } + } - private function isFnmatchPattern(string $path): bool - { - return preg_match('~[*?[\]]~', $path) > 0; - } + return false; + } + private function isFnmatchPattern(string $path): bool + { + return preg_match('~[*?[\]]~', $path) > 0; + } } diff --git a/src/File/FileExcluderFactory.php b/src/File/FileExcluderFactory.php index 5d39748cbf..599d2eaeac 100644 --- a/src/File/FileExcluderFactory.php +++ b/src/File/FileExcluderFactory.php @@ -1,77 +1,76 @@ -|array{analyse?: array, analyseAndScan?: array}|null */ - private ?array $excludePaths; - - /** - * @param FileExcluderRawFactory $fileExcluderRawFactory - * @param string[] $obsoleteExcludesAnalyse - * @param array|array{analyse?: array, analyseAndScan?: array}|null $excludePaths - */ - public function __construct( - FileExcluderRawFactory $fileExcluderRawFactory, - array $obsoleteExcludesAnalyse, - ?array $excludePaths - ) - { - $this->fileExcluderRawFactory = $fileExcluderRawFactory; - $this->obsoleteExcludesAnalyse = $obsoleteExcludesAnalyse; - $this->excludePaths = $excludePaths; - } - - public function createAnalyseFileExcluder(): FileExcluder - { - if ($this->excludePaths === null) { - return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); - } - - if (!array_key_exists('analyse', $this->excludePaths) && !array_key_exists('analyseAndScan', $this->excludePaths)) { - return $this->fileExcluderRawFactory->create($this->excludePaths); - } - - /** @var array{analyse?: array, analyseAndScan?: array} $excludePaths */ - $excludePaths = $this->excludePaths; - - $paths = []; - if (array_key_exists('analyse', $excludePaths)) { - $paths = $excludePaths['analyse']; - } - if (array_key_exists('analyseAndScan', $excludePaths)) { - $paths = array_merge($paths, $excludePaths['analyseAndScan']); - } - - return $this->fileExcluderRawFactory->create(array_values(array_unique($paths))); - } - - public function createScanFileExcluder(): FileExcluder - { - if ($this->excludePaths === null) { - return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); - } - - if (!array_key_exists('analyse', $this->excludePaths) && !array_key_exists('analyseAndScan', $this->excludePaths)) { - return $this->fileExcluderRawFactory->create($this->excludePaths); - } - - /** @var array{analyse?: array, analyseAndScan?: array} $excludePaths */ - $excludePaths = $this->excludePaths; - - $paths = []; - if (array_key_exists('analyseAndScan', $excludePaths)) { - $paths = $excludePaths['analyseAndScan']; - } - - return $this->fileExcluderRawFactory->create(array_values(array_unique($paths))); - } - + private FileExcluderRawFactory $fileExcluderRawFactory; + + /** @var string[] */ + private array $obsoleteExcludesAnalyse; + + /** @var array|array{analyse?: array, analyseAndScan?: array}|null */ + private ?array $excludePaths; + + /** + * @param FileExcluderRawFactory $fileExcluderRawFactory + * @param string[] $obsoleteExcludesAnalyse + * @param array|array{analyse?: array, analyseAndScan?: array}|null $excludePaths + */ + public function __construct( + FileExcluderRawFactory $fileExcluderRawFactory, + array $obsoleteExcludesAnalyse, + ?array $excludePaths + ) { + $this->fileExcluderRawFactory = $fileExcluderRawFactory; + $this->obsoleteExcludesAnalyse = $obsoleteExcludesAnalyse; + $this->excludePaths = $excludePaths; + } + + public function createAnalyseFileExcluder(): FileExcluder + { + if ($this->excludePaths === null) { + return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); + } + + if (!array_key_exists('analyse', $this->excludePaths) && !array_key_exists('analyseAndScan', $this->excludePaths)) { + return $this->fileExcluderRawFactory->create($this->excludePaths); + } + + /** @var array{analyse?: array, analyseAndScan?: array} $excludePaths */ + $excludePaths = $this->excludePaths; + + $paths = []; + if (array_key_exists('analyse', $excludePaths)) { + $paths = $excludePaths['analyse']; + } + if (array_key_exists('analyseAndScan', $excludePaths)) { + $paths = array_merge($paths, $excludePaths['analyseAndScan']); + } + + return $this->fileExcluderRawFactory->create(array_values(array_unique($paths))); + } + + public function createScanFileExcluder(): FileExcluder + { + if ($this->excludePaths === null) { + return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); + } + + if (!array_key_exists('analyse', $this->excludePaths) && !array_key_exists('analyseAndScan', $this->excludePaths)) { + return $this->fileExcluderRawFactory->create($this->excludePaths); + } + + /** @var array{analyse?: array, analyseAndScan?: array} $excludePaths */ + $excludePaths = $this->excludePaths; + + $paths = []; + if (array_key_exists('analyseAndScan', $excludePaths)) { + $paths = $excludePaths['analyseAndScan']; + } + + return $this->fileExcluderRawFactory->create(array_values(array_unique($paths))); + } } diff --git a/src/File/FileExcluderRawFactory.php b/src/File/FileExcluderRawFactory.php index b2f7e4ee77..a8cefe7572 100644 --- a/src/File/FileExcluderRawFactory.php +++ b/src/File/FileExcluderRawFactory.php @@ -1,16 +1,16 @@ -fileExcluder = $fileExcluder; - $this->fileHelper = $fileHelper; - $this->fileExtensions = $fileExtensions; - } - - /** - * @param string[] $paths - * @return FileFinderResult - */ - public function findFiles(array $paths): FileFinderResult - { - $onlyFiles = true; - $files = []; - foreach ($paths as $path) { - if (!file_exists($path)) { - throw new \PHPStan\File\PathNotFoundException($path); - } elseif (is_file($path)) { - $files[] = $this->fileHelper->normalizePath($path); - } else { - $finder = new Finder(); - $finder->followLinks(); - foreach ($finder->files()->name('*.{' . implode(',', $this->fileExtensions) . '}')->in($path) as $fileInfo) { - $files[] = $this->fileHelper->normalizePath($fileInfo->getPathname()); - $onlyFiles = false; - } - } - } - - $files = array_values(array_filter($files, function (string $file): bool { - return !$this->fileExcluder->isExcludedFromAnalysing($file); - })); - - return new FileFinderResult($files, $onlyFiles); - } - + private FileExcluder $fileExcluder; + + private FileHelper $fileHelper; + + /** @var string[] */ + private array $fileExtensions; + + /** + * @param FileExcluder $fileExcluder + * @param FileHelper $fileHelper + * @param string[] $fileExtensions + */ + public function __construct( + FileExcluder $fileExcluder, + FileHelper $fileHelper, + array $fileExtensions + ) { + $this->fileExcluder = $fileExcluder; + $this->fileHelper = $fileHelper; + $this->fileExtensions = $fileExtensions; + } + + /** + * @param string[] $paths + * @return FileFinderResult + */ + public function findFiles(array $paths): FileFinderResult + { + $onlyFiles = true; + $files = []; + foreach ($paths as $path) { + if (!file_exists($path)) { + throw new \PHPStan\File\PathNotFoundException($path); + } elseif (is_file($path)) { + $files[] = $this->fileHelper->normalizePath($path); + } else { + $finder = new Finder(); + $finder->followLinks(); + foreach ($finder->files()->name('*.{' . implode(',', $this->fileExtensions) . '}')->in($path) as $fileInfo) { + $files[] = $this->fileHelper->normalizePath($fileInfo->getPathname()); + $onlyFiles = false; + } + } + } + + $files = array_values(array_filter($files, function (string $file): bool { + return !$this->fileExcluder->isExcludedFromAnalysing($file); + })); + + return new FileFinderResult($files, $onlyFiles); + } } diff --git a/src/File/FileFinderResult.php b/src/File/FileFinderResult.php index db239b00cd..d0311d4739 100644 --- a/src/File/FileFinderResult.php +++ b/src/File/FileFinderResult.php @@ -1,36 +1,36 @@ -files = $files; - $this->onlyFiles = $onlyFiles; - } - - /** - * @return string[] - */ - public function getFiles(): array - { - return $this->files; - } - - public function isOnlyFiles(): bool - { - return $this->onlyFiles; - } - + /** @var string[] */ + private array $files; + + private bool $onlyFiles; + + /** + * @param string[] $files + * @param bool $onlyFiles + */ + public function __construct(array $files, bool $onlyFiles) + { + $this->files = $files; + $this->onlyFiles = $onlyFiles; + } + + /** + * @return string[] + */ + public function getFiles(): array + { + return $this->files; + } + + public function isOnlyFiles(): bool + { + return $this->onlyFiles; + } } diff --git a/src/File/FileHelper.php b/src/File/FileHelper.php index b351f1e11f..f4c08bfe54 100644 --- a/src/File/FileHelper.php +++ b/src/File/FileHelper.php @@ -1,4 +1,6 @@ -workingDirectory = $this->normalizePath($workingDirectory); - } - - public function getWorkingDirectory(): string - { - return $this->workingDirectory; - } - - public function absolutizePath(string $path): string - { - if (DIRECTORY_SEPARATOR === '/') { - if (substr($path, 0, 1) === '/') { - return $path; - } - } else { - if (substr($path, 1, 1) === ':') { - return $path; - } - } - if (\Nette\Utils\Strings::startsWith($path, 'phar://')) { - return $path; - } + public function __construct(string $workingDirectory) + { + $this->workingDirectory = $this->normalizePath($workingDirectory); + } - return rtrim($this->getWorkingDirectory(), '/\\') . DIRECTORY_SEPARATOR . ltrim($path, '/\\'); - } + public function getWorkingDirectory(): string + { + return $this->workingDirectory; + } - public function normalizePath(string $originalPath, string $directorySeparator = DIRECTORY_SEPARATOR): string - { - $matches = \Nette\Utils\Strings::match($originalPath, '~^([a-z]+)\\:\\/\\/(.+)~'); - if ($matches !== null) { - [, $scheme, $path] = $matches; - } else { - $scheme = null; - $path = $originalPath; - } + public function absolutizePath(string $path): string + { + if (DIRECTORY_SEPARATOR === '/') { + if (substr($path, 0, 1) === '/') { + return $path; + } + } else { + if (substr($path, 1, 1) === ':') { + return $path; + } + } + if (\Nette\Utils\Strings::startsWith($path, 'phar://')) { + return $path; + } - $path = str_replace('\\', '/', $path); - $path = Strings::replace($path, '~/{2,}~', '/'); + return rtrim($this->getWorkingDirectory(), '/\\') . DIRECTORY_SEPARATOR . ltrim($path, '/\\'); + } - $pathRoot = strpos($path, '/') === 0 ? $directorySeparator : ''; - $pathParts = explode('/', trim($path, '/')); + public function normalizePath(string $originalPath, string $directorySeparator = DIRECTORY_SEPARATOR): string + { + $matches = \Nette\Utils\Strings::match($originalPath, '~^([a-z]+)\\:\\/\\/(.+)~'); + if ($matches !== null) { + [, $scheme, $path] = $matches; + } else { + $scheme = null; + $path = $originalPath; + } - $normalizedPathParts = []; - foreach ($pathParts as $pathPart) { - if ($pathPart === '.') { - continue; - } - if ($pathPart === '..') { - /** @var string $removedPart */ - $removedPart = array_pop($normalizedPathParts); - if ($scheme === 'phar' && substr($removedPart, -5) === '.phar') { - $scheme = null; - } + $path = str_replace('\\', '/', $path); + $path = Strings::replace($path, '~/{2,}~', '/'); - } else { - $normalizedPathParts[] = $pathPart; - } - } + $pathRoot = strpos($path, '/') === 0 ? $directorySeparator : ''; + $pathParts = explode('/', trim($path, '/')); - return ($scheme !== null ? $scheme . '://' : '') . $pathRoot . implode($directorySeparator, $normalizedPathParts); - } + $normalizedPathParts = []; + foreach ($pathParts as $pathPart) { + if ($pathPart === '.') { + continue; + } + if ($pathPart === '..') { + /** @var string $removedPart */ + $removedPart = array_pop($normalizedPathParts); + if ($scheme === 'phar' && substr($removedPart, -5) === '.phar') { + $scheme = null; + } + } else { + $normalizedPathParts[] = $pathPart; + } + } + return ($scheme !== null ? $scheme . '://' : '') . $pathRoot . implode($directorySeparator, $normalizedPathParts); + } } diff --git a/src/File/FileMonitor.php b/src/File/FileMonitor.php index c25856a140..3436c2d461 100644 --- a/src/File/FileMonitor.php +++ b/src/File/FileMonitor.php @@ -1,4 +1,6 @@ -|null */ - private $fileHashes; - - /** @var array|null */ - private $paths; - - public function __construct(FileFinder $fileFinder) - { - $this->fileFinder = $fileFinder; - } - - /** - * @param array $paths - */ - public function initialize(array $paths): void - { - $finderResult = $this->fileFinder->findFiles($paths); - $fileHashes = []; - foreach ($finderResult->getFiles() as $filePath) { - $fileHashes[$filePath] = $this->getFileHash($filePath); - } - - $this->fileHashes = $fileHashes; - $this->paths = $paths; - } - - public function getChanges(): FileMonitorResult - { - if ($this->fileHashes === null || $this->paths === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - $finderResult = $this->fileFinder->findFiles($this->paths); - $oldFileHashes = $this->fileHashes; - $fileHashes = []; - $newFiles = []; - $changedFiles = []; - $deletedFiles = []; - foreach ($finderResult->getFiles() as $filePath) { - if (!array_key_exists($filePath, $oldFileHashes)) { - $newFiles[] = $filePath; - $fileHashes[$filePath] = $this->getFileHash($filePath); - continue; - } - - $oldHash = $oldFileHashes[$filePath]; - unset($oldFileHashes[$filePath]); - $newHash = $this->getFileHash($filePath); - $fileHashes[$filePath] = $newHash; - if ($oldHash === $newHash) { - continue; - } - - $changedFiles[] = $filePath; - } - - $this->fileHashes = $fileHashes; - - foreach (array_keys($oldFileHashes) as $file) { - $deletedFiles[] = $file; - } - - return new FileMonitorResult( - $newFiles, - $changedFiles, - $deletedFiles, - count($fileHashes) - ); - } - - private function getFileHash(string $filePath): string - { - return sha1(FileReader::read($filePath)); - } - + /** @var FileFinder */ + private $fileFinder; + + /** @var array|null */ + private $fileHashes; + + /** @var array|null */ + private $paths; + + public function __construct(FileFinder $fileFinder) + { + $this->fileFinder = $fileFinder; + } + + /** + * @param array $paths + */ + public function initialize(array $paths): void + { + $finderResult = $this->fileFinder->findFiles($paths); + $fileHashes = []; + foreach ($finderResult->getFiles() as $filePath) { + $fileHashes[$filePath] = $this->getFileHash($filePath); + } + + $this->fileHashes = $fileHashes; + $this->paths = $paths; + } + + public function getChanges(): FileMonitorResult + { + if ($this->fileHashes === null || $this->paths === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + $finderResult = $this->fileFinder->findFiles($this->paths); + $oldFileHashes = $this->fileHashes; + $fileHashes = []; + $newFiles = []; + $changedFiles = []; + $deletedFiles = []; + foreach ($finderResult->getFiles() as $filePath) { + if (!array_key_exists($filePath, $oldFileHashes)) { + $newFiles[] = $filePath; + $fileHashes[$filePath] = $this->getFileHash($filePath); + continue; + } + + $oldHash = $oldFileHashes[$filePath]; + unset($oldFileHashes[$filePath]); + $newHash = $this->getFileHash($filePath); + $fileHashes[$filePath] = $newHash; + if ($oldHash === $newHash) { + continue; + } + + $changedFiles[] = $filePath; + } + + $this->fileHashes = $fileHashes; + + foreach (array_keys($oldFileHashes) as $file) { + $deletedFiles[] = $file; + } + + return new FileMonitorResult( + $newFiles, + $changedFiles, + $deletedFiles, + count($fileHashes) + ); + } + + private function getFileHash(string $filePath): string + { + return sha1(FileReader::read($filePath)); + } } diff --git a/src/File/FileMonitorResult.php b/src/File/FileMonitorResult.php index 5e58357941..d5fffea6b0 100644 --- a/src/File/FileMonitorResult.php +++ b/src/File/FileMonitorResult.php @@ -1,51 +1,50 @@ -newFiles = $newFiles; - $this->changedFiles = $changedFiles; - $this->deletedFiles = $deletedFiles; - $this->totalFilesCount = $totalFilesCount; - } - - public function hasAnyChanges(): bool - { - return count($this->newFiles) > 0 - || count($this->changedFiles) > 0 - || count($this->deletedFiles) > 0; - } - - public function getTotalFilesCount(): int - { - return $this->totalFilesCount; - } - + /** @var string[] */ + private $newFiles; + + /** @var string[] */ + private $changedFiles; + + /** @var string[] */ + private $deletedFiles; + + /** @var int */ + private $totalFilesCount; + + /** + * @param string[] $newFiles + * @param string[] $changedFiles + * @param string[] $deletedFiles + * @param int $totalFilesCount + */ + public function __construct( + array $newFiles, + array $changedFiles, + array $deletedFiles, + int $totalFilesCount + ) { + $this->newFiles = $newFiles; + $this->changedFiles = $changedFiles; + $this->deletedFiles = $deletedFiles; + $this->totalFilesCount = $totalFilesCount; + } + + public function hasAnyChanges(): bool + { + return count($this->newFiles) > 0 + || count($this->changedFiles) > 0 + || count($this->deletedFiles) > 0; + } + + public function getTotalFilesCount(): int + { + return $this->totalFilesCount; + } } diff --git a/src/File/FileReader.php b/src/File/FileReader.php index 5a0c5570fc..5f5ef48016 100644 --- a/src/File/FileReader.php +++ b/src/File/FileReader.php @@ -1,4 +1,6 @@ -fallbackRelativePathHelper = $fallbackRelativePathHelper; - if ($directorySeparator === null) { - $directorySeparator = DIRECTORY_SEPARATOR; - } - - $this->directorySeparator = $directorySeparator; - $pathBeginning = null; - $pathToTrimArray = null; - $trimBeginning = static function (string $path): array { - if (substr($path, 0, 1) === '/') { - return [ - '/', - substr($path, 1), - ]; - } elseif (substr($path, 1, 1) === ':') { - return [ - substr($path, 0, 3), - substr($path, 3), - ]; - } - - return ['', $path]; - }; - - if ( - !in_array($currentWorkingDirectory, ['', '/'], true) - && !(strlen($currentWorkingDirectory) === 3 && substr($currentWorkingDirectory, 1, 1) === ':') - ) { - [$pathBeginning, $currentWorkingDirectory] = $trimBeginning($currentWorkingDirectory); - - /** @var string[] $pathToTrimArray */ - $pathToTrimArray = explode($directorySeparator, $currentWorkingDirectory); - } - foreach ($analysedPaths as $pathNumber => $path) { - [$tempPathBeginning, $path] = $trimBeginning($path); - - /** @var string[] $pathArray */ - $pathArray = explode($directorySeparator, $path); - $pathTempParts = []; - foreach ($pathArray as $i => $pathPart) { - if (\Nette\Utils\Strings::endsWith($pathPart, '.php')) { - continue; - } - if (!isset($pathToTrimArray[$i])) { - if ($pathNumber !== 0) { - $pathToTrimArray = $pathTempParts; - continue 2; - } - } elseif ($pathToTrimArray[$i] !== $pathPart) { - $pathToTrimArray = $pathTempParts; - continue 2; - } - - $pathTempParts[] = $pathPart; - } - - $pathBeginning = $tempPathBeginning; - $pathToTrimArray = $pathTempParts; - } - - if ($pathToTrimArray === null || count($pathToTrimArray) === 0) { - return; - } - - $pathToTrim = $pathBeginning . implode($directorySeparator, $pathToTrimArray); - $realPathToTrim = realpath($pathToTrim); - if ($realPathToTrim !== false) { - $pathToTrim = $realPathToTrim; - } - - $this->pathToTrim = $pathToTrim; - } - - public function getRelativePath(string $filename): string - { - if ( - $this->pathToTrim !== null - && strpos($filename, $this->pathToTrim) === 0 - ) { - return ltrim(substr($filename, strlen($this->pathToTrim)), $this->directorySeparator); - } - - return $this->fallbackRelativePathHelper->getRelativePath($filename); - } - + private RelativePathHelper $fallbackRelativePathHelper; + + private string $directorySeparator; + + private ?string $pathToTrim = null; + + /** + * @param RelativePathHelper $fallbackRelativePathHelper + * @param string $currentWorkingDirectory + * @param string[] $analysedPaths + * @param string|null $directorySeparator + */ + public function __construct( + RelativePathHelper $fallbackRelativePathHelper, + string $currentWorkingDirectory, + array $analysedPaths, + ?string $directorySeparator = null + ) { + $this->fallbackRelativePathHelper = $fallbackRelativePathHelper; + if ($directorySeparator === null) { + $directorySeparator = DIRECTORY_SEPARATOR; + } + + $this->directorySeparator = $directorySeparator; + $pathBeginning = null; + $pathToTrimArray = null; + $trimBeginning = static function (string $path): array { + if (substr($path, 0, 1) === '/') { + return [ + '/', + substr($path, 1), + ]; + } elseif (substr($path, 1, 1) === ':') { + return [ + substr($path, 0, 3), + substr($path, 3), + ]; + } + + return ['', $path]; + }; + + if ( + !in_array($currentWorkingDirectory, ['', '/'], true) + && !(strlen($currentWorkingDirectory) === 3 && substr($currentWorkingDirectory, 1, 1) === ':') + ) { + [$pathBeginning, $currentWorkingDirectory] = $trimBeginning($currentWorkingDirectory); + + /** @var string[] $pathToTrimArray */ + $pathToTrimArray = explode($directorySeparator, $currentWorkingDirectory); + } + foreach ($analysedPaths as $pathNumber => $path) { + [$tempPathBeginning, $path] = $trimBeginning($path); + + /** @var string[] $pathArray */ + $pathArray = explode($directorySeparator, $path); + $pathTempParts = []; + foreach ($pathArray as $i => $pathPart) { + if (\Nette\Utils\Strings::endsWith($pathPart, '.php')) { + continue; + } + if (!isset($pathToTrimArray[$i])) { + if ($pathNumber !== 0) { + $pathToTrimArray = $pathTempParts; + continue 2; + } + } elseif ($pathToTrimArray[$i] !== $pathPart) { + $pathToTrimArray = $pathTempParts; + continue 2; + } + + $pathTempParts[] = $pathPart; + } + + $pathBeginning = $tempPathBeginning; + $pathToTrimArray = $pathTempParts; + } + + if ($pathToTrimArray === null || count($pathToTrimArray) === 0) { + return; + } + + $pathToTrim = $pathBeginning . implode($directorySeparator, $pathToTrimArray); + $realPathToTrim = realpath($pathToTrim); + if ($realPathToTrim !== false) { + $pathToTrim = $realPathToTrim; + } + + $this->pathToTrim = $pathToTrim; + } + + public function getRelativePath(string $filename): string + { + if ( + $this->pathToTrim !== null + && strpos($filename, $this->pathToTrim) === 0 + ) { + return ltrim(substr($filename, strlen($this->pathToTrim)), $this->directorySeparator); + } + + return $this->fallbackRelativePathHelper->getRelativePath($filename); + } } diff --git a/src/File/NullRelativePathHelper.php b/src/File/NullRelativePathHelper.php index 1556984a90..94c70a384c 100644 --- a/src/File/NullRelativePathHelper.php +++ b/src/File/NullRelativePathHelper.php @@ -1,13 +1,13 @@ -parentDirectory = $parentDirectory; - } - - public function getRelativePath(string $filename): string - { - $parentParts = explode('/', trim(str_replace('\\', '/', $this->parentDirectory), '/')); - $parentPartsCount = count($parentParts); - $filenameParts = explode('/', trim(str_replace('\\', '/', $filename), '/')); - $filenamePartsCount = count($filenameParts); - - $i = 0; - for (; $i < $filenamePartsCount; $i++) { - if ($parentPartsCount < $i + 1) { - break; - } - - $parentPath = implode('/', array_slice($parentParts, 0, $i + 1)); - $filenamePath = implode('/', array_slice($filenameParts, 0, $i + 1)); - - if ($parentPath !== $filenamePath) { - break; - } - } - - if ($i === 0) { - return $filename; - } - - $dotsCount = $parentPartsCount - $i; - - return str_repeat('../', $dotsCount) . implode('/', array_slice($filenameParts, $i)); - } - + private string $parentDirectory; + + public function __construct(string $parentDirectory) + { + $this->parentDirectory = $parentDirectory; + } + + public function getRelativePath(string $filename): string + { + $parentParts = explode('/', trim(str_replace('\\', '/', $this->parentDirectory), '/')); + $parentPartsCount = count($parentParts); + $filenameParts = explode('/', trim(str_replace('\\', '/', $filename), '/')); + $filenamePartsCount = count($filenameParts); + + $i = 0; + for (; $i < $filenamePartsCount; $i++) { + if ($parentPartsCount < $i + 1) { + break; + } + + $parentPath = implode('/', array_slice($parentParts, 0, $i + 1)); + $filenamePath = implode('/', array_slice($filenameParts, 0, $i + 1)); + + if ($parentPath !== $filenamePath) { + break; + } + } + + if ($i === 0) { + return $filename; + } + + $dotsCount = $parentPartsCount - $i; + + return str_repeat('../', $dotsCount) . implode('/', array_slice($filenameParts, $i)); + } } diff --git a/src/File/PathNotFoundException.php b/src/File/PathNotFoundException.php index 185bcf459c..9ad14845f4 100644 --- a/src/File/PathNotFoundException.php +++ b/src/File/PathNotFoundException.php @@ -1,21 +1,21 @@ -path = $path; - } - - public function getPath(): string - { - return $this->path; - } + public function __construct(string $path) + { + parent::__construct(sprintf('Path %s does not exist', $path)); + $this->path = $path; + } + public function getPath(): string + { + return $this->path; + } } diff --git a/src/File/RelativePathHelper.php b/src/File/RelativePathHelper.php index d0dff0ab55..79657c99c4 100644 --- a/src/File/RelativePathHelper.php +++ b/src/File/RelativePathHelper.php @@ -1,10 +1,10 @@ -currentWorkingDirectory = $currentWorkingDirectory; - } - - public function getRelativePath(string $filename): string - { - if ($this->currentWorkingDirectory !== '' && strpos($filename, $this->currentWorkingDirectory) === 0) { - return substr($filename, strlen($this->currentWorkingDirectory) + 1); - } + public function __construct(string $currentWorkingDirectory) + { + $this->currentWorkingDirectory = $currentWorkingDirectory; + } - return $filename; - } + public function getRelativePath(string $filename): string + { + if ($this->currentWorkingDirectory !== '' && strpos($filename, $this->currentWorkingDirectory) === 0) { + return substr($filename, strlen($this->currentWorkingDirectory) + 1); + } + return $filename; + } } diff --git a/src/Internal/BytesHelper.php b/src/Internal/BytesHelper.php index 73561e0de5..59179f3f54 100644 --- a/src/Internal/BytesHelper.php +++ b/src/Internal/BytesHelper.php @@ -1,26 +1,26 @@ -getName(), [ - 'getByType', - ], true); - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - if (count($methodCall->args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - $argType = $scope->getType($methodCall->args[0]->value); - if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $type = new ObjectType($argType->getValue()); - if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { - $argType = $scope->getType($methodCall->args[1]->value); - if ($argType instanceof ConstantBooleanType && $argType->getValue()) { - $type = TypeCombinator::addNull($type); - } - } - - return $type; - } - + public function getClass(): string + { + return Container::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), [ + 'getByType', + ], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if (count($methodCall->args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + $argType = $scope->getType($methodCall->args[0]->value); + if (!$argType instanceof ConstantStringType) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $type = new ObjectType($argType->getValue()); + if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { + $argType = $scope->getType($methodCall->args[1]->value); + if ($argType instanceof ConstantBooleanType && $argType->getValue()) { + $type = TypeCombinator::addNull($type); + } + } + + return $type; + } } diff --git a/src/Internal/ScopeIsInClassTypeSpecifyingExtension.php b/src/Internal/ScopeIsInClassTypeSpecifyingExtension.php index e90103ffd6..2cf877d767 100644 --- a/src/Internal/ScopeIsInClassTypeSpecifyingExtension.php +++ b/src/Internal/ScopeIsInClassTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -isInMethodName = $isInMethodName; - $this->removeNullMethodName = $removeNullMethodName; - $this->reflectionProvider = $reflectionProvider; - } + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + public function __construct( + string $isInMethodName, + string $removeNullMethodName, + ReflectionProvider $reflectionProvider + ) { + $this->isInMethodName = $isInMethodName; + $this->removeNullMethodName = $removeNullMethodName; + $this->reflectionProvider = $reflectionProvider; + } - public function getClass(): string - { - return ClassMemberAccessAnswerer::class; - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === $this->isInMethodName - && !$context->null(); - } + public function getClass(): string + { + return ClassMemberAccessAnswerer::class; + } - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - $scopeClass = $this->reflectionProvider->getClass(Scope::class); - $methodVariants = $scopeClass - ->getMethod($this->removeNullMethodName, $scope) - ->getVariants(); + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + return $methodReflection->getName() === $this->isInMethodName + && !$context->null(); + } - return $this->typeSpecifier->create( - new MethodCall($node->var, $this->removeNullMethodName), - TypeCombinator::removeNull( - ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType() - ), - $context, - false, - $scope - ); - } + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $scopeClass = $this->reflectionProvider->getClass(Scope::class); + $methodVariants = $scopeClass + ->getMethod($this->removeNullMethodName, $scope) + ->getVariants(); + return $this->typeSpecifier->create( + new MethodCall($node->var, $this->removeNullMethodName), + TypeCombinator::removeNull( + ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType() + ), + $context, + false, + $scope + ); + } } diff --git a/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php b/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php index fa06f30e68..048a91fc1a 100644 --- a/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php +++ b/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'getInternal'; - } - - public function getTypeFromMethodCall( - MethodReflection $methodReflection, - MethodCall $methodCall, - Scope $scope - ): Type - { - if (count($methodCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $getterClosureType = $scope->getType($methodCall->args[1]->value); - return ParametersAcceptorSelector::selectSingle($getterClosureType->getCallableParametersAcceptors($scope))->getReturnType(); - } - + public function getClass(): string + { + return UnionType::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'getInternal'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + if (count($methodCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $getterClosureType = $scope->getType($methodCall->args[1]->value); + return ParametersAcceptorSelector::selectSingle($getterClosureType->getCallableParametersAcceptors($scope))->getReturnType(); + } } diff --git a/src/Node/BooleanAndNode.php b/src/Node/BooleanAndNode.php index 4d6452e850..e582ab8abe 100644 --- a/src/Node/BooleanAndNode.php +++ b/src/Node/BooleanAndNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - $this->rightScope = $rightScope; - } - - /** - * @return BooleanAnd|LogicalAnd - */ - public function getOriginalNode() - { - return $this->originalNode; - } - - public function getRightScope(): Scope - { - return $this->rightScope; - } - - public function getType(): string - { - return 'PHPStan_Node_BooleanAndNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + /** @var BooleanAnd|LogicalAnd */ + private $originalNode; + + private Scope $rightScope; + + /** + * @param BooleanAnd|LogicalAnd $originalNode + * @param Scope $rightScope + */ + public function __construct($originalNode, Scope $rightScope) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + $this->rightScope = $rightScope; + } + + /** + * @return BooleanAnd|LogicalAnd + */ + public function getOriginalNode() + { + return $this->originalNode; + } + + public function getRightScope(): Scope + { + return $this->rightScope; + } + + public function getType(): string + { + return 'PHPStan_Node_BooleanAndNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/BooleanOrNode.php b/src/Node/BooleanOrNode.php index 0682f66bb2..4f52c04bdc 100644 --- a/src/Node/BooleanOrNode.php +++ b/src/Node/BooleanOrNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - $this->rightScope = $rightScope; - } - - /** - * @return BooleanOr|LogicalOr - */ - public function getOriginalNode() - { - return $this->originalNode; - } - - public function getRightScope(): Scope - { - return $this->rightScope; - } - - public function getType(): string - { - return 'PHPStan_Node_BooleanOrNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + /** @var BooleanOr|LogicalOr */ + private $originalNode; + + private Scope $rightScope; + + /** + * @param BooleanOr|LogicalOr $originalNode + * @param Scope $rightScope + */ + public function __construct($originalNode, Scope $rightScope) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + $this->rightScope = $rightScope; + } + + /** + * @return BooleanOr|LogicalOr + */ + public function getOriginalNode() + { + return $this->originalNode; + } + + public function getRightScope(): Scope + { + return $this->rightScope; + } + + public function getType(): string + { + return 'PHPStan_Node_BooleanOrNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/CatchWithUnthrownExceptionNode.php b/src/Node/CatchWithUnthrownExceptionNode.php index 765a6f5f6e..6eba25c248 100644 --- a/src/Node/CatchWithUnthrownExceptionNode.php +++ b/src/Node/CatchWithUnthrownExceptionNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - $this->caughtType = $caughtType; - } - - public function getOriginalNode(): Catch_ - { - return $this->originalNode; - } - - public function getCaughtType(): Type - { - return $this->caughtType; - } - - public function getType(): string - { - return 'PHPStan_Node_CatchWithUnthrownExceptionNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private Catch_ $originalNode; + + private Type $caughtType; + + public function __construct(Catch_ $originalNode, Type $caughtType) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + $this->caughtType = $caughtType; + } + + public function getOriginalNode(): Catch_ + { + return $this->originalNode; + } + + public function getCaughtType(): Type + { + return $this->caughtType; + } + + public function getType(): string + { + return 'PHPStan_Node_CatchWithUnthrownExceptionNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/ClassConstantsNode.php b/src/Node/ClassConstantsNode.php index d100a9b575..b9d82862ec 100644 --- a/src/Node/ClassConstantsNode.php +++ b/src/Node/ClassConstantsNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->class = $class; - $this->constants = $constants; - $this->fetches = $fetches; - } + /** @var ClassConstantFetch[] */ + private array $fetches; - public function getClass(): ClassLike - { - return $this->class; - } + /** + * @param ClassLike $class + * @param ClassConst[] $constants + * @param ClassConstantFetch[] $fetches + */ + public function __construct(ClassLike $class, array $constants, array $fetches) + { + parent::__construct($class->getAttributes()); + $this->class = $class; + $this->constants = $constants; + $this->fetches = $fetches; + } - /** - * @return ClassConst[] - */ - public function getConstants(): array - { - return $this->constants; - } + public function getClass(): ClassLike + { + return $this->class; + } - /** - * @return ClassConstantFetch[] - */ - public function getFetches(): array - { - return $this->fetches; - } + /** + * @return ClassConst[] + */ + public function getConstants(): array + { + return $this->constants; + } - public function getType(): string - { - return 'PHPStan_Node_ClassPropertiesNode'; - } + /** + * @return ClassConstantFetch[] + */ + public function getFetches(): array + { + return $this->fetches; + } - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } + public function getType(): string + { + return 'PHPStan_Node_ClassPropertiesNode'; + } + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/ClassMethodsNode.php b/src/Node/ClassMethodsNode.php index 5186de283c..fd0c7e8f4c 100644 --- a/src/Node/ClassMethodsNode.php +++ b/src/Node/ClassMethodsNode.php @@ -1,4 +1,6 @@ - */ - private array $methodCalls; + /** @var ClassMethod[] */ + private array $methods; - /** - * @param ClassLike $class - * @param ClassMethod[] $methods - * @param array $methodCalls - */ - public function __construct(ClassLike $class, array $methods, array $methodCalls) - { - parent::__construct($class->getAttributes()); - $this->class = $class; - $this->methods = $methods; - $this->methodCalls = $methodCalls; - } + /** @var array */ + private array $methodCalls; - public function getClass(): ClassLike - { - return $this->class; - } + /** + * @param ClassLike $class + * @param ClassMethod[] $methods + * @param array $methodCalls + */ + public function __construct(ClassLike $class, array $methods, array $methodCalls) + { + parent::__construct($class->getAttributes()); + $this->class = $class; + $this->methods = $methods; + $this->methodCalls = $methodCalls; + } - /** - * @return ClassMethod[] - */ - public function getMethods(): array - { - return $this->methods; - } + public function getClass(): ClassLike + { + return $this->class; + } - /** - * @return array - */ - public function getMethodCalls(): array - { - return $this->methodCalls; - } + /** + * @return ClassMethod[] + */ + public function getMethods(): array + { + return $this->methods; + } - public function getType(): string - { - return 'PHPStan_Node_ClassMethodsNode'; - } + /** + * @return array + */ + public function getMethodCalls(): array + { + return $this->methodCalls; + } - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } + public function getType(): string + { + return 'PHPStan_Node_ClassMethodsNode'; + } + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 280555c926..a701b60468 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -1,4 +1,6 @@ - */ - private array $propertyUsages; - - /** @var array */ - private array $methodCalls; - - /** - * @param ClassLike $class - * @param ClassPropertyNode[] $properties - * @param array $propertyUsages - * @param array $methodCalls - */ - public function __construct(ClassLike $class, array $properties, array $propertyUsages, array $methodCalls) - { - parent::__construct($class->getAttributes()); - $this->class = $class; - $this->properties = $properties; - $this->propertyUsages = $propertyUsages; - $this->methodCalls = $methodCalls; - } - - public function getClass(): ClassLike - { - return $this->class; - } - - /** - * @return ClassPropertyNode[] - */ - public function getProperties(): array - { - return $this->properties; - } - - /** - * @return array - */ - public function getPropertyUsages(): array - { - return $this->propertyUsages; - } - - public function getType(): string - { - return 'PHPStan_Node_ClassPropertiesNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - - /** - * @param string[] $constructors - * @param ReadWritePropertiesExtension[] $extensions - * @return array{array, array} - */ - public function getUninitializedProperties( - Scope $scope, - array $constructors, - array $extensions - ): array - { - if (!$this->getClass() instanceof Class_) { - return [[], []]; - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $classReflection = $scope->getClassReflection(); - - $properties = []; - foreach ($this->getProperties() as $property) { - if ($property->isStatic()) { - continue; - } - if ($property->getNativeType() === null) { - continue; - } - if ($property->getDefault() !== null) { - continue; - } - $properties[$property->getName()] = $property; - } - - foreach (array_keys($properties) as $name) { - foreach ($extensions as $extension) { - if (!$classReflection->hasNativeProperty($name)) { - continue; - } - $propertyReflection = $classReflection->getNativeProperty($name); - if (!$extension->isInitialized($propertyReflection, $name)) { - continue; - } - unset($properties[$name]); - break; - } - } - - if ($constructors === []) { - return [$properties, []]; - } - $classType = new ObjectType($scope->getClassReflection()->getName()); - $methodsCalledFromConstructor = $this->getMethodsCalledFromConstructor($classType, $this->methodCalls, $constructors); - $prematureAccess = []; - foreach ($this->getPropertyUsages() as $usage) { - $fetch = $usage->getFetch(); - if (!$fetch instanceof PropertyFetch) { - continue; - } - $usageScope = $usage->getScope(); - if ($usageScope->getFunction() === null) { - continue; - } - $function = $usageScope->getFunction(); - if (!$function instanceof MethodReflection) { - continue; - } - if ($function->getDeclaringClass()->getName() !== $classReflection->getName()) { - continue; - } - if (!in_array($function->getName(), $methodsCalledFromConstructor, true)) { - continue; - } - - if (!$fetch->name instanceof Identifier) { - continue; - } - $propertyName = $fetch->name->toString(); - if (!array_key_exists($propertyName, $properties)) { - continue; - } - $fetchedOnType = $usageScope->getType($fetch->var); - if ($classType->isSuperTypeOf($fetchedOnType)->no()) { - continue; - } - if ($fetchedOnType instanceof MixedType) { - continue; - } - - if ($usage instanceof PropertyWrite) { - unset($properties[$propertyName]); - } elseif (array_key_exists($propertyName, $properties)) { - $prematureAccess[] = [ - $propertyName, - $fetch->getLine(), - ]; - } - } - - return [ - $properties, - $prematureAccess, - ]; - } - - /** - * @param ObjectType $classType - * @param MethodCall[] $methodCalls - * @param string[] $methods - * @return string[] - */ - private function getMethodsCalledFromConstructor( - ObjectType $classType, - array $methodCalls, - array $methods - ): array - { - $originalCount = count($methods); - foreach ($methodCalls as $methodCall) { - $methodCallNode = $methodCall->getNode(); - if ($methodCallNode instanceof Array_) { - continue; - } - if (!$methodCallNode->name instanceof Identifier) { - continue; - } - $callScope = $methodCall->getScope(); - if ($methodCallNode instanceof \PhpParser\Node\Expr\MethodCall) { - $calledOnType = $callScope->getType($methodCallNode->var); - } else { - if (!$methodCallNode->class instanceof Name) { - continue; - } - - $calledOnType = $callScope->resolveTypeByName($methodCallNode->class); - } - if ($classType->isSuperTypeOf($calledOnType)->no()) { - continue; - } - if ($calledOnType instanceof MixedType) { - continue; - } - $methodName = $methodCallNode->name->toString(); - if (in_array($methodName, $methods, true)) { - continue; - } - $inMethod = $callScope->getFunction(); - if (!$inMethod instanceof MethodReflection) { - continue; - } - if (!in_array($inMethod->getName(), $methods, true)) { - continue; - } - $methods[] = $methodName; - } - - if ($originalCount === count($methods)) { - return $methods; - } - - return $this->getMethodsCalledFromConstructor($classType, $methodCalls, $methods); - } - + private ClassLike $class; + + /** @var ClassPropertyNode[] */ + private array $properties; + + /** @var array */ + private array $propertyUsages; + + /** @var array */ + private array $methodCalls; + + /** + * @param ClassLike $class + * @param ClassPropertyNode[] $properties + * @param array $propertyUsages + * @param array $methodCalls + */ + public function __construct(ClassLike $class, array $properties, array $propertyUsages, array $methodCalls) + { + parent::__construct($class->getAttributes()); + $this->class = $class; + $this->properties = $properties; + $this->propertyUsages = $propertyUsages; + $this->methodCalls = $methodCalls; + } + + public function getClass(): ClassLike + { + return $this->class; + } + + /** + * @return ClassPropertyNode[] + */ + public function getProperties(): array + { + return $this->properties; + } + + /** + * @return array + */ + public function getPropertyUsages(): array + { + return $this->propertyUsages; + } + + public function getType(): string + { + return 'PHPStan_Node_ClassPropertiesNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + + /** + * @param string[] $constructors + * @param ReadWritePropertiesExtension[] $extensions + * @return array{array, array} + */ + public function getUninitializedProperties( + Scope $scope, + array $constructors, + array $extensions + ): array { + if (!$this->getClass() instanceof Class_) { + return [[], []]; + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + + $properties = []; + foreach ($this->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + if ($property->getNativeType() === null) { + continue; + } + if ($property->getDefault() !== null) { + continue; + } + $properties[$property->getName()] = $property; + } + + foreach (array_keys($properties) as $name) { + foreach ($extensions as $extension) { + if (!$classReflection->hasNativeProperty($name)) { + continue; + } + $propertyReflection = $classReflection->getNativeProperty($name); + if (!$extension->isInitialized($propertyReflection, $name)) { + continue; + } + unset($properties[$name]); + break; + } + } + + if ($constructors === []) { + return [$properties, []]; + } + $classType = new ObjectType($scope->getClassReflection()->getName()); + $methodsCalledFromConstructor = $this->getMethodsCalledFromConstructor($classType, $this->methodCalls, $constructors); + $prematureAccess = []; + foreach ($this->getPropertyUsages() as $usage) { + $fetch = $usage->getFetch(); + if (!$fetch instanceof PropertyFetch) { + continue; + } + $usageScope = $usage->getScope(); + if ($usageScope->getFunction() === null) { + continue; + } + $function = $usageScope->getFunction(); + if (!$function instanceof MethodReflection) { + continue; + } + if ($function->getDeclaringClass()->getName() !== $classReflection->getName()) { + continue; + } + if (!in_array($function->getName(), $methodsCalledFromConstructor, true)) { + continue; + } + + if (!$fetch->name instanceof Identifier) { + continue; + } + $propertyName = $fetch->name->toString(); + if (!array_key_exists($propertyName, $properties)) { + continue; + } + $fetchedOnType = $usageScope->getType($fetch->var); + if ($classType->isSuperTypeOf($fetchedOnType)->no()) { + continue; + } + if ($fetchedOnType instanceof MixedType) { + continue; + } + + if ($usage instanceof PropertyWrite) { + unset($properties[$propertyName]); + } elseif (array_key_exists($propertyName, $properties)) { + $prematureAccess[] = [ + $propertyName, + $fetch->getLine(), + ]; + } + } + + return [ + $properties, + $prematureAccess, + ]; + } + + /** + * @param ObjectType $classType + * @param MethodCall[] $methodCalls + * @param string[] $methods + * @return string[] + */ + private function getMethodsCalledFromConstructor( + ObjectType $classType, + array $methodCalls, + array $methods + ): array { + $originalCount = count($methods); + foreach ($methodCalls as $methodCall) { + $methodCallNode = $methodCall->getNode(); + if ($methodCallNode instanceof Array_) { + continue; + } + if (!$methodCallNode->name instanceof Identifier) { + continue; + } + $callScope = $methodCall->getScope(); + if ($methodCallNode instanceof \PhpParser\Node\Expr\MethodCall) { + $calledOnType = $callScope->getType($methodCallNode->var); + } else { + if (!$methodCallNode->class instanceof Name) { + continue; + } + + $calledOnType = $callScope->resolveTypeByName($methodCallNode->class); + } + if ($classType->isSuperTypeOf($calledOnType)->no()) { + continue; + } + if ($calledOnType instanceof MixedType) { + continue; + } + $methodName = $methodCallNode->name->toString(); + if (in_array($methodName, $methods, true)) { + continue; + } + $inMethod = $callScope->getFunction(); + if (!$inMethod instanceof MethodReflection) { + continue; + } + if (!in_array($inMethod->getName(), $methods, true)) { + continue; + } + $methods[] = $methodName; + } + + if ($originalCount === count($methods)) { + return $methods; + } + + return $this->getMethodsCalledFromConstructor($classType, $methodCalls, $methods); + } } diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index 62c4061065..5dfe78fd04 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->name = $name; - $this->flags = $flags; - $this->type = $type; - $this->default = $default; - $this->isPromoted = $isPromoted; - $this->phpDoc = $phpDoc; - } - - public function getName(): string - { - return $this->name; - } - - public function getFlags(): int - { - return $this->flags; - } - - public function getDefault(): ?Expr - { - return $this->default; - } - - public function isPromoted(): bool - { - return $this->isPromoted; - } - - public function getPhpDoc(): ?string - { - return $this->phpDoc; - } - - public function isPublic(): bool - { - return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 - || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; - } - - public function isProtected(): bool - { - return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); - } - - public function isPrivate(): bool - { - return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); - } - - public function isStatic(): bool - { - return (bool) ($this->flags & Class_::MODIFIER_STATIC); - } - - /** - * @return Identifier|Name|NullableType|UnionType|null - */ - public function getNativeType() - { - return $this->type; - } - - public function getType(): string - { - return 'PHPStan_Node_ClassPropertyNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private string $name; + + private int $flags; + + /** @var Identifier|Name|NullableType|UnionType|null */ + private $type; + + private ?Expr $default; + + private ?string $phpDoc; + + private bool $isPromoted; + + /** + * @param int $flags + * @param Identifier|Name|NullableType|UnionType|null $type + * @param string $name + * @param Expr|null $default + */ + public function __construct( + string $name, + int $flags, + $type, + ?Expr $default, + ?string $phpDoc, + bool $isPromoted, + Node $originalNode + ) { + parent::__construct($originalNode->getAttributes()); + $this->name = $name; + $this->flags = $flags; + $this->type = $type; + $this->default = $default; + $this->isPromoted = $isPromoted; + $this->phpDoc = $phpDoc; + } + + public function getName(): string + { + return $this->name; + } + + public function getFlags(): int + { + return $this->flags; + } + + public function getDefault(): ?Expr + { + return $this->default; + } + + public function isPromoted(): bool + { + return $this->isPromoted; + } + + public function getPhpDoc(): ?string + { + return $this->phpDoc; + } + + public function isPublic(): bool + { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + public function isProtected(): bool + { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + public function isPrivate(): bool + { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + public function isStatic(): bool + { + return (bool) ($this->flags & Class_::MODIFIER_STATIC); + } + + /** + * @return Identifier|Name|NullableType|UnionType|null + */ + public function getNativeType() + { + return $this->type; + } + + public function getType(): string + { + return 'PHPStan_Node_ClassPropertyNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 14baad972b..07231bb629 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -1,4 +1,6 @@ - */ - private array $propertyUsages = []; - - /** @var \PhpParser\Node\Stmt\ClassConst[] */ - private array $constants = []; - - /** @var ClassConstantFetch[] */ - private array $constantFetches = []; - - /** - * @param ClassReflection $classReflection - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - */ - public function __construct( - ClassReflection $classReflection, - callable $nodeCallback - ) - { - $this->classReflection = $classReflection; - $this->nodeCallback = $nodeCallback; - } - - /** - * @return ClassPropertyNode[] - */ - public function getProperties(): array - { - return $this->properties; - } - - /** - * @return \PhpParser\Node\Stmt\ClassMethod[] - */ - public function getMethods(): array - { - return $this->methods; - } - - /** - * @return Method\MethodCall[] - */ - public function getMethodCalls(): array - { - return $this->methodCalls; - } - - /** - * @return array - */ - public function getPropertyUsages(): array - { - return $this->propertyUsages; - } - - /** - * @return \PhpParser\Node\Stmt\ClassConst[] - */ - public function getConstants(): array - { - return $this->constants; - } - - /** - * @return ClassConstantFetch[] - */ - public function getConstantFetches(): array - { - return $this->constantFetches; - } - - public function __invoke(\PhpParser\Node $node, Scope $scope): void - { - $nodeCallback = $this->nodeCallback; - $nodeCallback($node, $scope); - $this->gatherNodes($node, $scope); - } - - private function gatherNodes(\PhpParser\Node $node, Scope $scope): void - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($scope->getClassReflection()->getName() !== $this->classReflection->getName()) { - return; - } - if ($node instanceof ClassPropertyNode && !$scope->isInTrait()) { - $this->properties[] = $node; - if ($node->isPromoted()) { - $this->propertyUsages[] = new PropertyWrite( - new PropertyFetch(new Expr\Variable('this'), new Identifier($node->getName())), - $scope - ); - } - return; - } - if ($node instanceof \PhpParser\Node\Stmt\ClassMethod && !$scope->isInTrait()) { - $this->methods[] = $node; - return; - } - if ($node instanceof \PhpParser\Node\Stmt\ClassConst) { - $this->constants[] = $node; - return; - } - if ($node instanceof MethodCall || $node instanceof StaticCall) { - $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); - return; - } - if ($node instanceof Array_ && count($node->items) === 2) { - $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); - return; - } - if ($node instanceof Expr\ClassConstFetch) { - $this->constantFetches[] = new ClassConstantFetch($node, $scope); - return; - } - if (!$node instanceof Expr) { - return; - } - if ($node instanceof Expr\AssignOp\Coalesce) { - $this->gatherNodes($node->var, $scope); - return; - } - if ($node instanceof \PhpParser\Node\Scalar\EncapsedStringPart) { - return; - } - $inAssign = $scope->isInExpressionAssign($node); - while ($node instanceof ArrayDimFetch) { - $node = $node->var; - } - if (!$node instanceof PropertyFetch && !$node instanceof StaticPropertyFetch) { - return; - } - - if ($inAssign) { - $this->propertyUsages[] = new PropertyWrite($node, $scope); - } else { - $this->propertyUsages[] = new PropertyRead($node, $scope); - } - } - + private ClassReflection $classReflection; + + /** @var callable(\PhpParser\Node $node, Scope $scope): void */ + private $nodeCallback; + + /** @var ClassPropertyNode[] */ + private array $properties = []; + + /** @var \PhpParser\Node\Stmt\ClassMethod[] */ + private array $methods = []; + + /** @var \PHPStan\Node\Method\MethodCall[] */ + private array $methodCalls = []; + + /** @var array */ + private array $propertyUsages = []; + + /** @var \PhpParser\Node\Stmt\ClassConst[] */ + private array $constants = []; + + /** @var ClassConstantFetch[] */ + private array $constantFetches = []; + + /** + * @param ClassReflection $classReflection + * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback + */ + public function __construct( + ClassReflection $classReflection, + callable $nodeCallback + ) { + $this->classReflection = $classReflection; + $this->nodeCallback = $nodeCallback; + } + + /** + * @return ClassPropertyNode[] + */ + public function getProperties(): array + { + return $this->properties; + } + + /** + * @return \PhpParser\Node\Stmt\ClassMethod[] + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * @return Method\MethodCall[] + */ + public function getMethodCalls(): array + { + return $this->methodCalls; + } + + /** + * @return array + */ + public function getPropertyUsages(): array + { + return $this->propertyUsages; + } + + /** + * @return \PhpParser\Node\Stmt\ClassConst[] + */ + public function getConstants(): array + { + return $this->constants; + } + + /** + * @return ClassConstantFetch[] + */ + public function getConstantFetches(): array + { + return $this->constantFetches; + } + + public function __invoke(\PhpParser\Node $node, Scope $scope): void + { + $nodeCallback = $this->nodeCallback; + $nodeCallback($node, $scope); + $this->gatherNodes($node, $scope); + } + + private function gatherNodes(\PhpParser\Node $node, Scope $scope): void + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($scope->getClassReflection()->getName() !== $this->classReflection->getName()) { + return; + } + if ($node instanceof ClassPropertyNode && !$scope->isInTrait()) { + $this->properties[] = $node; + if ($node->isPromoted()) { + $this->propertyUsages[] = new PropertyWrite( + new PropertyFetch(new Expr\Variable('this'), new Identifier($node->getName())), + $scope + ); + } + return; + } + if ($node instanceof \PhpParser\Node\Stmt\ClassMethod && !$scope->isInTrait()) { + $this->methods[] = $node; + return; + } + if ($node instanceof \PhpParser\Node\Stmt\ClassConst) { + $this->constants[] = $node; + return; + } + if ($node instanceof MethodCall || $node instanceof StaticCall) { + $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); + return; + } + if ($node instanceof Array_ && count($node->items) === 2) { + $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); + return; + } + if ($node instanceof Expr\ClassConstFetch) { + $this->constantFetches[] = new ClassConstantFetch($node, $scope); + return; + } + if (!$node instanceof Expr) { + return; + } + if ($node instanceof Expr\AssignOp\Coalesce) { + $this->gatherNodes($node->var, $scope); + return; + } + if ($node instanceof \PhpParser\Node\Scalar\EncapsedStringPart) { + return; + } + $inAssign = $scope->isInExpressionAssign($node); + while ($node instanceof ArrayDimFetch) { + $node = $node->var; + } + if (!$node instanceof PropertyFetch && !$node instanceof StaticPropertyFetch) { + return; + } + + if ($inAssign) { + $this->propertyUsages[] = new PropertyWrite($node, $scope); + } else { + $this->propertyUsages[] = new PropertyRead($node, $scope); + } + } } diff --git a/src/Node/ClosureReturnStatementsNode.php b/src/Node/ClosureReturnStatementsNode.php index d034518b1a..48b517554b 100644 --- a/src/Node/ClosureReturnStatementsNode.php +++ b/src/Node/ClosureReturnStatementsNode.php @@ -1,4 +1,6 @@ - */ - private array $yieldStatements; - - private StatementResult $statementResult; - - /** - * @param \PhpParser\Node\Expr\Closure $closureExpr - * @param \PHPStan\Node\ReturnStatement[] $returnStatements - * @param array $yieldStatements - * @param \PHPStan\Analyser\StatementResult $statementResult - */ - public function __construct( - Closure $closureExpr, - array $returnStatements, - array $yieldStatements, - StatementResult $statementResult - ) - { - parent::__construct($closureExpr->getAttributes()); - $this->closureExpr = $closureExpr; - $this->returnStatements = $returnStatements; - $this->yieldStatements = $yieldStatements; - $this->statementResult = $statementResult; - } - - public function getClosureExpr(): Closure - { - return $this->closureExpr; - } - - /** - * @return \PHPStan\Node\ReturnStatement[] - */ - public function getReturnStatements(): array - { - return $this->returnStatements; - } - - /** - * @return array - */ - public function getYieldStatements(): array - { - return $this->yieldStatements; - } - - public function getStatementResult(): StatementResult - { - return $this->statementResult; - } - - public function returnsByRef(): bool - { - return $this->closureExpr->byRef; - } - - public function getType(): string - { - return 'PHPStan_Node_ClosureReturnStatementsNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private \PhpParser\Node\Expr\Closure $closureExpr; + + /** @var \PHPStan\Node\ReturnStatement[] */ + private array $returnStatements; + + /** @var array */ + private array $yieldStatements; + + private StatementResult $statementResult; + + /** + * @param \PhpParser\Node\Expr\Closure $closureExpr + * @param \PHPStan\Node\ReturnStatement[] $returnStatements + * @param array $yieldStatements + * @param \PHPStan\Analyser\StatementResult $statementResult + */ + public function __construct( + Closure $closureExpr, + array $returnStatements, + array $yieldStatements, + StatementResult $statementResult + ) { + parent::__construct($closureExpr->getAttributes()); + $this->closureExpr = $closureExpr; + $this->returnStatements = $returnStatements; + $this->yieldStatements = $yieldStatements; + $this->statementResult = $statementResult; + } + + public function getClosureExpr(): Closure + { + return $this->closureExpr; + } + + /** + * @return \PHPStan\Node\ReturnStatement[] + */ + public function getReturnStatements(): array + { + return $this->returnStatements; + } + + /** + * @return array + */ + public function getYieldStatements(): array + { + return $this->yieldStatements; + } + + public function getStatementResult(): StatementResult + { + return $this->statementResult; + } + + public function returnsByRef(): bool + { + return $this->closureExpr->byRef; + } + + public function getType(): string + { + return 'PHPStan_Node_ClosureReturnStatementsNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/Constant/ClassConstantFetch.php b/src/Node/Constant/ClassConstantFetch.php index 0a68ffdec3..32a6dedd19 100644 --- a/src/Node/Constant/ClassConstantFetch.php +++ b/src/Node/Constant/ClassConstantFetch.php @@ -1,4 +1,6 @@ -node = $node; - $this->scope = $scope; - } + private Scope $scope; - public function getNode(): ClassConstFetch - { - return $this->node; - } + public function __construct(ClassConstFetch $node, Scope $scope) + { + $this->node = $node; + $this->scope = $scope; + } - public function getScope(): Scope - { - return $this->scope; - } + public function getNode(): ClassConstFetch + { + return $this->node; + } + public function getScope(): Scope + { + return $this->scope; + } } diff --git a/src/Node/ExecutionEndNode.php b/src/Node/ExecutionEndNode.php index d710f04a7e..dc173a2e29 100644 --- a/src/Node/ExecutionEndNode.php +++ b/src/Node/ExecutionEndNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->node = $node; - $this->statementResult = $statementResult; - $this->hasNativeReturnTypehint = $hasNativeReturnTypehint; - } - - public function getNode(): Node - { - return $this->node; - } - - public function getStatementResult(): StatementResult - { - return $this->statementResult; - } - - public function hasNativeReturnTypehint(): bool - { - return $this->hasNativeReturnTypehint; - } - - public function getType(): string - { - return 'PHPStan_Node_ExecutionEndNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private Node $node; + + private StatementResult $statementResult; + + private bool $hasNativeReturnTypehint; + + public function __construct( + Node $node, + StatementResult $statementResult, + bool $hasNativeReturnTypehint + ) { + parent::__construct($node->getAttributes()); + $this->node = $node; + $this->statementResult = $statementResult; + $this->hasNativeReturnTypehint = $hasNativeReturnTypehint; + } + + public function getNode(): Node + { + return $this->node; + } + + public function getStatementResult(): StatementResult + { + return $this->statementResult; + } + + public function hasNativeReturnTypehint(): bool + { + return $this->hasNativeReturnTypehint; + } + + public function getType(): string + { + return 'PHPStan_Node_ExecutionEndNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/FileNode.php b/src/Node/FileNode.php index d8f9c0d80f..2d47f1f5a4 100644 --- a/src/Node/FileNode.php +++ b/src/Node/FileNode.php @@ -1,4 +1,6 @@ -getAttributes() : []); - $this->nodes = $nodes; - } - - /** - * @return \PhpParser\Node[] - */ - public function getNodes(): array - { - return $this->nodes; - } - - public function getType(): string - { - return 'PHPStan_Node_FileNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + /** @var \PhpParser\Node[] */ + private array $nodes; + + /** + * @param \PhpParser\Node[] $nodes + */ + public function __construct(array $nodes) + { + $firstNode = $nodes[0] ?? null; + parent::__construct($firstNode !== null ? $firstNode->getAttributes() : []); + $this->nodes = $nodes; + } + + /** + * @return \PhpParser\Node[] + */ + public function getNodes(): array + { + return $this->nodes; + } + + public function getType(): string + { + return 'PHPStan_Node_FileNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/FinallyExitPointsNode.php b/src/Node/FinallyExitPointsNode.php index 9ccb45f47d..74f3d5e9fb 100644 --- a/src/Node/FinallyExitPointsNode.php +++ b/src/Node/FinallyExitPointsNode.php @@ -1,4 +1,6 @@ -finallyExitPoints = $finallyExitPoints; - $this->tryCatchExitPoints = $tryCatchExitPoints; - } - - /** - * @return StatementExitPoint[] - */ - public function getFinallyExitPoints(): array - { - return $this->finallyExitPoints; - } - - /** - * @return StatementExitPoint[] - */ - public function getTryCatchExitPoints(): array - { - return $this->tryCatchExitPoints; - } - - public function getType(): string - { - return 'PHPStan_Node_FinallyExitPointsNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + /** @var StatementExitPoint[] */ + private array $finallyExitPoints; + + /** @var StatementExitPoint[] */ + private array $tryCatchExitPoints; + + /** + * @param StatementExitPoint[] $finallyExitPoints + * @param StatementExitPoint[] $tryCatchExitPoints + */ + public function __construct(array $finallyExitPoints, array $tryCatchExitPoints) + { + parent::__construct([]); + $this->finallyExitPoints = $finallyExitPoints; + $this->tryCatchExitPoints = $tryCatchExitPoints; + } + + /** + * @return StatementExitPoint[] + */ + public function getFinallyExitPoints(): array + { + return $this->finallyExitPoints; + } + + /** + * @return StatementExitPoint[] + */ + public function getTryCatchExitPoints(): array + { + return $this->tryCatchExitPoints; + } + + public function getType(): string + { + return 'PHPStan_Node_FinallyExitPointsNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/FunctionReturnStatementsNode.php b/src/Node/FunctionReturnStatementsNode.php index 8658f356d0..c7854190f5 100644 --- a/src/Node/FunctionReturnStatementsNode.php +++ b/src/Node/FunctionReturnStatementsNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->function = $function; - $this->returnStatements = $returnStatements; - $this->statementResult = $statementResult; - } + private StatementResult $statementResult; - /** - * @return \PHPStan\Node\ReturnStatement[] - */ - public function getReturnStatements(): array - { - return $this->returnStatements; - } + /** + * @param \PhpParser\Node\Stmt\Function_ $function + * @param \PHPStan\Node\ReturnStatement[] $returnStatements + * @param \PHPStan\Analyser\StatementResult $statementResult + */ + public function __construct( + Function_ $function, + array $returnStatements, + StatementResult $statementResult + ) { + parent::__construct($function->getAttributes()); + $this->function = $function; + $this->returnStatements = $returnStatements; + $this->statementResult = $statementResult; + } - public function getStatementResult(): StatementResult - { - return $this->statementResult; - } + /** + * @return \PHPStan\Node\ReturnStatement[] + */ + public function getReturnStatements(): array + { + return $this->returnStatements; + } - public function returnsByRef(): bool - { - return $this->function->byRef; - } + public function getStatementResult(): StatementResult + { + return $this->statementResult; + } - public function getType(): string - { - return 'PHPStan_Node_FunctionReturnStatementsNode'; - } + public function returnsByRef(): bool + { + return $this->function->byRef; + } - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } + public function getType(): string + { + return 'PHPStan_Node_FunctionReturnStatementsNode'; + } + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/InArrowFunctionNode.php b/src/Node/InArrowFunctionNode.php index 9fb926e785..4d72a20deb 100644 --- a/src/Node/InArrowFunctionNode.php +++ b/src/Node/InArrowFunctionNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - } - - public function getOriginalNode(): \PhpParser\Node\Expr\ArrowFunction - { - return $this->originalNode; - } - - public function getType(): string - { - return 'PHPStan_Node_InArrowFunctionNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private \PhpParser\Node\Expr\ArrowFunction $originalNode; + + public function __construct(ArrowFunction $originalNode) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + } + + public function getOriginalNode(): \PhpParser\Node\Expr\ArrowFunction + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Node_InArrowFunctionNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/InClassMethodNode.php b/src/Node/InClassMethodNode.php index 116892703f..773d5ccba2 100644 --- a/src/Node/InClassMethodNode.php +++ b/src/Node/InClassMethodNode.php @@ -1,34 +1,34 @@ -getAttributes()); - $this->originalNode = $originalNode; - } - - public function getOriginalNode(): \PhpParser\Node\Stmt\ClassMethod - { - return $this->originalNode; - } - - public function getType(): string - { - return 'PHPStan_Stmt_InClassMethodNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private \PhpParser\Node\Stmt\ClassMethod $originalNode; + + public function __construct(\PhpParser\Node\Stmt\ClassMethod $originalNode) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + } + + public function getOriginalNode(): \PhpParser\Node\Stmt\ClassMethod + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Stmt_InClassMethodNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/InClassNode.php b/src/Node/InClassNode.php index a45216d1c8..258c67d9db 100644 --- a/src/Node/InClassNode.php +++ b/src/Node/InClassNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - $this->classReflection = $classReflection; - } - - public function getOriginalNode(): ClassLike - { - return $this->originalNode; - } - - public function getClassReflection(): ClassReflection - { - return $this->classReflection; - } - - public function getType(): string - { - return 'PHPStan_Stmt_InClassNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private ClassLike $originalNode; + + private ClassReflection $classReflection; + + public function __construct(ClassLike $originalNode, ClassReflection $classReflection) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + $this->classReflection = $classReflection; + } + + public function getOriginalNode(): ClassLike + { + return $this->originalNode; + } + + public function getClassReflection(): ClassReflection + { + return $this->classReflection; + } + + public function getType(): string + { + return 'PHPStan_Stmt_InClassNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/InClosureNode.php b/src/Node/InClosureNode.php index 8d08a3ffa1..d99e2d8c51 100644 --- a/src/Node/InClosureNode.php +++ b/src/Node/InClosureNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalNode = $originalNode; - } - - public function getOriginalNode(): Closure - { - return $this->originalNode; - } - - public function getType(): string - { - return 'PHPStan_Node_InClosureNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private \PhpParser\Node\Expr\Closure $originalNode; + + public function __construct(Closure $originalNode) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + } + + public function getOriginalNode(): Closure + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Node_InClosureNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/InFunctionNode.php b/src/Node/InFunctionNode.php index 632c20044b..95a2f2a5b1 100644 --- a/src/Node/InFunctionNode.php +++ b/src/Node/InFunctionNode.php @@ -1,34 +1,34 @@ -getAttributes()); - $this->originalNode = $originalNode; - } - - public function getOriginalNode(): \PhpParser\Node\Stmt\Function_ - { - return $this->originalNode; - } - - public function getType(): string - { - return 'PHPStan_Stmt_InFunctionNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private \PhpParser\Node\Stmt\Function_ $originalNode; + + public function __construct(\PhpParser\Node\Stmt\Function_ $originalNode) + { + parent::__construct($originalNode->getAttributes()); + $this->originalNode = $originalNode; + } + + public function getOriginalNode(): \PhpParser\Node\Stmt\Function_ + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Stmt_InFunctionNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/LiteralArrayItem.php b/src/Node/LiteralArrayItem.php index 014f711946..6e7b71d758 100644 --- a/src/Node/LiteralArrayItem.php +++ b/src/Node/LiteralArrayItem.php @@ -1,4 +1,6 @@ -scope = $scope; - $this->arrayItem = $arrayItem; - } + private ?ArrayItem $arrayItem; - public function getScope(): Scope - { - return $this->scope; - } + public function __construct(Scope $scope, ?ArrayItem $arrayItem) + { + $this->scope = $scope; + $this->arrayItem = $arrayItem; + } - public function getArrayItem(): ?ArrayItem - { - return $this->arrayItem; - } + public function getScope(): Scope + { + return $this->scope; + } + public function getArrayItem(): ?ArrayItem + { + return $this->arrayItem; + } } diff --git a/src/Node/LiteralArrayNode.php b/src/Node/LiteralArrayNode.php index 17528158da..5f4123997a 100644 --- a/src/Node/LiteralArrayNode.php +++ b/src/Node/LiteralArrayNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->itemNodes = $itemNodes; - } - - /** - * @return LiteralArrayItem[] - */ - public function getItemNodes(): array - { - return $this->itemNodes; - } - - public function getType(): string - { - return 'PHPStan_Node_LiteralArray'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + /** @var LiteralArrayItem[] */ + private array $itemNodes; + + /** + * @param Array_ $originalNode + * @param LiteralArrayItem[] $itemNodes + */ + public function __construct(Array_ $originalNode, array $itemNodes) + { + parent::__construct($originalNode->getAttributes()); + $this->itemNodes = $itemNodes; + } + + /** + * @return LiteralArrayItem[] + */ + public function getItemNodes(): array + { + return $this->itemNodes; + } + + public function getType(): string + { + return 'PHPStan_Node_LiteralArray'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/MatchExpressionArm.php b/src/Node/MatchExpressionArm.php index a748414220..8d0a0854fe 100644 --- a/src/Node/MatchExpressionArm.php +++ b/src/Node/MatchExpressionArm.php @@ -1,36 +1,36 @@ -conditions = $conditions; - $this->line = $line; - } - - /** - * @return MatchExpressionArmCondition[] - */ - public function getConditions(): array - { - return $this->conditions; - } - - public function getLine(): int - { - return $this->line; - } - + /** @var MatchExpressionArmCondition[] */ + private array $conditions; + + private int $line; + + /** + * @param MatchExpressionArmCondition[] $conditions + * @param int $line + */ + public function __construct(array $conditions, int $line) + { + $this->conditions = $conditions; + $this->line = $line; + } + + /** + * @return MatchExpressionArmCondition[] + */ + public function getConditions(): array + { + return $this->conditions; + } + + public function getLine(): int + { + return $this->line; + } } diff --git a/src/Node/MatchExpressionArmCondition.php b/src/Node/MatchExpressionArmCondition.php index a3e5cd2e90..084327ca02 100644 --- a/src/Node/MatchExpressionArmCondition.php +++ b/src/Node/MatchExpressionArmCondition.php @@ -1,4 +1,6 @@ -condition = $condition; - $this->scope = $scope; - $this->line = $line; - } + private int $line; - public function getCondition(): Expr - { - return $this->condition; - } + public function __construct(Expr $condition, Scope $scope, int $line) + { + $this->condition = $condition; + $this->scope = $scope; + $this->line = $line; + } - public function getScope(): Scope - { - return $this->scope; - } + public function getCondition(): Expr + { + return $this->condition; + } - public function getLine(): int - { - return $this->line; - } + public function getScope(): Scope + { + return $this->scope; + } + public function getLine(): int + { + return $this->line; + } } diff --git a/src/Node/MatchExpressionNode.php b/src/Node/MatchExpressionNode.php index 4670615cc9..f59856020e 100644 --- a/src/Node/MatchExpressionNode.php +++ b/src/Node/MatchExpressionNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->condition = $condition; - $this->arms = $arms; - $this->endScope = $endScope; - } + private Scope $endScope; - public function getCondition(): Expr - { - return $this->condition; - } + /** + * @param Expr $condition + * @param MatchExpressionArm[] $arms + */ + public function __construct( + Expr $condition, + array $arms, + Expr\Match_ $originalNode, + Scope $endScope + ) { + parent::__construct($originalNode->getAttributes()); + $this->condition = $condition; + $this->arms = $arms; + $this->endScope = $endScope; + } - /** - * @return MatchExpressionArm[] - */ - public function getArms(): array - { - return $this->arms; - } + public function getCondition(): Expr + { + return $this->condition; + } - public function getEndScope(): Scope - { - return $this->endScope; - } + /** + * @return MatchExpressionArm[] + */ + public function getArms(): array + { + return $this->arms; + } - public function getType(): string - { - return 'PHPStan_Node_MatchExpression'; - } + public function getEndScope(): Scope + { + return $this->endScope; + } - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } + public function getType(): string + { + return 'PHPStan_Node_MatchExpression'; + } + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/Method/MethodCall.php b/src/Node/Method/MethodCall.php index 8c96fb4885..7372167653 100644 --- a/src/Node/Method/MethodCall.php +++ b/src/Node/Method/MethodCall.php @@ -1,4 +1,6 @@ -node = $node; - $this->scope = $scope; - } - - /** - * @return \PhpParser\Node\Expr\MethodCall|StaticCall|Array_ - */ - public function getNode() - { - return $this->node; - } - - public function getScope(): Scope - { - return $this->scope; - } - + /** @var \PhpParser\Node\Expr\MethodCall|StaticCall|Array_ */ + private $node; + + private Scope $scope; + + /** + * @param \PhpParser\Node\Expr\MethodCall|StaticCall|Array_ $node + * @param Scope $scope + */ + public function __construct($node, Scope $scope) + { + $this->node = $node; + $this->scope = $scope; + } + + /** + * @return \PhpParser\Node\Expr\MethodCall|StaticCall|Array_ + */ + public function getNode() + { + return $this->node; + } + + public function getScope(): Scope + { + return $this->scope; + } } diff --git a/src/Node/MethodReturnStatementsNode.php b/src/Node/MethodReturnStatementsNode.php index b2000b20d5..d88e8e0914 100644 --- a/src/Node/MethodReturnStatementsNode.php +++ b/src/Node/MethodReturnStatementsNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->classMethod = $method; - $this->returnStatements = $returnStatements; - $this->statementResult = $statementResult; - } + private StatementResult $statementResult; - /** - * @return \PHPStan\Node\ReturnStatement[] - */ - public function getReturnStatements(): array - { - return $this->returnStatements; - } + /** + * @param \PhpParser\Node\Stmt\ClassMethod $method + * @param \PHPStan\Node\ReturnStatement[] $returnStatements + * @param \PHPStan\Analyser\StatementResult $statementResult + */ + public function __construct( + ClassMethod $method, + array $returnStatements, + StatementResult $statementResult + ) { + parent::__construct($method->getAttributes()); + $this->classMethod = $method; + $this->returnStatements = $returnStatements; + $this->statementResult = $statementResult; + } - public function getStatementResult(): StatementResult - { - return $this->statementResult; - } + /** + * @return \PHPStan\Node\ReturnStatement[] + */ + public function getReturnStatements(): array + { + return $this->returnStatements; + } - public function returnsByRef(): bool - { - return $this->classMethod->byRef; - } + public function getStatementResult(): StatementResult + { + return $this->statementResult; + } - public function getType(): string - { - return 'PHPStan_Node_FunctionReturnStatementsNode'; - } + public function returnsByRef(): bool + { + return $this->classMethod->byRef; + } - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } + public function getType(): string + { + return 'PHPStan_Node_FunctionReturnStatementsNode'; + } + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/Property/PropertyRead.php b/src/Node/Property/PropertyRead.php index 19478151fc..cb4471ae7e 100644 --- a/src/Node/Property/PropertyRead.php +++ b/src/Node/Property/PropertyRead.php @@ -1,4 +1,6 @@ -fetch = $fetch; - $this->scope = $scope; - } - - /** - * @return PropertyFetch|StaticPropertyFetch - */ - public function getFetch() - { - return $this->fetch; - } - - public function getScope(): Scope - { - return $this->scope; - } - + /** @var PropertyFetch|StaticPropertyFetch */ + private $fetch; + + private Scope $scope; + + /** + * PropertyWrite constructor. + * + * @param PropertyFetch|StaticPropertyFetch $fetch + * @param Scope $scope + */ + public function __construct($fetch, Scope $scope) + { + $this->fetch = $fetch; + $this->scope = $scope; + } + + /** + * @return PropertyFetch|StaticPropertyFetch + */ + public function getFetch() + { + return $this->fetch; + } + + public function getScope(): Scope + { + return $this->scope; + } } diff --git a/src/Node/Property/PropertyWrite.php b/src/Node/Property/PropertyWrite.php index e028662017..cedc1a9505 100644 --- a/src/Node/Property/PropertyWrite.php +++ b/src/Node/Property/PropertyWrite.php @@ -1,4 +1,6 @@ -fetch = $fetch; - $this->scope = $scope; - } - - /** - * @return PropertyFetch|StaticPropertyFetch - */ - public function getFetch() - { - return $this->fetch; - } - - public function getScope(): Scope - { - return $this->scope; - } - + /** @var PropertyFetch|StaticPropertyFetch */ + private $fetch; + + private Scope $scope; + + /** + * PropertyWrite constructor. + * + * @param PropertyFetch|StaticPropertyFetch $fetch + * @param Scope $scope + */ + public function __construct($fetch, Scope $scope) + { + $this->fetch = $fetch; + $this->scope = $scope; + } + + /** + * @return PropertyFetch|StaticPropertyFetch + */ + public function getFetch() + { + return $this->fetch; + } + + public function getScope(): Scope + { + return $this->scope; + } } diff --git a/src/Node/ReturnStatement.php b/src/Node/ReturnStatement.php index 4ea6266cfb..75354c71d5 100644 --- a/src/Node/ReturnStatement.php +++ b/src/Node/ReturnStatement.php @@ -1,4 +1,6 @@ -scope = $scope; - $this->returnNode = $returnNode; - } + private \PhpParser\Node\Stmt\Return_ $returnNode; - public function getScope(): Scope - { - return $this->scope; - } + public function __construct(Scope $scope, Return_ $returnNode) + { + $this->scope = $scope; + $this->returnNode = $returnNode; + } - public function getReturnNode(): Return_ - { - return $this->returnNode; - } + public function getScope(): Scope + { + return $this->scope; + } + public function getReturnNode(): Return_ + { + return $this->returnNode; + } } diff --git a/src/Node/ReturnStatementsNode.php b/src/Node/ReturnStatementsNode.php index 0f6105f2be..35319f08e8 100644 --- a/src/Node/ReturnStatementsNode.php +++ b/src/Node/ReturnStatementsNode.php @@ -1,4 +1,6 @@ -getAttributes()); - $this->originalStatement = $originalStatement; - } - - public function getOriginalStatement(): Stmt - { - return $this->originalStatement; - } - - public function getType(): string - { - return 'PHPStan_Stmt_UnreachableStatementNode'; - } - - /** - * @return string[] - */ - public function getSubNodeNames(): array - { - return []; - } - + private Stmt $originalStatement; + + public function __construct(Stmt $originalStatement) + { + parent::__construct($originalStatement->getAttributes()); + $this->originalStatement = $originalStatement; + } + + public function getOriginalStatement(): Stmt + { + return $this->originalStatement; + } + + public function getType(): string + { + return 'PHPStan_Stmt_UnreachableStatementNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } } diff --git a/src/Node/VirtualNode.php b/src/Node/VirtualNode.php index fcb077279a..915263cb86 100644 --- a/src/Node/VirtualNode.php +++ b/src/Node/VirtualNode.php @@ -1,4 +1,6 @@ -orderStack = [0]; - $this->depth = 0; - - return null; - } - - /** - * @param Node $node - * @return null - */ - public function enterNode(Node $node) - { - $order = $this->orderStack[count($this->orderStack) - 1]; - $node->setAttribute('statementOrder', $order); - $node->setAttribute('statementDepth', $this->depth); - - if ( - ($node instanceof Node\Expr || $node instanceof Node\Arg) - && count($this->expressionOrderStack) > 0 - ) { - $expressionOrder = $this->expressionOrderStack[count($this->expressionOrderStack) - 1]; - $node->setAttribute('expressionOrder', $expressionOrder); - $node->setAttribute('expressionDepth', $this->expressionDepth); - $this->expressionOrderStack[count($this->expressionOrderStack) - 1] = $expressionOrder + 1; - $this->expressionOrderStack[] = 0; - $this->expressionDepth++; - } - - if (!$node instanceof Node\Stmt) { - return null; - } - - $this->orderStack[count($this->orderStack) - 1] = $order + 1; - $this->orderStack[] = 0; - $this->depth++; - - $this->expressionOrderStack = [0]; - $this->expressionDepth = 0; - - return null; - } - - /** - * @param Node $node - * @return null - */ - public function leaveNode(Node $node) - { - if ($node instanceof Node\Expr) { - array_pop($this->expressionOrderStack); - $this->expressionDepth--; - } - if (!$node instanceof Node\Stmt) { - return null; - } - - array_pop($this->orderStack); - $this->depth--; - - return null; - } - + /** @var int[] */ + private array $orderStack = []; + + /** @var int[] */ + private array $expressionOrderStack = []; + + private int $depth = 0; + + private int $expressionDepth = 0; + + /** + * @param Node[] $nodes $nodes + * @return null + */ + public function beforeTraverse(array $nodes) + { + $this->orderStack = [0]; + $this->depth = 0; + + return null; + } + + /** + * @param Node $node + * @return null + */ + public function enterNode(Node $node) + { + $order = $this->orderStack[count($this->orderStack) - 1]; + $node->setAttribute('statementOrder', $order); + $node->setAttribute('statementDepth', $this->depth); + + if ( + ($node instanceof Node\Expr || $node instanceof Node\Arg) + && count($this->expressionOrderStack) > 0 + ) { + $expressionOrder = $this->expressionOrderStack[count($this->expressionOrderStack) - 1]; + $node->setAttribute('expressionOrder', $expressionOrder); + $node->setAttribute('expressionDepth', $this->expressionDepth); + $this->expressionOrderStack[count($this->expressionOrderStack) - 1] = $expressionOrder + 1; + $this->expressionOrderStack[] = 0; + $this->expressionDepth++; + } + + if (!$node instanceof Node\Stmt) { + return null; + } + + $this->orderStack[count($this->orderStack) - 1] = $order + 1; + $this->orderStack[] = 0; + $this->depth++; + + $this->expressionOrderStack = [0]; + $this->expressionDepth = 0; + + return null; + } + + /** + * @param Node $node + * @return null + */ + public function leaveNode(Node $node) + { + if ($node instanceof Node\Expr) { + array_pop($this->expressionOrderStack); + $this->expressionDepth--; + } + if (!$node instanceof Node\Stmt) { + return null; + } + + array_pop($this->orderStack); + $this->depth--; + + return null; + } } diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 48bc78f294..a88d9fad9d 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -1,4 +1,6 @@ -internalErrorsCountLimit = $internalErrorsCountLimit; - $this->processTimeout = $processTimeout; - $this->decoderBufferSize = $decoderBufferSize; - } - - /** - * @param Schedule $schedule - * @param string $mainScript - * @param \Closure(int): void|null $postFileCallback - * @param string|null $projectConfigFile - * @param string|null $tmpFile - * @param string|null $insteadOfFile - * @return AnalyserResult - */ - public function analyse( - Schedule $schedule, - string $mainScript, - ?\Closure $postFileCallback, - ?string $projectConfigFile, - ?string $tmpFile, - ?string $insteadOfFile, - InputInterface $input - ): AnalyserResult - { - $jobs = array_reverse($schedule->getJobs()); - $loop = new StreamSelectLoop(); - - $numberOfProcesses = $schedule->getNumberOfProcesses(); - $errors = []; - $internalErrors = []; - - $server = new \React\Socket\TcpServer('127.0.0.1:0', $loop); - $this->processPool = new ProcessPool($server); - $server->on('connection', function (ConnectionInterface $connection) use (&$jobs): void { - $decoder = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, $this->decoderBufferSize); - $encoder = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); - $decoder->on('data', function (array $data) use (&$jobs, $decoder, $encoder): void { - if ($data['action'] !== 'hello') { - return; - } - - $identifier = $data['identifier']; - $process = $this->processPool->getProcess($identifier); - $process->bindConnection($decoder, $encoder); - if (count($jobs) === 0) { - $this->processPool->tryQuitProcess($identifier); - return; - } - - $job = array_pop($jobs); - $process->request(['action' => 'analyse', 'files' => $job]); - }); - }); - /** @var string $serverAddress */ - $serverAddress = $server->getAddress(); - - /** @var int $serverPort */ - $serverPort = parse_url($serverAddress, PHP_URL_PORT); - - $internalErrorsCount = 0; - - $reachedInternalErrorsCountLimit = false; - - $handleError = function (\Throwable $error) use (&$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit): void { - $internalErrors[] = sprintf('Internal error: ' . $error->getMessage()); - $internalErrorsCount++; - $reachedInternalErrorsCountLimit = true; - $this->processPool->quitAll(); - }; - - $dependencies = []; - $exportedNodes = []; - for ($i = 0; $i < $numberOfProcesses; $i++) { - if (count($jobs) === 0) { - break; - } - - $processIdentifier = Random::generate(); - $commandOptions = [ - '--port', - (string) $serverPort, - '--identifier', - $processIdentifier, - ]; - - if ($tmpFile !== null && $insteadOfFile !== null) { - $commandOptions[] = '--tmp-file'; - $commandOptions[] = escapeshellarg($tmpFile); - $commandOptions[] = '--instead-of'; - $commandOptions[] = escapeshellarg($insteadOfFile); - } - - $process = new Process(ProcessHelper::getWorkerCommand( - $mainScript, - 'worker', - $projectConfigFile, - $commandOptions, - $input - ), $loop, $this->processTimeout); - $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$dependencies, &$exportedNodes, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier): void { - foreach ($json['errors'] as $jsonError) { - if (is_string($jsonError)) { - $internalErrors[] = sprintf('Internal error: %s', $jsonError); - continue; - } - - $errors[] = Error::decode($jsonError); - } - - /** - * @var string $file - * @var array $fileDependencies - */ - foreach ($json['dependencies'] as $file => $fileDependencies) { - $dependencies[$file] = $fileDependencies; - } - - /** - * @var string $file - * @var array $fileExportedNodes - */ - foreach ($json['exportedNodes'] as $file => $fileExportedNodes) { - if (count($fileExportedNodes) === 0) { - continue; - } - $exportedNodes[$file] = array_map(static function (array $node): ExportedNode { - $class = $node['type']; - - return $class::decode($node['data']); - }, $fileExportedNodes); - } - - if ($postFileCallback !== null) { - $postFileCallback($json['filesCount']); - } - - $internalErrorsCount += $json['internalErrorsCount']; - if ($internalErrorsCount >= $this->internalErrorsCountLimit) { - $reachedInternalErrorsCountLimit = true; - $this->processPool->quitAll(); - } - - if (count($jobs) === 0) { - $this->processPool->tryQuitProcess($processIdentifier); - return; - } - - $job = array_pop($jobs); - $process->request(['action' => 'analyse', 'files' => $job]); - }, $handleError, function ($exitCode, string $output) use (&$internalErrors, &$internalErrorsCount, $processIdentifier): void { - $this->processPool->tryQuitProcess($processIdentifier); - if ($exitCode === 0) { - return; - } - if ($exitCode === null) { - return; - } - - $internalErrors[] = sprintf('Child process error (exit code %d): %s', $exitCode, $output); - $internalErrorsCount++; - }); - $this->processPool->attachProcess($processIdentifier, $process); - } - - $loop->run(); - - if (count($jobs) > 0 && $internalErrorsCount === 0) { - $internalErrors[] = 'Some parallel worker jobs have not finished.'; - $internalErrorsCount++; - } - - return new AnalyserResult( - $errors, - $internalErrors, - $internalErrorsCount === 0 ? $dependencies : null, - $exportedNodes, - $reachedInternalErrorsCountLimit - ); - } - + private int $internalErrorsCountLimit; + + private float $processTimeout; + + private ProcessPool $processPool; + + private int $decoderBufferSize; + + public function __construct( + int $internalErrorsCountLimit, + float $processTimeout, + int $decoderBufferSize + ) { + $this->internalErrorsCountLimit = $internalErrorsCountLimit; + $this->processTimeout = $processTimeout; + $this->decoderBufferSize = $decoderBufferSize; + } + + /** + * @param Schedule $schedule + * @param string $mainScript + * @param \Closure(int): void|null $postFileCallback + * @param string|null $projectConfigFile + * @param string|null $tmpFile + * @param string|null $insteadOfFile + * @return AnalyserResult + */ + public function analyse( + Schedule $schedule, + string $mainScript, + ?\Closure $postFileCallback, + ?string $projectConfigFile, + ?string $tmpFile, + ?string $insteadOfFile, + InputInterface $input + ): AnalyserResult { + $jobs = array_reverse($schedule->getJobs()); + $loop = new StreamSelectLoop(); + + $numberOfProcesses = $schedule->getNumberOfProcesses(); + $errors = []; + $internalErrors = []; + + $server = new \React\Socket\TcpServer('127.0.0.1:0', $loop); + $this->processPool = new ProcessPool($server); + $server->on('connection', function (ConnectionInterface $connection) use (&$jobs): void { + $decoder = new Decoder($connection, true, 512, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0, $this->decoderBufferSize); + $encoder = new Encoder($connection, defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0); + $decoder->on('data', function (array $data) use (&$jobs, $decoder, $encoder): void { + if ($data['action'] !== 'hello') { + return; + } + + $identifier = $data['identifier']; + $process = $this->processPool->getProcess($identifier); + $process->bindConnection($decoder, $encoder); + if (count($jobs) === 0) { + $this->processPool->tryQuitProcess($identifier); + return; + } + + $job = array_pop($jobs); + $process->request(['action' => 'analyse', 'files' => $job]); + }); + }); + /** @var string $serverAddress */ + $serverAddress = $server->getAddress(); + + /** @var int $serverPort */ + $serverPort = parse_url($serverAddress, PHP_URL_PORT); + + $internalErrorsCount = 0; + + $reachedInternalErrorsCountLimit = false; + + $handleError = function (\Throwable $error) use (&$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit): void { + $internalErrors[] = sprintf('Internal error: ' . $error->getMessage()); + $internalErrorsCount++; + $reachedInternalErrorsCountLimit = true; + $this->processPool->quitAll(); + }; + + $dependencies = []; + $exportedNodes = []; + for ($i = 0; $i < $numberOfProcesses; $i++) { + if (count($jobs) === 0) { + break; + } + + $processIdentifier = Random::generate(); + $commandOptions = [ + '--port', + (string) $serverPort, + '--identifier', + $processIdentifier, + ]; + + if ($tmpFile !== null && $insteadOfFile !== null) { + $commandOptions[] = '--tmp-file'; + $commandOptions[] = escapeshellarg($tmpFile); + $commandOptions[] = '--instead-of'; + $commandOptions[] = escapeshellarg($insteadOfFile); + } + + $process = new Process(ProcessHelper::getWorkerCommand( + $mainScript, + 'worker', + $projectConfigFile, + $commandOptions, + $input + ), $loop, $this->processTimeout); + $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$dependencies, &$exportedNodes, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier): void { + foreach ($json['errors'] as $jsonError) { + if (is_string($jsonError)) { + $internalErrors[] = sprintf('Internal error: %s', $jsonError); + continue; + } + + $errors[] = Error::decode($jsonError); + } + + /** + * @var string $file + * @var array $fileDependencies + */ + foreach ($json['dependencies'] as $file => $fileDependencies) { + $dependencies[$file] = $fileDependencies; + } + + /** + * @var string $file + * @var array $fileExportedNodes + */ + foreach ($json['exportedNodes'] as $file => $fileExportedNodes) { + if (count($fileExportedNodes) === 0) { + continue; + } + $exportedNodes[$file] = array_map(static function (array $node): ExportedNode { + $class = $node['type']; + + return $class::decode($node['data']); + }, $fileExportedNodes); + } + + if ($postFileCallback !== null) { + $postFileCallback($json['filesCount']); + } + + $internalErrorsCount += $json['internalErrorsCount']; + if ($internalErrorsCount >= $this->internalErrorsCountLimit) { + $reachedInternalErrorsCountLimit = true; + $this->processPool->quitAll(); + } + + if (count($jobs) === 0) { + $this->processPool->tryQuitProcess($processIdentifier); + return; + } + + $job = array_pop($jobs); + $process->request(['action' => 'analyse', 'files' => $job]); + }, $handleError, function ($exitCode, string $output) use (&$internalErrors, &$internalErrorsCount, $processIdentifier): void { + $this->processPool->tryQuitProcess($processIdentifier); + if ($exitCode === 0) { + return; + } + if ($exitCode === null) { + return; + } + + $internalErrors[] = sprintf('Child process error (exit code %d): %s', $exitCode, $output); + $internalErrorsCount++; + }); + $this->processPool->attachProcess($processIdentifier, $process); + } + + $loop->run(); + + if (count($jobs) > 0 && $internalErrorsCount === 0) { + $internalErrors[] = 'Some parallel worker jobs have not finished.'; + $internalErrorsCount++; + } + + return new AnalyserResult( + $errors, + $internalErrors, + $internalErrorsCount === 0 ? $dependencies : null, + $exportedNodes, + $reachedInternalErrorsCountLimit + ); + } } diff --git a/src/Parallel/Process.php b/src/Parallel/Process.php index 8510b92bc8..7b2bacc39a 100644 --- a/src/Parallel/Process.php +++ b/src/Parallel/Process.php @@ -1,4 +1,6 @@ -command = $command; - $this->loop = $loop; - $this->timeoutSeconds = $timeoutSeconds; - } - - /** - * @param callable(mixed[] $json) : void $onData - * @param callable(\Throwable $exception) : void $onError - * @param callable(?int $exitCode, string $output) : void $onExit - */ - public function start(callable $onData, callable $onError, callable $onExit): void - { - $tmpStdOut = tmpfile(); - if ($tmpStdOut === false) { - throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stdout.'); - } - $tmpStdErr = tmpfile(); - if ($tmpStdErr === false) { - throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stderr.'); - } - $this->stdOut = $tmpStdOut; - $this->stdErr = $tmpStdErr; - $this->process = new \React\ChildProcess\Process($this->command, null, null, [ - 1 => $this->stdOut, - 2 => $this->stdErr, - ]); - $this->process->start($this->loop); - $this->onData = $onData; - $this->onError = $onError; - $this->process->on('exit', function ($exitCode) use ($onExit): void { - $this->cancelTimer(); - - $output = ''; - rewind($this->stdOut); - $stdOut = stream_get_contents($this->stdOut); - if (is_string($stdOut)) { - $output .= $stdOut; - } - - rewind($this->stdErr); - $stdErr = stream_get_contents($this->stdErr); - if (is_string($stdErr)) { - $output .= $stdErr; - } - $onExit($exitCode, $output); - fclose($this->stdOut); - fclose($this->stdErr); - }); - } - - private function cancelTimer(): void - { - if ($this->timer === null) { - return; - } - - $this->loop->cancelTimer($this->timer); - $this->timer = null; - } - - /** - * @param mixed[] $data - */ - public function request(array $data): void - { - $this->cancelTimer(); - $this->in->write($data); - $this->timer = $this->loop->addTimer($this->timeoutSeconds, function (): void { - $onError = $this->onError; - $onError(new \Exception(sprintf('Child process timed out after %.1f seconds. Try making it longer with parallel.processTimeout setting.', $this->timeoutSeconds))); - }); - } - - public function quit(): void - { - $this->cancelTimer(); - if (!$this->process->isRunning()) { - return; - } - - foreach ($this->process->pipes as $pipe) { - $pipe->close(); - } - - $this->in->end(); - } - - public function bindConnection(ReadableStreamInterface $out, WritableStreamInterface $in): void - { - $out->on('data', function (array $json): void { - if ($json['action'] !== 'result') { - return; - } - - $onData = $this->onData; - $onData($json['result']); - }); - $this->in = $in; - $out->on('error', function (\Throwable $error): void { - $onError = $this->onError; - $onError($error); - }); - $in->on('error', function (\Throwable $error): void { - $onError = $this->onError; - $onError($error); - }); - } - + private string $command; + + public \React\ChildProcess\Process $process; + + private LoopInterface $loop; + + private float $timeoutSeconds; + + private WritableStreamInterface $in; + + /** @var resource */ + private $stdOut; + + /** @var resource */ + private $stdErr; + + /** @var callable(mixed[] $json) : void */ + private $onData; + + /** @var callable(\Throwable $exception) : void */ + private $onError; + + private ?TimerInterface $timer = null; + + public function __construct( + string $command, + LoopInterface $loop, + float $timeoutSeconds + ) { + $this->command = $command; + $this->loop = $loop; + $this->timeoutSeconds = $timeoutSeconds; + } + + /** + * @param callable(mixed[] $json) : void $onData + * @param callable(\Throwable $exception) : void $onError + * @param callable(?int $exitCode, string $output) : void $onExit + */ + public function start(callable $onData, callable $onError, callable $onExit): void + { + $tmpStdOut = tmpfile(); + if ($tmpStdOut === false) { + throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stdout.'); + } + $tmpStdErr = tmpfile(); + if ($tmpStdErr === false) { + throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stderr.'); + } + $this->stdOut = $tmpStdOut; + $this->stdErr = $tmpStdErr; + $this->process = new \React\ChildProcess\Process($this->command, null, null, [ + 1 => $this->stdOut, + 2 => $this->stdErr, + ]); + $this->process->start($this->loop); + $this->onData = $onData; + $this->onError = $onError; + $this->process->on('exit', function ($exitCode) use ($onExit): void { + $this->cancelTimer(); + + $output = ''; + rewind($this->stdOut); + $stdOut = stream_get_contents($this->stdOut); + if (is_string($stdOut)) { + $output .= $stdOut; + } + + rewind($this->stdErr); + $stdErr = stream_get_contents($this->stdErr); + if (is_string($stdErr)) { + $output .= $stdErr; + } + $onExit($exitCode, $output); + fclose($this->stdOut); + fclose($this->stdErr); + }); + } + + private function cancelTimer(): void + { + if ($this->timer === null) { + return; + } + + $this->loop->cancelTimer($this->timer); + $this->timer = null; + } + + /** + * @param mixed[] $data + */ + public function request(array $data): void + { + $this->cancelTimer(); + $this->in->write($data); + $this->timer = $this->loop->addTimer($this->timeoutSeconds, function (): void { + $onError = $this->onError; + $onError(new \Exception(sprintf('Child process timed out after %.1f seconds. Try making it longer with parallel.processTimeout setting.', $this->timeoutSeconds))); + }); + } + + public function quit(): void + { + $this->cancelTimer(); + if (!$this->process->isRunning()) { + return; + } + + foreach ($this->process->pipes as $pipe) { + $pipe->close(); + } + + $this->in->end(); + } + + public function bindConnection(ReadableStreamInterface $out, WritableStreamInterface $in): void + { + $out->on('data', function (array $json): void { + if ($json['action'] !== 'result') { + return; + } + + $onData = $this->onData; + $onData($json['result']); + }); + $this->in = $in; + $out->on('error', function (\Throwable $error): void { + $onError = $this->onError; + $onError($error); + }); + $in->on('error', function (\Throwable $error): void { + $onError = $this->onError; + $onError($error); + }); + } } diff --git a/src/Parallel/ProcessPool.php b/src/Parallel/ProcessPool.php index a411712cef..34a8e331d6 100644 --- a/src/Parallel/ProcessPool.php +++ b/src/Parallel/ProcessPool.php @@ -1,4 +1,6 @@ - */ - private array $processes = []; - - public function __construct(TcpServer $server) - { - $this->server = $server; - } - - public function getProcess(string $identifier): Process - { - if (!array_key_exists($identifier, $this->processes)) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Process %s not found.', $identifier)); - } - - return $this->processes[$identifier]; - } - - public function attachProcess(string $identifier, Process $process): void - { - $this->processes[$identifier] = $process; - } - - public function tryQuitProcess(string $identifier): void - { - if (!array_key_exists($identifier, $this->processes)) { - return; - } - - $this->quitProcess($identifier); - } - - private function quitProcess(string $identifier): void - { - $process = $this->getProcess($identifier); - $process->quit(); - unset($this->processes[$identifier]); - if (count($this->processes) !== 0) { - return; - } - - $this->server->close(); - } - - public function quitAll(): void - { - foreach (array_keys($this->processes) as $identifier) { - $this->quitProcess($identifier); - } - } - + private TcpServer $server; + + /** @var array */ + private array $processes = []; + + public function __construct(TcpServer $server) + { + $this->server = $server; + } + + public function getProcess(string $identifier): Process + { + if (!array_key_exists($identifier, $this->processes)) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Process %s not found.', $identifier)); + } + + return $this->processes[$identifier]; + } + + public function attachProcess(string $identifier, Process $process): void + { + $this->processes[$identifier] = $process; + } + + public function tryQuitProcess(string $identifier): void + { + if (!array_key_exists($identifier, $this->processes)) { + return; + } + + $this->quitProcess($identifier); + } + + private function quitProcess(string $identifier): void + { + $process = $this->getProcess($identifier); + $process->quit(); + unset($this->processes[$identifier]); + if (count($this->processes) !== 0) { + return; + } + + $this->server->close(); + } + + public function quitAll(): void + { + foreach (array_keys($this->processes) as $identifier) { + $this->quitProcess($identifier); + } + } } diff --git a/src/Parallel/Schedule.php b/src/Parallel/Schedule.php index fef0e12903..63c6378881 100644 --- a/src/Parallel/Schedule.php +++ b/src/Parallel/Schedule.php @@ -1,36 +1,36 @@ -> */ - private array $jobs; - - /** - * @param int $numberOfProcesses - * @param array> $jobs - */ - public function __construct(int $numberOfProcesses, array $jobs) - { - $this->numberOfProcesses = $numberOfProcesses; - $this->jobs = $jobs; - } - - public function getNumberOfProcesses(): int - { - return $this->numberOfProcesses; - } - - /** - * @return array> - */ - public function getJobs(): array - { - return $this->jobs; - } - + private int $numberOfProcesses; + + /** @var array> */ + private array $jobs; + + /** + * @param int $numberOfProcesses + * @param array> $jobs + */ + public function __construct(int $numberOfProcesses, array $jobs) + { + $this->numberOfProcesses = $numberOfProcesses; + $this->jobs = $jobs; + } + + public function getNumberOfProcesses(): int + { + return $this->numberOfProcesses; + } + + /** + * @return array> + */ + public function getJobs(): array + { + return $this->jobs; + } } diff --git a/src/Parallel/Scheduler.php b/src/Parallel/Scheduler.php index 1a3e6126d5..b036e9db37 100644 --- a/src/Parallel/Scheduler.php +++ b/src/Parallel/Scheduler.php @@ -1,44 +1,42 @@ -jobSize = $jobSize; - $this->maximumNumberOfProcesses = $maximumNumberOfProcesses; - $this->minimumNumberOfJobsPerProcess = $minimumNumberOfJobsPerProcess; - } - - /** - * @param int $cpuCores - * @param array $files - * @return Schedule - */ - public function scheduleWork( - int $cpuCores, - array $files - ): Schedule - { - $jobs = array_chunk($files, $this->jobSize); - $numberOfProcesses = min( - max((int) floor(count($jobs) / $this->minimumNumberOfJobsPerProcess), 1), - $cpuCores - ); - - return new Schedule(min($numberOfProcesses, $this->maximumNumberOfProcesses), $jobs); - } - + private int $jobSize; + + private int $maximumNumberOfProcesses; + + private int $minimumNumberOfJobsPerProcess; + + public function __construct( + int $jobSize, + int $maximumNumberOfProcesses, + int $minimumNumberOfJobsPerProcess + ) { + $this->jobSize = $jobSize; + $this->maximumNumberOfProcesses = $maximumNumberOfProcesses; + $this->minimumNumberOfJobsPerProcess = $minimumNumberOfJobsPerProcess; + } + + /** + * @param int $cpuCores + * @param array $files + * @return Schedule + */ + public function scheduleWork( + int $cpuCores, + array $files + ): Schedule { + $jobs = array_chunk($files, $this->jobSize); + $numberOfProcesses = min( + max((int) floor(count($jobs) / $this->minimumNumberOfJobsPerProcess), 1), + $cpuCores + ); + + return new Schedule(min($numberOfProcesses, $this->maximumNumberOfProcesses), $jobs); + } } diff --git a/src/Parser/CachedParser.php b/src/Parser/CachedParser.php index f78bfc60f1..f49e1eb9bf 100644 --- a/src/Parser/CachedParser.php +++ b/src/Parser/CachedParser.php @@ -1,4 +1,6 @@ -*/ - private array $cachedNodesByString = []; - - private int $cachedNodesByStringCount = 0; - - private int $cachedNodesByStringCountMax; - - /** @var array */ - private array $parsedByString = []; - - public function __construct( - Parser $originalParser, - int $cachedNodesByStringCountMax - ) - { - $this->originalParser = $originalParser; - $this->cachedNodesByStringCountMax = $cachedNodesByStringCountMax; - } - - /** - * @param string $file path to a file to parse - * @return \PhpParser\Node\Stmt[] - */ - public function parseFile(string $file): array - { - if ($this->cachedNodesByStringCountMax !== 0 && $this->cachedNodesByStringCount >= $this->cachedNodesByStringCountMax) { - $this->cachedNodesByString = array_slice( - $this->cachedNodesByString, - 1, - null, - true - ); - - --$this->cachedNodesByStringCount; - } - - $sourceCode = FileReader::read($file); - if (!isset($this->cachedNodesByString[$sourceCode]) || isset($this->parsedByString[$sourceCode])) { - $this->cachedNodesByString[$sourceCode] = $this->originalParser->parseFile($file); - $this->cachedNodesByStringCount++; - unset($this->parsedByString[$sourceCode]); - } - - return $this->cachedNodesByString[$sourceCode]; - } - - /** - * @param string $sourceCode - * @return \PhpParser\Node\Stmt[] - */ - public function parseString(string $sourceCode): array - { - if ($this->cachedNodesByStringCountMax !== 0 && $this->cachedNodesByStringCount >= $this->cachedNodesByStringCountMax) { - $this->cachedNodesByString = array_slice( - $this->cachedNodesByString, - 1, - null, - true - ); - - --$this->cachedNodesByStringCount; - } - - if (!isset($this->cachedNodesByString[$sourceCode])) { - $this->cachedNodesByString[$sourceCode] = $this->originalParser->parseString($sourceCode); - $this->cachedNodesByStringCount++; - $this->parsedByString[$sourceCode] = true; - } - - return $this->cachedNodesByString[$sourceCode]; - } - - public function getCachedNodesByStringCount(): int - { - return $this->cachedNodesByStringCount; - } - - public function getCachedNodesByStringCountMax(): int - { - return $this->cachedNodesByStringCountMax; - } - - /** - * @return array - */ - public function getCachedNodesByString(): array - { - return $this->cachedNodesByString; - } - + private \PHPStan\Parser\Parser $originalParser; + + /** @var array*/ + private array $cachedNodesByString = []; + + private int $cachedNodesByStringCount = 0; + + private int $cachedNodesByStringCountMax; + + /** @var array */ + private array $parsedByString = []; + + public function __construct( + Parser $originalParser, + int $cachedNodesByStringCountMax + ) { + $this->originalParser = $originalParser; + $this->cachedNodesByStringCountMax = $cachedNodesByStringCountMax; + } + + /** + * @param string $file path to a file to parse + * @return \PhpParser\Node\Stmt[] + */ + public function parseFile(string $file): array + { + if ($this->cachedNodesByStringCountMax !== 0 && $this->cachedNodesByStringCount >= $this->cachedNodesByStringCountMax) { + $this->cachedNodesByString = array_slice( + $this->cachedNodesByString, + 1, + null, + true + ); + + --$this->cachedNodesByStringCount; + } + + $sourceCode = FileReader::read($file); + if (!isset($this->cachedNodesByString[$sourceCode]) || isset($this->parsedByString[$sourceCode])) { + $this->cachedNodesByString[$sourceCode] = $this->originalParser->parseFile($file); + $this->cachedNodesByStringCount++; + unset($this->parsedByString[$sourceCode]); + } + + return $this->cachedNodesByString[$sourceCode]; + } + + /** + * @param string $sourceCode + * @return \PhpParser\Node\Stmt[] + */ + public function parseString(string $sourceCode): array + { + if ($this->cachedNodesByStringCountMax !== 0 && $this->cachedNodesByStringCount >= $this->cachedNodesByStringCountMax) { + $this->cachedNodesByString = array_slice( + $this->cachedNodesByString, + 1, + null, + true + ); + + --$this->cachedNodesByStringCount; + } + + if (!isset($this->cachedNodesByString[$sourceCode])) { + $this->cachedNodesByString[$sourceCode] = $this->originalParser->parseString($sourceCode); + $this->cachedNodesByStringCount++; + $this->parsedByString[$sourceCode] = true; + } + + return $this->cachedNodesByString[$sourceCode]; + } + + public function getCachedNodesByStringCount(): int + { + return $this->cachedNodesByStringCount; + } + + public function getCachedNodesByStringCountMax(): int + { + return $this->cachedNodesByStringCountMax; + } + + /** + * @return array + */ + public function getCachedNodesByString(): array + { + return $this->cachedNodesByString; + } } diff --git a/src/Parser/FunctionCallStatementFinder.php b/src/Parser/FunctionCallStatementFinder.php index 7336d986d8..862837f1e7 100644 --- a/src/Parser/FunctionCallStatementFinder.php +++ b/src/Parser/FunctionCallStatementFinder.php @@ -1,4 +1,6 @@ -findFunctionCallInStatements($functionNames, $statement); - if ($result !== null) { - return $result; - } - } - - if (!($statement instanceof \PhpParser\Node)) { - continue; - } - - if ($statement instanceof FuncCall && $statement->name instanceof Name) { - if (in_array((string) $statement->name, $functionNames, true)) { - return $statement; - } - } - - $result = $this->findFunctionCallInStatements($functionNames, $statement); - if ($result !== null) { - return $result; - } - } - - return null; - } - + /** + * @param string[] $functionNames + * @param mixed $statements + * @return \PhpParser\Node|null + */ + public function findFunctionCallInStatements(array $functionNames, $statements): ?\PhpParser\Node + { + foreach ($statements as $statement) { + if (is_array($statement)) { + $result = $this->findFunctionCallInStatements($functionNames, $statement); + if ($result !== null) { + return $result; + } + } + + if (!($statement instanceof \PhpParser\Node)) { + continue; + } + + if ($statement instanceof FuncCall && $statement->name instanceof Name) { + if (in_array((string) $statement->name, $functionNames, true)) { + return $statement; + } + } + + $result = $this->findFunctionCallInStatements($functionNames, $statement); + if ($result !== null) { + return $result; + } + } + + return null; + } } diff --git a/src/Parser/LexerFactory.php b/src/Parser/LexerFactory.php index e7b8dae7ab..dd300a411f 100644 --- a/src/Parser/LexerFactory.php +++ b/src/Parser/LexerFactory.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function create(): Lexer - { - $options = ['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]; - if ($this->phpVersion->getVersionId() === PHP_VERSION_ID) { - return new Lexer($options); - } + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } - $options['phpVersion'] = $this->phpVersion->getVersionString(); + public function create(): Lexer + { + $options = ['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]; + if ($this->phpVersion->getVersionId() === PHP_VERSION_ID) { + return new Lexer($options); + } - return new Lexer\Emulative($options); - } + $options['phpVersion'] = $this->phpVersion->getVersionString(); + return new Lexer\Emulative($options); + } } diff --git a/src/Parser/NodeList.php b/src/Parser/NodeList.php index 18b52a456e..462b599c2b 100644 --- a/src/Parser/NodeList.php +++ b/src/Parser/NodeList.php @@ -1,4 +1,6 @@ -node = $node; - $this->next = $next; - } - - public function append(Node $node): self - { - $current = $this; - while ($current->next !== null) { - $current = $current->next; - } - - $new = new self($node); - $current->next = $new; - - return $new; - } - - public function getNode(): Node - { - return $this->node; - } - - public function getNext(): ?self - { - return $this->next; - } - + private Node $node; + + private ?self $next; + + public function __construct(Node $node, ?self $next = null) + { + $this->node = $node; + $this->next = $next; + } + + public function append(Node $node): self + { + $current = $this; + while ($current->next !== null) { + $current = $current->next; + } + + $new = new self($node); + $current->next = $new; + + return $new; + } + + public function getNode(): Node + { + return $this->node; + } + + public function getNext(): ?self + { + return $this->next; + } } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 4ed0100778..fb550e3580 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -1,22 +1,22 @@ -getMessage(); - }, $errors))); - $this->errors = $errors; - $this->parsedFile = $parsedFile; - } - - /** - * @return \PhpParser\Error[] - */ - public function getErrors(): array - { - return $this->errors; - } - - public function getParsedFile(): ?string - { - return $this->parsedFile; - } - + /** @var \PhpParser\Error[] */ + private array $errors; + + private ?string $parsedFile; + + /** + * @param \PhpParser\Error[] $errors + * @param string|null $parsedFile + */ + public function __construct( + array $errors, + ?string $parsedFile + ) { + parent::__construct(implode(', ', array_map(static function (Error $error): string { + return $error->getMessage(); + }, $errors))); + $this->errors = $errors; + $this->parsedFile = $parsedFile; + } + + /** + * @return \PhpParser\Error[] + */ + public function getErrors(): array + { + return $this->errors; + } + + public function getParsedFile(): ?string + { + return $this->parsedFile; + } } diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index 7fa5f6aba3..771dd1d97a 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -1,4 +1,6 @@ -fileHelper = $fileHelper; - $this->currentPhpVersionRichParser = $currentPhpVersionRichParser; - $this->currentPhpVersionSimpleParser = $currentPhpVersionSimpleParser; - $this->php8Parser = $php8Parser; - } - - public function parseFile(string $file): array - { - $file = $this->fileHelper->normalizePath($file, '/'); - if (strpos($file, 'vendor/jetbrains/phpstorm-stubs') !== false) { - return $this->php8Parser->parseFile($file); - } - - return $this->currentPhpVersionRichParser->parseFile($file); - } - - public function parseString(string $sourceCode): array - { - return $this->currentPhpVersionSimpleParser->parseString($sourceCode); - } - + private FileHelper $fileHelper; + + private Parser $currentPhpVersionRichParser; + + private Parser $currentPhpVersionSimpleParser; + + private Parser $php8Parser; + + public function __construct( + FileHelper $fileHelper, + Parser $currentPhpVersionRichParser, + Parser $currentPhpVersionSimpleParser, + Parser $php8Parser + ) { + $this->fileHelper = $fileHelper; + $this->currentPhpVersionRichParser = $currentPhpVersionRichParser; + $this->currentPhpVersionSimpleParser = $currentPhpVersionSimpleParser; + $this->php8Parser = $php8Parser; + } + + public function parseFile(string $file): array + { + $file = $this->fileHelper->normalizePath($file, '/'); + if (strpos($file, 'vendor/jetbrains/phpstorm-stubs') !== false) { + return $this->php8Parser->parseFile($file); + } + + return $this->currentPhpVersionRichParser->parseFile($file); + } + + public function parseString(string $sourceCode): array + { + return $this->currentPhpVersionSimpleParser->parseString($sourceCode); + } } diff --git a/src/Parser/PhpParserDecorator.php b/src/Parser/PhpParserDecorator.php index 6719215d69..8d60137c9e 100644 --- a/src/Parser/PhpParserDecorator.php +++ b/src/Parser/PhpParserDecorator.php @@ -1,4 +1,6 @@ -wrappedParser = $wrappedParser; - } - - /** - * @param string $code - * @param \PhpParser\ErrorHandler|null $errorHandler - * @return \PhpParser\Node\Stmt[] - */ - public function parse(string $code, ?ErrorHandler $errorHandler = null): array - { - try { - return $this->wrappedParser->parseString($code); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - $message = $e->getMessage(); - if ($e->getParsedFile() !== null) { - $message .= sprintf(' in file %s', $e->getParsedFile()); - } - throw new \PhpParser\Error($message); - } - } + public function __construct(\PHPStan\Parser\Parser $wrappedParser) + { + $this->wrappedParser = $wrappedParser; + } + /** + * @param string $code + * @param \PhpParser\ErrorHandler|null $errorHandler + * @return \PhpParser\Node\Stmt[] + */ + public function parse(string $code, ?ErrorHandler $errorHandler = null): array + { + try { + return $this->wrappedParser->parseString($code); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + $message = $e->getMessage(); + if ($e->getParsedFile() !== null) { + $message .= sprintf(' in file %s', $e->getParsedFile()); + } + throw new \PhpParser\Error($message); + } + } } diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index 2a5fc5d56c..7a95f7a3af 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->nameResolver = $nameResolver; - $this->nodeConnectingVisitor = $nodeConnectingVisitor; - $this->statementOrderVisitor = $statementOrderVisitor; - } + private StatementOrderVisitor $statementOrderVisitor; - /** - * @param string $file path to a file to parse - * @return \PhpParser\Node\Stmt[] - */ - public function parseFile(string $file): array - { - try { - return $this->parseString(FileReader::read($file)); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - throw new \PHPStan\Parser\ParserErrorsException($e->getErrors(), $file); - } - } + public function __construct( + \PhpParser\Parser $parser, + NameResolver $nameResolver, + NodeConnectingVisitor $nodeConnectingVisitor, + StatementOrderVisitor $statementOrderVisitor + ) { + $this->parser = $parser; + $this->nameResolver = $nameResolver; + $this->nodeConnectingVisitor = $nodeConnectingVisitor; + $this->statementOrderVisitor = $statementOrderVisitor; + } - /** - * @param string $sourceCode - * @return \PhpParser\Node\Stmt[] - */ - public function parseString(string $sourceCode): array - { - $errorHandler = new Collecting(); - $nodes = $this->parser->parse($sourceCode, $errorHandler); - if ($errorHandler->hasErrors()) { - throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null); - } - if ($nodes === null) { - throw new \PHPStan\ShouldNotHappenException(); - } + /** + * @param string $file path to a file to parse + * @return \PhpParser\Node\Stmt[] + */ + public function parseFile(string $file): array + { + try { + return $this->parseString(FileReader::read($file)); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + throw new \PHPStan\Parser\ParserErrorsException($e->getErrors(), $file); + } + } - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->nameResolver); - $nodeTraverser->addVisitor($this->nodeConnectingVisitor); - $nodeTraverser->addVisitor($this->statementOrderVisitor); + /** + * @param string $sourceCode + * @return \PhpParser\Node\Stmt[] + */ + public function parseString(string $sourceCode): array + { + $errorHandler = new Collecting(); + $nodes = $this->parser->parse($sourceCode, $errorHandler); + if ($errorHandler->hasErrors()) { + throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null); + } + if ($nodes === null) { + throw new \PHPStan\ShouldNotHappenException(); + } - /** @var array<\PhpParser\Node\Stmt> */ - return $nodeTraverser->traverse($nodes); - } + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->nameResolver); + $nodeTraverser->addVisitor($this->nodeConnectingVisitor); + $nodeTraverser->addVisitor($this->statementOrderVisitor); + /** @var array<\PhpParser\Node\Stmt> */ + return $nodeTraverser->traverse($nodes); + } } diff --git a/src/Parser/SimpleParser.php b/src/Parser/SimpleParser.php index f20603457b..de97379879 100644 --- a/src/Parser/SimpleParser.php +++ b/src/Parser/SimpleParser.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->nameResolver = $nameResolver; - } - - /** - * @param string $file path to a file to parse - * @return \PhpParser\Node\Stmt[] - */ - public function parseFile(string $file): array - { - try { - return $this->parseString(FileReader::read($file)); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - throw new \PHPStan\Parser\ParserErrorsException($e->getErrors(), $file); - } - } - - /** - * @param string $sourceCode - * @return \PhpParser\Node\Stmt[] - */ - public function parseString(string $sourceCode): array - { - $errorHandler = new Collecting(); - $nodes = $this->parser->parse($sourceCode, $errorHandler); - if ($errorHandler->hasErrors()) { - throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null); - } - if ($nodes === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->nameResolver); - - /** @var array<\PhpParser\Node\Stmt> */ - return $nodeTraverser->traverse($nodes); - } - + private \PhpParser\Parser $parser; + + private NameResolver $nameResolver; + + public function __construct( + \PhpParser\Parser $parser, + NameResolver $nameResolver + ) { + $this->parser = $parser; + $this->nameResolver = $nameResolver; + } + + /** + * @param string $file path to a file to parse + * @return \PhpParser\Node\Stmt[] + */ + public function parseFile(string $file): array + { + try { + return $this->parseString(FileReader::read($file)); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + throw new \PHPStan\Parser\ParserErrorsException($e->getErrors(), $file); + } + } + + /** + * @param string $sourceCode + * @return \PhpParser\Node\Stmt[] + */ + public function parseString(string $sourceCode): array + { + $errorHandler = new Collecting(); + $nodes = $this->parser->parse($sourceCode, $errorHandler); + if ($errorHandler->hasErrors()) { + throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null); + } + if ($nodes === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->nameResolver); + + /** @var array<\PhpParser\Node\Stmt> */ + return $nodeTraverser->traverse($nodes); + } } diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 3b673835a4..81dbb52444 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -1,104 +1,104 @@ -versionId = $versionId; - } - - public function getVersionId(): int - { - return $this->versionId; - } - - public function getVersionString(): string - { - $first = (int) floor($this->versionId / 10000); - $second = (int) floor(($this->versionId % 10000) / 100); - $third = (int) floor($this->versionId % 100); - - return $first . '.' . $second . ($third !== 0 ? '.' . $third : ''); - } - - public function supportsNullCoalesceAssign(): bool - { - return $this->versionId >= 70400; - } - - public function supportsParameterContravariance(): bool - { - return $this->versionId >= 70400; - } - - public function supportsReturnCovariance(): bool - { - return $this->versionId >= 70400; - } - - public function supportsNativeUnionTypes(): bool - { - return $this->versionId >= 80000; - } - - public function deprecatesRequiredParameterAfterOptional(): bool - { - return $this->versionId >= 80000; - } - - public function supportsLessOverridenParametersWithVariadic(): bool - { - return $this->versionId >= 80000; - } - - public function supportsThrowExpression(): bool - { - return $this->versionId >= 80000; - } - - public function supportsClassConstantOnExpression(): bool - { - return $this->versionId >= 80000; - } - - public function supportsLegacyConstructor(): bool - { - return $this->versionId < 80000; - } - - public function supportsPromotedProperties(): bool - { - return $this->versionId >= 80000; - } - - public function supportsParameterTypeWidening(): bool - { - return $this->versionId >= 70200; - } - - public function supportsUnsetCast(): bool - { - return $this->versionId < 80000; - } - - public function supportsNamedArguments(): bool - { - return $this->versionId >= 80000; - } - - public function throwsTypeErrorForInternalFunctions(): bool - { - return $this->versionId >= 80000; - } - - public function supportsHhPrintfSpecifier(): bool - { - return $this->versionId >= 80000; - } - + private int $versionId; + + public function __construct(int $versionId) + { + $this->versionId = $versionId; + } + + public function getVersionId(): int + { + return $this->versionId; + } + + public function getVersionString(): string + { + $first = (int) floor($this->versionId / 10000); + $second = (int) floor(($this->versionId % 10000) / 100); + $third = (int) floor($this->versionId % 100); + + return $first . '.' . $second . ($third !== 0 ? '.' . $third : ''); + } + + public function supportsNullCoalesceAssign(): bool + { + return $this->versionId >= 70400; + } + + public function supportsParameterContravariance(): bool + { + return $this->versionId >= 70400; + } + + public function supportsReturnCovariance(): bool + { + return $this->versionId >= 70400; + } + + public function supportsNativeUnionTypes(): bool + { + return $this->versionId >= 80000; + } + + public function deprecatesRequiredParameterAfterOptional(): bool + { + return $this->versionId >= 80000; + } + + public function supportsLessOverridenParametersWithVariadic(): bool + { + return $this->versionId >= 80000; + } + + public function supportsThrowExpression(): bool + { + return $this->versionId >= 80000; + } + + public function supportsClassConstantOnExpression(): bool + { + return $this->versionId >= 80000; + } + + public function supportsLegacyConstructor(): bool + { + return $this->versionId < 80000; + } + + public function supportsPromotedProperties(): bool + { + return $this->versionId >= 80000; + } + + public function supportsParameterTypeWidening(): bool + { + return $this->versionId >= 70200; + } + + public function supportsUnsetCast(): bool + { + return $this->versionId < 80000; + } + + public function supportsNamedArguments(): bool + { + return $this->versionId >= 80000; + } + + public function throwsTypeErrorForInternalFunctions(): bool + { + return $this->versionId >= 80000; + } + + public function supportsHhPrintfSpecifier(): bool + { + return $this->versionId >= 80000; + } } diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index 8d726313fd..691d1f4868 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -1,4 +1,6 @@ -versionId = $versionId; - $this->composerPhpVersion = $composerPhpVersion; - } - - public function create(): PhpVersion - { - $versionId = $this->versionId; - if ($versionId === null && $this->composerPhpVersion !== null) { - $parts = explode('.', $this->composerPhpVersion); - $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); - $tmp = max($tmp, 70100); - $versionId = min($tmp, 80099); - } - - if ($versionId === null) { - $versionId = PHP_VERSION_ID; - } - - return new PhpVersion($versionId); - } - + private ?int $versionId; + + private ?string $composerPhpVersion; + + public function __construct( + ?int $versionId, + ?string $composerPhpVersion + ) { + $this->versionId = $versionId; + $this->composerPhpVersion = $composerPhpVersion; + } + + public function create(): PhpVersion + { + $versionId = $this->versionId; + if ($versionId === null && $this->composerPhpVersion !== null) { + $parts = explode('.', $this->composerPhpVersion); + $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); + $tmp = max($tmp, 70100); + $versionId = min($tmp, 80099); + } + + if ($versionId === null) { + $versionId = PHP_VERSION_ID; + } + + return new PhpVersion($versionId); + } } diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index 92fd1aa675..06cff94d5b 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -1,4 +1,6 @@ -versionId = $versionId; - $this->readComposerPhpVersion = $readComposerPhpVersion; - $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } - - public function create(): PhpVersionFactory - { - $composerPhpVersion = null; - if ($this->readComposerPhpVersion && count($this->composerAutoloaderProjectPaths) > 0) { - $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; - if (is_file($composerJsonPath)) { - try { - $composerJsonContents = FileReader::read($composerJsonPath); - $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); - $platformVersion = $composer['config']['platform']['php'] ?? null; - if (is_string($platformVersion)) { - $composerPhpVersion = $platformVersion; - } - } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { - // pass - } - } - } - - return new PhpVersionFactory($this->versionId, $composerPhpVersion); - } - + private ?int $versionId; + + private bool $readComposerPhpVersion; + + /** @var string[] */ + private array $composerAutoloaderProjectPaths; + + /** + * @param bool $readComposerPhpVersion + * @param string[] $composerAutoloaderProjectPaths + */ + public function __construct( + ?int $versionId, + bool $readComposerPhpVersion, + array $composerAutoloaderProjectPaths + ) { + $this->versionId = $versionId; + $this->readComposerPhpVersion = $readComposerPhpVersion; + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + } + + public function create(): PhpVersionFactory + { + $composerPhpVersion = null; + if ($this->readComposerPhpVersion && count($this->composerAutoloaderProjectPaths) > 0) { + $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; + if (is_file($composerJsonPath)) { + try { + $composerJsonContents = FileReader::read($composerJsonPath); + $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); + $platformVersion = $composer['config']['platform']['php'] ?? null; + if (is_string($platformVersion)) { + $composerPhpVersion = $platformVersion; + } + } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { + // pass + } + } + } + + return new PhpVersionFactory($this->versionId, $composerPhpVersion); + } } diff --git a/src/PhpDoc/ConstExprNodeResolver.php b/src/PhpDoc/ConstExprNodeResolver.php index 291a376256..e77ce8fcf4 100644 --- a/src/PhpDoc/ConstExprNodeResolver.php +++ b/src/PhpDoc/ConstExprNodeResolver.php @@ -1,4 +1,6 @@ -resolveArrayNode($node); + } - public function resolve(ConstExprNode $node): Type - { - if ($node instanceof ConstExprArrayNode) { - return $this->resolveArrayNode($node); - } - - if ($node instanceof ConstExprFalseNode) { - return ConstantTypeHelper::getTypeFromValue(false); - } - - if ($node instanceof ConstExprTrueNode) { - return ConstantTypeHelper::getTypeFromValue(true); - } + if ($node instanceof ConstExprFalseNode) { + return ConstantTypeHelper::getTypeFromValue(false); + } - if ($node instanceof ConstExprFloatNode) { - return ConstantTypeHelper::getTypeFromValue((float) $node->value); - } + if ($node instanceof ConstExprTrueNode) { + return ConstantTypeHelper::getTypeFromValue(true); + } - if ($node instanceof ConstExprIntegerNode) { - return ConstantTypeHelper::getTypeFromValue((int) $node->value); - } + if ($node instanceof ConstExprFloatNode) { + return ConstantTypeHelper::getTypeFromValue((float) $node->value); + } - if ($node instanceof ConstExprNullNode) { - return ConstantTypeHelper::getTypeFromValue(null); - } + if ($node instanceof ConstExprIntegerNode) { + return ConstantTypeHelper::getTypeFromValue((int) $node->value); + } - if ($node instanceof ConstExprStringNode) { - return ConstantTypeHelper::getTypeFromValue($node->value); - } + if ($node instanceof ConstExprNullNode) { + return ConstantTypeHelper::getTypeFromValue(null); + } - return new MixedType(); - } + if ($node instanceof ConstExprStringNode) { + return ConstantTypeHelper::getTypeFromValue($node->value); + } - private function resolveArrayNode(ConstExprArrayNode $node): ArrayType - { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($node->items as $item) { - if ($item->key === null) { - $key = null; - } else { - $key = $this->resolve($item->key); - } - $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value)); - } + return new MixedType(); + } - return $arrayBuilder->getArray(); - } + private function resolveArrayNode(ConstExprArrayNode $node): ArrayType + { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($node->items as $item) { + if ($item->key === null) { + $key = null; + } else { + $key = $this->resolve($item->key); + } + $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value)); + } + return $arrayBuilder->getArray(); + } } diff --git a/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php b/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php index f6cf2220ff..5ccfdc3676 100644 --- a/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php +++ b/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php @@ -1,29 +1,29 @@ -container = $container; - } - - public function getRegistry(): TypeNodeResolverExtensionRegistry - { - if ($this->registry === null) { - $this->registry = new TypeNodeResolverExtensionRegistry( - $this->container->getByType(TypeNodeResolver::class), - $this->container->getServicesByTag(TypeNodeResolverExtension::EXTENSION_TAG) - ); - } - - return $this->registry; - } - + private \PHPStan\DependencyInjection\Container $container; + + private ?TypeNodeResolverExtensionRegistry $registry = null; + + public function __construct(\PHPStan\DependencyInjection\Container $container) + { + $this->container = $container; + } + + public function getRegistry(): TypeNodeResolverExtensionRegistry + { + if ($this->registry === null) { + $this->registry = new TypeNodeResolverExtensionRegistry( + $this->container->getByType(TypeNodeResolver::class), + $this->container->getServicesByTag(TypeNodeResolverExtension::EXTENSION_TAG) + ); + } + + return $this->registry; + } } diff --git a/src/PhpDoc/NameScopedPhpDocString.php b/src/PhpDoc/NameScopedPhpDocString.php index 3de6dd2194..89dfba5bc9 100644 --- a/src/PhpDoc/NameScopedPhpDocString.php +++ b/src/PhpDoc/NameScopedPhpDocString.php @@ -1,4 +1,6 @@ -phpDocString = $phpDocString; - $this->nameScope = $nameScope; - } - - public function getPhpDocString(): string - { - return $this->phpDocString; - } - - public function getNameScope(): NameScope - { - return $this->nameScope; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['phpDocString'], - $properties['nameScope'] - ); - } - + private string $phpDocString; + + private \PHPStan\Analyser\NameScope $nameScope; + + public function __construct(string $phpDocString, NameScope $nameScope) + { + $this->phpDocString = $phpDocString; + $this->nameScope = $nameScope; + } + + public function getPhpDocString(): string + { + return $this->phpDocString; + } + + public function getNameScope(): NameScope + { + return $this->nameScope; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['phpDocString'], + $properties['nameScope'] + ); + } } diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 880e8a92b4..6945a56bf2 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -1,4 +1,6 @@ - */ - private array $parameterNameMapping; - - /** @var array */ - private array $parents; - - /** - * @param string $docComment - * @param string $file - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param string|null $trait - * @param bool $explicit - * @param array $parameterNameMapping - * @param array $parents - */ - private function __construct( - string $docComment, - string $file, - ClassReflection $classReflection, - ?string $trait, - bool $explicit, - array $parameterNameMapping, - array $parents - ) - { - $this->docComment = $docComment; - $this->file = $file; - $this->classReflection = $classReflection; - $this->trait = $trait; - $this->explicit = $explicit; - $this->parameterNameMapping = $parameterNameMapping; - $this->parents = $parents; - } - - public function getDocComment(): string - { - return $this->docComment; - } - - public function getFile(): string - { - return $this->file; - } - - public function getClassReflection(): ClassReflection - { - return $this->classReflection; - } - - public function getTrait(): ?string - { - return $this->trait; - } - - public function isExplicit(): bool - { - return $this->explicit; - } - - /** - * @return array - */ - public function getParents(): array - { - return $this->parents; - } - - /** - * @template T - * @param array $array - * @return array - */ - public function transformArrayKeysWithParameterNameMapping(array $array): array - { - $newArray = []; - foreach ($array as $key => $value) { - if (!array_key_exists($key, $this->parameterNameMapping)) { - continue; - } - $newArray[$this->parameterNameMapping[$key]] = $value; - } - - return $newArray; - } - - /** - * @param string|null $docComment - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param string|null $trait - * @param string $propertyName - * @param string $file - * @param bool|null $explicit - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - * @return self - */ - public static function resolvePhpDocBlockForProperty( - ?string $docComment, - ClassReflection $classReflection, - ?string $trait, - string $propertyName, - string $file, - ?bool $explicit, - array $originalPositionalParameterNames, // unused - array $newPositionalParameterNames // unused - ): self - { - return self::resolvePhpDocBlockTree( - $docComment, - $classReflection, - $trait, - $propertyName, - $file, - 'hasNativeProperty', - 'getNativeProperty', - __FUNCTION__, - $explicit, - [], - [] - ); - } - - /** - * @param string|null $docComment - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param string|null $trait - * @param string $methodName - * @param string $file - * @param bool|null $explicit - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - * @return self - */ - public static function resolvePhpDocBlockForMethod( - ?string $docComment, - ClassReflection $classReflection, - ?string $trait, - string $methodName, - string $file, - ?bool $explicit, - array $originalPositionalParameterNames, - array $newPositionalParameterNames - ): self - { - return self::resolvePhpDocBlockTree( - $docComment, - $classReflection, - $trait, - $methodName, - $file, - 'hasNativeMethod', - 'getNativeMethod', - __FUNCTION__, - $explicit, - $originalPositionalParameterNames, - $newPositionalParameterNames - ); - } - - /** - * @param string|null $docComment - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param string|null $trait - * @param string $name - * @param string $file - * @param string $hasMethodName - * @param string $getMethodName - * @param string $resolveMethodName - * @param bool|null $explicit - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - * @return self - */ - private static function resolvePhpDocBlockTree( - ?string $docComment, - ClassReflection $classReflection, - ?string $trait, - string $name, - string $file, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, - ?bool $explicit, - array $originalPositionalParameterNames, - array $newPositionalParameterNames - ): self - { - $docBlocksFromParents = self::resolveParentPhpDocBlocks( - $classReflection, - $name, - $hasMethodName, - $getMethodName, - $resolveMethodName, - $explicit ?? $docComment !== null, - $newPositionalParameterNames - ); - - return new self( - $docComment ?? '/** */', - $file, - $classReflection, - $trait, - $explicit ?? true, - self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), - $docBlocksFromParents - ); - } - - /** - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - * @return array - */ - private static function remapParameterNames( - array $originalPositionalParameterNames, - array $newPositionalParameterNames - ): array - { - $parameterNameMapping = []; - foreach ($originalPositionalParameterNames as $i => $parameterName) { - if (!array_key_exists($i, $newPositionalParameterNames)) { - continue; - } - $parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName; - } - - return $parameterNameMapping; - } - - /** - * @param ClassReflection $classReflection - * @param string $name - * @param string $hasMethodName - * @param string $getMethodName - * @param string $resolveMethodName - * @param bool $explicit - * @param array $positionalParameterNames - * @return array - */ - private static function resolveParentPhpDocBlocks( - ClassReflection $classReflection, - string $name, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, - bool $explicit, - array $positionalParameterNames - ): array - { - $result = []; - $parentReflections = self::getParentReflections($classReflection); - - foreach ($parentReflections as $parentReflection) { - $oneResult = self::resolvePhpDocBlockFromClass( - $parentReflection, - $name, - $hasMethodName, - $getMethodName, - $resolveMethodName, - $explicit, - $positionalParameterNames - ); - - if ($oneResult === null) { // Null if it is private or from a wrong trait. - continue; - } - - $result[] = $oneResult; - } - - return $result; - } - - /** - * @param ClassReflection $classReflection - * @return array - */ - private static function getParentReflections(ClassReflection $classReflection): array - { - $result = []; - - $parent = $classReflection->getParentClass(); - if ($parent !== false) { - $result[] = $parent; - } - - foreach ($classReflection->getInterfaces() as $interface) { - $result[] = $interface; - } - - return $result; - } - - /** - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param string $name - * @param string $hasMethodName - * @param string $getMethodName - * @param string $resolveMethodName - * @param bool $explicit - * @param array $positionalParameterNames - * @return self|null - */ - private static function resolvePhpDocBlockFromClass( - ClassReflection $classReflection, - string $name, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, - bool $explicit, - array $positionalParameterNames - ): ?self - { - if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) { - /** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection $parentReflection */ - $parentReflection = $classReflection->$getMethodName($name); - if ($parentReflection->isPrivate()) { - return null; - } - - if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection) { - $traitReflection = $parentReflection->getDeclaringTrait(); - $positionalMethodParameterNames = []; - } elseif ($parentReflection instanceof MethodReflection) { - $traitReflection = null; - if ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) { - $traitReflection = $parentReflection->getDeclaringTrait(); - } - $methodVariants = $parentReflection->getVariants(); - $positionalMethodParameterNames = []; - $lowercaseMethodName = strtolower($parentReflection->getName()); - if ( - count($methodVariants) === 1 - && $lowercaseMethodName !== '__construct' - && $lowercaseMethodName !== strtolower($parentReflection->getDeclaringClass()->getName()) - ) { - $methodParameters = $methodVariants[0]->getParameters(); - foreach ($methodParameters as $methodParameter) { - $positionalMethodParameterNames[] = $methodParameter->getName(); - } - } else { - $positionalMethodParameterNames = $positionalParameterNames; - } - } else { - $traitReflection = null; - $positionalMethodParameterNames = []; - } - - $trait = $traitReflection !== null - ? $traitReflection->getName() - : null; - - return self::$resolveMethodName( - $parentReflection->getDocComment() ?? '/** */', - $classReflection, - $trait, - $name, - $classReflection->getFileNameWithPhpDocs(), - $explicit, - $positionalParameterNames, - $positionalMethodParameterNames - ); - } - - return null; - } - + private string $docComment; + + private string $file; + + private ClassReflection $classReflection; + + private ?string $trait; + + private bool $explicit; + + /** @var array */ + private array $parameterNameMapping; + + /** @var array */ + private array $parents; + + /** + * @param string $docComment + * @param string $file + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string|null $trait + * @param bool $explicit + * @param array $parameterNameMapping + * @param array $parents + */ + private function __construct( + string $docComment, + string $file, + ClassReflection $classReflection, + ?string $trait, + bool $explicit, + array $parameterNameMapping, + array $parents + ) { + $this->docComment = $docComment; + $this->file = $file; + $this->classReflection = $classReflection; + $this->trait = $trait; + $this->explicit = $explicit; + $this->parameterNameMapping = $parameterNameMapping; + $this->parents = $parents; + } + + public function getDocComment(): string + { + return $this->docComment; + } + + public function getFile(): string + { + return $this->file; + } + + public function getClassReflection(): ClassReflection + { + return $this->classReflection; + } + + public function getTrait(): ?string + { + return $this->trait; + } + + public function isExplicit(): bool + { + return $this->explicit; + } + + /** + * @return array + */ + public function getParents(): array + { + return $this->parents; + } + + /** + * @template T + * @param array $array + * @return array + */ + public function transformArrayKeysWithParameterNameMapping(array $array): array + { + $newArray = []; + foreach ($array as $key => $value) { + if (!array_key_exists($key, $this->parameterNameMapping)) { + continue; + } + $newArray[$this->parameterNameMapping[$key]] = $value; + } + + return $newArray; + } + + /** + * @param string|null $docComment + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string|null $trait + * @param string $propertyName + * @param string $file + * @param bool|null $explicit + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return self + */ + public static function resolvePhpDocBlockForProperty( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, + string $propertyName, + string $file, + ?bool $explicit, + array $originalPositionalParameterNames, // unused + array $newPositionalParameterNames // unused + ): self { + return self::resolvePhpDocBlockTree( + $docComment, + $classReflection, + $trait, + $propertyName, + $file, + 'hasNativeProperty', + 'getNativeProperty', + __FUNCTION__, + $explicit, + [], + [] + ); + } + + /** + * @param string|null $docComment + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string|null $trait + * @param string $methodName + * @param string $file + * @param bool|null $explicit + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return self + */ + public static function resolvePhpDocBlockForMethod( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, + string $methodName, + string $file, + ?bool $explicit, + array $originalPositionalParameterNames, + array $newPositionalParameterNames + ): self { + return self::resolvePhpDocBlockTree( + $docComment, + $classReflection, + $trait, + $methodName, + $file, + 'hasNativeMethod', + 'getNativeMethod', + __FUNCTION__, + $explicit, + $originalPositionalParameterNames, + $newPositionalParameterNames + ); + } + + /** + * @param string|null $docComment + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string|null $trait + * @param string $name + * @param string $file + * @param string $hasMethodName + * @param string $getMethodName + * @param string $resolveMethodName + * @param bool|null $explicit + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return self + */ + private static function resolvePhpDocBlockTree( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, + string $name, + string $file, + string $hasMethodName, + string $getMethodName, + string $resolveMethodName, + ?bool $explicit, + array $originalPositionalParameterNames, + array $newPositionalParameterNames + ): self { + $docBlocksFromParents = self::resolveParentPhpDocBlocks( + $classReflection, + $name, + $hasMethodName, + $getMethodName, + $resolveMethodName, + $explicit ?? $docComment !== null, + $newPositionalParameterNames + ); + + return new self( + $docComment ?? '/** */', + $file, + $classReflection, + $trait, + $explicit ?? true, + self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + $docBlocksFromParents + ); + } + + /** + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return array + */ + private static function remapParameterNames( + array $originalPositionalParameterNames, + array $newPositionalParameterNames + ): array { + $parameterNameMapping = []; + foreach ($originalPositionalParameterNames as $i => $parameterName) { + if (!array_key_exists($i, $newPositionalParameterNames)) { + continue; + } + $parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName; + } + + return $parameterNameMapping; + } + + /** + * @param ClassReflection $classReflection + * @param string $name + * @param string $hasMethodName + * @param string $getMethodName + * @param string $resolveMethodName + * @param bool $explicit + * @param array $positionalParameterNames + * @return array + */ + private static function resolveParentPhpDocBlocks( + ClassReflection $classReflection, + string $name, + string $hasMethodName, + string $getMethodName, + string $resolveMethodName, + bool $explicit, + array $positionalParameterNames + ): array { + $result = []; + $parentReflections = self::getParentReflections($classReflection); + + foreach ($parentReflections as $parentReflection) { + $oneResult = self::resolvePhpDocBlockFromClass( + $parentReflection, + $name, + $hasMethodName, + $getMethodName, + $resolveMethodName, + $explicit, + $positionalParameterNames + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $result[] = $oneResult; + } + + return $result; + } + + /** + * @param ClassReflection $classReflection + * @return array + */ + private static function getParentReflections(ClassReflection $classReflection): array + { + $result = []; + + $parent = $classReflection->getParentClass(); + if ($parent !== false) { + $result[] = $parent; + } + + foreach ($classReflection->getInterfaces() as $interface) { + $result[] = $interface; + } + + return $result; + } + + /** + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string $name + * @param string $hasMethodName + * @param string $getMethodName + * @param string $resolveMethodName + * @param bool $explicit + * @param array $positionalParameterNames + * @return self|null + */ + private static function resolvePhpDocBlockFromClass( + ClassReflection $classReflection, + string $name, + string $hasMethodName, + string $getMethodName, + string $resolveMethodName, + bool $explicit, + array $positionalParameterNames + ): ?self { + if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) { + /** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection $parentReflection */ + $parentReflection = $classReflection->$getMethodName($name); + if ($parentReflection->isPrivate()) { + return null; + } + + if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection) { + $traitReflection = $parentReflection->getDeclaringTrait(); + $positionalMethodParameterNames = []; + } elseif ($parentReflection instanceof MethodReflection) { + $traitReflection = null; + if ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) { + $traitReflection = $parentReflection->getDeclaringTrait(); + } + $methodVariants = $parentReflection->getVariants(); + $positionalMethodParameterNames = []; + $lowercaseMethodName = strtolower($parentReflection->getName()); + if ( + count($methodVariants) === 1 + && $lowercaseMethodName !== '__construct' + && $lowercaseMethodName !== strtolower($parentReflection->getDeclaringClass()->getName()) + ) { + $methodParameters = $methodVariants[0]->getParameters(); + foreach ($methodParameters as $methodParameter) { + $positionalMethodParameterNames[] = $methodParameter->getName(); + } + } else { + $positionalMethodParameterNames = $positionalParameterNames; + } + } else { + $traitReflection = null; + $positionalMethodParameterNames = []; + } + + $trait = $traitReflection !== null + ? $traitReflection->getName() + : null; + + return self::$resolveMethodName( + $parentReflection->getDocComment() ?? '/** */', + $classReflection, + $trait, + $name, + $classReflection->getFileNameWithPhpDocs(), + $explicit, + $positionalParameterNames, + $positionalMethodParameterNames + ); + } + + return null; + } } diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 67c86af318..d646ac43f2 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - } - - public function resolvePhpDocForProperty( - ?string $docComment, - ClassReflection $classReflection, - string $classReflectionFileName, - ?string $declaringTraitName, - string $propertyName - ): ResolvedPhpDocBlock - { - $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForProperty( - $docComment, - $classReflection, - null, - $propertyName, - $classReflectionFileName, - null, - [], - [] - ); + public function __construct( + FileTypeMapper $fileTypeMapper + ) { + $this->fileTypeMapper = $fileTypeMapper; + } - return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null); - } + public function resolvePhpDocForProperty( + ?string $docComment, + ClassReflection $classReflection, + string $classReflectionFileName, + ?string $declaringTraitName, + string $propertyName + ): ResolvedPhpDocBlock { + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForProperty( + $docComment, + $classReflection, + null, + $propertyName, + $classReflectionFileName, + null, + [], + [] + ); - /** - * @param string|null $docComment - * @param string $fileName - * @param ClassReflection $classReflection - * @param string|null $declaringTraitName - * @param string $methodName - * @param array $positionalParameterNames - * @return ResolvedPhpDocBlock - */ - public function resolvePhpDocForMethod( - ?string $docComment, - string $fileName, - ClassReflection $classReflection, - ?string $declaringTraitName, - string $methodName, - array $positionalParameterNames - ): ResolvedPhpDocBlock - { - $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod( - $docComment, - $classReflection, - $declaringTraitName, - $methodName, - $fileName, - null, - $positionalParameterNames, - $positionalParameterNames - ); + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null); + } - return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $phpDocBlock->getTrait(), $methodName); - } + /** + * @param string|null $docComment + * @param string $fileName + * @param ClassReflection $classReflection + * @param string|null $declaringTraitName + * @param string $methodName + * @param array $positionalParameterNames + * @return ResolvedPhpDocBlock + */ + public function resolvePhpDocForMethod( + ?string $docComment, + string $fileName, + ClassReflection $classReflection, + ?string $declaringTraitName, + string $methodName, + array $positionalParameterNames + ): ResolvedPhpDocBlock { + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod( + $docComment, + $classReflection, + $declaringTraitName, + $methodName, + $fileName, + null, + $positionalParameterNames, + $positionalParameterNames + ); - private function docBlockTreeToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName): ResolvedPhpDocBlock - { - $parents = []; - $parentPhpDocBlocks = []; + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $phpDocBlock->getTrait(), $methodName); + } - foreach ($phpDocBlock->getParents() as $parentPhpDocBlock) { - if ( - $parentPhpDocBlock->getClassReflection()->isBuiltin() - && $functionName !== null - && strtolower($functionName) === '__construct' - ) { - continue; - } - $parents[] = $this->docBlockTreeToResolvedDocBlock( - $parentPhpDocBlock, - $parentPhpDocBlock->getTrait(), - $functionName - ); - $parentPhpDocBlocks[] = $parentPhpDocBlock; - } + private function docBlockTreeToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName): ResolvedPhpDocBlock + { + $parents = []; + $parentPhpDocBlocks = []; - $oneResolvedDockBlock = $this->docBlockToResolvedDocBlock($phpDocBlock, $traitName, $functionName); - return $oneResolvedDockBlock->merge($parents, $parentPhpDocBlocks); - } + foreach ($phpDocBlock->getParents() as $parentPhpDocBlock) { + if ( + $parentPhpDocBlock->getClassReflection()->isBuiltin() + && $functionName !== null + && strtolower($functionName) === '__construct' + ) { + continue; + } + $parents[] = $this->docBlockTreeToResolvedDocBlock( + $parentPhpDocBlock, + $parentPhpDocBlock->getTrait(), + $functionName + ); + $parentPhpDocBlocks[] = $parentPhpDocBlock; + } - private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName): ResolvedPhpDocBlock - { - $classReflection = $phpDocBlock->getClassReflection(); + $oneResolvedDockBlock = $this->docBlockToResolvedDocBlock($phpDocBlock, $traitName, $functionName); + return $oneResolvedDockBlock->merge($parents, $parentPhpDocBlocks); + } - return $this->fileTypeMapper->getResolvedPhpDoc( - $phpDocBlock->getFile(), - $classReflection->getName(), - $traitName, - $functionName, - $phpDocBlock->getDocComment() - ); - } + private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName): ResolvedPhpDocBlock + { + $classReflection = $phpDocBlock->getClassReflection(); + return $this->fileTypeMapper->getResolvedPhpDoc( + $phpDocBlock->getFile(), + $classReflection->getName(), + $traitName, + $functionName, + $phpDocBlock->getDocComment() + ); + } } diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index aa188fe918..46d3442419 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -1,4 +1,6 @@ -typeNodeResolver = $typeNodeResolver; - $this->constExprNodeResolver = $constExprNodeResolver; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolveVarTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - $resolvedByTag = []; - foreach (['@var', '@psalm-var', '@phpstan-var'] as $tagName) { - $tagResolved = []; - foreach ($phpDocNode->getVarTagValues($tagName) as $tagValue) { - $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - if ($this->shouldSkipType($tagName, $type)) { - continue; - } - if ($tagValue->variableName !== '') { - $variableName = substr($tagValue->variableName, 1); - $resolved[$variableName] = new VarTag($type); - } else { - $varTag = new VarTag($type); - $tagResolved[] = $varTag; - } - } - - if (count($tagResolved) === 0) { - continue; - } - - $resolvedByTag[] = $tagResolved; - } - - if (count($resolvedByTag) > 0) { - return array_reverse($resolvedByTag)[0]; - } - - return $resolved; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach ($phpDocNode->getPropertyTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - - $resolved[$propertyName] = new PropertyTag( - $propertyType, - true, - true - ); - } - - foreach ($phpDocNode->getPropertyReadTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - - $resolved[$propertyName] = new PropertyTag( - $propertyType, - true, - false - ); - } - - foreach ($phpDocNode->getPropertyWriteTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - - $resolved[$propertyName] = new PropertyTag( - $propertyType, - false, - true - ); - } - - return $resolved; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@method', '@psalm-method', '@phpstan-method'] as $tagName) { - foreach ($phpDocNode->getMethodTagValues($tagName) as $tagValue) { - $parameters = []; - foreach ($tagValue->parameters as $parameterNode) { - $parameterName = substr($parameterNode->parameterName, 1); - $type = $parameterNode->type !== null - ? $this->typeNodeResolver->resolve($parameterNode->type, $nameScope) - : new MixedType(); - if ($parameterNode->defaultValue instanceof ConstExprNullNode) { - $type = TypeCombinator::addNull($type); - } - $defaultValue = null; - if ($parameterNode->defaultValue !== null) { - $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue); - } - - $parameters[$parameterName] = new MethodTagParameter( - $type, - $parameterNode->isReference - ? PassedByReference::createCreatesNewVariable() - : PassedByReference::createNo(), - $parameterNode->isVariadic || $parameterNode->defaultValue !== null, - $parameterNode->isVariadic, - $defaultValue - ); - } - - $resolved[$tagValue->methodName] = new MethodTag( - $tagValue->returnType !== null - ? $this->typeNodeResolver->resolve($tagValue->returnType, $nameScope) - : new MixedType(), - $tagValue->isStatic, - $parameters - ); - } - } - - return $resolved; - } - - /** - * @return array - */ - public function resolveExtendsTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@extends', '@template-extends', '@phpstan-extends'] as $tagName) { - foreach ($phpDocNode->getExtendsTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new ExtendsTag( - $this->typeNodeResolver->resolve($tagValue->type, $nameScope) - ); - } - } - - return $resolved; - } - - /** - * @return array - */ - public function resolveImplementsTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@implements', '@template-implements', '@phpstan-implements'] as $tagName) { - foreach ($phpDocNode->getImplementsTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new ImplementsTag( - $this->typeNodeResolver->resolve($tagValue->type, $nameScope) - ); - } - } - - return $resolved; - } - - /** - * @return array - */ - public function resolveUsesTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@use', '@template-use', '@phpstan-use'] as $tagName) { - foreach ($phpDocNode->getUsesTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new UsesTag( - $this->typeNodeResolver->resolve($tagValue->type, $nameScope) - ); - } - } - - return $resolved; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - $resolvedPrefix = []; - - $prefixPriority = [ - '' => 0, - 'psalm' => 1, - 'phpstan' => 2, - ]; - - foreach ($phpDocNode->getTags() as $phpDocTagNode) { - $valueNode = $phpDocTagNode->value; - if (!$valueNode instanceof TemplateTagValueNode) { - continue; - } - - $tagName = $phpDocTagNode->name; - if (in_array($tagName, ['@template', '@psalm-template', '@phpstan-template'], true)) { - $variance = TemplateTypeVariance::createInvariant(); - } elseif (in_array($tagName, ['@template-covariant', '@psalm-template-covariant', '@phpstan-template-covariant'], true)) { - $variance = TemplateTypeVariance::createCovariant(); - } else { - continue; - } - - if (strpos($tagName, '@psalm-') === 0) { - $prefix = 'psalm'; - } elseif (strpos($tagName, '@phpstan-') === 0) { - $prefix = 'phpstan'; - } else { - $prefix = ''; - } - - if (isset($resolved[$valueNode->name])) { - $setPrefix = $resolvedPrefix[$valueNode->name]; - if ($prefixPriority[$prefix] <= $prefixPriority[$setPrefix]) { - continue; - } - } - - $resolved[$valueNode->name] = new TemplateTag( - $valueNode->name, - $valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(), - $variance - ); - $resolvedPrefix[$valueNode->name] = $prefix; - } - - return $resolved; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolveParamTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@param', '@psalm-param', '@phpstan-param'] as $tagName) { - foreach ($phpDocNode->getParamTagValues($tagName) as $tagValue) { - $parameterName = substr($tagValue->parameterName, 1); - $parameterType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - if ($this->shouldSkipType($tagName, $parameterType)) { - continue; - } - - $resolved[$parameterName] = new ParamTag( - $parameterType, - $tagValue->isVariadic - ); - } - } - - return $resolved; - } - - public function resolveReturnTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\ReturnTag - { - $resolved = null; - - foreach (['@return', '@psalm-return', '@phpstan-return'] as $tagName) { - foreach ($phpDocNode->getReturnTagValues($tagName) as $tagValue) { - $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - if ($this->shouldSkipType($tagName, $type)) { - continue; - } - $resolved = new ReturnTag($type, true); - } - } - - return $resolved; - } - - public function resolveThrowsTags(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\ThrowsTag - { - foreach (['@phpstan-throws', '@throws'] as $tagName) { - $types = []; - - foreach ($phpDocNode->getThrowsTagValues($tagName) as $tagValue) { - $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - if ($this->shouldSkipType($tagName, $type)) { - continue; - } - - $types[] = $type; - } - - if (count($types) > 0) { - return new ThrowsTag(TypeCombinator::union(...$types)); - } - } - - return null; - } - - /** - * @param PhpDocNode $phpDocNode - * @param NameScope $nameScope - * @return array - */ - public function resolveMixinTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - return array_map(function (MixinTagValueNode $mixinTagValueNode) use ($nameScope): MixinTag { - return new MixinTag( - $this->typeNodeResolver->resolve($mixinTagValueNode->type, $nameScope) - ); - }, $phpDocNode->getMixinTagValues()); - } - - /** - * @return array - */ - public function resolveTypeAliasTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@psalm-type', '@phpstan-type'] as $tagName) { - foreach ($phpDocNode->getTypeAliasTagValues($tagName) as $typeAliasTagValue) { - $alias = $typeAliasTagValue->alias; - $typeNode = $typeAliasTagValue->type; - $resolved[$alias] = new TypeAliasTag($alias, $typeNode, $nameScope); - } - } - - return $resolved; - } - - /** - * @return array - */ - public function resolveTypeAliasImportTags(PhpDocNode $phpDocNode, NameScope $nameScope): array - { - $resolved = []; - - foreach (['@psalm-import-type', '@phpstan-import-type'] as $tagName) { - foreach ($phpDocNode->getTypeAliasImportTagValues($tagName) as $typeAliasImportTagValue) { - $importedAlias = $typeAliasImportTagValue->importedAlias; - $importedFrom = $nameScope->resolveStringName($typeAliasImportTagValue->importedFrom->name); - $importedAs = $typeAliasImportTagValue->importedAs; - $resolved[$importedAs ?? $importedAlias] = new TypeAliasImportTag($importedAlias, $importedFrom, $importedAs); - } - } - - return $resolved; - } - - public function resolveDeprecatedTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\DeprecatedTag - { - foreach ($phpDocNode->getDeprecatedTagValues() as $deprecatedTagValue) { - $description = (string) $deprecatedTagValue; - return new DeprecatedTag($description === '' ? null : $description); - } - - return null; - } - - public function resolveIsDeprecated(PhpDocNode $phpDocNode): bool - { - $deprecatedTags = $phpDocNode->getTagsByName('@deprecated'); - - return count($deprecatedTags) > 0; - } - - public function resolveIsInternal(PhpDocNode $phpDocNode): bool - { - $internalTags = $phpDocNode->getTagsByName('@internal'); - - return count($internalTags) > 0; - } - - public function resolveIsFinal(PhpDocNode $phpDocNode): bool - { - $finalTags = $phpDocNode->getTagsByName('@final'); - - return count($finalTags) > 0; - } - - public function resolveIsPure(PhpDocNode $phpDocNode): bool - { - foreach ($phpDocNode->getTags() as $phpDocTagNode) { - if (in_array($phpDocTagNode->name, ['@pure', '@psalm-pure', '@phpstan-pure'], true)) { - return true; - } - } - - return false; - } - - public function resolveIsImpure(PhpDocNode $phpDocNode): bool - { - foreach ($phpDocNode->getTags() as $phpDocTagNode) { - if (in_array($phpDocTagNode->name, ['@impure', '@phpstan-impure'], true)) { - return true; - } - } - - return false; - } - - private function shouldSkipType(string $tagName, Type $type): bool - { - if (strpos($tagName, '@psalm-') !== 0) { - return false; - } - - if ($type instanceof ErrorType) { - return true; - } - - return $type instanceof NeverType && !$type->isExplicit(); - } - + private TypeNodeResolver $typeNodeResolver; + + private ConstExprNodeResolver $constExprNodeResolver; + + public function __construct(TypeNodeResolver $typeNodeResolver, ConstExprNodeResolver $constExprNodeResolver) + { + $this->typeNodeResolver = $typeNodeResolver; + $this->constExprNodeResolver = $constExprNodeResolver; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolveVarTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + $resolvedByTag = []; + foreach (['@var', '@psalm-var', '@phpstan-var'] as $tagName) { + $tagResolved = []; + foreach ($phpDocNode->getVarTagValues($tagName) as $tagValue) { + $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + if ($this->shouldSkipType($tagName, $type)) { + continue; + } + if ($tagValue->variableName !== '') { + $variableName = substr($tagValue->variableName, 1); + $resolved[$variableName] = new VarTag($type); + } else { + $varTag = new VarTag($type); + $tagResolved[] = $varTag; + } + } + + if (count($tagResolved) === 0) { + continue; + } + + $resolvedByTag[] = $tagResolved; + } + + if (count($resolvedByTag) > 0) { + return array_reverse($resolvedByTag)[0]; + } + + return $resolved; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach ($phpDocNode->getPropertyTagValues() as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + + $resolved[$propertyName] = new PropertyTag( + $propertyType, + true, + true + ); + } + + foreach ($phpDocNode->getPropertyReadTagValues() as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + + $resolved[$propertyName] = new PropertyTag( + $propertyType, + true, + false + ); + } + + foreach ($phpDocNode->getPropertyWriteTagValues() as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + + $resolved[$propertyName] = new PropertyTag( + $propertyType, + false, + true + ); + } + + return $resolved; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@method', '@psalm-method', '@phpstan-method'] as $tagName) { + foreach ($phpDocNode->getMethodTagValues($tagName) as $tagValue) { + $parameters = []; + foreach ($tagValue->parameters as $parameterNode) { + $parameterName = substr($parameterNode->parameterName, 1); + $type = $parameterNode->type !== null + ? $this->typeNodeResolver->resolve($parameterNode->type, $nameScope) + : new MixedType(); + if ($parameterNode->defaultValue instanceof ConstExprNullNode) { + $type = TypeCombinator::addNull($type); + } + $defaultValue = null; + if ($parameterNode->defaultValue !== null) { + $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue); + } + + $parameters[$parameterName] = new MethodTagParameter( + $type, + $parameterNode->isReference + ? PassedByReference::createCreatesNewVariable() + : PassedByReference::createNo(), + $parameterNode->isVariadic || $parameterNode->defaultValue !== null, + $parameterNode->isVariadic, + $defaultValue + ); + } + + $resolved[$tagValue->methodName] = new MethodTag( + $tagValue->returnType !== null + ? $this->typeNodeResolver->resolve($tagValue->returnType, $nameScope) + : new MixedType(), + $tagValue->isStatic, + $parameters + ); + } + } + + return $resolved; + } + + /** + * @return array + */ + public function resolveExtendsTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@extends', '@template-extends', '@phpstan-extends'] as $tagName) { + foreach ($phpDocNode->getExtendsTagValues($tagName) as $tagValue) { + $resolved[$tagValue->type->type->name] = new ExtendsTag( + $this->typeNodeResolver->resolve($tagValue->type, $nameScope) + ); + } + } + + return $resolved; + } + + /** + * @return array + */ + public function resolveImplementsTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@implements', '@template-implements', '@phpstan-implements'] as $tagName) { + foreach ($phpDocNode->getImplementsTagValues($tagName) as $tagValue) { + $resolved[$tagValue->type->type->name] = new ImplementsTag( + $this->typeNodeResolver->resolve($tagValue->type, $nameScope) + ); + } + } + + return $resolved; + } + + /** + * @return array + */ + public function resolveUsesTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@use', '@template-use', '@phpstan-use'] as $tagName) { + foreach ($phpDocNode->getUsesTagValues($tagName) as $tagValue) { + $resolved[$tagValue->type->type->name] = new UsesTag( + $this->typeNodeResolver->resolve($tagValue->type, $nameScope) + ); + } + } + + return $resolved; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + $resolvedPrefix = []; + + $prefixPriority = [ + '' => 0, + 'psalm' => 1, + 'phpstan' => 2, + ]; + + foreach ($phpDocNode->getTags() as $phpDocTagNode) { + $valueNode = $phpDocTagNode->value; + if (!$valueNode instanceof TemplateTagValueNode) { + continue; + } + + $tagName = $phpDocTagNode->name; + if (in_array($tagName, ['@template', '@psalm-template', '@phpstan-template'], true)) { + $variance = TemplateTypeVariance::createInvariant(); + } elseif (in_array($tagName, ['@template-covariant', '@psalm-template-covariant', '@phpstan-template-covariant'], true)) { + $variance = TemplateTypeVariance::createCovariant(); + } else { + continue; + } + + if (strpos($tagName, '@psalm-') === 0) { + $prefix = 'psalm'; + } elseif (strpos($tagName, '@phpstan-') === 0) { + $prefix = 'phpstan'; + } else { + $prefix = ''; + } + + if (isset($resolved[$valueNode->name])) { + $setPrefix = $resolvedPrefix[$valueNode->name]; + if ($prefixPriority[$prefix] <= $prefixPriority[$setPrefix]) { + continue; + } + } + + $resolved[$valueNode->name] = new TemplateTag( + $valueNode->name, + $valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(), + $variance + ); + $resolvedPrefix[$valueNode->name] = $prefix; + } + + return $resolved; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolveParamTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@param', '@psalm-param', '@phpstan-param'] as $tagName) { + foreach ($phpDocNode->getParamTagValues($tagName) as $tagValue) { + $parameterName = substr($tagValue->parameterName, 1); + $parameterType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + if ($this->shouldSkipType($tagName, $parameterType)) { + continue; + } + + $resolved[$parameterName] = new ParamTag( + $parameterType, + $tagValue->isVariadic + ); + } + } + + return $resolved; + } + + public function resolveReturnTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\ReturnTag + { + $resolved = null; + + foreach (['@return', '@psalm-return', '@phpstan-return'] as $tagName) { + foreach ($phpDocNode->getReturnTagValues($tagName) as $tagValue) { + $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + if ($this->shouldSkipType($tagName, $type)) { + continue; + } + $resolved = new ReturnTag($type, true); + } + } + + return $resolved; + } + + public function resolveThrowsTags(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\ThrowsTag + { + foreach (['@phpstan-throws', '@throws'] as $tagName) { + $types = []; + + foreach ($phpDocNode->getThrowsTagValues($tagName) as $tagValue) { + $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + if ($this->shouldSkipType($tagName, $type)) { + continue; + } + + $types[] = $type; + } + + if (count($types) > 0) { + return new ThrowsTag(TypeCombinator::union(...$types)); + } + } + + return null; + } + + /** + * @param PhpDocNode $phpDocNode + * @param NameScope $nameScope + * @return array + */ + public function resolveMixinTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + return array_map(function (MixinTagValueNode $mixinTagValueNode) use ($nameScope): MixinTag { + return new MixinTag( + $this->typeNodeResolver->resolve($mixinTagValueNode->type, $nameScope) + ); + }, $phpDocNode->getMixinTagValues()); + } + + /** + * @return array + */ + public function resolveTypeAliasTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@psalm-type', '@phpstan-type'] as $tagName) { + foreach ($phpDocNode->getTypeAliasTagValues($tagName) as $typeAliasTagValue) { + $alias = $typeAliasTagValue->alias; + $typeNode = $typeAliasTagValue->type; + $resolved[$alias] = new TypeAliasTag($alias, $typeNode, $nameScope); + } + } + + return $resolved; + } + + /** + * @return array + */ + public function resolveTypeAliasImportTags(PhpDocNode $phpDocNode, NameScope $nameScope): array + { + $resolved = []; + + foreach (['@psalm-import-type', '@phpstan-import-type'] as $tagName) { + foreach ($phpDocNode->getTypeAliasImportTagValues($tagName) as $typeAliasImportTagValue) { + $importedAlias = $typeAliasImportTagValue->importedAlias; + $importedFrom = $nameScope->resolveStringName($typeAliasImportTagValue->importedFrom->name); + $importedAs = $typeAliasImportTagValue->importedAs; + $resolved[$importedAs ?? $importedAlias] = new TypeAliasImportTag($importedAlias, $importedFrom, $importedAs); + } + } + + return $resolved; + } + + public function resolveDeprecatedTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\DeprecatedTag + { + foreach ($phpDocNode->getDeprecatedTagValues() as $deprecatedTagValue) { + $description = (string) $deprecatedTagValue; + return new DeprecatedTag($description === '' ? null : $description); + } + + return null; + } + + public function resolveIsDeprecated(PhpDocNode $phpDocNode): bool + { + $deprecatedTags = $phpDocNode->getTagsByName('@deprecated'); + + return count($deprecatedTags) > 0; + } + + public function resolveIsInternal(PhpDocNode $phpDocNode): bool + { + $internalTags = $phpDocNode->getTagsByName('@internal'); + + return count($internalTags) > 0; + } + + public function resolveIsFinal(PhpDocNode $phpDocNode): bool + { + $finalTags = $phpDocNode->getTagsByName('@final'); + + return count($finalTags) > 0; + } + + public function resolveIsPure(PhpDocNode $phpDocNode): bool + { + foreach ($phpDocNode->getTags() as $phpDocTagNode) { + if (in_array($phpDocTagNode->name, ['@pure', '@psalm-pure', '@phpstan-pure'], true)) { + return true; + } + } + + return false; + } + + public function resolveIsImpure(PhpDocNode $phpDocNode): bool + { + foreach ($phpDocNode->getTags() as $phpDocTagNode) { + if (in_array($phpDocTagNode->name, ['@impure', '@phpstan-impure'], true)) { + return true; + } + } + + return false; + } + + private function shouldSkipType(string $tagName, Type $type): bool + { + if (strpos($tagName, '@psalm-') !== 0) { + return false; + } + + if ($type instanceof ErrorType) { + return true; + } + + return $type instanceof NeverType && !$type->isExplicit(); + } } diff --git a/src/PhpDoc/PhpDocStringResolver.php b/src/PhpDoc/PhpDocStringResolver.php index b503e5cd13..e2c9bc30f0 100644 --- a/src/PhpDoc/PhpDocStringResolver.php +++ b/src/PhpDoc/PhpDocStringResolver.php @@ -1,4 +1,6 @@ -phpDocLexer = $phpDocLexer; - $this->phpDocParser = $phpDocParser; - } + private PhpDocParser $phpDocParser; - public function resolve(string $phpDocString): PhpDocNode - { - $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); - $phpDocNode = $this->phpDocParser->parse($tokens); - $tokens->consumeTokenType(Lexer::TOKEN_END); // @phpstan-ignore-line + public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser) + { + $this->phpDocLexer = $phpDocLexer; + $this->phpDocParser = $phpDocParser; + } - return $phpDocNode; - } + public function resolve(string $phpDocString): PhpDocNode + { + $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); + $phpDocNode = $this->phpDocParser->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); // @phpstan-ignore-line + return $phpDocNode; + } } diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index d40ce8fa7d..454814ee46 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -1,4 +1,6 @@ - */ - private array $templateTags; + private ?string $filename; - private \PHPStan\PhpDoc\PhpDocNodeResolver $phpDocNodeResolver; + private ?NameScope $nameScope = null; - /** @var array|false */ - private $varTags = false; - - /** @var array|false */ - private $methodTags = false; - - /** @var array|false */ - private $propertyTags = false; - - /** @var array|false */ - private $extendsTags = false; - - /** @var array|false */ - private $implementsTags = false; - - /** @var array|false */ - private $usesTags = false; - - /** @var array|false */ - private $paramTags = false; - - /** @var \PHPStan\PhpDoc\Tag\ReturnTag|false|null */ - private $returnTag = false; - - /** @var \PHPStan\PhpDoc\Tag\ThrowsTag|false|null */ - private $throwsTag = false; - - /** @var array|false */ - private $mixinTags = false; - - /** @var array|false */ - private $typeAliasTags = false; - - /** @var array|false */ - private $typeAliasImportTags = false; - - /** @var \PHPStan\PhpDoc\Tag\DeprecatedTag|false|null */ - private $deprecatedTag = false; - - private ?bool $isDeprecated = null; - - private ?bool $isInternal = null; - - private ?bool $isFinal = null; - - /** @var bool|'notLoaded'|null */ - private $isPure = 'notLoaded'; - - private function __construct() - { - } - - /** - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode $phpDocNode - * @param string $phpDocString - * @param string $filename - * @param \PHPStan\Analyser\NameScope $nameScope - * @param \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap - * @param \PHPStan\PhpDoc\Tag\TemplateTag[] $templateTags - * @param \PHPStan\PhpDoc\PhpDocNodeResolver $phpDocNodeResolver - * @return self - */ - public static function create( - PhpDocNode $phpDocNode, - string $phpDocString, - string $filename, - NameScope $nameScope, - TemplateTypeMap $templateTypeMap, - array $templateTags, - PhpDocNodeResolver $phpDocNodeResolver - ): self - { - // new property also needs to be added to createEmpty() and merge() - $self = new self(); - $self->phpDocNode = $phpDocNode; - $self->phpDocNodes = [$phpDocNode]; - $self->phpDocString = $phpDocString; - $self->filename = $filename; - $self->nameScope = $nameScope; - $self->templateTypeMap = $templateTypeMap; - $self->templateTags = $templateTags; - $self->phpDocNodeResolver = $phpDocNodeResolver; - - return $self; - } - - public static function createEmpty(): self - { - // new property also needs to be added to merge() - $self = new self(); - $self->phpDocString = '/** */'; - $self->phpDocNodes = []; - $self->filename = null; - $self->templateTypeMap = TemplateTypeMap::createEmpty(); - $self->templateTags = []; - $self->varTags = []; - $self->methodTags = []; - $self->propertyTags = []; - $self->extendsTags = []; - $self->implementsTags = []; - $self->usesTags = []; - $self->paramTags = []; - $self->returnTag = null; - $self->throwsTag = null; - $self->mixinTags = []; - $self->typeAliasTags = []; - $self->typeAliasImportTags = []; - $self->deprecatedTag = null; - $self->isDeprecated = false; - $self->isInternal = false; - $self->isFinal = false; - $self->isPure = null; - - return $self; - } - - /** - * @param array $parents - * @param array $parentPhpDocBlocks - * @return self - */ - public function merge(array $parents, array $parentPhpDocBlocks): self - { - // new property also needs to be added to createEmpty() - $result = new self(); - // we will resolve everything on $this here so these properties don't have to be populated - // skip $result->phpDocNode - // skip $result->phpDocString - just for stubs - $phpDocNodes = $this->phpDocNodes; - foreach ($parents as $parent) { - foreach ($parent->phpDocNodes as $phpDocNode) { - $phpDocNodes[] = $phpDocNode; - } - } - $result->phpDocNodes = $phpDocNodes; - $result->filename = $this->filename; - // skip $result->nameScope - $result->templateTypeMap = $this->templateTypeMap; - $result->templateTags = $this->templateTags; - // skip $result->phpDocNodeResolver - $result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks); - $result->methodTags = $this->getMethodTags(); - $result->propertyTags = $this->getPropertyTags(); - $result->extendsTags = $this->getExtendsTags(); - $result->implementsTags = $this->getImplementsTags(); - $result->usesTags = $this->getUsesTags(); - $result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks); - $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $parents, $parentPhpDocBlocks); - $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents); - $result->mixinTags = $this->getMixinTags(); - $result->typeAliasTags = $this->getTypeAliasTags(); - $result->typeAliasImportTags = $this->getTypeAliasImportTags(); - $result->deprecatedTag = $this->getDeprecatedTag(); - $result->isDeprecated = $result->deprecatedTag !== null; - $result->isInternal = $this->isInternal(); - $result->isFinal = $this->isFinal(); - $result->isPure = $this->isPure(); - - return $result; - } - - /** - * @param array $parameterNameMapping - * @return self - */ - public function changeParameterNamesByMapping(array $parameterNameMapping): self - { - $paramTags = $this->getParamTags(); - - $newParamTags = []; - foreach ($paramTags as $key => $paramTag) { - if (!array_key_exists($key, $parameterNameMapping)) { - continue; - } - $newParamTags[$parameterNameMapping[$key]] = $paramTag; - } - - $self = new self(); - $self->phpDocNode = $this->phpDocNode; - $self->phpDocNodes = $this->phpDocNodes; - $self->phpDocString = $this->phpDocString; - $self->filename = $this->filename; - $self->nameScope = $this->nameScope; - $self->templateTypeMap = $this->templateTypeMap; - $self->templateTags = $this->templateTags; - $self->phpDocNodeResolver = $this->phpDocNodeResolver; - $self->varTags = $this->varTags; - $self->methodTags = $this->methodTags; - $self->propertyTags = $this->propertyTags; - $self->extendsTags = $this->extendsTags; - $self->implementsTags = $this->implementsTags; - $self->usesTags = $this->usesTags; - $self->paramTags = $newParamTags; - $self->returnTag = $this->returnTag; - $self->throwsTag = $this->throwsTag; - $self->mixinTags = $this->mixinTags; - $self->typeAliasTags = $this->typeAliasTags; - $self->typeAliasImportTags = $this->typeAliasImportTags; - $self->deprecatedTag = $this->deprecatedTag; - $self->isDeprecated = $this->isDeprecated; - $self->isInternal = $this->isInternal; - $self->isFinal = $this->isFinal; - $self->isPure = $this->isPure; - - return $self; - } - - public function getPhpDocString(): string - { - return $this->phpDocString; - } - - /** - * @return PhpDocNode[] - */ - public function getPhpDocNodes(): array - { - return $this->phpDocNodes; - } - - public function getFilename(): ?string - { - return $this->filename; - } - - private function getNameScope(): NameScope - { - return $this->nameScope; - } - - public function getNullableNameScope(): ?NameScope - { - return $this->nameScope; - } - - /** - * @return array - */ - public function getVarTags(): array - { - if ($this->varTags === false) { - $this->varTags = $this->phpDocNodeResolver->resolveVarTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->varTags; - } - - /** - * @return array - */ - public function getMethodTags(): array - { - if ($this->methodTags === false) { - $this->methodTags = $this->phpDocNodeResolver->resolveMethodTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->methodTags; - } - - /** - * @return array - */ - public function getPropertyTags(): array - { - if ($this->propertyTags === false) { - $this->propertyTags = $this->phpDocNodeResolver->resolvePropertyTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->propertyTags; - } - - /** - * @return array - */ - public function getTemplateTags(): array - { - return $this->templateTags; - } - - /** - * @return array - */ - public function getExtendsTags(): array - { - if ($this->extendsTags === false) { - $this->extendsTags = $this->phpDocNodeResolver->resolveExtendsTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->extendsTags; - } - - /** - * @return array - */ - public function getImplementsTags(): array - { - if ($this->implementsTags === false) { - $this->implementsTags = $this->phpDocNodeResolver->resolveImplementsTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->implementsTags; - } - - /** - * @return array - */ - public function getUsesTags(): array - { - if ($this->usesTags === false) { - $this->usesTags = $this->phpDocNodeResolver->resolveUsesTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->usesTags; - } - - /** - * @return array - */ - public function getParamTags(): array - { - if ($this->paramTags === false) { - $this->paramTags = $this->phpDocNodeResolver->resolveParamTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->paramTags; - } - - public function getReturnTag(): ?\PHPStan\PhpDoc\Tag\ReturnTag - { - if ($this->returnTag === false) { - $this->returnTag = $this->phpDocNodeResolver->resolveReturnTag( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->returnTag; - } - - public function getThrowsTag(): ?\PHPStan\PhpDoc\Tag\ThrowsTag - { - if ($this->throwsTag === false) { - $this->throwsTag = $this->phpDocNodeResolver->resolveThrowsTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->throwsTag; - } - - /** - * @return array - */ - public function getMixinTags(): array - { - if ($this->mixinTags === false) { - $this->mixinTags = $this->phpDocNodeResolver->resolveMixinTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - - return $this->mixinTags; - } - - /** - * @return array - */ - public function getTypeAliasTags(): array - { - if ($this->typeAliasTags === false) { - $this->typeAliasTags = $this->phpDocNodeResolver->resolveTypeAliasTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - - return $this->typeAliasTags; - } - - /** - * @return array - */ - public function getTypeAliasImportTags(): array - { - if ($this->typeAliasImportTags === false) { - $this->typeAliasImportTags = $this->phpDocNodeResolver->resolveTypeAliasImportTags( - $this->phpDocNode, - $this->getNameScope() - ); - } - - return $this->typeAliasImportTags; - } - - public function getDeprecatedTag(): ?\PHPStan\PhpDoc\Tag\DeprecatedTag - { - if ($this->deprecatedTag === false) { - $this->deprecatedTag = $this->phpDocNodeResolver->resolveDeprecatedTag( - $this->phpDocNode, - $this->getNameScope() - ); - } - return $this->deprecatedTag; - } - - public function isDeprecated(): bool - { - if ($this->isDeprecated === null) { - $this->isDeprecated = $this->phpDocNodeResolver->resolveIsDeprecated( - $this->phpDocNode - ); - } - return $this->isDeprecated; - } - - public function isInternal(): bool - { - if ($this->isInternal === null) { - $this->isInternal = $this->phpDocNodeResolver->resolveIsInternal( - $this->phpDocNode - ); - } - return $this->isInternal; - } - - public function isFinal(): bool - { - if ($this->isFinal === null) { - $this->isFinal = $this->phpDocNodeResolver->resolveIsFinal( - $this->phpDocNode - ); - } - return $this->isFinal; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->templateTypeMap; - } - - public function isPure(): ?bool - { - if ($this->isPure === 'notLoaded') { - $pure = $this->phpDocNodeResolver->resolveIsPure( - $this->phpDocNode - ); - if ($pure) { - $this->isPure = true; - return $this->isPure; - } else { - $impure = $this->phpDocNodeResolver->resolveIsImpure( - $this->phpDocNode - ); - if ($impure) { - $this->isPure = false; - return $this->isPure; - } - } - - $this->isPure = null; - } - - return $this->isPure; - } - - /** - * @param array $varTags - * @param array $parents - * @param array $parentPhpDocBlocks - * @return array - */ - private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array - { - // Only allow one var tag per comment. Check the parent if child does not have this tag. - if (count($varTags) > 0) { - return $varTags; - } - - foreach ($parents as $i => $parent) { - $result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]); - if ($result === null) { - continue; - } - - return $result; - } - - return []; - } - - /** - * @param ResolvedPhpDocBlock $parent - * @param PhpDocBlock $phpDocBlock - * @return array|null - */ - private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array - { - foreach ($parent->getVarTags() as $key => $parentVarTag) { - return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock)]; - } - - return null; - } - - /** - * @param array $paramTags - * @param array $parents - * @param array $parentPhpDocBlocks - * @return array - */ - private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array - { - foreach ($parents as $i => $parent) { - $paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]); - } - - return $paramTags; - } - - /** - * @param array $paramTags - * @param ResolvedPhpDocBlock $parent - * @param PhpDocBlock $phpDocBlock - * @return array - */ - private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array - { - $parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags()); - - foreach ($parentParamTags as $name => $parentParamTag) { - if (array_key_exists($name, $paramTags)) { - continue; - } - - $paramTags[$name] = self::resolveTemplateTypeInTag($parentParamTag, $phpDocBlock); - } - - return $paramTags; - } - - /** - * @param ReturnTag|null $returnTag - * @param array $parents - * @param array $parentPhpDocBlocks - * @return ReturnTag|Null - */ - private static function mergeReturnTags(?ReturnTag $returnTag, array $parents, array $parentPhpDocBlocks): ?ReturnTag - { - if ($returnTag !== null) { - return $returnTag; - } - - foreach ($parents as $i => $parent) { - $result = self::mergeOneParentReturnTag($returnTag, $parent, $parentPhpDocBlocks[$i]); - if ($result === null) { - continue; - } - - return $result; - } - - return null; - } - - private static function mergeOneParentReturnTag(?ReturnTag $returnTag, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag - { - $parentReturnTag = $parent->getReturnTag(); - if ($parentReturnTag === null) { - return $returnTag; - } - - $parentType = $parentReturnTag->getType(); - - // Each parent would overwrite the previous one except if it returns a less specific type. - // Do not care for incompatible types as there is a separate rule for that. - if ($returnTag !== null && $parentType->isSuperTypeOf($returnTag->getType())->yes()) { - return null; - } - - return self::resolveTemplateTypeInTag($parentReturnTag->toImplicit(), $phpDocBlock); - } - - /** - * @param array $parents - */ - private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag - { - if ($throwsTag !== null) { - return $throwsTag; - } - foreach ($parents as $parent) { - $result = $parent->getThrowsTag(); - if ($result === null) { - continue; - } - - return $result; - } - - return null; - } - - /** - * @template T of \PHPStan\PhpDoc\Tag\TypedTag - * @param T $tag - * @param PhpDocBlock $phpDocBlock - * @return T - */ - private static function resolveTemplateTypeInTag(TypedTag $tag, PhpDocBlock $phpDocBlock): TypedTag - { - $type = TemplateTypeHelper::resolveTemplateTypes( - $tag->getType(), - $phpDocBlock->getClassReflection()->getActiveTemplateTypeMap() - ); - return $tag->withType($type); - } + private TemplateTypeMap $templateTypeMap; + /** @var array */ + private array $templateTags; + + private \PHPStan\PhpDoc\PhpDocNodeResolver $phpDocNodeResolver; + + /** @var array|false */ + private $varTags = false; + + /** @var array|false */ + private $methodTags = false; + + /** @var array|false */ + private $propertyTags = false; + + /** @var array|false */ + private $extendsTags = false; + + /** @var array|false */ + private $implementsTags = false; + + /** @var array|false */ + private $usesTags = false; + + /** @var array|false */ + private $paramTags = false; + + /** @var \PHPStan\PhpDoc\Tag\ReturnTag|false|null */ + private $returnTag = false; + + /** @var \PHPStan\PhpDoc\Tag\ThrowsTag|false|null */ + private $throwsTag = false; + + /** @var array|false */ + private $mixinTags = false; + + /** @var array|false */ + private $typeAliasTags = false; + + /** @var array|false */ + private $typeAliasImportTags = false; + + /** @var \PHPStan\PhpDoc\Tag\DeprecatedTag|false|null */ + private $deprecatedTag = false; + + private ?bool $isDeprecated = null; + + private ?bool $isInternal = null; + + private ?bool $isFinal = null; + + /** @var bool|'notLoaded'|null */ + private $isPure = 'notLoaded'; + + private function __construct() + { + } + + /** + * @param \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode $phpDocNode + * @param string $phpDocString + * @param string $filename + * @param \PHPStan\Analyser\NameScope $nameScope + * @param \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap + * @param \PHPStan\PhpDoc\Tag\TemplateTag[] $templateTags + * @param \PHPStan\PhpDoc\PhpDocNodeResolver $phpDocNodeResolver + * @return self + */ + public static function create( + PhpDocNode $phpDocNode, + string $phpDocString, + string $filename, + NameScope $nameScope, + TemplateTypeMap $templateTypeMap, + array $templateTags, + PhpDocNodeResolver $phpDocNodeResolver + ): self { + // new property also needs to be added to createEmpty() and merge() + $self = new self(); + $self->phpDocNode = $phpDocNode; + $self->phpDocNodes = [$phpDocNode]; + $self->phpDocString = $phpDocString; + $self->filename = $filename; + $self->nameScope = $nameScope; + $self->templateTypeMap = $templateTypeMap; + $self->templateTags = $templateTags; + $self->phpDocNodeResolver = $phpDocNodeResolver; + + return $self; + } + + public static function createEmpty(): self + { + // new property also needs to be added to merge() + $self = new self(); + $self->phpDocString = '/** */'; + $self->phpDocNodes = []; + $self->filename = null; + $self->templateTypeMap = TemplateTypeMap::createEmpty(); + $self->templateTags = []; + $self->varTags = []; + $self->methodTags = []; + $self->propertyTags = []; + $self->extendsTags = []; + $self->implementsTags = []; + $self->usesTags = []; + $self->paramTags = []; + $self->returnTag = null; + $self->throwsTag = null; + $self->mixinTags = []; + $self->typeAliasTags = []; + $self->typeAliasImportTags = []; + $self->deprecatedTag = null; + $self->isDeprecated = false; + $self->isInternal = false; + $self->isFinal = false; + $self->isPure = null; + + return $self; + } + + /** + * @param array $parents + * @param array $parentPhpDocBlocks + * @return self + */ + public function merge(array $parents, array $parentPhpDocBlocks): self + { + // new property also needs to be added to createEmpty() + $result = new self(); + // we will resolve everything on $this here so these properties don't have to be populated + // skip $result->phpDocNode + // skip $result->phpDocString - just for stubs + $phpDocNodes = $this->phpDocNodes; + foreach ($parents as $parent) { + foreach ($parent->phpDocNodes as $phpDocNode) { + $phpDocNodes[] = $phpDocNode; + } + } + $result->phpDocNodes = $phpDocNodes; + $result->filename = $this->filename; + // skip $result->nameScope + $result->templateTypeMap = $this->templateTypeMap; + $result->templateTags = $this->templateTags; + // skip $result->phpDocNodeResolver + $result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks); + $result->methodTags = $this->getMethodTags(); + $result->propertyTags = $this->getPropertyTags(); + $result->extendsTags = $this->getExtendsTags(); + $result->implementsTags = $this->getImplementsTags(); + $result->usesTags = $this->getUsesTags(); + $result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks); + $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $parents, $parentPhpDocBlocks); + $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents); + $result->mixinTags = $this->getMixinTags(); + $result->typeAliasTags = $this->getTypeAliasTags(); + $result->typeAliasImportTags = $this->getTypeAliasImportTags(); + $result->deprecatedTag = $this->getDeprecatedTag(); + $result->isDeprecated = $result->deprecatedTag !== null; + $result->isInternal = $this->isInternal(); + $result->isFinal = $this->isFinal(); + $result->isPure = $this->isPure(); + + return $result; + } + + /** + * @param array $parameterNameMapping + * @return self + */ + public function changeParameterNamesByMapping(array $parameterNameMapping): self + { + $paramTags = $this->getParamTags(); + + $newParamTags = []; + foreach ($paramTags as $key => $paramTag) { + if (!array_key_exists($key, $parameterNameMapping)) { + continue; + } + $newParamTags[$parameterNameMapping[$key]] = $paramTag; + } + + $self = new self(); + $self->phpDocNode = $this->phpDocNode; + $self->phpDocNodes = $this->phpDocNodes; + $self->phpDocString = $this->phpDocString; + $self->filename = $this->filename; + $self->nameScope = $this->nameScope; + $self->templateTypeMap = $this->templateTypeMap; + $self->templateTags = $this->templateTags; + $self->phpDocNodeResolver = $this->phpDocNodeResolver; + $self->varTags = $this->varTags; + $self->methodTags = $this->methodTags; + $self->propertyTags = $this->propertyTags; + $self->extendsTags = $this->extendsTags; + $self->implementsTags = $this->implementsTags; + $self->usesTags = $this->usesTags; + $self->paramTags = $newParamTags; + $self->returnTag = $this->returnTag; + $self->throwsTag = $this->throwsTag; + $self->mixinTags = $this->mixinTags; + $self->typeAliasTags = $this->typeAliasTags; + $self->typeAliasImportTags = $this->typeAliasImportTags; + $self->deprecatedTag = $this->deprecatedTag; + $self->isDeprecated = $this->isDeprecated; + $self->isInternal = $this->isInternal; + $self->isFinal = $this->isFinal; + $self->isPure = $this->isPure; + + return $self; + } + + public function getPhpDocString(): string + { + return $this->phpDocString; + } + + /** + * @return PhpDocNode[] + */ + public function getPhpDocNodes(): array + { + return $this->phpDocNodes; + } + + public function getFilename(): ?string + { + return $this->filename; + } + + private function getNameScope(): NameScope + { + return $this->nameScope; + } + + public function getNullableNameScope(): ?NameScope + { + return $this->nameScope; + } + + /** + * @return array + */ + public function getVarTags(): array + { + if ($this->varTags === false) { + $this->varTags = $this->phpDocNodeResolver->resolveVarTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->varTags; + } + + /** + * @return array + */ + public function getMethodTags(): array + { + if ($this->methodTags === false) { + $this->methodTags = $this->phpDocNodeResolver->resolveMethodTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->methodTags; + } + + /** + * @return array + */ + public function getPropertyTags(): array + { + if ($this->propertyTags === false) { + $this->propertyTags = $this->phpDocNodeResolver->resolvePropertyTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->propertyTags; + } + + /** + * @return array + */ + public function getTemplateTags(): array + { + return $this->templateTags; + } + + /** + * @return array + */ + public function getExtendsTags(): array + { + if ($this->extendsTags === false) { + $this->extendsTags = $this->phpDocNodeResolver->resolveExtendsTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->extendsTags; + } + + /** + * @return array + */ + public function getImplementsTags(): array + { + if ($this->implementsTags === false) { + $this->implementsTags = $this->phpDocNodeResolver->resolveImplementsTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->implementsTags; + } + + /** + * @return array + */ + public function getUsesTags(): array + { + if ($this->usesTags === false) { + $this->usesTags = $this->phpDocNodeResolver->resolveUsesTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->usesTags; + } + + /** + * @return array + */ + public function getParamTags(): array + { + if ($this->paramTags === false) { + $this->paramTags = $this->phpDocNodeResolver->resolveParamTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->paramTags; + } + + public function getReturnTag(): ?\PHPStan\PhpDoc\Tag\ReturnTag + { + if ($this->returnTag === false) { + $this->returnTag = $this->phpDocNodeResolver->resolveReturnTag( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->returnTag; + } + + public function getThrowsTag(): ?\PHPStan\PhpDoc\Tag\ThrowsTag + { + if ($this->throwsTag === false) { + $this->throwsTag = $this->phpDocNodeResolver->resolveThrowsTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->throwsTag; + } + + /** + * @return array + */ + public function getMixinTags(): array + { + if ($this->mixinTags === false) { + $this->mixinTags = $this->phpDocNodeResolver->resolveMixinTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + + return $this->mixinTags; + } + + /** + * @return array + */ + public function getTypeAliasTags(): array + { + if ($this->typeAliasTags === false) { + $this->typeAliasTags = $this->phpDocNodeResolver->resolveTypeAliasTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + + return $this->typeAliasTags; + } + + /** + * @return array + */ + public function getTypeAliasImportTags(): array + { + if ($this->typeAliasImportTags === false) { + $this->typeAliasImportTags = $this->phpDocNodeResolver->resolveTypeAliasImportTags( + $this->phpDocNode, + $this->getNameScope() + ); + } + + return $this->typeAliasImportTags; + } + + public function getDeprecatedTag(): ?\PHPStan\PhpDoc\Tag\DeprecatedTag + { + if ($this->deprecatedTag === false) { + $this->deprecatedTag = $this->phpDocNodeResolver->resolveDeprecatedTag( + $this->phpDocNode, + $this->getNameScope() + ); + } + return $this->deprecatedTag; + } + + public function isDeprecated(): bool + { + if ($this->isDeprecated === null) { + $this->isDeprecated = $this->phpDocNodeResolver->resolveIsDeprecated( + $this->phpDocNode + ); + } + return $this->isDeprecated; + } + + public function isInternal(): bool + { + if ($this->isInternal === null) { + $this->isInternal = $this->phpDocNodeResolver->resolveIsInternal( + $this->phpDocNode + ); + } + return $this->isInternal; + } + + public function isFinal(): bool + { + if ($this->isFinal === null) { + $this->isFinal = $this->phpDocNodeResolver->resolveIsFinal( + $this->phpDocNode + ); + } + return $this->isFinal; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->templateTypeMap; + } + + public function isPure(): ?bool + { + if ($this->isPure === 'notLoaded') { + $pure = $this->phpDocNodeResolver->resolveIsPure( + $this->phpDocNode + ); + if ($pure) { + $this->isPure = true; + return $this->isPure; + } else { + $impure = $this->phpDocNodeResolver->resolveIsImpure( + $this->phpDocNode + ); + if ($impure) { + $this->isPure = false; + return $this->isPure; + } + } + + $this->isPure = null; + } + + return $this->isPure; + } + + /** + * @param array $varTags + * @param array $parents + * @param array $parentPhpDocBlocks + * @return array + */ + private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array + { + // Only allow one var tag per comment. Check the parent if child does not have this tag. + if (count($varTags) > 0) { + return $varTags; + } + + foreach ($parents as $i => $parent) { + $result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]); + if ($result === null) { + continue; + } + + return $result; + } + + return []; + } + + /** + * @param ResolvedPhpDocBlock $parent + * @param PhpDocBlock $phpDocBlock + * @return array|null + */ + private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array + { + foreach ($parent->getVarTags() as $key => $parentVarTag) { + return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock)]; + } + + return null; + } + + /** + * @param array $paramTags + * @param array $parents + * @param array $parentPhpDocBlocks + * @return array + */ + private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array + { + foreach ($parents as $i => $parent) { + $paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]); + } + + return $paramTags; + } + + /** + * @param array $paramTags + * @param ResolvedPhpDocBlock $parent + * @param PhpDocBlock $phpDocBlock + * @return array + */ + private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array + { + $parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags()); + + foreach ($parentParamTags as $name => $parentParamTag) { + if (array_key_exists($name, $paramTags)) { + continue; + } + + $paramTags[$name] = self::resolveTemplateTypeInTag($parentParamTag, $phpDocBlock); + } + + return $paramTags; + } + + /** + * @param ReturnTag|null $returnTag + * @param array $parents + * @param array $parentPhpDocBlocks + * @return ReturnTag|Null + */ + private static function mergeReturnTags(?ReturnTag $returnTag, array $parents, array $parentPhpDocBlocks): ?ReturnTag + { + if ($returnTag !== null) { + return $returnTag; + } + + foreach ($parents as $i => $parent) { + $result = self::mergeOneParentReturnTag($returnTag, $parent, $parentPhpDocBlocks[$i]); + if ($result === null) { + continue; + } + + return $result; + } + + return null; + } + + private static function mergeOneParentReturnTag(?ReturnTag $returnTag, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag + { + $parentReturnTag = $parent->getReturnTag(); + if ($parentReturnTag === null) { + return $returnTag; + } + + $parentType = $parentReturnTag->getType(); + + // Each parent would overwrite the previous one except if it returns a less specific type. + // Do not care for incompatible types as there is a separate rule for that. + if ($returnTag !== null && $parentType->isSuperTypeOf($returnTag->getType())->yes()) { + return null; + } + + return self::resolveTemplateTypeInTag($parentReturnTag->toImplicit(), $phpDocBlock); + } + + /** + * @param array $parents + */ + private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag + { + if ($throwsTag !== null) { + return $throwsTag; + } + foreach ($parents as $parent) { + $result = $parent->getThrowsTag(); + if ($result === null) { + continue; + } + + return $result; + } + + return null; + } + + /** + * @template T of \PHPStan\PhpDoc\Tag\TypedTag + * @param T $tag + * @param PhpDocBlock $phpDocBlock + * @return T + */ + private static function resolveTemplateTypeInTag(TypedTag $tag, PhpDocBlock $phpDocBlock): TypedTag + { + $type = TemplateTypeHelper::resolveTemplateTypes( + $tag->getType(), + $phpDocBlock->getClassReflection()->getActiveTemplateTypeMap() + ); + return $tag->withType($type); + } } diff --git a/src/PhpDoc/StubPhpDocProvider.php b/src/PhpDoc/StubPhpDocProvider.php index e11e0bb234..5e34e86a92 100644 --- a/src/PhpDoc/StubPhpDocProvider.php +++ b/src/PhpDoc/StubPhpDocProvider.php @@ -1,4 +1,6 @@ - */ - private array $classMap = []; - - /** @var array> */ - private array $propertyMap = []; - - /** @var array> */ - private array $methodMap = []; - - /** @var array */ - private array $functionMap = []; - - private bool $initialized = false; - - private bool $initializing = false; - - /** @var array */ - private array $knownClassesDocComments = []; - - /** @var array */ - private array $knownFunctionsDocComments = []; - - /** @var array> */ - private array $knownPropertiesDocComments = []; - - /** @var array> */ - private array $knownMethodsDocComments = []; - - /** @var array>> */ - private array $knownMethodsParameterNames = []; - - /** - * @param \PHPStan\Parser\Parser $parser - * @param string[] $stubFiles - */ - public function __construct( - Parser $parser, - FileTypeMapper $fileTypeMapper, - array $stubFiles - ) - { - $this->parser = $parser; - $this->fileTypeMapper = $fileTypeMapper; - $this->stubFiles = $stubFiles; - } - - public function findClassPhpDoc(string $className): ?ResolvedPhpDocBlock - { - if (!$this->isKnownClass($className)) { - return null; - } - - if (array_key_exists($className, $this->classMap)) { - return $this->classMap[$className]; - } - - if (array_key_exists($className, $this->knownClassesDocComments)) { - [$file, $docComment] = $this->knownClassesDocComments[$className]; - $this->classMap[$className] = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $className, - null, - null, - $docComment - ); - - return $this->classMap[$className]; - } - - return null; - } - - public function findPropertyPhpDoc(string $className, string $propertyName): ?ResolvedPhpDocBlock - { - if (!$this->isKnownClass($className)) { - return null; - } - - if (array_key_exists($propertyName, $this->propertyMap[$className])) { - return $this->propertyMap[$className][$propertyName]; - } - - if (array_key_exists($propertyName, $this->knownPropertiesDocComments[$className])) { - [$file, $docComment] = $this->knownPropertiesDocComments[$className][$propertyName]; - $this->propertyMap[$className][$propertyName] = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $className, - null, - null, - $docComment - ); - - return $this->propertyMap[$className][$propertyName]; - } - - return null; - } - - /** - * @param string $className - * @param string $methodName - * @param array $positionalParameterNames - * @return \PHPStan\PhpDoc\ResolvedPhpDocBlock|null - */ - public function findMethodPhpDoc(string $className, string $methodName, array $positionalParameterNames): ?ResolvedPhpDocBlock - { - if (!$this->isKnownClass($className)) { - return null; - } - - if (array_key_exists($methodName, $this->methodMap[$className])) { - return $this->methodMap[$className][$methodName]; - } - - if (array_key_exists($methodName, $this->knownMethodsDocComments[$className])) { - [$file, $docComment] = $this->knownMethodsDocComments[$className][$methodName]; - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $className, - null, - $methodName, - $docComment - ); - - if (!isset($this->knownMethodsParameterNames[$className][$methodName])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $methodParameterNames = $this->knownMethodsParameterNames[$className][$methodName]; - $parameterNameMapping = []; - foreach ($positionalParameterNames as $i => $parameterName) { - if (!array_key_exists($i, $methodParameterNames)) { - continue; - } - $parameterNameMapping[$methodParameterNames[$i]] = $parameterName; - } - - return $resolvedPhpDoc->changeParameterNamesByMapping($parameterNameMapping); - } - - return null; - } - - public function findFunctionPhpDoc(string $functionName): ?ResolvedPhpDocBlock - { - if (!$this->isKnownFunction($functionName)) { - return null; - } - - if (array_key_exists($functionName, $this->functionMap)) { - return $this->functionMap[$functionName]; - } - - if (array_key_exists($functionName, $this->knownFunctionsDocComments)) { - [$file, $docComment] = $this->knownFunctionsDocComments[$functionName]; - $this->functionMap[$functionName] = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - null, - null, - $functionName, - $docComment - ); - - return $this->functionMap[$functionName]; - } - - return null; - } - - public function isKnownClass(string $className): bool - { - $this->initializeKnownElements(); - - if (array_key_exists($className, $this->classMap)) { - return true; - } - - return array_key_exists($className, $this->knownClassesDocComments); - } - - private function isKnownFunction(string $functionName): bool - { - $this->initializeKnownElements(); - - if (array_key_exists($functionName, $this->functionMap)) { - return true; - } - - return array_key_exists($functionName, $this->knownFunctionsDocComments); - } - - private function initializeKnownElements(): void - { - if ($this->initializing) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($this->initialized) { - return; - } - - $this->initializing = true; - - try { - foreach ($this->stubFiles as $stubFile) { - $nodes = $this->parser->parseFile($stubFile); - foreach ($nodes as $node) { - $this->initializeKnownElementNode($stubFile, $node); - } - } - } finally { - $this->initializing = false; - $this->initialized = true; - } - } - - private function initializeKnownElementNode(string $stubFile, Node $node): void - { - if ($node instanceof Node\Stmt\Namespace_) { - foreach ($node->stmts as $stmt) { - $this->initializeKnownElementNode($stubFile, $stmt); - } - return; - } - - if ($node instanceof Node\Stmt\Function_) { - $functionName = (string) $node->namespacedName; - $docComment = $node->getDocComment(); - if ($docComment === null) { - $this->functionMap[$functionName] = null; - return; - } - $this->knownFunctionsDocComments[$functionName] = [$stubFile, $docComment->getText()]; - return; - } - - if (!$node instanceof Class_ && !$node instanceof Interface_ && !$node instanceof Trait_) { - return; - } - - if (!isset($node->namespacedName)) { - return; - } - - $className = (string) $node->namespacedName; - $docComment = $node->getDocComment(); - if ($docComment === null) { - $this->classMap[$className] = null; - } else { - $this->knownClassesDocComments[$className] = [$stubFile, $docComment->getText()]; - } - - $this->methodMap[$className] = []; - $this->propertyMap[$className] = []; - $this->knownPropertiesDocComments[$className] = []; - $this->knownMethodsDocComments[$className] = []; - - foreach ($node->stmts as $stmt) { - $docComment = $stmt->getDocComment(); - if ($stmt instanceof Node\Stmt\Property) { - foreach ($stmt->props as $property) { - if ($docComment === null) { - $this->propertyMap[$className][$property->name->toString()] = null; - continue; - } - $this->knownPropertiesDocComments[$className][$property->name->toString()] = [$stubFile, $docComment->getText()]; - } - } elseif ($stmt instanceof Node\Stmt\ClassMethod) { - if ($docComment === null) { - $this->methodMap[$className][$stmt->name->toString()] = null; - continue; - } - - $methodName = $stmt->name->toString(); - $this->knownMethodsDocComments[$className][$methodName] = [$stubFile, $docComment->getText()]; - $this->knownMethodsParameterNames[$className][$methodName] = array_map(static function (Node\Param $param): string { - if (!$param->var instanceof Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $param->var->name; - }, $stmt->getParams()); - } - } - } - + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + + /** @var string[] */ + private array $stubFiles; + + /** @var array */ + private array $classMap = []; + + /** @var array> */ + private array $propertyMap = []; + + /** @var array> */ + private array $methodMap = []; + + /** @var array */ + private array $functionMap = []; + + private bool $initialized = false; + + private bool $initializing = false; + + /** @var array */ + private array $knownClassesDocComments = []; + + /** @var array */ + private array $knownFunctionsDocComments = []; + + /** @var array> */ + private array $knownPropertiesDocComments = []; + + /** @var array> */ + private array $knownMethodsDocComments = []; + + /** @var array>> */ + private array $knownMethodsParameterNames = []; + + /** + * @param \PHPStan\Parser\Parser $parser + * @param string[] $stubFiles + */ + public function __construct( + Parser $parser, + FileTypeMapper $fileTypeMapper, + array $stubFiles + ) { + $this->parser = $parser; + $this->fileTypeMapper = $fileTypeMapper; + $this->stubFiles = $stubFiles; + } + + public function findClassPhpDoc(string $className): ?ResolvedPhpDocBlock + { + if (!$this->isKnownClass($className)) { + return null; + } + + if (array_key_exists($className, $this->classMap)) { + return $this->classMap[$className]; + } + + if (array_key_exists($className, $this->knownClassesDocComments)) { + [$file, $docComment] = $this->knownClassesDocComments[$className]; + $this->classMap[$className] = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $className, + null, + null, + $docComment + ); + + return $this->classMap[$className]; + } + + return null; + } + + public function findPropertyPhpDoc(string $className, string $propertyName): ?ResolvedPhpDocBlock + { + if (!$this->isKnownClass($className)) { + return null; + } + + if (array_key_exists($propertyName, $this->propertyMap[$className])) { + return $this->propertyMap[$className][$propertyName]; + } + + if (array_key_exists($propertyName, $this->knownPropertiesDocComments[$className])) { + [$file, $docComment] = $this->knownPropertiesDocComments[$className][$propertyName]; + $this->propertyMap[$className][$propertyName] = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $className, + null, + null, + $docComment + ); + + return $this->propertyMap[$className][$propertyName]; + } + + return null; + } + + /** + * @param string $className + * @param string $methodName + * @param array $positionalParameterNames + * @return \PHPStan\PhpDoc\ResolvedPhpDocBlock|null + */ + public function findMethodPhpDoc(string $className, string $methodName, array $positionalParameterNames): ?ResolvedPhpDocBlock + { + if (!$this->isKnownClass($className)) { + return null; + } + + if (array_key_exists($methodName, $this->methodMap[$className])) { + return $this->methodMap[$className][$methodName]; + } + + if (array_key_exists($methodName, $this->knownMethodsDocComments[$className])) { + [$file, $docComment] = $this->knownMethodsDocComments[$className][$methodName]; + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $className, + null, + $methodName, + $docComment + ); + + if (!isset($this->knownMethodsParameterNames[$className][$methodName])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $methodParameterNames = $this->knownMethodsParameterNames[$className][$methodName]; + $parameterNameMapping = []; + foreach ($positionalParameterNames as $i => $parameterName) { + if (!array_key_exists($i, $methodParameterNames)) { + continue; + } + $parameterNameMapping[$methodParameterNames[$i]] = $parameterName; + } + + return $resolvedPhpDoc->changeParameterNamesByMapping($parameterNameMapping); + } + + return null; + } + + public function findFunctionPhpDoc(string $functionName): ?ResolvedPhpDocBlock + { + if (!$this->isKnownFunction($functionName)) { + return null; + } + + if (array_key_exists($functionName, $this->functionMap)) { + return $this->functionMap[$functionName]; + } + + if (array_key_exists($functionName, $this->knownFunctionsDocComments)) { + [$file, $docComment] = $this->knownFunctionsDocComments[$functionName]; + $this->functionMap[$functionName] = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + null, + null, + $functionName, + $docComment + ); + + return $this->functionMap[$functionName]; + } + + return null; + } + + public function isKnownClass(string $className): bool + { + $this->initializeKnownElements(); + + if (array_key_exists($className, $this->classMap)) { + return true; + } + + return array_key_exists($className, $this->knownClassesDocComments); + } + + private function isKnownFunction(string $functionName): bool + { + $this->initializeKnownElements(); + + if (array_key_exists($functionName, $this->functionMap)) { + return true; + } + + return array_key_exists($functionName, $this->knownFunctionsDocComments); + } + + private function initializeKnownElements(): void + { + if ($this->initializing) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($this->initialized) { + return; + } + + $this->initializing = true; + + try { + foreach ($this->stubFiles as $stubFile) { + $nodes = $this->parser->parseFile($stubFile); + foreach ($nodes as $node) { + $this->initializeKnownElementNode($stubFile, $node); + } + } + } finally { + $this->initializing = false; + $this->initialized = true; + } + } + + private function initializeKnownElementNode(string $stubFile, Node $node): void + { + if ($node instanceof Node\Stmt\Namespace_) { + foreach ($node->stmts as $stmt) { + $this->initializeKnownElementNode($stubFile, $stmt); + } + return; + } + + if ($node instanceof Node\Stmt\Function_) { + $functionName = (string) $node->namespacedName; + $docComment = $node->getDocComment(); + if ($docComment === null) { + $this->functionMap[$functionName] = null; + return; + } + $this->knownFunctionsDocComments[$functionName] = [$stubFile, $docComment->getText()]; + return; + } + + if (!$node instanceof Class_ && !$node instanceof Interface_ && !$node instanceof Trait_) { + return; + } + + if (!isset($node->namespacedName)) { + return; + } + + $className = (string) $node->namespacedName; + $docComment = $node->getDocComment(); + if ($docComment === null) { + $this->classMap[$className] = null; + } else { + $this->knownClassesDocComments[$className] = [$stubFile, $docComment->getText()]; + } + + $this->methodMap[$className] = []; + $this->propertyMap[$className] = []; + $this->knownPropertiesDocComments[$className] = []; + $this->knownMethodsDocComments[$className] = []; + + foreach ($node->stmts as $stmt) { + $docComment = $stmt->getDocComment(); + if ($stmt instanceof Node\Stmt\Property) { + foreach ($stmt->props as $property) { + if ($docComment === null) { + $this->propertyMap[$className][$property->name->toString()] = null; + continue; + } + $this->knownPropertiesDocComments[$className][$property->name->toString()] = [$stubFile, $docComment->getText()]; + } + } elseif ($stmt instanceof Node\Stmt\ClassMethod) { + if ($docComment === null) { + $this->methodMap[$className][$stmt->name->toString()] = null; + continue; + } + + $methodName = $stmt->name->toString(); + $this->knownMethodsDocComments[$className][$methodName] = [$stubFile, $docComment->getText()]; + $this->knownMethodsParameterNames[$className][$methodName] = array_map(static function (Node\Param $param): string { + if (!$param->var instanceof Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $param->var->name; + }, $stmt->getParams()); + } + } + } } diff --git a/src/PhpDoc/StubSourceLocatorFactory.php b/src/PhpDoc/StubSourceLocatorFactory.php index a3a1215c34..5715a0c11c 100644 --- a/src/PhpDoc/StubSourceLocatorFactory.php +++ b/src/PhpDoc/StubSourceLocatorFactory.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; - $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; - $this->container = $container; - $this->stubFiles = $stubFiles; - } - - public function create(): SourceLocator - { - $locators = []; - $astLocator = new Locator($this->parser, function (): FunctionReflector { - return $this->container->getService('stubFunctionReflector'); - }); - foreach ($this->stubFiles as $stubFile) { - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($stubFile); - } - - $locators[] = new PhpInternalSourceLocator($astLocator, $this->phpStormStubsSourceStubber); - - return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); - } - + private \PhpParser\Parser $parser; + + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; + + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository; + + private \PHPStan\DependencyInjection\Container $container; + + /** @var string[] */ + private array $stubFiles; + + /** + * @param string[] $stubFiles + */ + public function __construct( + \PhpParser\Parser $parser, + PhpStormStubsSourceStubber $phpStormStubsSourceStubber, + OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, + Container $container, + array $stubFiles + ) { + $this->parser = $parser; + $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; + $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; + $this->container = $container; + $this->stubFiles = $stubFiles; + } + + public function create(): SourceLocator + { + $locators = []; + $astLocator = new Locator($this->parser, function (): FunctionReflector { + return $this->container->getService('stubFunctionReflector'); + }); + foreach ($this->stubFiles as $stubFile) { + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($stubFile); + } + + $locators[] = new PhpInternalSourceLocator($astLocator, $this->phpStormStubsSourceStubber); + + return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); + } } diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 5cbb04f9b1..3216dc6579 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -1,4 +1,6 @@ -derivativeContainerFactory = $derivativeContainerFactory; - } - - /** - * @param string[] $stubFiles - * @return \PHPStan\Analyser\Error[] - */ - public function validate(array $stubFiles, bool $debug): array - { - if (count($stubFiles) === 0) { - return []; - } - - $originalBroker = Broker::getInstance(); - $container = $this->derivativeContainerFactory->create([ - __DIR__ . '/../../conf/config.stubValidator.neon', - ]); - - $ruleRegistry = $this->getRuleRegistry($container); - - /** @var FileAnalyser $fileAnalyser */ - $fileAnalyser = $container->getByType(FileAnalyser::class); - - /** @var NodeScopeResolver $nodeScopeResolver */ - $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); - $nodeScopeResolver->setAnalysedFiles($stubFiles); - - $analysedFiles = array_fill_keys($stubFiles, true); - - $errors = []; - foreach ($stubFiles as $stubFile) { - try { - $tmpErrors = $fileAnalyser->analyseFile( - $stubFile, - $analysedFiles, - $ruleRegistry, - static function (): void { - } - )->getErrors(); - foreach ($tmpErrors as $tmpError) { - $errors[] = $tmpError->withoutTip(); - } - } catch (\Throwable $e) { - if ($debug) { - throw $e; - } - - $internalErrorMessage = sprintf('Internal error: %s', $e->getMessage()); - $errors[] = new Error($internalErrorMessage, $stubFile, null, $e); - } - } - - Broker::registerInstance($originalBroker); - - return $errors; - } - - private function getRuleRegistry(Container $container): Registry - { - $fileTypeMapper = $container->getByType(FileTypeMapper::class); - $genericObjectTypeCheck = $container->getByType(GenericObjectTypeCheck::class); - $genericAncestorsCheck = $container->getByType(GenericAncestorsCheck::class); - $templateTypeCheck = $container->getByType(TemplateTypeCheck::class); - $varianceCheck = $container->getByType(VarianceCheck::class); - $reflectionProvider = $container->getByType(ReflectionProvider::class); - $classCaseSensitivityCheck = $container->getByType(ClassCaseSensitivityCheck::class); - $functionDefinitionCheck = $container->getByType(FunctionDefinitionCheck::class); - $missingTypehintCheck = $container->getByType(MissingTypehintCheck::class); - - return new Registry([ - // level 0 - new ExistingClassesInClassImplementsRule($classCaseSensitivityCheck, $reflectionProvider), - new ExistingClassesInInterfaceExtendsRule($classCaseSensitivityCheck, $reflectionProvider), - new ExistingClassInClassExtendsRule($classCaseSensitivityCheck, $reflectionProvider), - new ExistingClassInTraitUseRule($classCaseSensitivityCheck, $reflectionProvider), - new ExistingClassesInTypehintsRule($functionDefinitionCheck), - new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), - new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, true, false), - - // level 2 - new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck), - new ClassTemplateTypeRule($templateTypeCheck), - new FunctionTemplateTypeRule($fileTypeMapper, $templateTypeCheck), - new FunctionSignatureVarianceRule($varianceCheck), - new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck), - new InterfaceTemplateTypeRule($fileTypeMapper, $templateTypeCheck), - new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck), - new MethodSignatureVarianceRule($varianceCheck), - new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck), - new IncompatiblePhpDocTypeRule( - $fileTypeMapper, - $genericObjectTypeCheck - ), - new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck), - new InvalidPhpDocTagValueRule( - $container->getByType(Lexer::class), - $container->getByType(PhpDocParser::class) - ), - new InvalidThrowsPhpDocValueRule($fileTypeMapper), - - // level 6 - new MissingFunctionParameterTypehintRule($missingTypehintCheck), - new MissingFunctionReturnTypehintRule($missingTypehintCheck), - new MissingMethodParameterTypehintRule($missingTypehintCheck), - new MissingMethodReturnTypehintRule($missingTypehintCheck), - new MissingPropertyTypehintRule($missingTypehintCheck), - ]); - } - + private \PHPStan\DependencyInjection\DerivativeContainerFactory $derivativeContainerFactory; + + public function __construct( + DerivativeContainerFactory $derivativeContainerFactory + ) { + $this->derivativeContainerFactory = $derivativeContainerFactory; + } + + /** + * @param string[] $stubFiles + * @return \PHPStan\Analyser\Error[] + */ + public function validate(array $stubFiles, bool $debug): array + { + if (count($stubFiles) === 0) { + return []; + } + + $originalBroker = Broker::getInstance(); + $container = $this->derivativeContainerFactory->create([ + __DIR__ . '/../../conf/config.stubValidator.neon', + ]); + + $ruleRegistry = $this->getRuleRegistry($container); + + /** @var FileAnalyser $fileAnalyser */ + $fileAnalyser = $container->getByType(FileAnalyser::class); + + /** @var NodeScopeResolver $nodeScopeResolver */ + $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); + $nodeScopeResolver->setAnalysedFiles($stubFiles); + + $analysedFiles = array_fill_keys($stubFiles, true); + + $errors = []; + foreach ($stubFiles as $stubFile) { + try { + $tmpErrors = $fileAnalyser->analyseFile( + $stubFile, + $analysedFiles, + $ruleRegistry, + static function (): void { + } + )->getErrors(); + foreach ($tmpErrors as $tmpError) { + $errors[] = $tmpError->withoutTip(); + } + } catch (\Throwable $e) { + if ($debug) { + throw $e; + } + + $internalErrorMessage = sprintf('Internal error: %s', $e->getMessage()); + $errors[] = new Error($internalErrorMessage, $stubFile, null, $e); + } + } + + Broker::registerInstance($originalBroker); + + return $errors; + } + + private function getRuleRegistry(Container $container): Registry + { + $fileTypeMapper = $container->getByType(FileTypeMapper::class); + $genericObjectTypeCheck = $container->getByType(GenericObjectTypeCheck::class); + $genericAncestorsCheck = $container->getByType(GenericAncestorsCheck::class); + $templateTypeCheck = $container->getByType(TemplateTypeCheck::class); + $varianceCheck = $container->getByType(VarianceCheck::class); + $reflectionProvider = $container->getByType(ReflectionProvider::class); + $classCaseSensitivityCheck = $container->getByType(ClassCaseSensitivityCheck::class); + $functionDefinitionCheck = $container->getByType(FunctionDefinitionCheck::class); + $missingTypehintCheck = $container->getByType(MissingTypehintCheck::class); + + return new Registry([ + // level 0 + new ExistingClassesInClassImplementsRule($classCaseSensitivityCheck, $reflectionProvider), + new ExistingClassesInInterfaceExtendsRule($classCaseSensitivityCheck, $reflectionProvider), + new ExistingClassInClassExtendsRule($classCaseSensitivityCheck, $reflectionProvider), + new ExistingClassInTraitUseRule($classCaseSensitivityCheck, $reflectionProvider), + new ExistingClassesInTypehintsRule($functionDefinitionCheck), + new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), + new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, true, false), + + // level 2 + new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck), + new ClassTemplateTypeRule($templateTypeCheck), + new FunctionTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new FunctionSignatureVarianceRule($varianceCheck), + new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck), + new InterfaceTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new MethodSignatureVarianceRule($varianceCheck), + new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new IncompatiblePhpDocTypeRule( + $fileTypeMapper, + $genericObjectTypeCheck + ), + new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck), + new InvalidPhpDocTagValueRule( + $container->getByType(Lexer::class), + $container->getByType(PhpDocParser::class) + ), + new InvalidThrowsPhpDocValueRule($fileTypeMapper), + + // level 6 + new MissingFunctionParameterTypehintRule($missingTypehintCheck), + new MissingFunctionReturnTypehintRule($missingTypehintCheck), + new MissingMethodParameterTypehintRule($missingTypehintCheck), + new MissingMethodReturnTypehintRule($missingTypehintCheck), + new MissingPropertyTypehintRule($missingTypehintCheck), + ]); + } } diff --git a/src/PhpDoc/Tag/DeprecatedTag.php b/src/PhpDoc/Tag/DeprecatedTag.php index cf0ef9316a..350320e3d5 100644 --- a/src/PhpDoc/Tag/DeprecatedTag.php +++ b/src/PhpDoc/Tag/DeprecatedTag.php @@ -1,20 +1,20 @@ -message = $message; - } - - public function getMessage(): ?string - { - return $this->message; - } + public function __construct(?string $message) + { + $this->message = $message; + } + public function getMessage(): ?string + { + return $this->message; + } } diff --git a/src/PhpDoc/Tag/ExtendsTag.php b/src/PhpDoc/Tag/ExtendsTag.php index 6d6e542fc8..244bf30e62 100644 --- a/src/PhpDoc/Tag/ExtendsTag.php +++ b/src/PhpDoc/Tag/ExtendsTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } + public function __construct(Type $type) + { + $this->type = $type; + } + public function getType(): Type + { + return $this->type; + } } diff --git a/src/PhpDoc/Tag/ImplementsTag.php b/src/PhpDoc/Tag/ImplementsTag.php index f987d2f8f0..a6cef85498 100644 --- a/src/PhpDoc/Tag/ImplementsTag.php +++ b/src/PhpDoc/Tag/ImplementsTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } + public function __construct(Type $type) + { + $this->type = $type; + } + public function getType(): Type + { + return $this->type; + } } diff --git a/src/PhpDoc/Tag/MethodTag.php b/src/PhpDoc/Tag/MethodTag.php index 8650d8e33f..e32a72571a 100644 --- a/src/PhpDoc/Tag/MethodTag.php +++ b/src/PhpDoc/Tag/MethodTag.php @@ -1,4 +1,6 @@ - */ - private array $parameters; - - /** - * @param \PHPStan\Type\Type $returnType - * @param bool $isStatic - * @param array $parameters - */ - public function __construct( - Type $returnType, - bool $isStatic, - array $parameters - ) - { - $this->returnType = $returnType; - $this->isStatic = $isStatic; - $this->parameters = $parameters; - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - public function isStatic(): bool - { - return $this->isStatic; - } - - /** - * @return array - */ - public function getParameters(): array - { - return $this->parameters; - } - + private \PHPStan\Type\Type $returnType; + + private bool $isStatic; + + /** @var array */ + private array $parameters; + + /** + * @param \PHPStan\Type\Type $returnType + * @param bool $isStatic + * @param array $parameters + */ + public function __construct( + Type $returnType, + bool $isStatic, + array $parameters + ) { + $this->returnType = $returnType; + $this->isStatic = $isStatic; + $this->parameters = $parameters; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function isStatic(): bool + { + return $this->isStatic; + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } } diff --git a/src/PhpDoc/Tag/MethodTagParameter.php b/src/PhpDoc/Tag/MethodTagParameter.php index 95c9d1fc1e..7c44ff98d9 100644 --- a/src/PhpDoc/Tag/MethodTagParameter.php +++ b/src/PhpDoc/Tag/MethodTagParameter.php @@ -1,4 +1,6 @@ -type = $type; - $this->passedByReference = $passedByReference; - $this->isOptional = $isOptional; - $this->isVariadic = $isVariadic; - $this->defaultValue = $defaultValue; - } - - public function getType(): Type - { - return $this->type; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isOptional(): bool - { - return $this->isOptional; - } - - public function isVariadic(): bool - { - return $this->isVariadic; - } - - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } - + private \PHPStan\Type\Type $type; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private bool $isOptional; + + private bool $isVariadic; + + private ?\PHPStan\Type\Type $defaultValue; + + public function __construct( + Type $type, + PassedByReference $passedByReference, + bool $isOptional, + bool $isVariadic, + ?Type $defaultValue + ) { + $this->type = $type; + $this->passedByReference = $passedByReference; + $this->isOptional = $isOptional; + $this->isVariadic = $isVariadic; + $this->defaultValue = $defaultValue; + } + + public function getType(): Type + { + return $this->type; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isOptional(): bool + { + return $this->isOptional; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } } diff --git a/src/PhpDoc/Tag/MixinTag.php b/src/PhpDoc/Tag/MixinTag.php index 31be7f58c7..267de92609 100644 --- a/src/PhpDoc/Tag/MixinTag.php +++ b/src/PhpDoc/Tag/MixinTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } + public function __construct(Type $type) + { + $this->type = $type; + } + public function getType(): Type + { + return $this->type; + } } diff --git a/src/PhpDoc/Tag/ParamTag.php b/src/PhpDoc/Tag/ParamTag.php index 2b006d3ef8..d467127f29 100644 --- a/src/PhpDoc/Tag/ParamTag.php +++ b/src/PhpDoc/Tag/ParamTag.php @@ -1,4 +1,6 @@ -type = $type; - $this->isVariadic = $isVariadic; - } - - public function getType(): Type - { - return $this->type; - } - - public function isVariadic(): bool - { - return $this->isVariadic; - } - - /** - * @param Type $type - * @return self - */ - public function withType(Type $type): TypedTag - { - return new self($type, $this->isVariadic); - } - + private \PHPStan\Type\Type $type; + + private bool $isVariadic; + + public function __construct(Type $type, bool $isVariadic) + { + $this->type = $type; + $this->isVariadic = $isVariadic; + } + + public function getType(): Type + { + return $this->type; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + /** + * @param Type $type + * @return self + */ + public function withType(Type $type): TypedTag + { + return new self($type, $this->isVariadic); + } } diff --git a/src/PhpDoc/Tag/PropertyTag.php b/src/PhpDoc/Tag/PropertyTag.php index c6dce435b0..b5f3bd28ad 100644 --- a/src/PhpDoc/Tag/PropertyTag.php +++ b/src/PhpDoc/Tag/PropertyTag.php @@ -1,4 +1,6 @@ -type = $type; - $this->readable = $readable; - $this->writable = $writable; - } - - public function getType(): Type - { - return $this->type; - } - - public function isReadable(): bool - { - return $this->readable; - } - - public function isWritable(): bool - { - return $this->writable; - } - + private \PHPStan\Type\Type $type; + + private bool $readable; + + private bool $writable; + + public function __construct( + Type $type, + bool $readable, + bool $writable + ) { + $this->type = $type; + $this->readable = $readable; + $this->writable = $writable; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } } diff --git a/src/PhpDoc/Tag/ReturnTag.php b/src/PhpDoc/Tag/ReturnTag.php index 3374939e8a..3a04e35133 100644 --- a/src/PhpDoc/Tag/ReturnTag.php +++ b/src/PhpDoc/Tag/ReturnTag.php @@ -1,4 +1,6 @@ -type = $type; - $this->isExplicit = $isExplicit; - } - - public function getType(): Type - { - return $this->type; - } - - public function isExplicit(): bool - { - return $this->isExplicit; - } - - /** - * @param Type $type - * @return self - */ - public function withType(Type $type): TypedTag - { - return new self($type, $this->isExplicit); - } - - public function toImplicit(): self - { - return new self($this->type, false); - } - + private \PHPStan\Type\Type $type; + + private bool $isExplicit; + + public function __construct(Type $type, bool $isExplicit) + { + $this->type = $type; + $this->isExplicit = $isExplicit; + } + + public function getType(): Type + { + return $this->type; + } + + public function isExplicit(): bool + { + return $this->isExplicit; + } + + /** + * @param Type $type + * @return self + */ + public function withType(Type $type): TypedTag + { + return new self($type, $this->isExplicit); + } + + public function toImplicit(): self + { + return new self($this->type, false); + } } diff --git a/src/PhpDoc/Tag/TemplateTag.php b/src/PhpDoc/Tag/TemplateTag.php index 36f4b774e1..ee53027e3f 100644 --- a/src/PhpDoc/Tag/TemplateTag.php +++ b/src/PhpDoc/Tag/TemplateTag.php @@ -1,4 +1,6 @@ -name = $name; - $this->bound = $bound; - $this->variance = $variance; - } + private TemplateTypeVariance $variance; - public function getName(): string - { - return $this->name; - } + public function __construct(string $name, Type $bound, TemplateTypeVariance $variance) + { + $this->name = $name; + $this->bound = $bound; + $this->variance = $variance; + } - public function getBound(): Type - { - return $this->bound; - } + public function getName(): string + { + return $this->name; + } - public function getVariance(): TemplateTypeVariance - { - return $this->variance; - } + public function getBound(): Type + { + return $this->bound; + } + public function getVariance(): TemplateTypeVariance + { + return $this->variance; + } } diff --git a/src/PhpDoc/Tag/ThrowsTag.php b/src/PhpDoc/Tag/ThrowsTag.php index d9c690ca36..c641649cb1 100644 --- a/src/PhpDoc/Tag/ThrowsTag.php +++ b/src/PhpDoc/Tag/ThrowsTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } + public function __construct(Type $type) + { + $this->type = $type; + } + public function getType(): Type + { + return $this->type; + } } diff --git a/src/PhpDoc/Tag/TypeAliasImportTag.php b/src/PhpDoc/Tag/TypeAliasImportTag.php index 62fb7cdb77..750040f903 100644 --- a/src/PhpDoc/Tag/TypeAliasImportTag.php +++ b/src/PhpDoc/Tag/TypeAliasImportTag.php @@ -1,36 +1,36 @@ -importedAlias = $importedAlias; - $this->importedFrom = $importedFrom; - $this->importedAs = $importedAs; - } + private ?string $importedAs; - public function getImportedAlias(): string - { - return $this->importedAlias; - } + public function __construct(string $importedAlias, string $importedFrom, ?string $importedAs) + { + $this->importedAlias = $importedAlias; + $this->importedFrom = $importedFrom; + $this->importedAs = $importedAs; + } - public function getImportedFrom(): string - { - return $this->importedFrom; - } + public function getImportedAlias(): string + { + return $this->importedAlias; + } - public function getImportedAs(): ?string - { - return $this->importedAs; - } + public function getImportedFrom(): string + { + return $this->importedFrom; + } + public function getImportedAs(): ?string + { + return $this->importedAs; + } } diff --git a/src/PhpDoc/Tag/TypeAliasTag.php b/src/PhpDoc/Tag/TypeAliasTag.php index d5ed9646cf..e24f279f23 100644 --- a/src/PhpDoc/Tag/TypeAliasTag.php +++ b/src/PhpDoc/Tag/TypeAliasTag.php @@ -1,4 +1,6 @@ -aliasName = $aliasName; - $this->typeNode = $typeNode; - $this->nameScope = $nameScope; - } - - public function getAliasName(): string - { - return $this->aliasName; - } - - public function getTypeAlias(): \PHPStan\Type\TypeAlias - { - return new \PHPStan\Type\TypeAlias( - $this->typeNode, - $this->nameScope - ); - } - + private string $aliasName; + + private TypeNode $typeNode; + + private NameScope $nameScope; + + public function __construct( + string $aliasName, + TypeNode $typeNode, + NameScope $nameScope + ) { + $this->aliasName = $aliasName; + $this->typeNode = $typeNode; + $this->nameScope = $nameScope; + } + + public function getAliasName(): string + { + return $this->aliasName; + } + + public function getTypeAlias(): \PHPStan\Type\TypeAlias + { + return new \PHPStan\Type\TypeAlias( + $this->typeNode, + $this->nameScope + ); + } } diff --git a/src/PhpDoc/Tag/TypedTag.php b/src/PhpDoc/Tag/TypedTag.php index 843fcb16b8..1ae5e4f1c9 100644 --- a/src/PhpDoc/Tag/TypedTag.php +++ b/src/PhpDoc/Tag/TypedTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } + public function __construct(Type $type) + { + $this->type = $type; + } + public function getType(): Type + { + return $this->type; + } } diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index 4966f802c2..2128c094fb 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getType(): Type - { - return $this->type; - } - - /** - * @param Type $type - * @return self - */ - public function withType(Type $type): TypedTag - { - return new self($type); - } - + private \PHPStan\Type\Type $type; + + public function __construct(Type $type) + { + $this->type = $type; + } + + public function getType(): Type + { + return $this->type; + } + + /** + * @param Type $type + * @return self + */ + public function withType(Type $type): TypedTag + { + return new self($type); + } } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 3c6d6ce47e..bb451d9f50 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1,4 +1,6 @@ -extensionRegistryProvider = $extensionRegistryProvider; + $this->container = $container; + } + + public function resolve(TypeNode $typeNode, NameScope $nameScope): Type + { + foreach ($this->extensionRegistryProvider->getRegistry()->getExtensions() as $extension) { + $type = $extension->resolve($typeNode, $nameScope); + if ($type !== null) { + return $type; + } + } + + if ($typeNode instanceof IdentifierTypeNode) { + return $this->resolveIdentifierTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ThisTypeNode) { + return $this->resolveThisTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof NullableTypeNode) { + return $this->resolveNullableTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof UnionTypeNode) { + return $this->resolveUnionTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof IntersectionTypeNode) { + return $this->resolveIntersectionTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ArrayTypeNode) { + return $this->resolveArrayTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof GenericTypeNode) { + return $this->resolveGenericTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof CallableTypeNode) { + return $this->resolveCallableTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ArrayShapeNode) { + return $this->resolveArrayShapeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ConstTypeNode) { + return $this->resolveConstTypeNode($typeNode, $nameScope); + } + + return new ErrorType(); + } + + private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameScope $nameScope): Type + { + switch (strtolower($typeNode->name)) { + case 'int': + case 'integer': + return new IntegerType(); + + case 'positive-int': + return IntegerRangeType::fromInterval(1, null); + + case 'negative-int': + return IntegerRangeType::fromInterval(null, -1); + + case 'string': + return new StringType(); + + case 'class-string': + return new ClassStringType(); + + case 'callable-string': + return new IntersectionType([new StringType(), new CallableType()]); + + case 'array-key': + return new BenevolentUnionType([new IntegerType(), new StringType()]); + + case 'scalar': + return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]); + + case 'number': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } - private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider; - - private Container $container; - - public function __construct( - TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider, - Container $container - ) - { - $this->extensionRegistryProvider = $extensionRegistryProvider; - $this->container = $container; - } - - public function resolve(TypeNode $typeNode, NameScope $nameScope): Type - { - foreach ($this->extensionRegistryProvider->getRegistry()->getExtensions() as $extension) { - $type = $extension->resolve($typeNode, $nameScope); - if ($type !== null) { - return $type; - } - } - - if ($typeNode instanceof IdentifierTypeNode) { - return $this->resolveIdentifierTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof ThisTypeNode) { - return $this->resolveThisTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof NullableTypeNode) { - return $this->resolveNullableTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof UnionTypeNode) { - return $this->resolveUnionTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof IntersectionTypeNode) { - return $this->resolveIntersectionTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof ArrayTypeNode) { - return $this->resolveArrayTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof GenericTypeNode) { - return $this->resolveGenericTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof CallableTypeNode) { - return $this->resolveCallableTypeNode($typeNode, $nameScope); - - } elseif ($typeNode instanceof ArrayShapeNode) { - return $this->resolveArrayShapeNode($typeNode, $nameScope); - } elseif ($typeNode instanceof ConstTypeNode) { - return $this->resolveConstTypeNode($typeNode, $nameScope); - } - - return new ErrorType(); - } - - private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameScope $nameScope): Type - { - switch (strtolower($typeNode->name)) { - case 'int': - case 'integer': - return new IntegerType(); - - case 'positive-int': - return IntegerRangeType::fromInterval(1, null); - - case 'negative-int': - return IntegerRangeType::fromInterval(null, -1); - - case 'string': - return new StringType(); - - case 'class-string': - return new ClassStringType(); - - case 'callable-string': - return new IntersectionType([new StringType(), new CallableType()]); - - case 'array-key': - return new BenevolentUnionType([new IntegerType(), new StringType()]); - - case 'scalar': - return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]); - - case 'number': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - - if ($type !== null) { - return $type; - } - - return new UnionType([new IntegerType(), new FloatType()]); - - case 'numeric': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - - if ($type !== null) { - return $type; - } - - return new UnionType([ - new IntegerType(), - new FloatType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]), - ]); - - case 'numeric-string': - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - - case 'bool': - return new BooleanType(); - - case 'boolean': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - - if ($type !== null) { - return $type; - } - - return new BooleanType(); - - case 'true': - return new ConstantBooleanType(true); - - case 'false': - return new ConstantBooleanType(false); - - case 'null': - return new NullType(); - - case 'float': - return new FloatType(); - - case 'double': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - - if ($type !== null) { - return $type; - } + return new UnionType([new IntegerType(), new FloatType()]); - return new FloatType(); + case 'numeric': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + + return new UnionType([ + new IntegerType(), + new FloatType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + ]); + + case 'numeric-string': + return new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]); + + case 'bool': + return new BooleanType(); + + case 'boolean': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + + return new BooleanType(); + + case 'true': + return new ConstantBooleanType(true); + + case 'false': + return new ConstantBooleanType(false); + + case 'null': + return new NullType(); + + case 'float': + return new FloatType(); + + case 'double': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } - case 'array': - case 'associative-array': - return new ArrayType(new MixedType(), new MixedType()); + return new FloatType(); - case 'non-empty-array': - return TypeCombinator::intersect( - new ArrayType(new MixedType(), new MixedType()), - new NonEmptyArrayType() - ); + case 'array': + case 'associative-array': + return new ArrayType(new MixedType(), new MixedType()); - case 'iterable': - return new IterableType(new MixedType(), new MixedType()); + case 'non-empty-array': + return TypeCombinator::intersect( + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType() + ); - case 'callable': - return new CallableType(); + case 'iterable': + return new IterableType(new MixedType(), new MixedType()); - case 'resource': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + case 'callable': + return new CallableType(); - if ($type !== null) { - return $type; - } + case 'resource': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - return new ResourceType(); + if ($type !== null) { + return $type; + } - case 'mixed': - return new MixedType(true); + return new ResourceType(); - case 'void': - return new VoidType(); + case 'mixed': + return new MixedType(true); - case 'object': - return new ObjectWithoutClassType(); + case 'void': + return new VoidType(); - case 'never': - $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); - - if ($type !== null) { - return $type; - } - - return new NeverType(true); - - case 'never-return': - case 'never-returns': - case 'no-return': - case 'noreturn': - return new NeverType(true); - - case 'list': - return new ArrayType(new IntegerType(), new MixedType()); - case 'non-empty-list': - return TypeCombinator::intersect( - new ArrayType(new IntegerType(), new MixedType()), - new NonEmptyArrayType() - ); - } - - if ($nameScope->getClassName() !== null) { - switch (strtolower($typeNode->name)) { - case 'self': - return new ObjectType($nameScope->getClassName()); - - case 'static': - return new StaticType($nameScope->getClassName()); - - case 'parent': - if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { - $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); - if ($classReflection->getParentClass() !== false) { - return new ObjectType($classReflection->getParentClass()->getName()); - } - } - - return new NonexistentParentClassType(); - } - } - - if (!$nameScope->shouldBypassTypeAliases()) { - $typeAlias = $this->getTypeAliasResolver()->resolveTypeAlias($typeNode->name, $nameScope); - if ($typeAlias !== null) { - return $typeAlias; - } - } - - $templateType = $nameScope->resolveTemplateTypeName($typeNode->name); - if ($templateType !== null) { - return $templateType; - } - - $stringName = $nameScope->resolveStringName($typeNode->name); - if (strpos($stringName, '-') !== false && strpos($stringName, 'OCI-') !== 0) { - return new ErrorType(); - } - - return new ObjectType($stringName); - } - - private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type - { - if ($nameScope->hasUseAlias($typeNode->name)) { - return new ObjectType($nameScope->resolveStringName($typeNode->name)); - } - - if ($nameScope->getNamespace() === null) { - return null; - } - - $className = $nameScope->resolveStringName($typeNode->name); - - if ($this->getReflectionProvider()->hasClass($className)) { - return new ObjectType($className); - } - - return null; - } - - private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type - { - $className = $nameScope->getClassName(); - if ($className !== null) { - if ($this->getReflectionProvider()->hasClass($className)) { - return new ThisType($this->getReflectionProvider()->getClass($className)); - } - } - - return new ErrorType(); - } - - private function resolveNullableTypeNode(NullableTypeNode $typeNode, NameScope $nameScope): Type - { - return TypeCombinator::addNull($this->resolve($typeNode->type, $nameScope)); - } - - private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameScope): Type - { - $iterableTypeNodes = []; - $otherTypeNodes = []; - - foreach ($typeNode->types as $innerTypeNode) { - if ($innerTypeNode instanceof ArrayTypeNode) { - $iterableTypeNodes[] = $innerTypeNode->type; - } else { - $otherTypeNodes[] = $innerTypeNode; - } - } - - $otherTypeTypes = $this->resolveMultiple($otherTypeNodes, $nameScope); - if (count($iterableTypeNodes) > 0) { - $arrayTypeTypes = $this->resolveMultiple($iterableTypeNodes, $nameScope); - $arrayTypeType = TypeCombinator::union(...$arrayTypeTypes); - $addArray = true; - - foreach ($otherTypeTypes as &$type) { - if (!$type->isIterable()->yes() || !$type->getIterableValueType()->isSuperTypeOf($arrayTypeType)->yes()) { - continue; - } - - if ($type instanceof ObjectType) { - $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]); - } elseif ($type instanceof ArrayType) { - $type = new ArrayType(new MixedType(), $arrayTypeType); - } elseif ($type instanceof IterableType) { - $type = new IterableType(new MixedType(), $arrayTypeType); - } else { - continue; - } - - $addArray = false; - } - - if ($addArray) { - $otherTypeTypes[] = new ArrayType(new MixedType(), $arrayTypeType); - } - } - - return TypeCombinator::union(...$otherTypeTypes); - } - - private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, NameScope $nameScope): Type - { - $types = $this->resolveMultiple($typeNode->types, $nameScope); - return TypeCombinator::intersect(...$types); - } - - private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type - { - $itemType = $this->resolve($typeNode->type, $nameScope); - return new ArrayType(new MixedType(), $itemType); - } - - private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $nameScope): Type - { - $mainTypeName = strtolower($typeNode->type->name); - $genericTypes = $this->resolveMultiple($typeNode->genericTypes, $nameScope); - - if ($mainTypeName === 'array' || $mainTypeName === 'non-empty-array') { - if (count($genericTypes) === 1) { // array - $arrayType = new ArrayType(new MixedType(true), $genericTypes[0]); - } elseif (count($genericTypes) === 2) { // array - $arrayType = new ArrayType($genericTypes[0], $genericTypes[1]); - } else { - return new ErrorType(); - } - - if ($mainTypeName === 'non-empty-array') { - return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); - } - - return $arrayType; - } elseif ($mainTypeName === 'list' || $mainTypeName === 'non-empty-list') { - if (count($genericTypes) === 1) { // list - $listType = new ArrayType(new IntegerType(), $genericTypes[0]); - if ($mainTypeName === 'non-empty-list') { - return TypeCombinator::intersect($listType, new NonEmptyArrayType()); - } - - return $listType; - } - - return new ErrorType(); - } elseif ($mainTypeName === 'iterable') { - if (count($genericTypes) === 1) { // iterable - return new IterableType(new MixedType(true), $genericTypes[0]); - - } - - if (count($genericTypes) === 2) { // iterable - return new IterableType($genericTypes[0], $genericTypes[1]); - } - } elseif ($mainTypeName === 'class-string') { - if (count($genericTypes) === 1) { - $genericType = $genericTypes[0]; - if ((new ObjectWithoutClassType())->isSuperTypeOf($genericType)->yes() || $genericType instanceof MixedType) { - return new GenericClassStringType($genericType); - } - } - - return new ErrorType(); - } - - $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope); - - if ($mainType instanceof TypeWithClassName) { - if (!$this->getReflectionProvider()->hasClass($mainType->getClassName())) { - return new GenericObjectType($mainType->getClassName(), $genericTypes); - } - - $classReflection = $this->getReflectionProvider()->getClass($mainType->getClassName()); - if ($classReflection->isGeneric()) { - if (in_array($mainType->getClassName(), [ - \Traversable::class, - \IteratorAggregate::class, - \Iterator::class, - ], true)) { - if (count($genericTypes) === 1) { - return new GenericObjectType($mainType->getClassName(), [ - new MixedType(true), - $genericTypes[0], - ]); - } - - if (count($genericTypes) === 2) { - return new GenericObjectType($mainType->getClassName(), [ - $genericTypes[0], - $genericTypes[1], - ]); - } - } - if ($mainType->getClassName() === \Generator::class) { - if (count($genericTypes) === 1) { - $mixed = new MixedType(true); - return new GenericObjectType($mainType->getClassName(), [ - $mixed, - $genericTypes[0], - $mixed, - $mixed, - ]); - } - - if (count($genericTypes) === 2) { - $mixed = new MixedType(true); - return new GenericObjectType($mainType->getClassName(), [ - $genericTypes[0], - $genericTypes[1], - $mixed, - $mixed, - ]); - } - } - - if (!$mainType->isIterable()->yes()) { - return new GenericObjectType($mainType->getClassName(), $genericTypes); - } - - if ( - count($genericTypes) !== 1 - || $classReflection->getTemplateTypeMap()->count() === 1 - ) { - return new GenericObjectType($mainType->getClassName(), $genericTypes); - } - } - } - - if ($mainType->isIterable()->yes()) { - if (count($genericTypes) === 1) { // Foo - return TypeCombinator::intersect( - $mainType, - new IterableType(new MixedType(true), $genericTypes[0]) - ); - } - - if (count($genericTypes) === 2) { // Foo - return TypeCombinator::intersect( - $mainType, - new IterableType($genericTypes[0], $genericTypes[1]) - ); - } - } - - if ($mainType instanceof TypeWithClassName) { - return new GenericObjectType($mainType->getClassName(), $genericTypes); - } - - return new ErrorType(); - } - - private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type - { - $mainType = $this->resolve($typeNode->identifier, $nameScope); - $isVariadic = false; - $parameters = array_map( - function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection { - $isVariadic = $isVariadic || $parameterNode->isVariadic; - $parameterName = $parameterNode->parameterName; - if (strpos($parameterName, '$') === 0) { - $parameterName = substr($parameterName, 1); - } - return new NativeParameterReflection( - $parameterName, - $parameterNode->isOptional || $parameterNode->isVariadic, - $this->resolve($parameterNode->type, $nameScope), - $parameterNode->isReference ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(), - $parameterNode->isVariadic, - null - ); - }, - $typeNode->parameters - ); - $returnType = $this->resolve($typeNode->returnType, $nameScope); - - if ($mainType instanceof CallableType) { - return new CallableType($parameters, $returnType, $isVariadic); - - } elseif ( - $mainType instanceof ObjectType - && $mainType->getClassName() === \Closure::class - ) { - return new ClosureType($parameters, $returnType, $isVariadic); - } - - return new ErrorType(); - } - - private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type - { - $builder = ConstantArrayTypeBuilder::createEmpty(); - - foreach ($typeNode->items as $itemNode) { - $offsetType = null; - if ($itemNode->keyName instanceof ConstExprIntegerNode) { - $offsetType = new ConstantIntegerType((int) $itemNode->keyName->value); - } elseif ($itemNode->keyName instanceof IdentifierTypeNode) { - $offsetType = new ConstantStringType($itemNode->keyName->name); - } elseif ($itemNode->keyName instanceof ConstExprStringNode) { - $offsetType = new ConstantStringType($itemNode->keyName->value); - } elseif ($itemNode->keyName !== null) { - throw new \PHPStan\ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName)); - } - $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional); - } - - return $builder->getArray(); - } - - private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type - { - $constExpr = $typeNode->constExpr; - if ($constExpr instanceof ConstExprArrayNode) { - throw new \PHPStan\ShouldNotHappenException(); // we prefer array shapes - } - - if ( - $constExpr instanceof ConstExprFalseNode - || $constExpr instanceof ConstExprTrueNode - || $constExpr instanceof ConstExprNullNode - ) { - throw new \PHPStan\ShouldNotHappenException(); // we prefer IdentifierTypeNode - } - - if ($constExpr instanceof ConstFetchNode) { - if ($constExpr->className === '') { - throw new \PHPStan\ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode - } - - if ($nameScope->getClassName() !== null) { - switch (strtolower($constExpr->className)) { - case 'static': - case 'self': - $className = $nameScope->getClassName(); - break; - - case 'parent': - if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { - $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); - if ($classReflection->getParentClass() === false) { - return new ErrorType(); - - } - - $className = $classReflection->getParentClass()->getName(); - } - } - } - - if (!isset($className)) { - $className = $nameScope->resolveStringName($constExpr->className); - } - - if (!$this->getReflectionProvider()->hasClass($className)) { - return new ErrorType(); - } - - $classReflection = $this->getReflectionProvider()->getClass($className); - - $constantName = $constExpr->name; - if (Strings::endsWith($constantName, '*')) { - $constantNameStartsWith = Strings::substring($constantName, 0, Strings::length($constantName) - 1); - $constantTypes = []; - foreach ($classReflection->getNativeReflection()->getConstants() as $classConstantName => $constantValue) { - if (!Strings::startsWith($classConstantName, $constantNameStartsWith)) { - continue; - } - - $constantTypes[] = ConstantTypeHelper::getTypeFromValue($constantValue); - } - - if (count($constantTypes) === 0) { - return new ErrorType(); - } - - return TypeCombinator::union(...$constantTypes); - } - - if (!$classReflection->hasConstant($constantName)) { - return new ErrorType(); - } - - return $classReflection->getConstant($constantName)->getValueType(); - } - - if ($constExpr instanceof ConstExprFloatNode) { - return ConstantTypeHelper::getTypeFromValue((float) $constExpr->value); - } - - if ($constExpr instanceof ConstExprIntegerNode) { - return ConstantTypeHelper::getTypeFromValue((int) $constExpr->value); - } - - if ($constExpr instanceof ConstExprStringNode) { - return ConstantTypeHelper::getTypeFromValue($constExpr->value); - } - - return new ErrorType(); - } - - /** - * @param TypeNode[] $typeNodes - * @param NameScope $nameScope - * @return Type[] - */ - public function resolveMultiple(array $typeNodes, NameScope $nameScope): array - { - $types = []; - foreach ($typeNodes as $typeNode) { - $types[] = $this->resolve($typeNode, $nameScope); - } - - return $types; - } - - private function getReflectionProvider(): ReflectionProvider - { - return $this->container->getByType(ReflectionProvider::class); - } - - private function getTypeAliasResolver(): TypeAliasResolver - { - return $this->container->getByType(TypeAliasResolver::class); - } + case 'object': + return new ObjectWithoutClassType(); + case 'never': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + + return new NeverType(true); + + case 'never-return': + case 'never-returns': + case 'no-return': + case 'noreturn': + return new NeverType(true); + + case 'list': + return new ArrayType(new IntegerType(), new MixedType()); + case 'non-empty-list': + return TypeCombinator::intersect( + new ArrayType(new IntegerType(), new MixedType()), + new NonEmptyArrayType() + ); + } + + if ($nameScope->getClassName() !== null) { + switch (strtolower($typeNode->name)) { + case 'self': + return new ObjectType($nameScope->getClassName()); + + case 'static': + return new StaticType($nameScope->getClassName()); + + case 'parent': + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + if ($classReflection->getParentClass() !== false) { + return new ObjectType($classReflection->getParentClass()->getName()); + } + } + + return new NonexistentParentClassType(); + } + } + + if (!$nameScope->shouldBypassTypeAliases()) { + $typeAlias = $this->getTypeAliasResolver()->resolveTypeAlias($typeNode->name, $nameScope); + if ($typeAlias !== null) { + return $typeAlias; + } + } + + $templateType = $nameScope->resolveTemplateTypeName($typeNode->name); + if ($templateType !== null) { + return $templateType; + } + + $stringName = $nameScope->resolveStringName($typeNode->name); + if (strpos($stringName, '-') !== false && strpos($stringName, 'OCI-') !== 0) { + return new ErrorType(); + } + + return new ObjectType($stringName); + } + + private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type + { + if ($nameScope->hasUseAlias($typeNode->name)) { + return new ObjectType($nameScope->resolveStringName($typeNode->name)); + } + + if ($nameScope->getNamespace() === null) { + return null; + } + + $className = $nameScope->resolveStringName($typeNode->name); + + if ($this->getReflectionProvider()->hasClass($className)) { + return new ObjectType($className); + } + + return null; + } + + private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type + { + $className = $nameScope->getClassName(); + if ($className !== null) { + if ($this->getReflectionProvider()->hasClass($className)) { + return new ThisType($this->getReflectionProvider()->getClass($className)); + } + } + + return new ErrorType(); + } + + private function resolveNullableTypeNode(NullableTypeNode $typeNode, NameScope $nameScope): Type + { + return TypeCombinator::addNull($this->resolve($typeNode->type, $nameScope)); + } + + private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameScope): Type + { + $iterableTypeNodes = []; + $otherTypeNodes = []; + + foreach ($typeNode->types as $innerTypeNode) { + if ($innerTypeNode instanceof ArrayTypeNode) { + $iterableTypeNodes[] = $innerTypeNode->type; + } else { + $otherTypeNodes[] = $innerTypeNode; + } + } + + $otherTypeTypes = $this->resolveMultiple($otherTypeNodes, $nameScope); + if (count($iterableTypeNodes) > 0) { + $arrayTypeTypes = $this->resolveMultiple($iterableTypeNodes, $nameScope); + $arrayTypeType = TypeCombinator::union(...$arrayTypeTypes); + $addArray = true; + + foreach ($otherTypeTypes as &$type) { + if (!$type->isIterable()->yes() || !$type->getIterableValueType()->isSuperTypeOf($arrayTypeType)->yes()) { + continue; + } + + if ($type instanceof ObjectType) { + $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]); + } elseif ($type instanceof ArrayType) { + $type = new ArrayType(new MixedType(), $arrayTypeType); + } elseif ($type instanceof IterableType) { + $type = new IterableType(new MixedType(), $arrayTypeType); + } else { + continue; + } + + $addArray = false; + } + + if ($addArray) { + $otherTypeTypes[] = new ArrayType(new MixedType(), $arrayTypeType); + } + } + + return TypeCombinator::union(...$otherTypeTypes); + } + + private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, NameScope $nameScope): Type + { + $types = $this->resolveMultiple($typeNode->types, $nameScope); + return TypeCombinator::intersect(...$types); + } + + private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type + { + $itemType = $this->resolve($typeNode->type, $nameScope); + return new ArrayType(new MixedType(), $itemType); + } + + private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $nameScope): Type + { + $mainTypeName = strtolower($typeNode->type->name); + $genericTypes = $this->resolveMultiple($typeNode->genericTypes, $nameScope); + + if ($mainTypeName === 'array' || $mainTypeName === 'non-empty-array') { + if (count($genericTypes) === 1) { // array + $arrayType = new ArrayType(new MixedType(true), $genericTypes[0]); + } elseif (count($genericTypes) === 2) { // array + $arrayType = new ArrayType($genericTypes[0], $genericTypes[1]); + } else { + return new ErrorType(); + } + + if ($mainTypeName === 'non-empty-array') { + return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + + return $arrayType; + } elseif ($mainTypeName === 'list' || $mainTypeName === 'non-empty-list') { + if (count($genericTypes) === 1) { // list + $listType = new ArrayType(new IntegerType(), $genericTypes[0]); + if ($mainTypeName === 'non-empty-list') { + return TypeCombinator::intersect($listType, new NonEmptyArrayType()); + } + + return $listType; + } + + return new ErrorType(); + } elseif ($mainTypeName === 'iterable') { + if (count($genericTypes) === 1) { // iterable + return new IterableType(new MixedType(true), $genericTypes[0]); + } + + if (count($genericTypes) === 2) { // iterable + return new IterableType($genericTypes[0], $genericTypes[1]); + } + } elseif ($mainTypeName === 'class-string') { + if (count($genericTypes) === 1) { + $genericType = $genericTypes[0]; + if ((new ObjectWithoutClassType())->isSuperTypeOf($genericType)->yes() || $genericType instanceof MixedType) { + return new GenericClassStringType($genericType); + } + } + + return new ErrorType(); + } + + $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope); + + if ($mainType instanceof TypeWithClassName) { + if (!$this->getReflectionProvider()->hasClass($mainType->getClassName())) { + return new GenericObjectType($mainType->getClassName(), $genericTypes); + } + + $classReflection = $this->getReflectionProvider()->getClass($mainType->getClassName()); + if ($classReflection->isGeneric()) { + if (in_array($mainType->getClassName(), [ + \Traversable::class, + \IteratorAggregate::class, + \Iterator::class, + ], true)) { + if (count($genericTypes) === 1) { + return new GenericObjectType($mainType->getClassName(), [ + new MixedType(true), + $genericTypes[0], + ]); + } + + if (count($genericTypes) === 2) { + return new GenericObjectType($mainType->getClassName(), [ + $genericTypes[0], + $genericTypes[1], + ]); + } + } + if ($mainType->getClassName() === \Generator::class) { + if (count($genericTypes) === 1) { + $mixed = new MixedType(true); + return new GenericObjectType($mainType->getClassName(), [ + $mixed, + $genericTypes[0], + $mixed, + $mixed, + ]); + } + + if (count($genericTypes) === 2) { + $mixed = new MixedType(true); + return new GenericObjectType($mainType->getClassName(), [ + $genericTypes[0], + $genericTypes[1], + $mixed, + $mixed, + ]); + } + } + + if (!$mainType->isIterable()->yes()) { + return new GenericObjectType($mainType->getClassName(), $genericTypes); + } + + if ( + count($genericTypes) !== 1 + || $classReflection->getTemplateTypeMap()->count() === 1 + ) { + return new GenericObjectType($mainType->getClassName(), $genericTypes); + } + } + } + + if ($mainType->isIterable()->yes()) { + if (count($genericTypes) === 1) { // Foo + return TypeCombinator::intersect( + $mainType, + new IterableType(new MixedType(true), $genericTypes[0]) + ); + } + + if (count($genericTypes) === 2) { // Foo + return TypeCombinator::intersect( + $mainType, + new IterableType($genericTypes[0], $genericTypes[1]) + ); + } + } + + if ($mainType instanceof TypeWithClassName) { + return new GenericObjectType($mainType->getClassName(), $genericTypes); + } + + return new ErrorType(); + } + + private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type + { + $mainType = $this->resolve($typeNode->identifier, $nameScope); + $isVariadic = false; + $parameters = array_map( + function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection { + $isVariadic = $isVariadic || $parameterNode->isVariadic; + $parameterName = $parameterNode->parameterName; + if (strpos($parameterName, '$') === 0) { + $parameterName = substr($parameterName, 1); + } + return new NativeParameterReflection( + $parameterName, + $parameterNode->isOptional || $parameterNode->isVariadic, + $this->resolve($parameterNode->type, $nameScope), + $parameterNode->isReference ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(), + $parameterNode->isVariadic, + null + ); + }, + $typeNode->parameters + ); + $returnType = $this->resolve($typeNode->returnType, $nameScope); + + if ($mainType instanceof CallableType) { + return new CallableType($parameters, $returnType, $isVariadic); + } elseif ( + $mainType instanceof ObjectType + && $mainType->getClassName() === \Closure::class + ) { + return new ClosureType($parameters, $returnType, $isVariadic); + } + + return new ErrorType(); + } + + private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($typeNode->items as $itemNode) { + $offsetType = null; + if ($itemNode->keyName instanceof ConstExprIntegerNode) { + $offsetType = new ConstantIntegerType((int) $itemNode->keyName->value); + } elseif ($itemNode->keyName instanceof IdentifierTypeNode) { + $offsetType = new ConstantStringType($itemNode->keyName->name); + } elseif ($itemNode->keyName instanceof ConstExprStringNode) { + $offsetType = new ConstantStringType($itemNode->keyName->value); + } elseif ($itemNode->keyName !== null) { + throw new \PHPStan\ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName)); + } + $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional); + } + + return $builder->getArray(); + } + + private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type + { + $constExpr = $typeNode->constExpr; + if ($constExpr instanceof ConstExprArrayNode) { + throw new \PHPStan\ShouldNotHappenException(); // we prefer array shapes + } + + if ( + $constExpr instanceof ConstExprFalseNode + || $constExpr instanceof ConstExprTrueNode + || $constExpr instanceof ConstExprNullNode + ) { + throw new \PHPStan\ShouldNotHappenException(); // we prefer IdentifierTypeNode + } + + if ($constExpr instanceof ConstFetchNode) { + if ($constExpr->className === '') { + throw new \PHPStan\ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode + } + + if ($nameScope->getClassName() !== null) { + switch (strtolower($constExpr->className)) { + case 'static': + case 'self': + $className = $nameScope->getClassName(); + break; + + case 'parent': + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + if ($classReflection->getParentClass() === false) { + return new ErrorType(); + } + + $className = $classReflection->getParentClass()->getName(); + } + } + } + + if (!isset($className)) { + $className = $nameScope->resolveStringName($constExpr->className); + } + + if (!$this->getReflectionProvider()->hasClass($className)) { + return new ErrorType(); + } + + $classReflection = $this->getReflectionProvider()->getClass($className); + + $constantName = $constExpr->name; + if (Strings::endsWith($constantName, '*')) { + $constantNameStartsWith = Strings::substring($constantName, 0, Strings::length($constantName) - 1); + $constantTypes = []; + foreach ($classReflection->getNativeReflection()->getConstants() as $classConstantName => $constantValue) { + if (!Strings::startsWith($classConstantName, $constantNameStartsWith)) { + continue; + } + + $constantTypes[] = ConstantTypeHelper::getTypeFromValue($constantValue); + } + + if (count($constantTypes) === 0) { + return new ErrorType(); + } + + return TypeCombinator::union(...$constantTypes); + } + + if (!$classReflection->hasConstant($constantName)) { + return new ErrorType(); + } + + return $classReflection->getConstant($constantName)->getValueType(); + } + + if ($constExpr instanceof ConstExprFloatNode) { + return ConstantTypeHelper::getTypeFromValue((float) $constExpr->value); + } + + if ($constExpr instanceof ConstExprIntegerNode) { + return ConstantTypeHelper::getTypeFromValue((int) $constExpr->value); + } + + if ($constExpr instanceof ConstExprStringNode) { + return ConstantTypeHelper::getTypeFromValue($constExpr->value); + } + + return new ErrorType(); + } + + /** + * @param TypeNode[] $typeNodes + * @param NameScope $nameScope + * @return Type[] + */ + public function resolveMultiple(array $typeNodes, NameScope $nameScope): array + { + $types = []; + foreach ($typeNodes as $typeNode) { + $types[] = $this->resolve($typeNode, $nameScope); + } + + return $types; + } + + private function getReflectionProvider(): ReflectionProvider + { + return $this->container->getByType(ReflectionProvider::class); + } + + private function getTypeAliasResolver(): TypeAliasResolver + { + return $this->container->getByType(TypeAliasResolver::class); + } } diff --git a/src/PhpDoc/TypeNodeResolverAwareExtension.php b/src/PhpDoc/TypeNodeResolverAwareExtension.php index 38a65f172d..ff31bd3c27 100644 --- a/src/PhpDoc/TypeNodeResolverAwareExtension.php +++ b/src/PhpDoc/TypeNodeResolverAwareExtension.php @@ -1,10 +1,10 @@ -setTypeNodeResolver($typeNodeResolver); - } - $this->extensions = $extensions; - } + /** + * @param TypeNodeResolverExtension[] $extensions + */ + public function __construct( + TypeNodeResolver $typeNodeResolver, + array $extensions + ) { + foreach ($extensions as $extension) { + if (!$extension instanceof TypeNodeResolverAwareExtension) { + continue; + } - /** - * @return TypeNodeResolverExtension[] - */ - public function getExtensions(): array - { - return $this->extensions; - } + $extension->setTypeNodeResolver($typeNodeResolver); + } + $this->extensions = $extensions; + } + /** + * @return TypeNodeResolverExtension[] + */ + public function getExtensions(): array + { + return $this->extensions; + } } diff --git a/src/PhpDoc/TypeNodeResolverExtensionRegistryProvider.php b/src/PhpDoc/TypeNodeResolverExtensionRegistryProvider.php index 377e613fa8..611454996d 100644 --- a/src/PhpDoc/TypeNodeResolverExtensionRegistryProvider.php +++ b/src/PhpDoc/TypeNodeResolverExtensionRegistryProvider.php @@ -1,10 +1,10 @@ -typeLexer = $typeLexer; - $this->typeParser = $typeParser; - $this->typeNodeResolver = $typeNodeResolver; - } + private TypeNodeResolver $typeNodeResolver; - public function resolve(string $typeString, ?NameScope $nameScope = null): Type - { - $tokens = new TokenIterator($this->typeLexer->tokenize($typeString)); - $typeNode = $this->typeParser->parse($tokens); - $tokens->consumeTokenType(Lexer::TOKEN_END); // @phpstan-ignore-line + public function __construct(Lexer $typeLexer, TypeParser $typeParser, TypeNodeResolver $typeNodeResolver) + { + $this->typeLexer = $typeLexer; + $this->typeParser = $typeParser; + $this->typeNodeResolver = $typeNodeResolver; + } - return $this->typeNodeResolver->resolve($typeNode, $nameScope ?? new NameScope(null, [])); - } + public function resolve(string $typeString, ?NameScope $nameScope = null): Type + { + $tokens = new TokenIterator($this->typeLexer->tokenize($typeString)); + $typeNode = $this->typeParser->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); // @phpstan-ignore-line + return $this->typeNodeResolver->resolve($typeNode, $nameScope ?? new NameScope(null, [])); + } } diff --git a/src/Process/CpuCoreCounter.php b/src/Process/CpuCoreCounter.php index 611157c48c..49618a92e7 100644 --- a/src/Process/CpuCoreCounter.php +++ b/src/Process/CpuCoreCounter.php @@ -1,49 +1,49 @@ -count !== null) { - return $this->count; - } - - // from brianium/paratest - $cores = 2; - if (@is_file('/proc/cpuinfo')) { - // Linux (and potentially Windows with linux sub systems) - $cpuinfo = @file_get_contents('/proc/cpuinfo'); - if ($cpuinfo !== false) { - preg_match_all('/^processor/m', $cpuinfo, $matches); - return $this->count = count($matches[0]); - } - } - - if (\DIRECTORY_SEPARATOR === '\\') { - // Windows - $process = @popen('wmic cpu get NumberOfLogicalProcessors', 'rb'); - if (is_resource($process)) { - fgets($process); - $cores = (int) fgets($process); - pclose($process); - } - - return $this->count = $cores; - } - - $process = @\popen('sysctl -n hw.ncpu', 'rb'); - if (is_resource($process)) { - // *nix (Linux, BSD and Mac) - $cores = (int) fgets($process); - pclose($process); - } - - return $this->count = $cores; - } - + private ?int $count = null; + + public function getNumberOfCpuCores(): int + { + if ($this->count !== null) { + return $this->count; + } + + // from brianium/paratest + $cores = 2; + if (@is_file('/proc/cpuinfo')) { + // Linux (and potentially Windows with linux sub systems) + $cpuinfo = @file_get_contents('/proc/cpuinfo'); + if ($cpuinfo !== false) { + preg_match_all('/^processor/m', $cpuinfo, $matches); + return $this->count = count($matches[0]); + } + } + + if (\DIRECTORY_SEPARATOR === '\\') { + // Windows + $process = @popen('wmic cpu get NumberOfLogicalProcessors', 'rb'); + if (is_resource($process)) { + fgets($process); + $cores = (int) fgets($process); + pclose($process); + } + + return $this->count = $cores; + } + + $process = @\popen('sysctl -n hw.ncpu', 'rb'); + if (is_resource($process)) { + // *nix (Linux, BSD and Mac) + $cores = (int) fgets($process); + pclose($process); + } + + return $this->count = $cores; + } } diff --git a/src/Process/ProcessCanceledException.php b/src/Process/ProcessCanceledException.php index fb1e5775d8..243c40ac26 100644 --- a/src/Process/ProcessCanceledException.php +++ b/src/Process/ProcessCanceledException.php @@ -1,8 +1,9 @@ -getOption('memory-limit') === null) { - $processCommandArray[] = '-d'; - $processCommandArray[] = 'memory_limit=' . ini_get('memory_limit'); - } - - foreach ([$mainScript, $commandName] as $arg) { - $processCommandArray[] = escapeshellarg($arg); - } + if ($input->getOption('memory-limit') === null) { + $processCommandArray[] = '-d'; + $processCommandArray[] = 'memory_limit=' . ini_get('memory_limit'); + } - if ($projectConfigFile !== null) { - $processCommandArray[] = '--configuration'; - $processCommandArray[] = escapeshellarg($projectConfigFile); - } + foreach ([$mainScript, $commandName] as $arg) { + $processCommandArray[] = escapeshellarg($arg); + } - $options = [ - 'paths-file', - AnalyseCommand::OPTION_LEVEL, - 'autoload-file', - 'memory-limit', - 'xdebug', - ]; - foreach ($options as $optionName) { - /** @var bool|string|null $optionValue */ - $optionValue = $input->getOption($optionName); - if (is_bool($optionValue)) { - if ($optionValue === true) { - $processCommandArray[] = sprintf('--%s', $optionName); - } - continue; - } - if ($optionValue === null) { - continue; - } + if ($projectConfigFile !== null) { + $processCommandArray[] = '--configuration'; + $processCommandArray[] = escapeshellarg($projectConfigFile); + } - $processCommandArray[] = sprintf('--%s=%s', $optionName, escapeshellarg($optionValue)); - } + $options = [ + 'paths-file', + AnalyseCommand::OPTION_LEVEL, + 'autoload-file', + 'memory-limit', + 'xdebug', + ]; + foreach ($options as $optionName) { + /** @var bool|string|null $optionValue */ + $optionValue = $input->getOption($optionName); + if (is_bool($optionValue)) { + if ($optionValue === true) { + $processCommandArray[] = sprintf('--%s', $optionName); + } + continue; + } + if ($optionValue === null) { + continue; + } - $processCommandArray = array_merge($processCommandArray, $additionalItems); + $processCommandArray[] = sprintf('--%s=%s', $optionName, escapeshellarg($optionValue)); + } - $processCommandArray[] = '--'; + $processCommandArray = array_merge($processCommandArray, $additionalItems); - /** @var string[] $paths */ - $paths = $input->getArgument('paths'); - foreach ($paths as $path) { - $processCommandArray[] = escapeshellarg($path); - } + $processCommandArray[] = '--'; - return implode(' ', $processCommandArray); - } + /** @var string[] $paths */ + $paths = $input->getArgument('paths'); + foreach ($paths as $path) { + $processCommandArray[] = escapeshellarg($path); + } + return implode(' ', $processCommandArray); + } } diff --git a/src/Process/ProcessPromise.php b/src/Process/ProcessPromise.php index 8441d82427..be5cb416c7 100644 --- a/src/Process/ProcessPromise.php +++ b/src/Process/ProcessPromise.php @@ -1,4 +1,6 @@ -loop = $loop; - $this->name = $name; - $this->command = $command; - $this->deferred = new Deferred(); - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return ExtendedPromiseInterface&CancellablePromiseInterface - */ - public function run(): CancellablePromiseInterface - { - $tmpStdOutResource = tmpfile(); - if ($tmpStdOutResource === false) { - throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stdout.'); - } - $tmpStdErrResource = tmpfile(); - if ($tmpStdErrResource === false) { - throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stderr.'); - } - - $this->process = new Process($this->command, null, null, [ - 1 => $tmpStdOutResource, - 2 => $tmpStdErrResource, - ]); - $this->process->start($this->loop); - - $this->process->on('exit', function ($exitCode) use ($tmpStdOutResource, $tmpStdErrResource): void { - if ($this->canceled) { - fclose($tmpStdOutResource); - fclose($tmpStdErrResource); - return; - } - rewind($tmpStdOutResource); - $stdOut = stream_get_contents($tmpStdOutResource); - fclose($tmpStdOutResource); - - rewind($tmpStdErrResource); - $stdErr = stream_get_contents($tmpStdErrResource); - fclose($tmpStdErrResource); - - if ($exitCode === null) { - $this->deferred->reject(new \PHPStan\Process\ProcessCrashedException($stdOut . $stdErr)); - return; - } - - if ($exitCode === 0) { - $this->deferred->resolve($stdOut); - return; - } - - $this->deferred->reject(new \PHPStan\Process\ProcessCrashedException($stdOut . $stdErr)); - }); - - /** @var ExtendedPromiseInterface&CancellablePromiseInterface */ - return $this->deferred->promise(); - } - - public function cancel(): void - { - if ($this->process === null) { - throw new \PHPStan\ShouldNotHappenException('Cancelling process before running'); - } - $this->canceled = true; - $this->process->terminate(); - $this->deferred->reject(new \PHPStan\Process\ProcessCanceledException()); - } - + /** @var LoopInterface */ + private $loop; + + /** @var string */ + private $name; + + /** @var string */ + private $command; + + private Deferred $deferred; + + private ?Process $process = null; + + private bool $canceled = false; + + public function __construct(LoopInterface $loop, string $name, string $command) + { + $this->loop = $loop; + $this->name = $name; + $this->command = $command; + $this->deferred = new Deferred(); + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return ExtendedPromiseInterface&CancellablePromiseInterface + */ + public function run(): CancellablePromiseInterface + { + $tmpStdOutResource = tmpfile(); + if ($tmpStdOutResource === false) { + throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stdout.'); + } + $tmpStdErrResource = tmpfile(); + if ($tmpStdErrResource === false) { + throw new \PHPStan\ShouldNotHappenException('Failed creating temp file for stderr.'); + } + + $this->process = new Process($this->command, null, null, [ + 1 => $tmpStdOutResource, + 2 => $tmpStdErrResource, + ]); + $this->process->start($this->loop); + + $this->process->on('exit', function ($exitCode) use ($tmpStdOutResource, $tmpStdErrResource): void { + if ($this->canceled) { + fclose($tmpStdOutResource); + fclose($tmpStdErrResource); + return; + } + rewind($tmpStdOutResource); + $stdOut = stream_get_contents($tmpStdOutResource); + fclose($tmpStdOutResource); + + rewind($tmpStdErrResource); + $stdErr = stream_get_contents($tmpStdErrResource); + fclose($tmpStdErrResource); + + if ($exitCode === null) { + $this->deferred->reject(new \PHPStan\Process\ProcessCrashedException($stdOut . $stdErr)); + return; + } + + if ($exitCode === 0) { + $this->deferred->resolve($stdOut); + return; + } + + $this->deferred->reject(new \PHPStan\Process\ProcessCrashedException($stdOut . $stdErr)); + }); + + /** @var ExtendedPromiseInterface&CancellablePromiseInterface */ + return $this->deferred->promise(); + } + + public function cancel(): void + { + if ($this->process === null) { + throw new \PHPStan\ShouldNotHappenException('Cancelling process before running'); + } + $this->canceled = true; + $this->process->terminate(); + $this->deferred->reject(new \PHPStan\Process\ProcessCanceledException()); + } } diff --git a/src/Process/Runnable/Runnable.php b/src/Process/Runnable/Runnable.php index e25e9154cc..3edd2fa341 100644 --- a/src/Process/Runnable/Runnable.php +++ b/src/Process/Runnable/Runnable.php @@ -1,4 +1,6 @@ - */ - private array $queue = []; - - /** @var SplObjectStorage */ - private SplObjectStorage $running; - - public function __construct(RunnableQueueLogger $logger, int $maxSize) - { - $this->logger = $logger; - $this->maxSize = $maxSize; - - /** @var SplObjectStorage $running */ - $running = new SplObjectStorage(); - $this->running = $running; - } - - public function getQueueSize(): int - { - $allSize = 0; - foreach ($this->queue as [$runnable, $size, $deferred]) { - $allSize += $size; - } - - return $allSize; - } - - public function getRunningSize(): int - { - $allSize = 0; - foreach ($this->running as $running) { // phpcs:ignore - [$size] = $this->running->getInfo(); - $allSize += $size; - } - - return $allSize; - } - - public function queue(Runnable $runnable, int $size): CancellablePromiseInterface - { - if ($size > $this->maxSize) { - throw new \PHPStan\ShouldNotHappenException('Runnable size exceeds queue maxSize.'); - } - - $deferred = new Deferred(static function () use ($runnable): void { - $runnable->cancel(); - }); - $this->queue[] = [$runnable, $size, $deferred]; - $this->drainQueue(); - - /** @var CancellablePromiseInterface */ - return $deferred->promise(); - } - - private function drainQueue(): void - { - if (count($this->queue) === 0) { - $this->logger->log('Queue empty'); - return; - } - - $currentQueueSize = $this->getRunningSize(); - if ($currentQueueSize > $this->maxSize) { - throw new \PHPStan\ShouldNotHappenException('Running overflow'); - } - - if ($currentQueueSize === $this->maxSize) { - $this->logger->log('Queue is full'); - return; - } - - $this->logger->log('Queue not full - looking at first item in the queue'); - - [$runnable, $runnableSize, $deferred] = $this->queue[0]; - - $newSize = $currentQueueSize + $runnableSize; - if ($newSize > $this->maxSize) { - $this->logger->log( - sprintf( - 'Canot remote first item from the queue - it has size %d, current queue size is %d, new size would be %d', - $runnableSize, - $currentQueueSize, - $newSize - ) - ); - return; - } - - $this->logger->log(sprintf('Removing top item from queue - new size is %d', $newSize)); - - /** @var array{Runnable, int, Deferred} $popped */ - $popped = array_shift($this->queue); - if ($popped[0] !== $runnable || $popped[1] !== $runnableSize || $popped[2] !== $deferred) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $this->running->attach($runnable, [$runnableSize, $deferred]); - $this->logger->log(sprintf('Running process %s', $runnable->getName())); - $runnable->run()->then(function ($value) use ($runnable, $deferred): void { - $this->logger->log(sprintf('Process %s finished successfully', $runnable->getName())); - $deferred->resolve($value); - $this->running->detach($runnable); - $this->drainQueue(); - }, function (\Throwable $e) use ($runnable, $deferred): void { - $this->logger->log(sprintf('Process %s finished unsuccessfully: %s', $runnable->getName(), $e->getMessage())); - $deferred->reject($e); - $this->running->detach($runnable); - $this->drainQueue(); - }); - } - - public function cancelAll(): void - { - foreach ($this->queue as [$runnable, $size, $deferred]) { - $deferred->promise()->cancel(); // @phpstan-ignore-line - } - - $runningDeferreds = []; - foreach ($this->running as $running) { // phpcs:ignore - [,$deferred] = $this->running->getInfo(); - $runningDeferreds[] = $deferred; - } - - foreach ($runningDeferreds as $deferred) { - $deferred->promise()->cancel(); // @phpstan-ignore-line - } - } - + private RunnableQueueLogger $logger; + + private int $maxSize; + + /** @var array */ + private array $queue = []; + + /** @var SplObjectStorage */ + private SplObjectStorage $running; + + public function __construct(RunnableQueueLogger $logger, int $maxSize) + { + $this->logger = $logger; + $this->maxSize = $maxSize; + + /** @var SplObjectStorage $running */ + $running = new SplObjectStorage(); + $this->running = $running; + } + + public function getQueueSize(): int + { + $allSize = 0; + foreach ($this->queue as [$runnable, $size, $deferred]) { + $allSize += $size; + } + + return $allSize; + } + + public function getRunningSize(): int + { + $allSize = 0; + foreach ($this->running as $running) { // phpcs:ignore + [$size] = $this->running->getInfo(); + $allSize += $size; + } + + return $allSize; + } + + public function queue(Runnable $runnable, int $size): CancellablePromiseInterface + { + if ($size > $this->maxSize) { + throw new \PHPStan\ShouldNotHappenException('Runnable size exceeds queue maxSize.'); + } + + $deferred = new Deferred(static function () use ($runnable): void { + $runnable->cancel(); + }); + $this->queue[] = [$runnable, $size, $deferred]; + $this->drainQueue(); + + /** @var CancellablePromiseInterface */ + return $deferred->promise(); + } + + private function drainQueue(): void + { + if (count($this->queue) === 0) { + $this->logger->log('Queue empty'); + return; + } + + $currentQueueSize = $this->getRunningSize(); + if ($currentQueueSize > $this->maxSize) { + throw new \PHPStan\ShouldNotHappenException('Running overflow'); + } + + if ($currentQueueSize === $this->maxSize) { + $this->logger->log('Queue is full'); + return; + } + + $this->logger->log('Queue not full - looking at first item in the queue'); + + [$runnable, $runnableSize, $deferred] = $this->queue[0]; + + $newSize = $currentQueueSize + $runnableSize; + if ($newSize > $this->maxSize) { + $this->logger->log( + sprintf( + 'Canot remote first item from the queue - it has size %d, current queue size is %d, new size would be %d', + $runnableSize, + $currentQueueSize, + $newSize + ) + ); + return; + } + + $this->logger->log(sprintf('Removing top item from queue - new size is %d', $newSize)); + + /** @var array{Runnable, int, Deferred} $popped */ + $popped = array_shift($this->queue); + if ($popped[0] !== $runnable || $popped[1] !== $runnableSize || $popped[2] !== $deferred) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $this->running->attach($runnable, [$runnableSize, $deferred]); + $this->logger->log(sprintf('Running process %s', $runnable->getName())); + $runnable->run()->then(function ($value) use ($runnable, $deferred): void { + $this->logger->log(sprintf('Process %s finished successfully', $runnable->getName())); + $deferred->resolve($value); + $this->running->detach($runnable); + $this->drainQueue(); + }, function (\Throwable $e) use ($runnable, $deferred): void { + $this->logger->log(sprintf('Process %s finished unsuccessfully: %s', $runnable->getName(), $e->getMessage())); + $deferred->reject($e); + $this->running->detach($runnable); + $this->drainQueue(); + }); + } + + public function cancelAll(): void + { + foreach ($this->queue as [$runnable, $size, $deferred]) { + $deferred->promise()->cancel(); // @phpstan-ignore-line + } + + $runningDeferreds = []; + foreach ($this->running as $running) { // phpcs:ignore + [,$deferred] = $this->running->getInfo(); + $runningDeferreds[] = $deferred; + } + + foreach ($runningDeferreds as $deferred) { + $deferred->promise()->cancel(); // @phpstan-ignore-line + } + } } diff --git a/src/Process/Runnable/RunnableQueueLogger.php b/src/Process/Runnable/RunnableQueueLogger.php index a29ada14b3..b3f62084d7 100644 --- a/src/Process/Runnable/RunnableQueueLogger.php +++ b/src/Process/Runnable/RunnableQueueLogger.php @@ -1,10 +1,10 @@ -name = $name; - $this->declaringClass = $declaringClass; - $this->returnType = $returnType; - $this->parameters = $parameters; - $this->isStatic = $isStatic; - $this->isVariadic = $isVariadic; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - public function isStatic(): bool - { - return $this->isStatic; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getVariants(): array - { - if ($this->variants === null) { - $this->variants = [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - $this->parameters, - $this->isVariadic, - $this->returnType - ), - ]; - } - return $this->variants; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getThrowType(): ?Type - { - return null; - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDocComment(): ?string - { - return null; - } - + private string $name; + + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private Type $returnType; + + private bool $isStatic; + + /** @var \PHPStan\Reflection\Annotations\AnnotationsMethodParameterReflection[] */ + private array $parameters; + + private bool $isVariadic; + + /** @var FunctionVariant[]|null */ + private ?array $variants = null; + + /** + * @param string $name + * @param ClassReflection $declaringClass + * @param Type $returnType + * @param \PHPStan\Reflection\Annotations\AnnotationsMethodParameterReflection[] $parameters + * @param bool $isStatic + * @param bool $isVariadic + */ + public function __construct( + string $name, + ClassReflection $declaringClass, + Type $returnType, + array $parameters, + bool $isStatic, + bool $isVariadic + ) { + $this->name = $name; + $this->declaringClass = $declaringClass; + $this->returnType = $returnType; + $this->parameters = $parameters; + $this->isStatic = $isStatic; + $this->isVariadic = $isVariadic; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + public function isStatic(): bool + { + return $this->isStatic; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getVariants(): array + { + if ($this->variants === null) { + $this->variants = [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + $this->parameters, + $this->isVariadic, + $this->returnType + ), + ]; + } + return $this->variants; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index acbaaaac09..4b445100c5 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->type = $type; - $this->readable = $readable; - $this->writable = $writable; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getReadableType(): Type - { - return $this->type; - } - - public function getWritableType(): Type - { - return $this->type; - } - - public function canChangeTypeAfterAssignment(): bool - { - return true; - } - - public function isReadable(): bool - { - return $this->readable; - } - - public function isWritable(): bool - { - return $this->writable; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDocComment(): ?string - { - return null; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private \PHPStan\Type\Type $type; + + private bool $readable; + + private bool $writable; + + public function __construct( + ClassReflection $declaringClass, + Type $type, + bool $readable = true, + bool $writable = true + ) { + $this->declaringClass = $declaringClass; + $this->type = $type; + $this->readable = $readable; + $this->writable = $writable; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getReadableType(): Type + { + return $this->type; + } + + public function getWritableType(): Type + { + return $this->type; + } + + public function canChangeTypeAfterAssignment(): bool + { + return true; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index 40d9c42981..5c08240da0 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -1,4 +1,6 @@ -name = $name; - $this->type = $type; - $this->passedByReference = $passedByReference; - $this->isOptional = $isOptional; - $this->isVariadic = $isVariadic; - $this->defaultValue = $defaultValue; - } + private ?Type $defaultValue; - public function getName(): string - { - return $this->name; - } + public function __construct(string $name, Type $type, PassedByReference $passedByReference, bool $isOptional, bool $isVariadic, ?Type $defaultValue) + { + $this->name = $name; + $this->type = $type; + $this->passedByReference = $passedByReference; + $this->isOptional = $isOptional; + $this->isVariadic = $isVariadic; + $this->defaultValue = $defaultValue; + } - public function isOptional(): bool - { - return $this->isOptional; - } + public function getName(): string + { + return $this->name; + } - public function getType(): Type - { - return $this->type; - } + public function isOptional(): bool + { + return $this->isOptional; + } - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } + public function getType(): Type + { + return $this->type; + } - public function isVariadic(): bool - { - return $this->isVariadic; - } + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } + public function isVariadic(): bool + { + return $this->isVariadic; + } + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } } diff --git a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php index 29044363fe..4f71059d07 100644 --- a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php @@ -1,4 +1,6 @@ -methods[$classReflection->getCacheKey()])) { - $this->methods[$classReflection->getCacheKey()] = $this->createMethods($classReflection, $classReflection); - } - - return isset($this->methods[$classReflection->getCacheKey()][$methodName]); - } + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + if (!isset($this->methods[$classReflection->getCacheKey()])) { + $this->methods[$classReflection->getCacheKey()] = $this->createMethods($classReflection, $classReflection); + } - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - return $this->methods[$classReflection->getCacheKey()][$methodName]; - } + return isset($this->methods[$classReflection->getCacheKey()][$methodName]); + } - /** - * @param ClassReflection $classReflection - * @param ClassReflection $declaringClass - * @return MethodReflection[] - */ - private function createMethods( - ClassReflection $classReflection, - ClassReflection $declaringClass - ): array - { - $methods = []; - foreach ($classReflection->getTraits() as $traitClass) { - $methods += $this->createMethods($traitClass, $classReflection); - } - foreach ($classReflection->getParents() as $parentClass) { - $methods += $this->createMethods($parentClass, $parentClass); - foreach ($parentClass->getTraits() as $traitClass) { - $methods += $this->createMethods($traitClass, $parentClass); - } - } - foreach ($classReflection->getInterfaces() as $interfaceClass) { - $methods += $this->createMethods($interfaceClass, $interfaceClass); - } + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + return $this->methods[$classReflection->getCacheKey()][$methodName]; + } - $fileName = $classReflection->getFileName(); - if ($fileName === false) { - return $methods; - } + /** + * @param ClassReflection $classReflection + * @param ClassReflection $declaringClass + * @return MethodReflection[] + */ + private function createMethods( + ClassReflection $classReflection, + ClassReflection $declaringClass + ): array { + $methods = []; + foreach ($classReflection->getTraits() as $traitClass) { + $methods += $this->createMethods($traitClass, $classReflection); + } + foreach ($classReflection->getParents() as $parentClass) { + $methods += $this->createMethods($parentClass, $parentClass); + foreach ($parentClass->getTraits() as $traitClass) { + $methods += $this->createMethods($traitClass, $parentClass); + } + } + foreach ($classReflection->getInterfaces() as $interfaceClass) { + $methods += $this->createMethods($interfaceClass, $interfaceClass); + } - $methodTags = $classReflection->getMethodTags(); - foreach ($methodTags as $methodName => $methodTag) { - $parameters = []; - foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { - $parameters[] = new AnnotationsMethodParameterReflection( - $parameterName, - $parameterTag->getType(), - $parameterTag->passedByReference(), - $parameterTag->isOptional(), - $parameterTag->isVariadic(), - $parameterTag->getDefaultValue() - ); - } + $fileName = $classReflection->getFileName(); + if ($fileName === false) { + return $methods; + } - $methods[$methodName] = new AnnotationMethodReflection( - $methodName, - $declaringClass, - TemplateTypeHelper::resolveTemplateTypes( - $methodTag->getReturnType(), - $classReflection->getActiveTemplateTypeMap() - ), - $parameters, - $methodTag->isStatic(), - $this->detectMethodVariadic($parameters) - ); - } - return $methods; - } + $methodTags = $classReflection->getMethodTags(); + foreach ($methodTags as $methodName => $methodTag) { + $parameters = []; + foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { + $parameters[] = new AnnotationsMethodParameterReflection( + $parameterName, + $parameterTag->getType(), + $parameterTag->passedByReference(), + $parameterTag->isOptional(), + $parameterTag->isVariadic(), + $parameterTag->getDefaultValue() + ); + } - /** - * @param AnnotationsMethodParameterReflection[] $parameters - * @return bool - */ - private function detectMethodVariadic(array $parameters): bool - { - if ($parameters === []) { - return false; - } + $methods[$methodName] = new AnnotationMethodReflection( + $methodName, + $declaringClass, + TemplateTypeHelper::resolveTemplateTypes( + $methodTag->getReturnType(), + $classReflection->getActiveTemplateTypeMap() + ), + $parameters, + $methodTag->isStatic(), + $this->detectMethodVariadic($parameters) + ); + } + return $methods; + } - $possibleVariadicParameterIndex = count($parameters) - 1; - $possibleVariadicParameter = $parameters[$possibleVariadicParameterIndex]; + /** + * @param AnnotationsMethodParameterReflection[] $parameters + * @return bool + */ + private function detectMethodVariadic(array $parameters): bool + { + if ($parameters === []) { + return false; + } - return $possibleVariadicParameter->isVariadic(); - } + $possibleVariadicParameterIndex = count($parameters) - 1; + $possibleVariadicParameter = $parameters[$possibleVariadicParameterIndex]; + return $possibleVariadicParameter->isVariadic(); + } } diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index 24d2d9b014..7dbbc27533 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -1,4 +1,6 @@ -properties[$classReflection->getCacheKey()])) { - $this->properties[$classReflection->getCacheKey()] = $this->createProperties($classReflection, $classReflection); - } - - return isset($this->properties[$classReflection->getCacheKey()][$propertyName]); - } + public function hasProperty(ClassReflection $classReflection, string $propertyName): bool + { + if (!isset($this->properties[$classReflection->getCacheKey()])) { + $this->properties[$classReflection->getCacheKey()] = $this->createProperties($classReflection, $classReflection); + } - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection - { - return $this->properties[$classReflection->getCacheKey()][$propertyName]; - } + return isset($this->properties[$classReflection->getCacheKey()][$propertyName]); + } - /** - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @param \PHPStan\Reflection\ClassReflection $declaringClass - * @return \PHPStan\Reflection\PropertyReflection[] - */ - private function createProperties( - ClassReflection $classReflection, - ClassReflection $declaringClass - ): array - { - $properties = []; - foreach ($classReflection->getTraits() as $traitClass) { - $properties += $this->createProperties($traitClass, $classReflection); - } - foreach ($classReflection->getParents() as $parentClass) { - $properties += $this->createProperties($parentClass, $parentClass); - foreach ($parentClass->getTraits() as $traitClass) { - $properties += $this->createProperties($traitClass, $parentClass); - } - } + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + return $this->properties[$classReflection->getCacheKey()][$propertyName]; + } - foreach ($classReflection->getInterfaces() as $interfaceClass) { - $properties += $this->createProperties($interfaceClass, $interfaceClass); - } + /** + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param \PHPStan\Reflection\ClassReflection $declaringClass + * @return \PHPStan\Reflection\PropertyReflection[] + */ + private function createProperties( + ClassReflection $classReflection, + ClassReflection $declaringClass + ): array { + $properties = []; + foreach ($classReflection->getTraits() as $traitClass) { + $properties += $this->createProperties($traitClass, $classReflection); + } + foreach ($classReflection->getParents() as $parentClass) { + $properties += $this->createProperties($parentClass, $parentClass); + foreach ($parentClass->getTraits() as $traitClass) { + $properties += $this->createProperties($traitClass, $parentClass); + } + } - $fileName = $classReflection->getFileName(); - if ($fileName === false) { - return $properties; - } + foreach ($classReflection->getInterfaces() as $interfaceClass) { + $properties += $this->createProperties($interfaceClass, $interfaceClass); + } - $propertyTags = $classReflection->getPropertyTags(); - foreach ($propertyTags as $propertyName => $propertyTag) { - $properties[$propertyName] = new AnnotationPropertyReflection( - $declaringClass, - TemplateTypeHelper::resolveTemplateTypes( - $propertyTag->getType(), - $classReflection->getActiveTemplateTypeMap() - ), - $propertyTag->isReadable(), - $propertyTag->isWritable() - ); - } + $fileName = $classReflection->getFileName(); + if ($fileName === false) { + return $properties; + } - return $properties; - } + $propertyTags = $classReflection->getPropertyTags(); + foreach ($propertyTags as $propertyName => $propertyTag) { + $properties[$propertyName] = new AnnotationPropertyReflection( + $declaringClass, + TemplateTypeHelper::resolveTemplateTypes( + $propertyTag->getType(), + $classReflection->getActiveTemplateTypeMap() + ), + $propertyTag->isReadable(), + $propertyTag->isWritable() + ); + } + return $properties; + } } diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 336c2d40d6..60b48e545b 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -1,4 +1,6 @@ -reflectionProviderProvider = $reflectionProviderProvider; - $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; - $this->classReflector = $classReflector; - $this->fileTypeMapper = $fileTypeMapper; - $this->phpVersion = $phpVersion; - $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; - $this->stubPhpDocProvider = $stubPhpDocProvider; - $this->functionReflectionFactory = $functionReflectionFactory; - $this->relativePathHelper = $relativePathHelper; - $this->anonymousClassNameHelper = $anonymousClassNameHelper; - $this->printer = $printer; - $this->fileHelper = $fileHelper; - $this->functionReflector = $functionReflector; - $this->constantReflector = $constantReflector; - $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; - } - - public function hasClass(string $className): bool - { - if (isset(self::$anonymousClasses[$className])) { - return true; - } - - try { - $this->classReflector->reflect($className); - return true; - } catch (IdentifierNotFound $e) { - return false; - } catch (InvalidIdentifierName $e) { - return false; - } - } - - public function getClass(string $className): ClassReflection - { - if (isset(self::$anonymousClasses[$className])) { - return self::$anonymousClasses[$className]; - } - - try { - $reflectionClass = $this->classReflector->reflect($className); - } catch (IdentifierNotFound $e) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - $reflectionClassName = strtolower($reflectionClass->getName()); - - if (array_key_exists($reflectionClassName, $this->classReflections)) { - return $this->classReflections[$reflectionClassName]; - } - - $classReflection = new ClassReflection( - $this->reflectionProviderProvider->getReflectionProvider(), - $this->fileTypeMapper, - $this->phpVersion, - $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), - $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), - $reflectionClass->getName(), - new ReflectionClass($reflectionClass), - null, - null, - $this->stubPhpDocProvider->findClassPhpDoc($reflectionClass->getName()) - ); - - $this->classReflections[$reflectionClassName] = $classReflection; - - return $classReflection; - } - - public function getClassName(string $className): string - { - if (!$this->hasClass($className)) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - if (isset(self::$anonymousClasses[$className])) { - return self::$anonymousClasses[$className]->getDisplayName(); - } - - $reflectionClass = $this->classReflector->reflect($className); - - return $reflectionClass->getName(); - } - - public function supportsAnonymousClasses(): bool - { - return true; - } - - public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - if (isset($classNode->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!$scope->isInTrait()) { - $scopeFile = $scope->getFile(); - } else { - $scopeFile = $scope->getTraitReflection()->getFileName(); - if ($scopeFile === false) { - $scopeFile = $scope->getFile(); - } - } - - $filename = $this->fileHelper->normalizePath($this->relativePathHelper->getRelativePath($scopeFile), '/'); - $className = $this->anonymousClassNameHelper->getAnonymousClassName( - $classNode, - $scopeFile - ); - $classNode->name = new \PhpParser\Node\Identifier($className); - $classNode->setAttribute('anonymousClass', true); - - if (isset(self::$anonymousClasses[$className])) { - return self::$anonymousClasses[$className]; - } - - $reflectionClass = \PHPStan\BetterReflection\Reflection\ReflectionClass::createFromNode( - $this->classReflector, - $classNode, - new LocatedSource($this->printer->prettyPrint([$classNode]), $scopeFile), - null - ); - - self::$anonymousClasses[$className] = new ClassReflection( - $this->reflectionProviderProvider->getReflectionProvider(), - $this->fileTypeMapper, - $this->phpVersion, - $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), - $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), - sprintf('class@anonymous/%s:%s', $filename, $classNode->getLine()), - new ReflectionClass($reflectionClass), - $scopeFile, - null, - $this->stubPhpDocProvider->findClassPhpDoc($className) - ); - $this->classReflections[$className] = self::$anonymousClasses[$className]; - - return self::$anonymousClasses[$className]; - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->resolveFunctionName($nameNode, $scope) !== null; - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection - { - $functionName = $this->resolveFunctionName($nameNode, $scope); - if ($functionName === null) { - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } - - $lowerCasedFunctionName = strtolower($functionName); - if (isset($this->functionReflections[$lowerCasedFunctionName])) { - return $this->functionReflections[$lowerCasedFunctionName]; - } - - $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); - if ($nativeFunctionReflection !== null) { - $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; - return $nativeFunctionReflection; - } - - $this->functionReflections[$lowerCasedFunctionName] = $this->getCustomFunction($functionName); - - return $this->functionReflections[$lowerCasedFunctionName]; - } - - private function getCustomFunction(string $functionName): \PHPStan\Reflection\Php\PhpFunctionReflection - { - $reflectionFunction = new ReflectionFunction($this->functionReflector->reflect($functionName)); - $templateTypeMap = TemplateTypeMap::createEmpty(); - $phpDocParameterTags = []; - $phpDocReturnTag = null; - $phpDocThrowsTag = null; - $deprecatedTag = null; - $isDeprecated = false; - $isInternal = false; - $isFinal = false; - $isPure = null; - $resolvedPhpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($reflectionFunction->getName()); - if ($resolvedPhpDoc === null && $reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) { - $fileName = $reflectionFunction->getFileName(); - $docComment = $reflectionFunction->getDocComment(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); - } - - if ($resolvedPhpDoc !== null) { - $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); - $phpDocParameterTags = $resolvedPhpDoc->getParamTags(); - $phpDocReturnTag = $resolvedPhpDoc->getReturnTag(); - $phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag(); - $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $isPure = $resolvedPhpDoc->isPure(); - } - - return $this->functionReflectionFactory->create( - $reflectionFunction, - $templateTypeMap, - array_map(static function (ParamTag $paramTag): Type { - return $paramTag->getType(); - }, $phpDocParameterTags), - $phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null, - $phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null, - $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, - $isDeprecated, - $isInternal, - $isFinal, - $reflectionFunction->getFileName(), - $isPure - ); - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->resolveName($nameNode, function (string $name): bool { - try { - $this->functionReflector->reflect($name); - return true; - } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { - // pass - } catch (InvalidIdentifierName $e) { - // pass - } - - if ($this->nativeFunctionReflectionProvider->findFunctionReflection($name) !== null) { - return $this->phpstormStubsSourceStubber->isPresentFunction($name) !== false; - } - return false; - }, $scope); - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->resolveConstantName($nameNode, $scope) !== null; - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - $constantName = $this->resolveConstantName($nameNode, $scope); - if ($constantName === null) { - throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); - } - - $constantReflection = $this->constantReflector->reflect($constantName); - try { - $constantValue = $constantReflection->getValue(); - $constantValueType = ConstantTypeHelper::getTypeFromValue($constantValue); - $fileName = $constantReflection->getFileName(); - } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { - $constantValueType = new MixedType(); - $fileName = null; - } - - return new RuntimeConstantReflection( - $constantName, - $constantValueType, - $fileName - ); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->resolveName($nameNode, function (string $name): bool { - try { - $this->constantReflector->reflect($name); - return true; - } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { - // pass - } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { - // pass - } - return false; - }, $scope); - } - - /** - * @param \PhpParser\Node\Name $nameNode - * @param \Closure(string $name): bool $existsCallback - * @param Scope|null $scope - * @return string|null - */ - private function resolveName( - \PhpParser\Node\Name $nameNode, - \Closure $existsCallback, - ?Scope $scope - ): ?string - { - $name = (string) $nameNode; - if ($scope !== null && $scope->getNamespace() !== null && !$nameNode->isFullyQualified()) { - $namespacedName = sprintf('%s\\%s', $scope->getNamespace(), $name); - if ($existsCallback($namespacedName)) { - return $namespacedName; - } - } - - if ($existsCallback($name)) { - return $name; - } - - return null; - } - + private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider; + + private \PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider; + + private \PHPStan\BetterReflection\Reflector\ClassReflector $classReflector; + + private \PHPStan\BetterReflection\Reflector\FunctionReflector $functionReflector; + + private \PHPStan\BetterReflection\Reflector\ConstantReflector $constantReflector; + + private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + + private PhpVersion $phpVersion; + + private \PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider $nativeFunctionReflectionProvider; + + private StubPhpDocProvider $stubPhpDocProvider; + + private \PHPStan\Reflection\FunctionReflectionFactory $functionReflectionFactory; + + private RelativePathHelper $relativePathHelper; + + private AnonymousClassNameHelper $anonymousClassNameHelper; + + private \PhpParser\PrettyPrinter\Standard $printer; + + private \PHPStan\File\FileHelper $fileHelper; + + private PhpStormStubsSourceStubber $phpstormStubsSourceStubber; + + /** @var \PHPStan\Reflection\FunctionReflection[] */ + private array $functionReflections = []; + + /** @var \PHPStan\Reflection\ClassReflection[] */ + private array $classReflections = []; + + /** @var \PHPStan\Reflection\ClassReflection[] */ + private static array $anonymousClasses = []; + + public function __construct( + ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, + ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, + ClassReflector $classReflector, + FileTypeMapper $fileTypeMapper, + PhpVersion $phpVersion, + NativeFunctionReflectionProvider $nativeFunctionReflectionProvider, + StubPhpDocProvider $stubPhpDocProvider, + FunctionReflectionFactory $functionReflectionFactory, + RelativePathHelper $relativePathHelper, + AnonymousClassNameHelper $anonymousClassNameHelper, + Standard $printer, + FileHelper $fileHelper, + FunctionReflector $functionReflector, + ConstantReflector $constantReflector, + PhpStormStubsSourceStubber $phpstormStubsSourceStubber + ) { + $this->reflectionProviderProvider = $reflectionProviderProvider; + $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; + $this->classReflector = $classReflector; + $this->fileTypeMapper = $fileTypeMapper; + $this->phpVersion = $phpVersion; + $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; + $this->stubPhpDocProvider = $stubPhpDocProvider; + $this->functionReflectionFactory = $functionReflectionFactory; + $this->relativePathHelper = $relativePathHelper; + $this->anonymousClassNameHelper = $anonymousClassNameHelper; + $this->printer = $printer; + $this->fileHelper = $fileHelper; + $this->functionReflector = $functionReflector; + $this->constantReflector = $constantReflector; + $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; + } + + public function hasClass(string $className): bool + { + if (isset(self::$anonymousClasses[$className])) { + return true; + } + + try { + $this->classReflector->reflect($className); + return true; + } catch (IdentifierNotFound $e) { + return false; + } catch (InvalidIdentifierName $e) { + return false; + } + } + + public function getClass(string $className): ClassReflection + { + if (isset(self::$anonymousClasses[$className])) { + return self::$anonymousClasses[$className]; + } + + try { + $reflectionClass = $this->classReflector->reflect($className); + } catch (IdentifierNotFound $e) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + $reflectionClassName = strtolower($reflectionClass->getName()); + + if (array_key_exists($reflectionClassName, $this->classReflections)) { + return $this->classReflections[$reflectionClassName]; + } + + $classReflection = new ClassReflection( + $this->reflectionProviderProvider->getReflectionProvider(), + $this->fileTypeMapper, + $this->phpVersion, + $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $reflectionClass->getName(), + new ReflectionClass($reflectionClass), + null, + null, + $this->stubPhpDocProvider->findClassPhpDoc($reflectionClass->getName()) + ); + + $this->classReflections[$reflectionClassName] = $classReflection; + + return $classReflection; + } + + public function getClassName(string $className): string + { + if (!$this->hasClass($className)) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + if (isset(self::$anonymousClasses[$className])) { + return self::$anonymousClasses[$className]->getDisplayName(); + } + + $reflectionClass = $this->classReflector->reflect($className); + + return $reflectionClass->getName(); + } + + public function supportsAnonymousClasses(): bool + { + return true; + } + + public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection + { + if (isset($classNode->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!$scope->isInTrait()) { + $scopeFile = $scope->getFile(); + } else { + $scopeFile = $scope->getTraitReflection()->getFileName(); + if ($scopeFile === false) { + $scopeFile = $scope->getFile(); + } + } + + $filename = $this->fileHelper->normalizePath($this->relativePathHelper->getRelativePath($scopeFile), '/'); + $className = $this->anonymousClassNameHelper->getAnonymousClassName( + $classNode, + $scopeFile + ); + $classNode->name = new \PhpParser\Node\Identifier($className); + $classNode->setAttribute('anonymousClass', true); + + if (isset(self::$anonymousClasses[$className])) { + return self::$anonymousClasses[$className]; + } + + $reflectionClass = \PHPStan\BetterReflection\Reflection\ReflectionClass::createFromNode( + $this->classReflector, + $classNode, + new LocatedSource($this->printer->prettyPrint([$classNode]), $scopeFile), + null + ); + + self::$anonymousClasses[$className] = new ClassReflection( + $this->reflectionProviderProvider->getReflectionProvider(), + $this->fileTypeMapper, + $this->phpVersion, + $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + sprintf('class@anonymous/%s:%s', $filename, $classNode->getLine()), + new ReflectionClass($reflectionClass), + $scopeFile, + null, + $this->stubPhpDocProvider->findClassPhpDoc($className) + ); + $this->classReflections[$className] = self::$anonymousClasses[$className]; + + return self::$anonymousClasses[$className]; + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->resolveFunctionName($nameNode, $scope) !== null; + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection + { + $functionName = $this->resolveFunctionName($nameNode, $scope); + if ($functionName === null) { + throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); + } + + $lowerCasedFunctionName = strtolower($functionName); + if (isset($this->functionReflections[$lowerCasedFunctionName])) { + return $this->functionReflections[$lowerCasedFunctionName]; + } + + $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); + if ($nativeFunctionReflection !== null) { + $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; + return $nativeFunctionReflection; + } + + $this->functionReflections[$lowerCasedFunctionName] = $this->getCustomFunction($functionName); + + return $this->functionReflections[$lowerCasedFunctionName]; + } + + private function getCustomFunction(string $functionName): \PHPStan\Reflection\Php\PhpFunctionReflection + { + $reflectionFunction = new ReflectionFunction($this->functionReflector->reflect($functionName)); + $templateTypeMap = TemplateTypeMap::createEmpty(); + $phpDocParameterTags = []; + $phpDocReturnTag = null; + $phpDocThrowsTag = null; + $deprecatedTag = null; + $isDeprecated = false; + $isInternal = false; + $isFinal = false; + $isPure = null; + $resolvedPhpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($reflectionFunction->getName()); + if ($resolvedPhpDoc === null && $reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) { + $fileName = $reflectionFunction->getFileName(); + $docComment = $reflectionFunction->getDocComment(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); + } + + if ($resolvedPhpDoc !== null) { + $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); + $phpDocParameterTags = $resolvedPhpDoc->getParamTags(); + $phpDocReturnTag = $resolvedPhpDoc->getReturnTag(); + $phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag(); + $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); + $isPure = $resolvedPhpDoc->isPure(); + } + + return $this->functionReflectionFactory->create( + $reflectionFunction, + $templateTypeMap, + array_map(static function (ParamTag $paramTag): Type { + return $paramTag->getType(); + }, $phpDocParameterTags), + $phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null, + $phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null, + $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, + $isDeprecated, + $isInternal, + $isFinal, + $reflectionFunction->getFileName(), + $isPure + ); + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->resolveName($nameNode, function (string $name): bool { + try { + $this->functionReflector->reflect($name); + return true; + } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { + // pass + } catch (InvalidIdentifierName $e) { + // pass + } + + if ($this->nativeFunctionReflectionProvider->findFunctionReflection($name) !== null) { + return $this->phpstormStubsSourceStubber->isPresentFunction($name) !== false; + } + return false; + }, $scope); + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->resolveConstantName($nameNode, $scope) !== null; + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + $constantName = $this->resolveConstantName($nameNode, $scope); + if ($constantName === null) { + throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); + } + + $constantReflection = $this->constantReflector->reflect($constantName); + try { + $constantValue = $constantReflection->getValue(); + $constantValueType = ConstantTypeHelper::getTypeFromValue($constantValue); + $fileName = $constantReflection->getFileName(); + } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { + $constantValueType = new MixedType(); + $fileName = null; + } + + return new RuntimeConstantReflection( + $constantName, + $constantValueType, + $fileName + ); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->resolveName($nameNode, function (string $name): bool { + try { + $this->constantReflector->reflect($name); + return true; + } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { + // pass + } catch (UnableToCompileNode | NotAClassReflection | NotAnInterfaceReflection $e) { + // pass + } + return false; + }, $scope); + } + + /** + * @param \PhpParser\Node\Name $nameNode + * @param \Closure(string $name): bool $existsCallback + * @param Scope|null $scope + * @return string|null + */ + private function resolveName( + \PhpParser\Node\Name $nameNode, + \Closure $existsCallback, + ?Scope $scope + ): ?string { + $name = (string) $nameNode; + if ($scope !== null && $scope->getNamespace() !== null && !$nameNode->isFullyQualified()) { + $namespacedName = sprintf('%s\\%s', $scope->getNamespace(), $name); + if ($existsCallback($namespacedName)) { + return $namespacedName; + } + } + + if ($existsCallback($name)) { + return $name; + } + + return null; + } } diff --git a/src/Reflection/BetterReflection/BetterReflectionProviderFactory.php b/src/Reflection/BetterReflection/BetterReflectionProviderFactory.php index 903fc7c02b..dfbf45f694 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProviderFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionProviderFactory.php @@ -1,4 +1,6 @@ -parser = $parser; - $this->php8Parser = $php8Parser; - $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; - $this->reflectionSourceStubber = $reflectionSourceStubber; - $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; - $this->optimizedDirectorySourceLocatorRepository = $optimizedDirectorySourceLocatorRepository; - $this->composerJsonAndInstalledJsonSourceLocatorMaker = $composerJsonAndInstalledJsonSourceLocatorMaker; - $this->autoloadSourceLocator = $autoloadSourceLocator; - $this->container = $container; - $this->autoloadDirectories = $autoloadDirectories; - $this->autoloadFiles = $autoloadFiles; - $this->scanFiles = $scanFiles; - $this->scanDirectories = $scanDirectories; - $this->analysedPaths = $analysedPaths; - $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - $this->analysedPathsFromConfig = $analysedPathsFromConfig; - $this->singleReflectionFile = $singleReflectionFile; - $this->staticReflectionClassNamePatterns = $staticReflectionClassNamePatterns; - } - - public function create(): SourceLocator - { - $locators = []; - - if ($this->singleReflectionFile !== null) { - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($this->singleReflectionFile); - } - - $analysedDirectories = []; - $analysedFiles = []; - - foreach (array_merge($this->analysedPaths, $this->analysedPathsFromConfig) as $analysedPath) { - if (is_file($analysedPath)) { - $analysedFiles[] = $analysedPath; - continue; - } - - if (!is_dir($analysedPath)) { - continue; - } - - $analysedDirectories[] = $analysedPath; - } - - $analysedFiles = array_unique(array_merge($analysedFiles, $this->autoloadFiles, $this->scanFiles)); - foreach ($analysedFiles as $analysedFile) { - $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($analysedFile); - } - - $directories = array_unique(array_merge($analysedDirectories, $this->autoloadDirectories, $this->scanDirectories)); - foreach ($directories as $directory) { - $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($directory); - } - - $astLocator = new Locator($this->parser, function (): FunctionReflector { - return $this->container->getService('betterReflectionFunctionReflector'); - }); - - $astPhp8Locator = new Locator($this->php8Parser, function (): FunctionReflector { - return $this->container->getService('betterReflectionFunctionReflector'); - }); - - $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); - $locators[] = new ClassBlacklistSourceLocator($this->autoloadSourceLocator, $this->staticReflectionClassNamePatterns); - foreach ($this->composerAutoloaderProjectPaths as $composerAutoloaderProjectPath) { - $locator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerAutoloaderProjectPath); - if ($locator === null) { - continue; - } - $locators[] = $locator; - } - $locators[] = new ClassWhitelistSourceLocator($this->autoloadSourceLocator, $this->staticReflectionClassNamePatterns); - $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); - $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); - - return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); - } - + /** @var \PhpParser\Parser */ + private $parser; + + /** @var \PhpParser\Parser */ + private $php8Parser; + + /** @var PhpStormStubsSourceStubber */ + private $phpstormStubsSourceStubber; + + /** @var ReflectionSourceStubber */ + private $reflectionSourceStubber; + + /** @var \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository */ + private $optimizedSingleFileSourceLocatorRepository; + + /** @var \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository */ + private $optimizedDirectorySourceLocatorRepository; + + /** @var ComposerJsonAndInstalledJsonSourceLocatorMaker */ + private $composerJsonAndInstalledJsonSourceLocatorMaker; + + /** @var AutoloadSourceLocator */ + private $autoloadSourceLocator; + + /** @var \PHPStan\DependencyInjection\Container */ + private $container; + + /** @var string[] */ + private $autoloadDirectories; + + /** @var string[] */ + private $autoloadFiles; + + /** @var string[] */ + private $scanFiles; + + /** @var string[] */ + private $scanDirectories; + + /** @var string[] */ + private $analysedPaths; + + /** @var string[] */ + private $composerAutoloaderProjectPaths; + + /** @var string[] */ + private $analysedPathsFromConfig; + + /** @var string|null */ + private $singleReflectionFile; + + /** @var string[] */ + private array $staticReflectionClassNamePatterns; + + /** + * @param string[] $autoloadDirectories + * @param string[] $autoloadFiles + * @param string[] $scanFiles + * @param string[] $scanDirectories + * @param string[] $analysedPaths + * @param string[] $composerAutoloaderProjectPaths + * @param string[] $analysedPathsFromConfig + * @param string|null $singleReflectionFile, + * @param string[] $staticReflectionClassNamePatterns + */ + public function __construct( + \PhpParser\Parser $parser, + \PhpParser\Parser $php8Parser, + PhpStormStubsSourceStubber $phpstormStubsSourceStubber, + ReflectionSourceStubber $reflectionSourceStubber, + OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, + OptimizedDirectorySourceLocatorRepository $optimizedDirectorySourceLocatorRepository, + ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker, + AutoloadSourceLocator $autoloadSourceLocator, + Container $container, + array $autoloadDirectories, + array $autoloadFiles, + array $scanFiles, + array $scanDirectories, + array $analysedPaths, + array $composerAutoloaderProjectPaths, + array $analysedPathsFromConfig, + ?string $singleReflectionFile, + array $staticReflectionClassNamePatterns + ) { + $this->parser = $parser; + $this->php8Parser = $php8Parser; + $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; + $this->reflectionSourceStubber = $reflectionSourceStubber; + $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; + $this->optimizedDirectorySourceLocatorRepository = $optimizedDirectorySourceLocatorRepository; + $this->composerJsonAndInstalledJsonSourceLocatorMaker = $composerJsonAndInstalledJsonSourceLocatorMaker; + $this->autoloadSourceLocator = $autoloadSourceLocator; + $this->container = $container; + $this->autoloadDirectories = $autoloadDirectories; + $this->autoloadFiles = $autoloadFiles; + $this->scanFiles = $scanFiles; + $this->scanDirectories = $scanDirectories; + $this->analysedPaths = $analysedPaths; + $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; + $this->analysedPathsFromConfig = $analysedPathsFromConfig; + $this->singleReflectionFile = $singleReflectionFile; + $this->staticReflectionClassNamePatterns = $staticReflectionClassNamePatterns; + } + + public function create(): SourceLocator + { + $locators = []; + + if ($this->singleReflectionFile !== null) { + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($this->singleReflectionFile); + } + + $analysedDirectories = []; + $analysedFiles = []; + + foreach (array_merge($this->analysedPaths, $this->analysedPathsFromConfig) as $analysedPath) { + if (is_file($analysedPath)) { + $analysedFiles[] = $analysedPath; + continue; + } + + if (!is_dir($analysedPath)) { + continue; + } + + $analysedDirectories[] = $analysedPath; + } + + $analysedFiles = array_unique(array_merge($analysedFiles, $this->autoloadFiles, $this->scanFiles)); + foreach ($analysedFiles as $analysedFile) { + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($analysedFile); + } + + $directories = array_unique(array_merge($analysedDirectories, $this->autoloadDirectories, $this->scanDirectories)); + foreach ($directories as $directory) { + $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($directory); + } + + $astLocator = new Locator($this->parser, function (): FunctionReflector { + return $this->container->getService('betterReflectionFunctionReflector'); + }); + + $astPhp8Locator = new Locator($this->php8Parser, function (): FunctionReflector { + return $this->container->getService('betterReflectionFunctionReflector'); + }); + + $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); + $locators[] = new ClassBlacklistSourceLocator($this->autoloadSourceLocator, $this->staticReflectionClassNamePatterns); + foreach ($this->composerAutoloaderProjectPaths as $composerAutoloaderProjectPath) { + $locator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerAutoloaderProjectPath); + if ($locator === null) { + continue; + } + $locators[] = $locator; + } + $locators[] = new ClassWhitelistSourceLocator($this->autoloadSourceLocator, $this->staticReflectionClassNamePatterns); + $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + + return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); + } } diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php index d34a77a86f..62af8a4c2b 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php @@ -1,4 +1,6 @@ - */ + private array $reflections = []; - /** @var array */ - private array $reflections = []; - - /** - * Create a ReflectionClass for the specified $className. - * - * @return \PHPStan\BetterReflection\Reflection\ReflectionClass - * - * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound - */ - public function reflect(string $className): Reflection - { - $lowerClassName = strtolower($className); - if (isset($this->reflections[$lowerClassName])) { - if ($this->reflections[$lowerClassName] instanceof \Throwable) { - throw $this->reflections[$lowerClassName]; - } - return $this->reflections[$lowerClassName]; - } - - try { - return $this->reflections[$lowerClassName] = parent::reflect($className); - } catch (\Throwable $e) { - $this->reflections[$lowerClassName] = $e; - throw $e; - } - } + /** + * Create a ReflectionClass for the specified $className. + * + * @return \PHPStan\BetterReflection\Reflection\ReflectionClass + * + * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound + */ + public function reflect(string $className): Reflection + { + $lowerClassName = strtolower($className); + if (isset($this->reflections[$lowerClassName])) { + if ($this->reflections[$lowerClassName] instanceof \Throwable) { + throw $this->reflections[$lowerClassName]; + } + return $this->reflections[$lowerClassName]; + } + try { + return $this->reflections[$lowerClassName] = parent::reflect($className); + } catch (\Throwable $e) { + $this->reflections[$lowerClassName] = $e; + throw $e; + } + } } diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php index aca5db5d44..e297013cc8 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php @@ -1,4 +1,6 @@ - */ + private array $reflections = []; - /** @var array */ - private array $reflections = []; - - /** - * Create a ReflectionConstant for the specified $constantName. - * - * @return \PHPStan\BetterReflection\Reflection\ReflectionConstant - * - * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound - */ - public function reflect(string $constantName): Reflection - { - if (isset($this->reflections[$constantName])) { - if ($this->reflections[$constantName] instanceof \Throwable) { - throw $this->reflections[$constantName]; - } - return $this->reflections[$constantName]; - } - - try { - return $this->reflections[$constantName] = parent::reflect($constantName); - } catch (\Throwable $e) { - $this->reflections[$constantName] = $e; - throw $e; - } - } + /** + * Create a ReflectionConstant for the specified $constantName. + * + * @return \PHPStan\BetterReflection\Reflection\ReflectionConstant + * + * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound + */ + public function reflect(string $constantName): Reflection + { + if (isset($this->reflections[$constantName])) { + if ($this->reflections[$constantName] instanceof \Throwable) { + throw $this->reflections[$constantName]; + } + return $this->reflections[$constantName]; + } + try { + return $this->reflections[$constantName] = parent::reflect($constantName); + } catch (\Throwable $e) { + $this->reflections[$constantName] = $e; + throw $e; + } + } } diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php index 078392ef1b..560518c902 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php @@ -1,4 +1,6 @@ - */ + private array $reflections = []; - /** @var array */ - private array $reflections = []; - - /** - * Create a ReflectionFunction for the specified $functionName. - * - * @return \PHPStan\BetterReflection\Reflection\ReflectionFunction - * - * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound - */ - public function reflect(string $functionName): Reflection - { - $lowerFunctionName = strtolower($functionName); - if (isset($this->reflections[$lowerFunctionName])) { - if ($this->reflections[$lowerFunctionName] instanceof \Throwable) { - throw $this->reflections[$lowerFunctionName]; - } - return $this->reflections[$lowerFunctionName]; - } - - try { - return $this->reflections[$lowerFunctionName] = parent::reflect($functionName); - } catch (\Throwable $e) { - $this->reflections[$lowerFunctionName] = $e; - throw $e; - } - } + /** + * Create a ReflectionFunction for the specified $functionName. + * + * @return \PHPStan\BetterReflection\Reflection\ReflectionFunction + * + * @throws \PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound + */ + public function reflect(string $functionName): Reflection + { + $lowerFunctionName = strtolower($functionName); + if (isset($this->reflections[$lowerFunctionName])) { + if ($this->reflections[$lowerFunctionName] instanceof \Throwable) { + throw $this->reflections[$lowerFunctionName]; + } + return $this->reflections[$lowerFunctionName]; + } + try { + return $this->reflections[$lowerFunctionName] = parent::reflect($functionName); + } catch (\Throwable $e) { + $this->reflections[$lowerFunctionName] = $e; + throw $e; + } + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index d99b4b8ce3..cd88099b3d 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -1,4 +1,6 @@ ->> */ - private array $classNodes = []; - - /** @var array */ - private array $classReflections = []; - - /** @var array> */ - private array $functionNodes = []; - - /** @var array> */ - private array $constantNodes = []; - - /** @var array */ - private array $locatedSourcesByFile = []; - - public function __construct(FileNodesFetcher $fileNodesFetcher) - { - $this->fileNodesFetcher = $fileNodesFetcher; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isFunction()) { - $functionName = $identifier->getName(); - $loweredFunctionName = strtolower($functionName); - if (array_key_exists($loweredFunctionName, $this->functionNodes)) { - $nodeToReflection = new NodeToReflection(); - return $nodeToReflection->__invoke( - $reflector, - $this->functionNodes[$loweredFunctionName]->getNode(), - $this->locatedSourcesByFile[$this->functionNodes[$loweredFunctionName]->getFileName()], - $this->functionNodes[$loweredFunctionName]->getNamespace() - ); - } - if (!function_exists($functionName)) { - return null; - } - - $reflection = new ReflectionFunction($functionName); - $reflectionFileName = $reflection->getFileName(); - - if (!is_string($reflectionFileName)) { - return null; - } - if (!file_exists($reflectionFileName)) { - return null; - } - - return $this->findReflection($reflector, $reflectionFileName, $identifier, null); - } - - if ($identifier->isConstant()) { - $constantName = $identifier->getName(); - $nodeToReflection = new NodeToReflection(); - foreach ($this->constantNodes as $stmtConst) { - if ($stmtConst->getNode() instanceof FuncCall) { - $constantReflection = $nodeToReflection->__invoke( - $reflector, - $stmtConst->getNode(), - $this->locatedSourcesByFile[$stmtConst->getFileName()], - $stmtConst->getNamespace() - ); - if ($constantReflection === null) { - continue; - } - if (!$constantReflection instanceof ReflectionConstant) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($constantReflection->getName() !== $identifier->getName()) { - continue; - } - - return $constantReflection; - } - - foreach (array_keys($stmtConst->getNode()->consts) as $i) { - $constantReflection = $nodeToReflection->__invoke( - $reflector, - $stmtConst->getNode(), - $this->locatedSourcesByFile[$stmtConst->getFileName()], - $stmtConst->getNamespace(), - $i - ); - if ($constantReflection === null) { - continue; - } - if (!$constantReflection instanceof ReflectionConstant) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($constantReflection->getName() !== $identifier->getName()) { - continue; - } - - return $constantReflection; - } - } - - if (!defined($constantName)) { - return null; - } - - $reflection = ReflectionConstant::createFromNode( - $reflector, - new FuncCall(new Name('define'), [ - new Arg(new String_($constantName)), - new Arg(new String_('')), // not actually used - ]), - new LocatedSource('', null), - null, - null - ); - $reflection->populateValue(constant($constantName)); - - return $reflection; - } - - if (!$identifier->isClass()) { - return null; - } - - $loweredClassName = strtolower($identifier->getName()); - if (array_key_exists($loweredClassName, $this->classReflections)) { - return $this->classReflections[$loweredClassName]; - } - - $locateResult = $this->locateClassByName($identifier->getName()); - if ($locateResult === null) { - if (array_key_exists($loweredClassName, $this->classNodes)) { - foreach ($this->classNodes[$loweredClassName] as $classNode) { - $nodeToReflection = new NodeToReflection(); - return $this->classReflections[$loweredClassName] = $nodeToReflection->__invoke( - $reflector, - $classNode->getNode(), - $this->locatedSourcesByFile[$classNode->getFileName()], - $classNode->getNamespace() - ); - } - } - return null; - } - [$potentiallyLocatedFile, $className, $startLine] = $locateResult; - - return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()), $startLine); - } - - private function findReflection(Reflector $reflector, string $file, Identifier $identifier, ?int $startLine): ?Reflection - { - if (!array_key_exists($file, $this->locatedSourcesByFile)) { - $result = $this->fileNodesFetcher->fetchNodes($file); - $this->locatedSourcesByFile[$file] = $result->getLocatedSource(); - foreach ($result->getClassNodes() as $className => $fetchedClassNodes) { - foreach ($fetchedClassNodes as $fetchedClassNode) { - $this->classNodes[$className][] = $fetchedClassNode; - } - } - foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) { - $this->functionNodes[$functionName] = $fetchedFunctionNode; - } - foreach ($result->getConstantNodes() as $fetchedConstantNode) { - $this->constantNodes[] = $fetchedConstantNode; - } - $locatedSource = $result->getLocatedSource(); - } else { - $locatedSource = $this->locatedSourcesByFile[$file]; - } - - $nodeToReflection = new NodeToReflection(); - if ($identifier->isClass()) { - $identifierName = strtolower($identifier->getName()); - if (array_key_exists($identifierName, $this->classReflections)) { - return $this->classReflections[$identifierName]; - } - if (!array_key_exists($identifierName, $this->classNodes)) { - return null; - } - - foreach ($this->classNodes[$identifierName] as $classNode) { - if ($startLine !== null) { - if (count($classNode->getNode()->attrGroups) > 0 && PHP_VERSION_ID < 80000) { - $startLine--; - } - if ($startLine !== $classNode->getNode()->getStartLine()) { - continue; - } - } - - return $this->classReflections[$identifierName] = $nodeToReflection->__invoke( - $reflector, - $classNode->getNode(), - $locatedSource, - $classNode->getNamespace() - ); - } - - return null; - } - if ($identifier->isFunction()) { - $identifierName = strtolower($identifier->getName()); - if (!array_key_exists($identifierName, $this->functionNodes)) { - return null; - } - - return $nodeToReflection->__invoke( - $reflector, - $this->functionNodes[$identifierName]->getNode(), - $locatedSource, - $this->functionNodes[$identifierName]->getNamespace() - ); - } - - return null; - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return []; - } - - /** - * Attempt to locate a class by name. - * - * If class already exists, simply use internal reflection API to get the - * filename and store it. - * - * If class does not exist, we make an assumption that whatever autoloaders - * that are registered will be loading a file. We then override the file:// - * protocol stream wrapper to "capture" the filename we expect the class to - * be in, and then restore it. Note that class_exists will cause an error - * that it cannot find the file, so we squelch the errors by overriding the - * error handler temporarily. - * - * @throws ReflectionException - * @return array{string, string, int|null}|null - */ - private function locateClassByName(string $className): ?array - { - if (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false)) { - $reflection = new ReflectionClass($className); - $filename = $reflection->getFileName(); - - if (!is_string($filename)) { - return null; - } - - if (!file_exists($filename)) { - return null; - } - - return [$filename, $reflection->getName(), $reflection->getStartLine() !== false ? $reflection->getStartLine() : null]; - } - - $this->silenceErrors(); - - try { - /** @var array{string, string, null}|null */ - return FileReadTrapStreamWrapper::withStreamWrapperOverride( - static function () use ($className): ?array { - $functions = spl_autoload_functions(); - if ($functions === false) { - return null; - } - - foreach ($functions as $preExistingAutoloader) { - $preExistingAutoloader($className); - - /** - * This static variable is populated by the side-effect of the stream wrapper - * trying to read the file path when `include()` is used by an autoloader. - * - * This will not be `null` when the autoloader tried to read a file. - */ - if (FileReadTrapStreamWrapper::$autoloadLocatedFile !== null) { - return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className, null]; - } - } - - return null; - } - ); - } finally { - restore_error_handler(); - } - } - - private function silenceErrors(): void - { - set_error_handler(static function (): bool { - return true; - }); - } - + private FileNodesFetcher $fileNodesFetcher; + + /** @var array>> */ + private array $classNodes = []; + + /** @var array */ + private array $classReflections = []; + + /** @var array> */ + private array $functionNodes = []; + + /** @var array> */ + private array $constantNodes = []; + + /** @var array */ + private array $locatedSourcesByFile = []; + + public function __construct(FileNodesFetcher $fileNodesFetcher) + { + $this->fileNodesFetcher = $fileNodesFetcher; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isFunction()) { + $functionName = $identifier->getName(); + $loweredFunctionName = strtolower($functionName); + if (array_key_exists($loweredFunctionName, $this->functionNodes)) { + $nodeToReflection = new NodeToReflection(); + return $nodeToReflection->__invoke( + $reflector, + $this->functionNodes[$loweredFunctionName]->getNode(), + $this->locatedSourcesByFile[$this->functionNodes[$loweredFunctionName]->getFileName()], + $this->functionNodes[$loweredFunctionName]->getNamespace() + ); + } + if (!function_exists($functionName)) { + return null; + } + + $reflection = new ReflectionFunction($functionName); + $reflectionFileName = $reflection->getFileName(); + + if (!is_string($reflectionFileName)) { + return null; + } + if (!file_exists($reflectionFileName)) { + return null; + } + + return $this->findReflection($reflector, $reflectionFileName, $identifier, null); + } + + if ($identifier->isConstant()) { + $constantName = $identifier->getName(); + $nodeToReflection = new NodeToReflection(); + foreach ($this->constantNodes as $stmtConst) { + if ($stmtConst->getNode() instanceof FuncCall) { + $constantReflection = $nodeToReflection->__invoke( + $reflector, + $stmtConst->getNode(), + $this->locatedSourcesByFile[$stmtConst->getFileName()], + $stmtConst->getNamespace() + ); + if ($constantReflection === null) { + continue; + } + if (!$constantReflection instanceof ReflectionConstant) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($constantReflection->getName() !== $identifier->getName()) { + continue; + } + + return $constantReflection; + } + + foreach (array_keys($stmtConst->getNode()->consts) as $i) { + $constantReflection = $nodeToReflection->__invoke( + $reflector, + $stmtConst->getNode(), + $this->locatedSourcesByFile[$stmtConst->getFileName()], + $stmtConst->getNamespace(), + $i + ); + if ($constantReflection === null) { + continue; + } + if (!$constantReflection instanceof ReflectionConstant) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($constantReflection->getName() !== $identifier->getName()) { + continue; + } + + return $constantReflection; + } + } + + if (!defined($constantName)) { + return null; + } + + $reflection = ReflectionConstant::createFromNode( + $reflector, + new FuncCall(new Name('define'), [ + new Arg(new String_($constantName)), + new Arg(new String_('')), // not actually used + ]), + new LocatedSource('', null), + null, + null + ); + $reflection->populateValue(constant($constantName)); + + return $reflection; + } + + if (!$identifier->isClass()) { + return null; + } + + $loweredClassName = strtolower($identifier->getName()); + if (array_key_exists($loweredClassName, $this->classReflections)) { + return $this->classReflections[$loweredClassName]; + } + + $locateResult = $this->locateClassByName($identifier->getName()); + if ($locateResult === null) { + if (array_key_exists($loweredClassName, $this->classNodes)) { + foreach ($this->classNodes[$loweredClassName] as $classNode) { + $nodeToReflection = new NodeToReflection(); + return $this->classReflections[$loweredClassName] = $nodeToReflection->__invoke( + $reflector, + $classNode->getNode(), + $this->locatedSourcesByFile[$classNode->getFileName()], + $classNode->getNamespace() + ); + } + } + return null; + } + [$potentiallyLocatedFile, $className, $startLine] = $locateResult; + + return $this->findReflection($reflector, $potentiallyLocatedFile, new Identifier($className, $identifier->getType()), $startLine); + } + + private function findReflection(Reflector $reflector, string $file, Identifier $identifier, ?int $startLine): ?Reflection + { + if (!array_key_exists($file, $this->locatedSourcesByFile)) { + $result = $this->fileNodesFetcher->fetchNodes($file); + $this->locatedSourcesByFile[$file] = $result->getLocatedSource(); + foreach ($result->getClassNodes() as $className => $fetchedClassNodes) { + foreach ($fetchedClassNodes as $fetchedClassNode) { + $this->classNodes[$className][] = $fetchedClassNode; + } + } + foreach ($result->getFunctionNodes() as $functionName => $fetchedFunctionNode) { + $this->functionNodes[$functionName] = $fetchedFunctionNode; + } + foreach ($result->getConstantNodes() as $fetchedConstantNode) { + $this->constantNodes[] = $fetchedConstantNode; + } + $locatedSource = $result->getLocatedSource(); + } else { + $locatedSource = $this->locatedSourcesByFile[$file]; + } + + $nodeToReflection = new NodeToReflection(); + if ($identifier->isClass()) { + $identifierName = strtolower($identifier->getName()); + if (array_key_exists($identifierName, $this->classReflections)) { + return $this->classReflections[$identifierName]; + } + if (!array_key_exists($identifierName, $this->classNodes)) { + return null; + } + + foreach ($this->classNodes[$identifierName] as $classNode) { + if ($startLine !== null) { + if (count($classNode->getNode()->attrGroups) > 0 && PHP_VERSION_ID < 80000) { + $startLine--; + } + if ($startLine !== $classNode->getNode()->getStartLine()) { + continue; + } + } + + return $this->classReflections[$identifierName] = $nodeToReflection->__invoke( + $reflector, + $classNode->getNode(), + $locatedSource, + $classNode->getNamespace() + ); + } + + return null; + } + if ($identifier->isFunction()) { + $identifierName = strtolower($identifier->getName()); + if (!array_key_exists($identifierName, $this->functionNodes)) { + return null; + } + + return $nodeToReflection->__invoke( + $reflector, + $this->functionNodes[$identifierName]->getNode(), + $locatedSource, + $this->functionNodes[$identifierName]->getNamespace() + ); + } + + return null; + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return []; + } + + /** + * Attempt to locate a class by name. + * + * If class already exists, simply use internal reflection API to get the + * filename and store it. + * + * If class does not exist, we make an assumption that whatever autoloaders + * that are registered will be loading a file. We then override the file:// + * protocol stream wrapper to "capture" the filename we expect the class to + * be in, and then restore it. Note that class_exists will cause an error + * that it cannot find the file, so we squelch the errors by overriding the + * error handler temporarily. + * + * @throws ReflectionException + * @return array{string, string, int|null}|null + */ + private function locateClassByName(string $className): ?array + { + if (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false)) { + $reflection = new ReflectionClass($className); + $filename = $reflection->getFileName(); + + if (!is_string($filename)) { + return null; + } + + if (!file_exists($filename)) { + return null; + } + + return [$filename, $reflection->getName(), $reflection->getStartLine() !== false ? $reflection->getStartLine() : null]; + } + + $this->silenceErrors(); + + try { + /** @var array{string, string, null}|null */ + return FileReadTrapStreamWrapper::withStreamWrapperOverride( + static function () use ($className): ?array { + $functions = spl_autoload_functions(); + if ($functions === false) { + return null; + } + + foreach ($functions as $preExistingAutoloader) { + $preExistingAutoloader($className); + + /** + * This static variable is populated by the side-effect of the stream wrapper + * trying to read the file path when `include()` is used by an autoloader. + * + * This will not be `null` when the autoloader tried to read a file. + */ + if (FileReadTrapStreamWrapper::$autoloadLocatedFile !== null) { + return [FileReadTrapStreamWrapper::$autoloadLocatedFile, $className, null]; + } + } + + return null; + } + ); + } finally { + restore_error_handler(); + } + } + + private function silenceErrors(): void + { + set_error_handler(static function (): bool { + return true; + }); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 30ef0852e4..536d7f8b09 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -1,4 +1,6 @@ ->> */ - private array $classNodes; - - /** @var array> */ - private array $functionNodes; - - /** @var array> */ - private array $constantNodes; - - private ?\PhpParser\Node\Stmt\Namespace_ $currentNamespaceNode = null; - - public function enterNode(\PhpParser\Node $node): ?int - { - if ($node instanceof Namespace_) { - $this->currentNamespaceNode = $node; - } - - if ($node instanceof \PhpParser\Node\Stmt\ClassLike) { - if ($node->name !== null) { - $fullClassName = $node->name->toString(); - if ($this->currentNamespaceNode !== null && $this->currentNamespaceNode->name !== null) { - $fullClassName = $this->currentNamespaceNode->name . '\\' . $fullClassName; - } - $this->classNodes[strtolower($fullClassName)][] = new FetchedNode( - $node, - $this->currentNamespaceNode, - $this->fileName - ); - } - - return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - if ($node instanceof \PhpParser\Node\Stmt\Function_) { - $this->functionNodes[strtolower($node->namespacedName->toString())] = new FetchedNode( - $node, - $this->currentNamespaceNode, - $this->fileName - ); - - return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - if ($node instanceof \PhpParser\Node\Stmt\Const_) { - $this->constantNodes[] = new FetchedNode( - $node, - $this->currentNamespaceNode, - $this->fileName - ); - - return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - if ($node instanceof \PhpParser\Node\Expr\FuncCall) { - try { - ConstantNodeChecker::assertValidDefineFunctionCall($node); - } catch (InvalidConstantNode $e) { - return null; - } - - /** @var \PhpParser\Node\Scalar\String_ $nameNode */ - $nameNode = $node->args[0]->value; - $constantName = $nameNode->value; - - if (defined($constantName)) { - $constantValue = constant($constantName); - $node->args[1]->value = BuilderHelpers::normalizeValue($constantValue); - } - - $constantNode = new FetchedNode( - $node, - $this->currentNamespaceNode, - $this->fileName - ); - $this->constantNodes[] = $constantNode; - - return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - return null; - } - - /** - * @param \PhpParser\Node $node - * @return null - */ - public function leaveNode(\PhpParser\Node $node) - { - if (!$node instanceof Namespace_) { - return null; - } - - $this->currentNamespaceNode = null; - return null; - } - - /** - * @return array>> - */ - public function getClassNodes(): array - { - return $this->classNodes; - } - - /** - * @return array> - */ - public function getFunctionNodes(): array - { - return $this->functionNodes; - } - - /** - * @return array> - */ - public function getConstantNodes(): array - { - return $this->constantNodes; - } - - public function reset(string $fileName): void - { - $this->classNodes = []; - $this->functionNodes = []; - $this->constantNodes = []; - $this->fileName = $fileName; - } - + private string $fileName; + + /** @var array>> */ + private array $classNodes; + + /** @var array> */ + private array $functionNodes; + + /** @var array> */ + private array $constantNodes; + + private ?\PhpParser\Node\Stmt\Namespace_ $currentNamespaceNode = null; + + public function enterNode(\PhpParser\Node $node): ?int + { + if ($node instanceof Namespace_) { + $this->currentNamespaceNode = $node; + } + + if ($node instanceof \PhpParser\Node\Stmt\ClassLike) { + if ($node->name !== null) { + $fullClassName = $node->name->toString(); + if ($this->currentNamespaceNode !== null && $this->currentNamespaceNode->name !== null) { + $fullClassName = $this->currentNamespaceNode->name . '\\' . $fullClassName; + } + $this->classNodes[strtolower($fullClassName)][] = new FetchedNode( + $node, + $this->currentNamespaceNode, + $this->fileName + ); + } + + return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if ($node instanceof \PhpParser\Node\Stmt\Function_) { + $this->functionNodes[strtolower($node->namespacedName->toString())] = new FetchedNode( + $node, + $this->currentNamespaceNode, + $this->fileName + ); + + return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if ($node instanceof \PhpParser\Node\Stmt\Const_) { + $this->constantNodes[] = new FetchedNode( + $node, + $this->currentNamespaceNode, + $this->fileName + ); + + return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if ($node instanceof \PhpParser\Node\Expr\FuncCall) { + try { + ConstantNodeChecker::assertValidDefineFunctionCall($node); + } catch (InvalidConstantNode $e) { + return null; + } + + /** @var \PhpParser\Node\Scalar\String_ $nameNode */ + $nameNode = $node->args[0]->value; + $constantName = $nameNode->value; + + if (defined($constantName)) { + $constantValue = constant($constantName); + $node->args[1]->value = BuilderHelpers::normalizeValue($constantValue); + } + + $constantNode = new FetchedNode( + $node, + $this->currentNamespaceNode, + $this->fileName + ); + $this->constantNodes[] = $constantNode; + + return \PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } + + /** + * @param \PhpParser\Node $node + * @return null + */ + public function leaveNode(\PhpParser\Node $node) + { + if (!$node instanceof Namespace_) { + return null; + } + + $this->currentNamespaceNode = null; + return null; + } + + /** + * @return array>> + */ + public function getClassNodes(): array + { + return $this->classNodes; + } + + /** + * @return array> + */ + public function getFunctionNodes(): array + { + return $this->functionNodes; + } + + /** + * @return array> + */ + public function getConstantNodes(): array + { + return $this->constantNodes; + } + + public function reset(string $fileName): void + { + $this->classNodes = []; + $this->functionNodes = []; + $this->constantNodes = []; + $this->fileName = $fileName; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/ClassBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/ClassBlacklistSourceLocator.php index 9b4ca0d402..c5b8452018 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ClassBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/ClassBlacklistSourceLocator.php @@ -1,4 +1,6 @@ -sourceLocator = $sourceLocator; - $this->patterns = $patterns; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - foreach ($this->patterns as $pattern) { - if (Strings::match($identifier->getName(), $pattern) !== null) { - return null; - } - } - } - - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); - } - + private SourceLocator $sourceLocator; + + /** @var string[] */ + private array $patterns; + + /** + * @param SourceLocator $sourceLocator + * @param string[] $patterns + */ + public function __construct( + SourceLocator $sourceLocator, + array $patterns + ) { + $this->sourceLocator = $sourceLocator; + $this->patterns = $patterns; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isClass()) { + foreach ($this->patterns as $pattern) { + if (Strings::match($identifier->getName(), $pattern) !== null) { + return null; + } + } + } + + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/ClassWhitelistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/ClassWhitelistSourceLocator.php index cff56df029..6a4f2f5405 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ClassWhitelistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/ClassWhitelistSourceLocator.php @@ -1,4 +1,6 @@ -sourceLocator = $sourceLocator; - $this->patterns = $patterns; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - foreach ($this->patterns as $pattern) { - if (Strings::match($identifier->getName(), $pattern) !== null) { - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - } - - return null; - } - - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); - } - + private SourceLocator $sourceLocator; + + /** @var string[] */ + private array $patterns; + + /** + * @param SourceLocator $sourceLocator + * @param string[] $patterns + */ + public function __construct( + SourceLocator $sourceLocator, + array $patterns + ) { + $this->sourceLocator = $sourceLocator; + $this->patterns = $patterns; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isClass()) { + foreach ($this->patterns as $pattern) { + if (Strings::match($identifier->getName(), $pattern) !== null) { + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + } + + return null; + } + + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php index cc0e55dea6..4499ba2937 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php +++ b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php @@ -1,4 +1,6 @@ -optimizedDirectorySourceLocatorRepository = $optimizedDirectorySourceLocatorRepository; - $this->optimizedPsrAutoloaderLocatorFactory = $optimizedPsrAutoloaderLocatorFactory; - $this->optimizedDirectorySourceLocatorFactory = $optimizedDirectorySourceLocatorFactory; - } - - public function create(string $projectInstallationPath): ?SourceLocator - { - $composerJsonPath = $projectInstallationPath . '/composer.json'; - if (!is_file($composerJsonPath)) { - return null; - } - $installedJsonPath = $projectInstallationPath . '/vendor/composer/installed.json'; - if (!is_file($installedJsonPath)) { - return null; - } - - $installedJsonDirectoryPath = dirname($installedJsonPath); - - try { - $composerJsonContents = FileReader::read($composerJsonPath); - $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); - } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { - return null; - } - - try { - $installedJsonContents = FileReader::read($installedJsonPath); - $installedJson = Json::decode($installedJsonContents, Json::FORCE_ARRAY); - } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { - return null; - } - - $installed = $installedJson['packages'] ?? $installedJson; - - $classMapPaths = array_merge( - $this->prefixPaths($this->packageToClassMapPaths($composer), $projectInstallationPath . '/'), - ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { - return $this->prefixPaths( - $this->packageToClassMapPaths($package), - $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package) - ); - }, $installed) - ); - $classMapFiles = array_filter($classMapPaths, 'is_file'); - $classMapDirectories = array_filter($classMapPaths, 'is_dir'); - $filePaths = array_merge( - $this->prefixPaths($this->packageToFilePaths($composer), $projectInstallationPath . '/'), - ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { - return $this->prefixPaths( - $this->packageToFilePaths($package), - $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package) - ); - }, $installed) - ); - - $locators = []; - $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( - Psr4Mapping::fromArrayMappings(array_merge_recursive( - $this->prefixWithInstallationPath($this->packageToPsr4AutoloadNamespaces($composer), $projectInstallationPath), - ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { - return $this->prefixWithPackagePath( - $this->packageToPsr4AutoloadNamespaces($package), - $projectInstallationPath, - $installedJsonDirectoryPath, - $package - ); - }, $installed) - )) - ); - - $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( - Psr0Mapping::fromArrayMappings(array_merge_recursive( - $this->prefixWithInstallationPath($this->packageToPsr0AutoloadNamespaces($composer), $projectInstallationPath), - ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { - return $this->prefixWithPackagePath( - $this->packageToPsr0AutoloadNamespaces($package), - $projectInstallationPath, - $installedJsonDirectoryPath, - $package - ); - }, $installed) - )) - ); - - foreach ($classMapDirectories as $classMapDirectory) { - if (!is_dir($classMapDirectory)) { - continue; - } - $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($classMapDirectory); - } - - $files = []; - - foreach (array_merge($classMapFiles, $filePaths) as $file) { - if (!is_file($file)) { - continue; - } - $files[] = $file; - } - - if (count($files) > 0) { - $locators[] = $this->optimizedDirectorySourceLocatorFactory->createByFiles($files); - } - - return new AggregateSourceLocator($locators); - } - - /** - * @param mixed[] $package - * - * @return array> - */ - private function packageToPsr4AutoloadNamespaces(array $package): array - { - return array_map(static function ($namespacePaths): array { - return (array) $namespacePaths; - }, $package['autoload']['psr-4'] ?? []); - } - - /** - * @param mixed[] $package - * - * @return array> - */ - private function packageToPsr0AutoloadNamespaces(array $package): array - { - return array_map(static function ($namespacePaths): array { - return (array) $namespacePaths; - }, $package['autoload']['psr-0'] ?? []); - } - - /** - * @param mixed[] $package - * - * @return array - */ - private function packageToClassMapPaths(array $package): array - { - return $package['autoload']['classmap'] ?? []; - } - - /** - * @param mixed[] $package - * - * @return array - */ - private function packageToFilePaths(array $package): array - { - return $package['autoload']['files'] ?? []; - } - - /** - * @param mixed[] $package - */ - private function packagePrefixPath( - string $projectInstallationPath, - string $installedJsonDirectoryPath, - array $package - ): string - { - if (array_key_exists('install-path', $package)) { - return $installedJsonDirectoryPath . '/' . $package['install-path'] . '/'; - } - - return $projectInstallationPath . '/vendor/' . $package['name'] . '/'; - } - - /** - * @param array> $paths - * @param array> $package - * - * @return array> - */ - private function prefixWithPackagePath(array $paths, string $projectInstallationPath, string $installedJsonDirectoryPath, array $package): array - { - $prefix = $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package); - - return array_map(function (array $paths) use ($prefix): array { - return $this->prefixPaths($paths, $prefix); - }, $paths); - } - - /** - * @param array> $paths - * - * @return array> - */ - private function prefixWithInstallationPath(array $paths, string $trimmedInstallationPath): array - { - return array_map(function (array $paths) use ($trimmedInstallationPath): array { - return $this->prefixPaths($paths, $trimmedInstallationPath . '/'); - }, $paths); - } - - /** - * @param array $paths - * - * @return array - */ - private function prefixPaths(array $paths, string $prefix): array - { - return array_map(static function (string $path) use ($prefix): string { - return $prefix . $path; - }, $paths); - } - + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository $optimizedDirectorySourceLocatorRepository; + + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory $optimizedPsrAutoloaderLocatorFactory; + + private OptimizedDirectorySourceLocatorFactory $optimizedDirectorySourceLocatorFactory; + + public function __construct( + OptimizedDirectorySourceLocatorRepository $optimizedDirectorySourceLocatorRepository, + OptimizedPsrAutoloaderLocatorFactory $optimizedPsrAutoloaderLocatorFactory, + OptimizedDirectorySourceLocatorFactory $optimizedDirectorySourceLocatorFactory + ) { + $this->optimizedDirectorySourceLocatorRepository = $optimizedDirectorySourceLocatorRepository; + $this->optimizedPsrAutoloaderLocatorFactory = $optimizedPsrAutoloaderLocatorFactory; + $this->optimizedDirectorySourceLocatorFactory = $optimizedDirectorySourceLocatorFactory; + } + + public function create(string $projectInstallationPath): ?SourceLocator + { + $composerJsonPath = $projectInstallationPath . '/composer.json'; + if (!is_file($composerJsonPath)) { + return null; + } + $installedJsonPath = $projectInstallationPath . '/vendor/composer/installed.json'; + if (!is_file($installedJsonPath)) { + return null; + } + + $installedJsonDirectoryPath = dirname($installedJsonPath); + + try { + $composerJsonContents = FileReader::read($composerJsonPath); + $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); + } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { + return null; + } + + try { + $installedJsonContents = FileReader::read($installedJsonPath); + $installedJson = Json::decode($installedJsonContents, Json::FORCE_ARRAY); + } catch (\PHPStan\File\CouldNotReadFileException | \Nette\Utils\JsonException $e) { + return null; + } + + $installed = $installedJson['packages'] ?? $installedJson; + + $classMapPaths = array_merge( + $this->prefixPaths($this->packageToClassMapPaths($composer), $projectInstallationPath . '/'), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixPaths( + $this->packageToClassMapPaths($package), + $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package) + ); + }, $installed) + ); + $classMapFiles = array_filter($classMapPaths, 'is_file'); + $classMapDirectories = array_filter($classMapPaths, 'is_dir'); + $filePaths = array_merge( + $this->prefixPaths($this->packageToFilePaths($composer), $projectInstallationPath . '/'), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixPaths( + $this->packageToFilePaths($package), + $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package) + ); + }, $installed) + ); + + $locators = []; + $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( + Psr4Mapping::fromArrayMappings(array_merge_recursive( + $this->prefixWithInstallationPath($this->packageToPsr4AutoloadNamespaces($composer), $projectInstallationPath), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixWithPackagePath( + $this->packageToPsr4AutoloadNamespaces($package), + $projectInstallationPath, + $installedJsonDirectoryPath, + $package + ); + }, $installed) + )) + ); + + $locators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( + Psr0Mapping::fromArrayMappings(array_merge_recursive( + $this->prefixWithInstallationPath($this->packageToPsr0AutoloadNamespaces($composer), $projectInstallationPath), + ...array_map(function (array $package) use ($projectInstallationPath, $installedJsonDirectoryPath): array { + return $this->prefixWithPackagePath( + $this->packageToPsr0AutoloadNamespaces($package), + $projectInstallationPath, + $installedJsonDirectoryPath, + $package + ); + }, $installed) + )) + ); + + foreach ($classMapDirectories as $classMapDirectory) { + if (!is_dir($classMapDirectory)) { + continue; + } + $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($classMapDirectory); + } + + $files = []; + + foreach (array_merge($classMapFiles, $filePaths) as $file) { + if (!is_file($file)) { + continue; + } + $files[] = $file; + } + + if (count($files) > 0) { + $locators[] = $this->optimizedDirectorySourceLocatorFactory->createByFiles($files); + } + + return new AggregateSourceLocator($locators); + } + + /** + * @param mixed[] $package + * + * @return array> + */ + private function packageToPsr4AutoloadNamespaces(array $package): array + { + return array_map(static function ($namespacePaths): array { + return (array) $namespacePaths; + }, $package['autoload']['psr-4'] ?? []); + } + + /** + * @param mixed[] $package + * + * @return array> + */ + private function packageToPsr0AutoloadNamespaces(array $package): array + { + return array_map(static function ($namespacePaths): array { + return (array) $namespacePaths; + }, $package['autoload']['psr-0'] ?? []); + } + + /** + * @param mixed[] $package + * + * @return array + */ + private function packageToClassMapPaths(array $package): array + { + return $package['autoload']['classmap'] ?? []; + } + + /** + * @param mixed[] $package + * + * @return array + */ + private function packageToFilePaths(array $package): array + { + return $package['autoload']['files'] ?? []; + } + + /** + * @param mixed[] $package + */ + private function packagePrefixPath( + string $projectInstallationPath, + string $installedJsonDirectoryPath, + array $package + ): string { + if (array_key_exists('install-path', $package)) { + return $installedJsonDirectoryPath . '/' . $package['install-path'] . '/'; + } + + return $projectInstallationPath . '/vendor/' . $package['name'] . '/'; + } + + /** + * @param array> $paths + * @param array> $package + * + * @return array> + */ + private function prefixWithPackagePath(array $paths, string $projectInstallationPath, string $installedJsonDirectoryPath, array $package): array + { + $prefix = $this->packagePrefixPath($projectInstallationPath, $installedJsonDirectoryPath, $package); + + return array_map(function (array $paths) use ($prefix): array { + return $this->prefixPaths($paths, $prefix); + }, $paths); + } + + /** + * @param array> $paths + * + * @return array> + */ + private function prefixWithInstallationPath(array $paths, string $trimmedInstallationPath): array + { + return array_map(function (array $paths) use ($trimmedInstallationPath): array { + return $this->prefixPaths($paths, $trimmedInstallationPath . '/'); + }, $paths); + } + + /** + * @param array $paths + * + * @return array + */ + private function prefixPaths(array $paths, string $prefix): array + { + return array_map(static function (string $path) use ($prefix): string { + return $prefix . $path; + }, $paths); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php b/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php index c5a48bf720..a3489b8b65 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php +++ b/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php @@ -1,4 +1,6 @@ -node = $node; - $this->namespace = $namespace; - $this->fileName = $fileName; - } - - /** - * @return T - */ - public function getNode(): \PhpParser\Node - { - return $this->node; - } - - public function getNamespace(): ?\PhpParser\Node\Stmt\Namespace_ - { - return $this->namespace; - } - - public function getFileName(): string - { - return $this->fileName; - } - + /** @var T */ + private \PhpParser\Node $node; + + private ?\PhpParser\Node\Stmt\Namespace_ $namespace; + + private string $fileName; + + /** + * @param T $node + * @param \PhpParser\Node\Stmt\Namespace_|null $namespace + * @param string $fileName + */ + public function __construct( + \PhpParser\Node $node, + ?\PhpParser\Node\Stmt\Namespace_ $namespace, + string $fileName + ) { + $this->node = $node; + $this->namespace = $namespace; + $this->fileName = $fileName; + } + + /** + * @return T + */ + public function getNode(): \PhpParser\Node + { + return $this->node; + } + + public function getNamespace(): ?\PhpParser\Node\Stmt\Namespace_ + { + return $this->namespace; + } + + public function getFileName(): string + { + return $this->fileName; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php index 5791a790b6..590798dae2 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php +++ b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php @@ -1,4 +1,6 @@ ->> */ + private array $classNodes; - /** @var array>> */ - private array $classNodes; - - /** @var array> */ - private array $functionNodes; - - /** @var array> */ - private array $constantNodes; + /** @var array> */ + private array $functionNodes; - private \PHPStan\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource; + /** @var array> */ + private array $constantNodes; - /** - * @param array>> $classNodes - * @param array> $functionNodes - * @param array> $constantNodes - * @param \PHPStan\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource - */ - public function __construct( - array $classNodes, - array $functionNodes, - array $constantNodes, - LocatedSource $locatedSource - ) - { - $this->classNodes = $classNodes; - $this->functionNodes = $functionNodes; - $this->constantNodes = $constantNodes; - $this->locatedSource = $locatedSource; - } + private \PHPStan\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource; - /** - * @return array>> - */ - public function getClassNodes(): array - { - return $this->classNodes; - } + /** + * @param array>> $classNodes + * @param array> $functionNodes + * @param array> $constantNodes + * @param \PHPStan\BetterReflection\SourceLocator\Located\LocatedSource $locatedSource + */ + public function __construct( + array $classNodes, + array $functionNodes, + array $constantNodes, + LocatedSource $locatedSource + ) { + $this->classNodes = $classNodes; + $this->functionNodes = $functionNodes; + $this->constantNodes = $constantNodes; + $this->locatedSource = $locatedSource; + } - /** - * @return array> - */ - public function getFunctionNodes(): array - { - return $this->functionNodes; - } + /** + * @return array>> + */ + public function getClassNodes(): array + { + return $this->classNodes; + } - /** - * @return array> - */ - public function getConstantNodes(): array - { - return $this->constantNodes; - } + /** + * @return array> + */ + public function getFunctionNodes(): array + { + return $this->functionNodes; + } - public function getLocatedSource(): LocatedSource - { - return $this->locatedSource; - } + /** + * @return array> + */ + public function getConstantNodes(): array + { + return $this->constantNodes; + } + public function getLocatedSource(): LocatedSource + { + return $this->locatedSource; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php b/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php index db88b2b177..70be845b6e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php @@ -1,4 +1,6 @@ -cachingVisitor = $cachingVisitor; - $this->parser = $parser; - } - - public function fetchNodes(string $fileName): FetchedNodesResult - { - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->cachingVisitor); - - $contents = FileReader::read($fileName); - $locatedSource = new LocatedSource($contents, $fileName); - - try { - /** @var \PhpParser\Node[] $ast */ - $ast = $this->parser->parseFile($fileName); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - return new FetchedNodesResult([], [], [], $locatedSource); - } - $this->cachingVisitor->reset($fileName); - $nodeTraverser->traverse($ast); - - return new FetchedNodesResult( - $this->cachingVisitor->getClassNodes(), - $this->cachingVisitor->getFunctionNodes(), - $this->cachingVisitor->getConstantNodes(), - $locatedSource - ); - } - + private \PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor $cachingVisitor; + + private Parser $parser; + + public function __construct( + CachingVisitor $cachingVisitor, + Parser $parser + ) { + $this->cachingVisitor = $cachingVisitor; + $this->parser = $parser; + } + + public function fetchNodes(string $fileName): FetchedNodesResult + { + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->cachingVisitor); + + $contents = FileReader::read($fileName); + $locatedSource = new LocatedSource($contents, $fileName); + + try { + /** @var \PhpParser\Node[] $ast */ + $ast = $this->parser->parseFile($fileName); + } catch (\PHPStan\Parser\ParserErrorsException $e) { + return new FetchedNodesResult([], [], [], $locatedSource); + } + $this->cachingVisitor->reset($fileName); + $nodeTraverser->traverse($ast); + + return new FetchedNodesResult( + $this->cachingVisitor->getClassNodes(), + $this->cachingVisitor->getFunctionNodes(), + $this->cachingVisitor->getConstantNodes(), + $locatedSource + ); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php index 7efba32de1..02344f9384 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php @@ -1,4 +1,6 @@ -readFromFile = false; - $this->seekPosition = 0; - - return true; - } - - /** - * Since we allow our wrapper's stream_open() to succeed, we need to - * simulate a successful read so autoloaders with require() don't explode. - * - * @param int $count - * - * @return string - */ - public function stream_read($count): string - { - $this->readFromFile = true; - - // Dummy return value that is also valid PHP for require(). We'll read - // and process the file elsewhere, so it's OK to provide dummy data for - // this read. - return ''; - } - - /** - * Since we allowed the open to succeed, we should allow the close to occur - * as well. - * - * @return void - */ - public function stream_close(): void - { - // no op - } - - /** - * Required for `require_once` and `include_once` to work per PHP.net - * comment referenced below. We delegate to url_stat(). - * - * @see https://www.php.net/manual/en/function.stream-wrapper-register.php#51855 - * - * @return mixed[]|bool - */ - public function stream_stat() - { - if (self::$autoloadLocatedFile === null) { - return false; - } - - return $this->url_stat(self::$autoloadLocatedFile, STREAM_URL_STAT_QUIET); - } - - /** - * url_stat is triggered by calls like "file_exists". The call to "file_exists" must not be overloaded. - * This function restores the original "file" stream, issues a call to "stat" to get the real results, - * and then re-registers the AutoloadSourceLocator stream wrapper. - * - * @internal do not call this method directly! This is stream wrapper - * voodoo logic that you **DO NOT** want to touch! - * - * @see https://php.net/manual/en/class.streamwrapper.php - * @see https://php.net/manual/en/streamwrapper.url-stat.php - * - * @param string $path - * @param int $flags - * - * @return mixed[]|bool - */ - public function url_stat($path, $flags) - { - if (self::$registeredStreamWrapperProtocols === null) { - throw new \PHPStan\ShouldNotHappenException(self::class . ' not registered: cannot operate. Do not call this method directly.'); - } - - foreach (self::$registeredStreamWrapperProtocols as $protocol) { - stream_wrapper_restore($protocol); - } - - if (($flags & STREAM_URL_STAT_QUIET) !== 0) { - $result = @stat($path); - } else { - $result = stat($path); - } - - foreach (self::$registeredStreamWrapperProtocols as $protocol) { - stream_wrapper_unregister($protocol); - stream_wrapper_register($protocol, self::class); - } - - return $result; - } - - /** - * Simulates behavior of reading from an empty file. - * - * @return bool - */ - public function stream_eof(): bool - { - return $this->readFromFile; - } - - public function stream_flush(): bool - { - return true; - } - - public function stream_tell(): int - { - return $this->seekPosition; - } - - /** - * @param int $offset - * @param int $whence - * @return bool - */ - public function stream_seek($offset, $whence): bool - { - switch ($whence) { - // Behavior is the same for a zero-length file - case SEEK_SET: - case SEEK_END: - if ($offset < 0) { - return false; - } - $this->seekPosition = $offset; - return true; - - case SEEK_CUR: - if ($offset < 0) { - return false; - } - $this->seekPosition += $offset; - return true; - - default: - return false; - } - } - - /** - * @param int $option - * @param int $arg1 - * @param int $arg2 - * @return bool - */ - public function stream_set_option($option, $arg1, $arg2): bool - { - return false; - } - + private const DEFAULT_STREAM_WRAPPER_PROTOCOLS = [ + 'file', + 'phar', + ]; + + /** @var string[]|null */ + private static ?array $registeredStreamWrapperProtocols; + + public static ?string $autoloadLocatedFile = null; + + private bool $readFromFile = false; + + private int $seekPosition = 0; + + /** + * @param string[] $streamWrapperProtocols + * + * @return mixed + * + * @psalm-template ExecutedMethodReturnType of mixed + * @psalm-param callable() : ExecutedMethodReturnType $executeMeWithinStreamWrapperOverride + * @psalm-return ExecutedMethodReturnType + */ + public static function withStreamWrapperOverride( + callable $executeMeWithinStreamWrapperOverride, + array $streamWrapperProtocols = self::DEFAULT_STREAM_WRAPPER_PROTOCOLS + ) { + self::$registeredStreamWrapperProtocols = $streamWrapperProtocols; + self::$autoloadLocatedFile = null; + + try { + foreach ($streamWrapperProtocols as $protocol) { + stream_wrapper_unregister($protocol); + stream_wrapper_register($protocol, self::class); + } + + $result = $executeMeWithinStreamWrapperOverride(); + } finally { + foreach ($streamWrapperProtocols as $protocol) { + stream_wrapper_restore($protocol); + } + } + + self::$registeredStreamWrapperProtocols = null; + self::$autoloadLocatedFile = null; + + return $result; + } + + /** + * Our wrapper simply records which file we tried to load and returns + * boolean false indicating failure. + * + * @internal do not call this method directly! This is stream wrapper + * voodoo logic that you **DO NOT** want to touch! + * + * @see https://php.net/manual/en/class.streamwrapper.php + * @see https://php.net/manual/en/streamwrapper.stream-open.php + * + * @param string $path + * @param string $mode + * @param int $options + * @param string $openedPath + */ + public function stream_open($path, $mode, $options, &$openedPath): bool + { + self::$autoloadLocatedFile = $path; + $this->readFromFile = false; + $this->seekPosition = 0; + + return true; + } + + /** + * Since we allow our wrapper's stream_open() to succeed, we need to + * simulate a successful read so autoloaders with require() don't explode. + * + * @param int $count + * + * @return string + */ + public function stream_read($count): string + { + $this->readFromFile = true; + + // Dummy return value that is also valid PHP for require(). We'll read + // and process the file elsewhere, so it's OK to provide dummy data for + // this read. + return ''; + } + + /** + * Since we allowed the open to succeed, we should allow the close to occur + * as well. + * + * @return void + */ + public function stream_close(): void + { + // no op + } + + /** + * Required for `require_once` and `include_once` to work per PHP.net + * comment referenced below. We delegate to url_stat(). + * + * @see https://www.php.net/manual/en/function.stream-wrapper-register.php#51855 + * + * @return mixed[]|bool + */ + public function stream_stat() + { + if (self::$autoloadLocatedFile === null) { + return false; + } + + return $this->url_stat(self::$autoloadLocatedFile, STREAM_URL_STAT_QUIET); + } + + /** + * url_stat is triggered by calls like "file_exists". The call to "file_exists" must not be overloaded. + * This function restores the original "file" stream, issues a call to "stat" to get the real results, + * and then re-registers the AutoloadSourceLocator stream wrapper. + * + * @internal do not call this method directly! This is stream wrapper + * voodoo logic that you **DO NOT** want to touch! + * + * @see https://php.net/manual/en/class.streamwrapper.php + * @see https://php.net/manual/en/streamwrapper.url-stat.php + * + * @param string $path + * @param int $flags + * + * @return mixed[]|bool + */ + public function url_stat($path, $flags) + { + if (self::$registeredStreamWrapperProtocols === null) { + throw new \PHPStan\ShouldNotHappenException(self::class . ' not registered: cannot operate. Do not call this method directly.'); + } + + foreach (self::$registeredStreamWrapperProtocols as $protocol) { + stream_wrapper_restore($protocol); + } + + if (($flags & STREAM_URL_STAT_QUIET) !== 0) { + $result = @stat($path); + } else { + $result = stat($path); + } + + foreach (self::$registeredStreamWrapperProtocols as $protocol) { + stream_wrapper_unregister($protocol); + stream_wrapper_register($protocol, self::class); + } + + return $result; + } + + /** + * Simulates behavior of reading from an empty file. + * + * @return bool + */ + public function stream_eof(): bool + { + return $this->readFromFile; + } + + public function stream_flush(): bool + { + return true; + } + + public function stream_tell(): int + { + return $this->seekPosition; + } + + /** + * @param int $offset + * @param int $whence + * @return bool + */ + public function stream_seek($offset, $whence): bool + { + switch ($whence) { + // Behavior is the same for a zero-length file + case SEEK_SET: + case SEEK_END: + if ($offset < 0) { + return false; + } + $this->seekPosition = $offset; + return true; + + case SEEK_CUR: + if ($offset < 0) { + return false; + } + $this->seekPosition += $offset; + return true; + + default: + return false; + } + } + + /** + * @param int $option + * @param int $arg1 + * @param int $arg2 + * @return bool + */ + public function stream_set_option($option, $arg1, $arg2): bool + { + return false; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index 9aaa11a6e4..1c32d3f505 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -1,4 +1,6 @@ -|null */ - private ?array $classToFile = null; - - /** @var array>|null */ - private ?array $functionToFiles = null; - - /** @var array> */ - private array $classNodes = []; - - /** @var array> */ - private array $functionNodes = []; - - /** @var array */ - private array $locatedSourcesByFile = []; - - /** - * @param FileNodesFetcher $fileNodesFetcher - * @param string[] $files - */ - public function __construct( - FileNodesFetcher $fileNodesFetcher, - array $files - ) - { - $this->fileNodesFetcher = $fileNodesFetcher; - $this->files = $files; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - $className = strtolower($identifier->getName()); - if (array_key_exists($className, $this->classNodes)) { - return $this->nodeToReflection($reflector, $this->classNodes[$className]); - } - - $file = $this->findFileByClass($className); - if ($file === null) { - return null; - } - - $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); - $locatedSource = $fetchedNodesResult->getLocatedSource(); - $this->locatedSourcesByFile[$file] = $locatedSource; - foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNodes) { - foreach ($fetchedClassNodes as $fetchedClassNode) { - $this->classNodes[$identifierName] = $fetchedClassNode; - break; - } - } - - if (!array_key_exists($className, $this->classNodes)) { - return null; - } - - return $this->nodeToReflection($reflector, $this->classNodes[$className]); - } - - if ($identifier->isFunction()) { - $functionName = strtolower($identifier->getName()); - if (array_key_exists($functionName, $this->functionNodes)) { - return $this->nodeToReflection($reflector, $this->functionNodes[$functionName]); - } - - $files = $this->findFilesByFunction($functionName); - foreach ($files as $file) { - $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); - $locatedSource = $fetchedNodesResult->getLocatedSource(); - $this->locatedSourcesByFile[$file] = $locatedSource; - foreach ($fetchedNodesResult->getFunctionNodes() as $identifierName => $fetchedFunctionNode) { - $this->functionNodes[$identifierName] = $fetchedFunctionNode; - } - } - - if (!array_key_exists($functionName, $this->functionNodes)) { - return null; - } - - return $this->nodeToReflection($reflector, $this->functionNodes[$functionName]); - } - - return null; - } - - /** - * @param Reflector $reflector - * @param FetchedNode<\PhpParser\Node\Stmt\ClassLike>|FetchedNode<\PhpParser\Node\Stmt\Function_> $fetchedNode - * @return Reflection - */ - private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode): Reflection - { - $nodeToReflection = new NodeToReflection(); - $reflection = $nodeToReflection->__invoke( - $reflector, - $fetchedNode->getNode(), - $this->locatedSourcesByFile[$fetchedNode->getFileName()], - $fetchedNode->getNamespace() - ); - - if ($reflection === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $reflection; - } - - private function findFileByClass(string $className): ?string - { - if ($this->classToFile === null) { - $this->init(); - if ($this->classToFile === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - } - - if (!array_key_exists($className, $this->classToFile)) { - return null; - } - - return $this->classToFile[$className]; - } - - /** - * @param string $functionName - * @return string[] - */ - private function findFilesByFunction(string $functionName): array - { - if ($this->functionToFiles === null) { - $this->init(); - if ($this->functionToFiles === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - } - - if (!array_key_exists($functionName, $this->functionToFiles)) { - return []; - } - - return $this->functionToFiles[$functionName]; - } - - private function init(): void - { - $classToFile = []; - $functionToFiles = []; - foreach ($this->files as $file) { - $symbols = $this->findSymbols($file); - $classesInFile = $symbols['classes']; - $functionsInFile = $symbols['functions']; - foreach ($classesInFile as $classInFile) { - $classToFile[$classInFile] = $file; - } - foreach ($functionsInFile as $functionInFile) { - if (!array_key_exists($functionInFile, $functionToFiles)) { - $functionToFiles[$functionInFile] = []; - } - $functionToFiles[$functionInFile][] = $file; - } - } - - $this->classToFile = $classToFile; - $this->functionToFiles = $functionToFiles; - } - - /** - * Inspired by Composer\Autoload\ClassMapGenerator::findClasses() - * @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216 - * - * @param string $file - * @return array{classes: string[], functions: string[]} - */ - private function findSymbols(string $file): array - { - $contents = @php_strip_whitespace($file); - if ($contents === '') { - return ['classes' => [], 'functions' => []]; - } - - if (!preg_match('{\b(?:class|interface|trait|function)\s}i', $contents)) { - return ['classes' => [], 'functions' => []]; - } - - // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); - // strip strings - $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); - // strip leading non-php code if needed - if (substr($contents, 0, 2) !== ' [], 'functions' => []]; - } - } - // strip non-php blocks in the file - $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?>'); - if ($pos !== false && strpos(substr($contents, $pos), '|null */ + private ?array $classToFile = null; + + /** @var array>|null */ + private ?array $functionToFiles = null; + + /** @var array> */ + private array $classNodes = []; + + /** @var array> */ + private array $functionNodes = []; + + /** @var array */ + private array $locatedSourcesByFile = []; + + /** + * @param FileNodesFetcher $fileNodesFetcher + * @param string[] $files + */ + public function __construct( + FileNodesFetcher $fileNodesFetcher, + array $files + ) { + $this->fileNodesFetcher = $fileNodesFetcher; + $this->files = $files; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isClass()) { + $className = strtolower($identifier->getName()); + if (array_key_exists($className, $this->classNodes)) { + return $this->nodeToReflection($reflector, $this->classNodes[$className]); + } + + $file = $this->findFileByClass($className); + if ($file === null) { + return null; + } + + $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); + $locatedSource = $fetchedNodesResult->getLocatedSource(); + $this->locatedSourcesByFile[$file] = $locatedSource; + foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNodes) { + foreach ($fetchedClassNodes as $fetchedClassNode) { + $this->classNodes[$identifierName] = $fetchedClassNode; + break; + } + } + + if (!array_key_exists($className, $this->classNodes)) { + return null; + } + + return $this->nodeToReflection($reflector, $this->classNodes[$className]); + } + + if ($identifier->isFunction()) { + $functionName = strtolower($identifier->getName()); + if (array_key_exists($functionName, $this->functionNodes)) { + return $this->nodeToReflection($reflector, $this->functionNodes[$functionName]); + } + + $files = $this->findFilesByFunction($functionName); + foreach ($files as $file) { + $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); + $locatedSource = $fetchedNodesResult->getLocatedSource(); + $this->locatedSourcesByFile[$file] = $locatedSource; + foreach ($fetchedNodesResult->getFunctionNodes() as $identifierName => $fetchedFunctionNode) { + $this->functionNodes[$identifierName] = $fetchedFunctionNode; + } + } + + if (!array_key_exists($functionName, $this->functionNodes)) { + return null; + } + + return $this->nodeToReflection($reflector, $this->functionNodes[$functionName]); + } + + return null; + } + + /** + * @param Reflector $reflector + * @param FetchedNode<\PhpParser\Node\Stmt\ClassLike>|FetchedNode<\PhpParser\Node\Stmt\Function_> $fetchedNode + * @return Reflection + */ + private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode): Reflection + { + $nodeToReflection = new NodeToReflection(); + $reflection = $nodeToReflection->__invoke( + $reflector, + $fetchedNode->getNode(), + $this->locatedSourcesByFile[$fetchedNode->getFileName()], + $fetchedNode->getNamespace() + ); + + if ($reflection === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $reflection; + } + + private function findFileByClass(string $className): ?string + { + if ($this->classToFile === null) { + $this->init(); + if ($this->classToFile === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + } + + if (!array_key_exists($className, $this->classToFile)) { + return null; + } + + return $this->classToFile[$className]; + } + + /** + * @param string $functionName + * @return string[] + */ + private function findFilesByFunction(string $functionName): array + { + if ($this->functionToFiles === null) { + $this->init(); + if ($this->functionToFiles === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + } + + if (!array_key_exists($functionName, $this->functionToFiles)) { + return []; + } + + return $this->functionToFiles[$functionName]; + } + + private function init(): void + { + $classToFile = []; + $functionToFiles = []; + foreach ($this->files as $file) { + $symbols = $this->findSymbols($file); + $classesInFile = $symbols['classes']; + $functionsInFile = $symbols['functions']; + foreach ($classesInFile as $classInFile) { + $classToFile[$classInFile] = $file; + } + foreach ($functionsInFile as $functionInFile) { + if (!array_key_exists($functionInFile, $functionToFiles)) { + $functionToFiles[$functionInFile] = []; + } + $functionToFiles[$functionInFile][] = $file; + } + } + + $this->classToFile = $classToFile; + $this->functionToFiles = $functionToFiles; + } + + /** + * Inspired by Composer\Autoload\ClassMapGenerator::findClasses() + * @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216 + * + * @param string $file + * @return array{classes: string[], functions: string[]} + */ + private function findSymbols(string $file): array + { + $contents = @php_strip_whitespace($file); + if ($contents === '') { + return ['classes' => [], 'functions' => []]; + } + + if (!preg_match('{\b(?:class|interface|trait|function)\s}i', $contents)) { + return ['classes' => [], 'functions' => []]; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== ' [], 'functions' => []]; + } + } + // strip non-php blocks in the file + $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?>'); + if ($pos !== false && strpos(substr($contents, $pos), '])(?Pclass|interface|trait|function) \s++ (?P&\s*)? (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] ) }ix', $contents, $matches); - $classes = []; - $functions = []; - $namespace = ''; - - for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { - if (!empty($matches['ns'][$i])) { // phpcs:disable - $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; - } else { - $name = $matches['name'][$i]; - // skip anon classes extending/implementing - if ($name === 'extends' || $name === 'implements') { - continue; - } - $namespacedName = strtolower(ltrim($namespace . $name, '\\')); - - if ($matches['type'][$i] === 'function') { - $functions[] = $namespacedName; - } else { - $classes[] = $namespacedName; - } - } - } - - return [ - 'classes' => $classes, - 'functions' => $functions, - ]; - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return []; - } - + $classes = []; + $functions = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { // phpcs:disable + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + // skip anon classes extending/implementing + if ($name === 'extends' || $name === 'implements') { + continue; + } + $namespacedName = strtolower(ltrim($namespace . $name, '\\')); + + if ($matches['type'][$i] === 'function') { + $functions[] = $namespacedName; + } else { + $classes[] = $namespacedName; + } + } + } + + return [ + 'classes' => $classes, + 'functions' => $functions, + ]; + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return []; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php index 71aee274ae..3a3a5433fb 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php @@ -1,4 +1,6 @@ -fileNodesFetcher = $fileNodesFetcher; - $this->fileFinder = $fileFinder; - } - - public function createByDirectory(string $directory): OptimizedDirectorySourceLocator - { - return new OptimizedDirectorySourceLocator( - $this->fileNodesFetcher, - $this->fileFinder->findFiles([$directory])->getFiles() - ); - } - - /** - * @param string[] $files - * @return OptimizedDirectorySourceLocator - */ - public function createByFiles(array $files): OptimizedDirectorySourceLocator - { - return new OptimizedDirectorySourceLocator( - $this->fileNodesFetcher, - $files - ); - } - + private FileNodesFetcher $fileNodesFetcher; + + private FileFinder $fileFinder; + + public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder) + { + $this->fileNodesFetcher = $fileNodesFetcher; + $this->fileFinder = $fileFinder; + } + + public function createByDirectory(string $directory): OptimizedDirectorySourceLocator + { + return new OptimizedDirectorySourceLocator( + $this->fileNodesFetcher, + $this->fileFinder->findFiles([$directory])->getFiles() + ); + } + + /** + * @param string[] $files + * @return OptimizedDirectorySourceLocator + */ + public function createByFiles(array $files): OptimizedDirectorySourceLocator + { + return new OptimizedDirectorySourceLocator( + $this->fileNodesFetcher, + $files + ); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php index 9f385a6d17..ea38a8ea0b 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php @@ -1,29 +1,29 @@ - */ - private array $locators = []; - - public function __construct(OptimizedDirectorySourceLocatorFactory $factory) - { - $this->factory = $factory; - } + /** @var array */ + private array $locators = []; - public function getOrCreate(string $directory): OptimizedDirectorySourceLocator - { - if (array_key_exists($directory, $this->locators)) { - return $this->locators[$directory]; - } + public function __construct(OptimizedDirectorySourceLocatorFactory $factory) + { + $this->factory = $factory; + } - $this->locators[$directory] = $this->factory->createByDirectory($directory); + public function getOrCreate(string $directory): OptimizedDirectorySourceLocator + { + if (array_key_exists($directory, $this->locators)) { + return $this->locators[$directory]; + } - return $this->locators[$directory]; - } + $this->locators[$directory] = $this->factory->createByDirectory($directory); + return $this->locators[$directory]; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php index b065a1f7ef..9d03fbe499 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php @@ -1,4 +1,6 @@ -mapping = $mapping; - $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - foreach ($this->mapping->resolvePossibleFilePaths($identifier) as $file) { - if (!file_exists($file)) { - continue; - } - - $reflection = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($file)->locateIdentifier($reflector, $identifier); - if ($reflection === null) { - continue; - } - - return $reflection; - } - - return null; - } - - /** - * @return Reflection[] - */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return []; - } - + private PsrAutoloaderMapping $mapping; + + private \PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository; + + public function __construct( + PsrAutoloaderMapping $mapping, + OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository + ) { + $this->mapping = $mapping; + $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + foreach ($this->mapping->resolvePossibleFilePaths($identifier) as $file) { + if (!file_exists($file)) { + continue; + } + + $reflection = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($file)->locateIdentifier($reflector, $identifier); + if ($reflection === null) { + continue; + } + + return $reflection; + } + + return null; + } + + /** + * @return Reflection[] + */ + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return []; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php index 4a466b7e2d..f2fa4bc27e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocatorFactory.php @@ -1,4 +1,6 @@ -fileNodesFetcher = $fileNodesFetcher; - $this->fileName = $fileName; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($this->fetchedNodesResult === null) { - $this->fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($this->fileName); - } - $nodeToReflection = new NodeToReflection(); - if ($identifier->isClass()) { - $classNodes = $this->fetchedNodesResult->getClassNodes(); - $className = strtolower($identifier->getName()); - if (!array_key_exists($className, $classNodes)) { - return null; - } - - foreach ($classNodes[$className] as $classNode) { - $classReflection = $nodeToReflection->__invoke( - $reflector, - $classNode->getNode(), - $this->fetchedNodesResult->getLocatedSource(), - $classNode->getNamespace() - ); - if (!$classReflection instanceof ReflectionClass) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $classReflection; - } - } - - if ($identifier->isFunction()) { - $functionNodes = $this->fetchedNodesResult->getFunctionNodes(); - $functionName = strtolower($identifier->getName()); - if (!array_key_exists($functionName, $functionNodes)) { - return null; - } - - $functionReflection = $nodeToReflection->__invoke( - $reflector, - $functionNodes[$functionName]->getNode(), - $this->fetchedNodesResult->getLocatedSource(), - $functionNodes[$functionName]->getNamespace() - ); - if (!$functionReflection instanceof ReflectionFunction) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $functionReflection; - } - - if ($identifier->isConstant()) { - $constantNodes = $this->fetchedNodesResult->getConstantNodes(); - foreach ($constantNodes as $stmtConst) { - if ($stmtConst->getNode() instanceof FuncCall) { - $constantReflection = $nodeToReflection->__invoke( - $reflector, - $stmtConst->getNode(), - $this->fetchedNodesResult->getLocatedSource(), - $stmtConst->getNamespace() - ); - if ($constantReflection === null) { - continue; - } - if (!$constantReflection instanceof ReflectionConstant) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($constantReflection->getName() !== $identifier->getName()) { - continue; - } - - return $constantReflection; - } - - foreach (array_keys($stmtConst->getNode()->consts) as $i) { - $constantReflection = $nodeToReflection->__invoke( - $reflector, - $stmtConst->getNode(), - $this->fetchedNodesResult->getLocatedSource(), - $stmtConst->getNamespace(), - $i - ); - if ($constantReflection === null) { - continue; - } - if (!$constantReflection instanceof ReflectionConstant) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($constantReflection->getName() !== $identifier->getName()) { - continue; - } - - return $constantReflection; - } - } - - return null; - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return []; - } - + private \PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher $fileNodesFetcher; + + private string $fileName; + + private ?\PHPStan\Reflection\BetterReflection\SourceLocator\FetchedNodesResult $fetchedNodesResult = null; + + public function __construct( + FileNodesFetcher $fileNodesFetcher, + string $fileName + ) { + $this->fileNodesFetcher = $fileNodesFetcher; + $this->fileName = $fileName; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($this->fetchedNodesResult === null) { + $this->fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($this->fileName); + } + $nodeToReflection = new NodeToReflection(); + if ($identifier->isClass()) { + $classNodes = $this->fetchedNodesResult->getClassNodes(); + $className = strtolower($identifier->getName()); + if (!array_key_exists($className, $classNodes)) { + return null; + } + + foreach ($classNodes[$className] as $classNode) { + $classReflection = $nodeToReflection->__invoke( + $reflector, + $classNode->getNode(), + $this->fetchedNodesResult->getLocatedSource(), + $classNode->getNamespace() + ); + if (!$classReflection instanceof ReflectionClass) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $classReflection; + } + } + + if ($identifier->isFunction()) { + $functionNodes = $this->fetchedNodesResult->getFunctionNodes(); + $functionName = strtolower($identifier->getName()); + if (!array_key_exists($functionName, $functionNodes)) { + return null; + } + + $functionReflection = $nodeToReflection->__invoke( + $reflector, + $functionNodes[$functionName]->getNode(), + $this->fetchedNodesResult->getLocatedSource(), + $functionNodes[$functionName]->getNamespace() + ); + if (!$functionReflection instanceof ReflectionFunction) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $functionReflection; + } + + if ($identifier->isConstant()) { + $constantNodes = $this->fetchedNodesResult->getConstantNodes(); + foreach ($constantNodes as $stmtConst) { + if ($stmtConst->getNode() instanceof FuncCall) { + $constantReflection = $nodeToReflection->__invoke( + $reflector, + $stmtConst->getNode(), + $this->fetchedNodesResult->getLocatedSource(), + $stmtConst->getNamespace() + ); + if ($constantReflection === null) { + continue; + } + if (!$constantReflection instanceof ReflectionConstant) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($constantReflection->getName() !== $identifier->getName()) { + continue; + } + + return $constantReflection; + } + + foreach (array_keys($stmtConst->getNode()->consts) as $i) { + $constantReflection = $nodeToReflection->__invoke( + $reflector, + $stmtConst->getNode(), + $this->fetchedNodesResult->getLocatedSource(), + $stmtConst->getNamespace(), + $i + ); + if ($constantReflection === null) { + continue; + } + if (!$constantReflection instanceof ReflectionConstant) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($constantReflection->getName() !== $identifier->getName()) { + continue; + } + + return $constantReflection; + } + } + + return null; + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return []; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorFactory.php index fec37445af..6832f7b97a 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorFactory.php @@ -1,10 +1,10 @@ - */ - private array $locators = []; - - public function __construct(OptimizedSingleFileSourceLocatorFactory $factory) - { - $this->factory = $factory; - } + /** @var array */ + private array $locators = []; - public function getOrCreate(string $fileName): OptimizedSingleFileSourceLocator - { - if (array_key_exists($fileName, $this->locators)) { - return $this->locators[$fileName]; - } + public function __construct(OptimizedSingleFileSourceLocatorFactory $factory) + { + $this->factory = $factory; + } - $this->locators[$fileName] = $this->factory->create($fileName); + public function getOrCreate(string $fileName): OptimizedSingleFileSourceLocator + { + if (array_key_exists($fileName, $this->locators)) { + return $this->locators[$fileName]; + } - return $this->locators[$fileName]; - } + $this->locators[$fileName] = $this->factory->create($fileName); + return $this->locators[$fileName]; + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php index 9d9b7ec942..4e883e826f 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php @@ -1,4 +1,6 @@ -sourceLocator = $sourceLocator; - $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - if ($this->phpStormStubsSourceStubber->isPresentClass($identifier->getName()) === false) { - return null; - } - } - - if ($identifier->isFunction()) { - if ($this->phpStormStubsSourceStubber->isPresentFunction($identifier->getName()) === false) { - return null; - } - } - - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); - } - + private SourceLocator $sourceLocator; + + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; + + public function __construct( + SourceLocator $sourceLocator, + PhpStormStubsSourceStubber $phpStormStubsSourceStubber + ) { + $this->sourceLocator = $sourceLocator; + $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isClass()) { + if ($this->phpStormStubsSourceStubber->isPresentClass($identifier->getName()) === false) { + return null; + } + } + + if ($identifier->isFunction()) { + if ($this->phpStormStubsSourceStubber->isPresentFunction($identifier->getName()) === false) { + return null; + } + } + + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); + } } diff --git a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php index 2afa00d549..fb7b58cd12 100644 --- a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php @@ -1,4 +1,6 @@ -sourceLocator = $sourceLocator; - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - $className = $identifier->getName(); - if (!class_exists($className, false)) { - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - $reflection = new \ReflectionClass($className); - if ($reflection->getFileName() === false) { - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - return null; - } - - return $this->sourceLocator->locateIdentifier($reflector, $identifier); - } - - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); - } - + private SourceLocator $sourceLocator; + + public function __construct(SourceLocator $sourceLocator) + { + $this->sourceLocator = $sourceLocator; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + if ($identifier->isClass()) { + $className = $identifier->getName(); + if (!class_exists($className, false)) { + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + $reflection = new \ReflectionClass($className); + if ($reflection->getFileName() === false) { + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + return null; + } + + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType); + } } diff --git a/src/Reflection/BetterReflection/SourceStubber/Php8StubsSourceStubber.php b/src/Reflection/BetterReflection/SourceStubber/Php8StubsSourceStubber.php index c482ee70ef..eb7a0856c7 100644 --- a/src/Reflection/BetterReflection/SourceStubber/Php8StubsSourceStubber.php +++ b/src/Reflection/BetterReflection/SourceStubber/Php8StubsSourceStubber.php @@ -1,4 +1,6 @@ -getExtensionFromFilePath($relativeFilePath), $file); - } - - public function generateFunctionStub(string $functionName): ?StubData - { - $lowerFunctionName = strtolower($functionName); - if (!array_key_exists($lowerFunctionName, Php8StubsMap::FUNCTIONS)) { - return null; - } - - $relativeFilePath = Php8StubsMap::FUNCTIONS[$lowerFunctionName]; - $file = self::DIRECTORY . '/' . $relativeFilePath; - - return new StubData(FileReader::read($file), $this->getExtensionFromFilePath($relativeFilePath), $file); - } - - public function generateConstantStub(string $constantName): ?StubData - { - return null; - } - - private function getExtensionFromFilePath(string $relativeFilePath): string - { - $pathParts = explode('/', $relativeFilePath); - if ($pathParts[1] === 'Zend') { - return 'Core'; - } - - return $pathParts[2]; - } - + private const DIRECTORY = __DIR__ . '/../../../../vendor/phpstan/php-8-stubs'; + + public function hasClass(string $className): bool + { + $className = strtolower($className); + return array_key_exists($className, Php8StubsMap::CLASSES); + } + + public function generateClassStub(string $className): ?StubData + { + $lowerClassName = strtolower($className); + if (!array_key_exists($lowerClassName, Php8StubsMap::CLASSES)) { + return null; + } + + $relativeFilePath = Php8StubsMap::CLASSES[$lowerClassName]; + $file = self::DIRECTORY . '/' . $relativeFilePath; + + return new StubData(FileReader::read($file), $this->getExtensionFromFilePath($relativeFilePath), $file); + } + + public function generateFunctionStub(string $functionName): ?StubData + { + $lowerFunctionName = strtolower($functionName); + if (!array_key_exists($lowerFunctionName, Php8StubsMap::FUNCTIONS)) { + return null; + } + + $relativeFilePath = Php8StubsMap::FUNCTIONS[$lowerFunctionName]; + $file = self::DIRECTORY . '/' . $relativeFilePath; + + return new StubData(FileReader::read($file), $this->getExtensionFromFilePath($relativeFilePath), $file); + } + + public function generateConstantStub(string $constantName): ?StubData + { + return null; + } + + private function getExtensionFromFilePath(string $relativeFilePath): string + { + $pathParts = explode('/', $relativeFilePath); + if ($pathParts[1] === 'Zend') { + return 'Core'; + } + + return $pathParts[2]; + } } diff --git a/src/Reflection/BrokerAwareExtension.php b/src/Reflection/BrokerAwareExtension.php index fbe01b804c..c7a837e760 100644 --- a/src/Reflection/BrokerAwareExtension.php +++ b/src/Reflection/BrokerAwareExtension.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->reflection = $reflection; - $this->deprecatedDescription = $deprecatedDescription; - $this->isDeprecated = $isDeprecated; - $this->isInternal = $isInternal; - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getFileName(): ?string - { - $fileName = $this->declaringClass->getFileName(); - if ($fileName === false) { - return null; - } - - return $fileName; - } - - /** - * @return mixed - */ - public function getValue() - { - return $this->reflection->getValue(); - } - - public function getValueType(): Type - { - if ($this->valueType === null) { - $this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue()); - } - return $this->valueType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return true; - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isDeprecated); - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isInternal); - } - - public function getDocComment(): ?string - { - $docComment = $this->reflection->getDocComment(); - if ($docComment === false) { - return null; - } - - return $docComment; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private \ReflectionClassConstant $reflection; + + private ?string $deprecatedDescription; + + private bool $isDeprecated; + + private bool $isInternal; + + private ?Type $valueType = null; + + public function __construct( + ClassReflection $declaringClass, + \ReflectionClassConstant $reflection, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal + ) { + $this->declaringClass = $declaringClass; + $this->reflection = $reflection; + $this->deprecatedDescription = $deprecatedDescription; + $this->isDeprecated = $isDeprecated; + $this->isInternal = $isInternal; + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getFileName(): ?string + { + $fileName = $this->declaringClass->getFileName(); + if ($fileName === false) { + return null; + } + + return $fileName; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->reflection->getValue(); + } + + public function getValueType(): Type + { + if ($this->valueType === null) { + $this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue()); + } + return $this->valueType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return true; + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isDeprecated); + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function getDocComment(): ?string + { + $docComment = $this->reflection->getDocComment(); + if ($docComment === false) { + return null; + } + + return $docComment; + } } diff --git a/src/Reflection/ClassMemberAccessAnswerer.php b/src/Reflection/ClassMemberAccessAnswerer.php index b19f8021d0..d4cd901ce9 100644 --- a/src/Reflection/ClassMemberAccessAnswerer.php +++ b/src/Reflection/ClassMemberAccessAnswerer.php @@ -1,18 +1,18 @@ -|null */ - private ?array $ancestors = null; + private ?ResolvedPhpDocBlock $stubPhpDocBlock; - private ?string $cacheKey = null; + private ?string $extraCacheKey; - /** @var array */ - private array $subclasses = []; + /** @var array|null */ + private ?array $ancestors = null; - /** @var string|false|null */ - private $filename; + private ?string $cacheKey = null; - /** @var string|false|null */ - private $reflectionDocComment; - - /** @var \PHPStan\Reflection\ClassReflection[]|null */ - private ?array $cachedInterfaces = null; - - /** @var \PHPStan\Reflection\ClassReflection|false|null */ - private $cachedParentClass = null; + /** @var array */ + private array $subclasses = []; - /** @var array|null */ - private ?array $typeAliases = null; + /** @var string|false|null */ + private $filename; + + /** @var string|false|null */ + private $reflectionDocComment; + + /** @var \PHPStan\Reflection\ClassReflection[]|null */ + private ?array $cachedInterfaces = null; - /** @var array */ - private static array $resolvingTypeAliasImports = []; + /** @var \PHPStan\Reflection\ClassReflection|false|null */ + private $cachedParentClass = null; + + /** @var array|null */ + private ?array $typeAliases = null; - /** - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param \PHPStan\Type\FileTypeMapper $fileTypeMapper - * @param \PHPStan\Reflection\PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions - * @param \PHPStan\Reflection\MethodsClassReflectionExtension[] $methodsClassReflectionExtensions - * @param string $displayName - * @param \ReflectionClass $reflection - * @param string|null $anonymousFilename - * @param ResolvedPhpDocBlock|null $stubPhpDocBlock - * @param string|null $extraCacheKey - */ - public function __construct( - ReflectionProvider $reflectionProvider, - FileTypeMapper $fileTypeMapper, - PhpVersion $phpVersion, - array $propertiesClassReflectionExtensions, - array $methodsClassReflectionExtensions, - string $displayName, - \ReflectionClass $reflection, - ?string $anonymousFilename, - ?TemplateTypeMap $resolvedTemplateTypeMap, - ?ResolvedPhpDocBlock $stubPhpDocBlock, - ?string $extraCacheKey = null - ) - { - $this->reflectionProvider = $reflectionProvider; - $this->fileTypeMapper = $fileTypeMapper; - $this->phpVersion = $phpVersion; - $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; - $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; - $this->displayName = $displayName; - $this->reflection = $reflection; - $this->anonymousFilename = $anonymousFilename; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - $this->stubPhpDocBlock = $stubPhpDocBlock; - $this->extraCacheKey = $extraCacheKey; - } - - public function getNativeReflection(): \ReflectionClass - { - return $this->reflection; - } - - /** - * @return string|false - */ - public function getFileName() - { - if (isset($this->filename)) { - return $this->filename; - } - - if ($this->anonymousFilename !== null) { - return $this->filename = $this->anonymousFilename; - } - $fileName = $this->reflection->getFileName(); - if ($fileName === false) { - return $this->filename = false; - } - - if (!file_exists($fileName)) { - return $this->filename = false; - } - - return $this->filename = $fileName; - } - - public function getFileNameWithPhpDocs(): ?string - { - if ($this->stubPhpDocBlock !== null) { - return $this->stubPhpDocBlock->getFilename(); - } - - $filename = $this->getFileName(); - if ($filename === false) { - return null; - } - - return $filename; - } - - /** - * @return false|\PHPStan\Reflection\ClassReflection - */ - public function getParentClass() - { - if ($this->cachedParentClass !== null) { - return $this->cachedParentClass; - } - - $parentClass = $this->reflection->getParentClass(); - - if ($parentClass === false) { - return $this->cachedParentClass = false; - } - - $extendsTag = $this->getFirstExtendsTag(); - - if ($extendsTag !== null && $this->isValidAncestorType($extendsTag->getType(), [$parentClass->getName()])) { - $extendedType = $extendsTag->getType(); - - if ($this->isGeneric()) { - $extendedType = TemplateTypeHelper::resolveTemplateTypes( - $extendedType, - $this->getActiveTemplateTypeMap() - ); - } - - if (!$extendedType instanceof GenericObjectType) { - return $this->reflectionProvider->getClass($parentClass->getName()); - } - - return $extendedType->getClassReflection() ?? $this->reflectionProvider->getClass($parentClass->getName()); - } - - $parentReflection = $this->reflectionProvider->getClass($parentClass->getName()); - if ($parentReflection->isGeneric()) { - return $parentReflection->withTypes( - array_values($parentReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()) - ); - } - - $this->cachedParentClass = $parentReflection; - - return $parentReflection; - } - - /** - * @return class-string - */ - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getDisplayName(bool $withTemplateTypes = true): string - { - $name = $this->displayName; - - if ( - $withTemplateTypes === false - || $this->resolvedTemplateTypeMap === null - || count($this->resolvedTemplateTypeMap->getTypes()) === 0 - ) { - return $name; - } - - return $name . '<' . implode(',', array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::typeOnly()); - }, $this->resolvedTemplateTypeMap->getTypes())) . '>'; - } - - public function getCacheKey(): string - { - $cacheKey = $this->cacheKey; - if ($cacheKey !== null) { - return $this->cacheKey; - } - - $cacheKey = $this->displayName; - - if ($this->resolvedTemplateTypeMap !== null) { - $cacheKey .= '<' . implode(',', array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::cache()); - }, $this->resolvedTemplateTypeMap->getTypes())) . '>'; - } - - if ($this->extraCacheKey !== null) { - $cacheKey .= '-' . $this->extraCacheKey; - } - - $this->cacheKey = $cacheKey; - - return $cacheKey; - } - - /** - * @return int[] - */ - public function getClassHierarchyDistances(): array - { - if ($this->classHierarchyDistances === null) { - $distance = 0; - $distances = [ - $this->getName() => $distance, - ]; - $currentClassReflection = $this->getNativeReflection(); - foreach ($this->collectTraits($this->getNativeReflection()) as $trait) { - $distance++; - if (array_key_exists($trait->getName(), $distances)) { - continue; - } - - $distances[$trait->getName()] = $distance; - } - - while ($currentClassReflection->getParentClass() !== false) { - $distance++; - $parentClassName = $currentClassReflection->getParentClass()->getName(); - if (!array_key_exists($parentClassName, $distances)) { - $distances[$parentClassName] = $distance; - } - $currentClassReflection = $currentClassReflection->getParentClass(); - foreach ($this->collectTraits($currentClassReflection) as $trait) { - $distance++; - if (array_key_exists($trait->getName(), $distances)) { - continue; - } - - $distances[$trait->getName()] = $distance; - } - } - foreach ($this->getNativeReflection()->getInterfaces() as $interface) { - $distance++; - if (array_key_exists($interface->getName(), $distances)) { - continue; - } - - $distances[$interface->getName()] = $distance; - } - - $this->classHierarchyDistances = $distances; - } - - return $this->classHierarchyDistances; - } - - /** - * @param \ReflectionClass $class - * @return \ReflectionClass[] - */ - private function collectTraits(\ReflectionClass $class): array - { - $traits = []; - $traitsLeftToAnalyze = $class->getTraits(); - - while (count($traitsLeftToAnalyze) !== 0) { - $trait = reset($traitsLeftToAnalyze); - $traits[] = $trait; - - foreach ($trait->getTraits() as $subTrait) { - if (in_array($subTrait, $traits, true)) { - continue; - } - - $traitsLeftToAnalyze[] = $subTrait; - } - - array_shift($traitsLeftToAnalyze); - } - - return $traits; - } - - public function hasProperty(string $propertyName): bool - { - foreach ($this->propertiesClassReflectionExtensions as $extension) { - if ($extension->hasProperty($this, $propertyName)) { - return true; - } - } - - return false; - } - - public function hasMethod(string $methodName): bool - { - foreach ($this->methodsClassReflectionExtensions as $extension) { - if ($extension->hasMethod($this, $methodName)) { - return true; - } - } - - return false; - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - $key = $methodName; - if ($scope->isInClass()) { - $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); - } - if (!isset($this->methods[$key])) { - foreach ($this->methodsClassReflectionExtensions as $extension) { - if (!$extension->hasMethod($this, $methodName)) { - continue; - } - - $method = $extension->getMethod($this, $methodName); - if ($scope->canCallMethod($method)) { - return $this->methods[$key] = $method; - } - $this->methods[$key] = $method; - } - } - - if (!isset($this->methods[$key])) { - throw new \PHPStan\Reflection\MissingMethodFromReflectionException($this->getName(), $methodName); - } - - return $this->methods[$key]; - } - - public function hasNativeMethod(string $methodName): bool - { - return $this->getPhpExtension()->hasNativeMethod($this, $methodName); - } - - public function getNativeMethod(string $methodName): MethodReflection - { - if (!$this->hasNativeMethod($methodName)) { - throw new \PHPStan\Reflection\MissingMethodFromReflectionException($this->getName(), $methodName); - } - return $this->getPhpExtension()->getNativeMethod($this, $methodName); - } - - /** - * @deprecated Use ClassReflection::getNativeReflection() instead. - * @return MethodReflection[] - */ - public function getNativeMethods(): array - { - $methods = []; - foreach ($this->reflection->getMethods() as $method) { - $methods[] = $this->getNativeMethod($method->getName()); - } - - return $methods; - } - - public function hasConstructor(): bool - { - return $this->findConstructor() !== null; - } - - public function getConstructor(): MethodReflection - { - $constructor = $this->findConstructor(); - if ($constructor === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - return $this->getNativeMethod($constructor->getName()); - } - - private function findConstructor(): ?ReflectionMethod - { - $constructor = $this->reflection->getConstructor(); - if ($constructor === null) { - return null; - } - - if ($this->phpVersion->supportsLegacyConstructor()) { - return $constructor; - } - - if (strtolower($constructor->getName()) !== '__construct') { - return null; - } - - return $constructor; - } - - private function getPhpExtension(): PhpClassReflectionExtension - { - $extension = $this->methodsClassReflectionExtensions[0]; - if (!$extension instanceof PhpClassReflectionExtension) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $extension; - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - $key = $propertyName; - if ($scope->isInClass()) { - $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); - } - if (!isset($this->properties[$key])) { - foreach ($this->propertiesClassReflectionExtensions as $extension) { - if (!$extension->hasProperty($this, $propertyName)) { - continue; - } - - $property = $extension->getProperty($this, $propertyName); - if ($scope->canAccessProperty($property)) { - return $this->properties[$key] = $property; - } - $this->properties[$key] = $property; - } - } - - if (!isset($this->properties[$key])) { - throw new \PHPStan\Reflection\MissingPropertyFromReflectionException($this->getName(), $propertyName); - } - - return $this->properties[$key]; - } - - public function hasNativeProperty(string $propertyName): bool - { - return $this->getPhpExtension()->hasProperty($this, $propertyName); - } - - public function getNativeProperty(string $propertyName): PhpPropertyReflection - { - if (!$this->hasNativeProperty($propertyName)) { - throw new \PHPStan\Reflection\MissingPropertyFromReflectionException($this->getName(), $propertyName); - } - - return $this->getPhpExtension()->getNativeProperty($this, $propertyName); - } - - public function isAbstract(): bool - { - return $this->reflection->isAbstract(); - } - - public function isInterface(): bool - { - return $this->reflection->isInterface(); - } - - public function isTrait(): bool - { - return $this->reflection->isTrait(); - } - - public function isClass(): bool - { - return !$this->isInterface() && !$this->isTrait(); - } - - public function isAnonymous(): bool - { - return $this->anonymousFilename !== null; - } - - public function isSubclassOf(string $className): bool - { - if (isset($this->subclasses[$className])) { - return $this->subclasses[$className]; - } - - if (!$this->reflectionProvider->hasClass($className)) { - return $this->subclasses[$className] = false; - } - - try { - return $this->subclasses[$className] = $this->reflection->isSubclassOf($className); - } catch (\ReflectionException $e) { - return $this->subclasses[$className] = false; - } - } - - public function implementsInterface(string $className): bool - { - try { - return $this->reflection->implementsInterface($className); - } catch (\ReflectionException $e) { - return false; - } - } - - /** - * @return \PHPStan\Reflection\ClassReflection[] - */ - public function getParents(): array - { - $parents = []; - $parent = $this->getParentClass(); - while ($parent !== false) { - $parents[] = $parent; - $parent = $parent->getParentClass(); - } - - return $parents; - } - - /** - * @return \PHPStan\Reflection\ClassReflection[] - */ - public function getInterfaces(): array - { - if ($this->cachedInterfaces !== null) { - return $this->cachedInterfaces; - } - - $interfaces = []; - - $parent = $this->getParentClass(); - if ($parent !== false) { - foreach ($parent->getInterfaces() as $interface) { - $interfaces[$interface->getName()] = $interface; - } - } - - if ($this->reflection->isInterface()) { - $implementsTags = $this->getExtendsTags(); - } else { - $implementsTags = $this->getImplementsTags(); - } - - $interfaceNames = $this->reflection->getInterfaceNames(); - $genericInterfaces = []; - - foreach ($implementsTags as $implementsTag) { - $implementedType = $implementsTag->getType(); - - if (!$this->isValidAncestorType($implementedType, $interfaceNames)) { - continue; - } - - if ($this->isGeneric()) { - $implementedType = TemplateTypeHelper::resolveTemplateTypes( - $implementedType, - $this->getActiveTemplateTypeMap() - ); - } - - if (!$implementedType instanceof GenericObjectType) { - continue; - } - - $reflectionIface = $implementedType->getClassReflection(); - if ($reflectionIface === null) { - continue; - } - - $genericInterfaces[] = $reflectionIface; - } - - foreach ($genericInterfaces as $genericInterface) { - $interfaces = array_merge($interfaces, $genericInterface->getInterfaces()); - } - - foreach ($genericInterfaces as $genericInterface) { - $interfaces[$genericInterface->getName()] = $genericInterface; - } - - foreach ($interfaceNames as $interfaceName) { - if (isset($interfaces[$interfaceName])) { - continue; - } - - $interfaceReflection = $this->reflectionProvider->getClass($interfaceName); - if (!$interfaceReflection->isGeneric()) { - $interfaces[$interfaceName] = $interfaceReflection; - continue; - } - - $interfaces[$interfaceName] = $interfaceReflection->withTypes( - array_values($interfaceReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()) - ); - } - - $this->cachedInterfaces = $interfaces; - - return $interfaces; - } - - /** - * @return \PHPStan\Reflection\ClassReflection[] - */ - public function getTraits(): array - { - return array_map(function (\ReflectionClass $trait): ClassReflection { - return $this->reflectionProvider->getClass($trait->getName()); - }, $this->getNativeReflection()->getTraits()); - } - - /** - * @return string[] - */ - public function getParentClassesNames(): array - { - $parentNames = []; - $currentClassReflection = $this; - while ($currentClassReflection->getParentClass() !== false) { - $parentNames[] = $currentClassReflection->getParentClass()->getName(); - $currentClassReflection = $currentClassReflection->getParentClass(); - } - - return $parentNames; - } - - public function hasConstant(string $name): bool - { - if (!$this->getNativeReflection()->hasConstant($name)) { - return false; - } - - $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name); - if ($reflectionConstant === false) { - return false; - } - - return $this->reflectionProvider->hasClass($reflectionConstant->getDeclaringClass()->getName()); - } - - public function getConstant(string $name): ConstantReflection - { - if (!isset($this->constants[$name])) { - $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name); - if ($reflectionConstant === false) { - throw new \PHPStan\Reflection\MissingConstantFromReflectionException($this->getName(), $name); - } - - $deprecatedDescription = null; - $isDeprecated = false; - $isInternal = false; - $declaringClass = $reflectionConstant->getDeclaringClass(); - $fileName = $declaringClass->getFileName(); - if ($reflectionConstant->getDocComment() !== false && $fileName !== false) { - $docComment = $reflectionConstant->getDocComment(); - $className = $declaringClass->getName(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $className, null, null, $docComment); - - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - } - - $this->constants[$name] = new ClassConstantReflection( - $this->reflectionProvider->getClass($declaringClass->getName()), - $reflectionConstant, - $deprecatedDescription, - $isDeprecated, - $isInternal - ); - } - return $this->constants[$name]; - } - - public function hasTraitUse(string $traitName): bool - { - return in_array($traitName, $this->getTraitNames(), true); - } - - /** - * @return string[] - */ - private function getTraitNames(): array - { - $class = $this->reflection; - $traitNames = $class->getTraitNames(); - while ($class->getParentClass() !== false) { - $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames()))); - $class = $class->getParentClass(); - } - - return $traitNames; - } - - /** - * @return array - */ - public function getTypeAliases(): array - { - if ($this->typeAliases === null) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return $this->typeAliases = []; - } - - $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags(); - $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags(); - - // prevent circular imports - if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) { - throw new \PHPStan\Type\CircularTypeAliasDefinitionException(); - } - - self::$resolvingTypeAliasImports[$this->getName()] = true; - - $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias { - $importedAlias = $typeAliasImportTag->getImportedAlias(); - $importedFromClassName = $typeAliasImportTag->getImportedFrom(); - - if (!$this->reflectionProvider->hasClass($importedFromClassName)) { - return null; - } - - $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName); - - try { - $typeAliases = $importedFromReflection->getTypeAliases(); - } catch (\PHPStan\Type\CircularTypeAliasDefinitionException $e) { - return TypeAlias::invalid(); - } - - if (!array_key_exists($importedAlias, $typeAliases)) { - return null; - } - - return $typeAliases[$importedAlias]; - }, $typeAliasImportTags); - - unset(self::$resolvingTypeAliasImports[$this->getName()]); - - $localAliases = array_map(static function (TypeAliasTag $typeAliasTag): TypeAlias { - return $typeAliasTag->getTypeAlias(); - }, $typeAliasTags); - - $this->typeAliases = array_filter( - array_merge($importedAliases, $localAliases), - static function (?TypeAlias $typeAlias): bool { - return $typeAlias !== null; - } - ); - } - - return $this->typeAliases; - } - - public function getDeprecatedDescription(): ?string - { - if ($this->deprecatedDescription === null && $this->isDeprecated()) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) { - $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage(); - } - } - - return $this->deprecatedDescription; - } - - public function isDeprecated(): bool - { - if ($this->isDeprecated === null) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - $this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated(); - } - - return $this->isDeprecated; - } - - public function isBuiltin(): bool - { - return $this->reflection->isInternal(); - } - - public function isInternal(): bool - { - if ($this->isInternal === null) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal(); - } - - return $this->isInternal; - } - - public function isFinal(): bool - { - if ($this->isFinal === null) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - $this->isFinal = $this->reflection->isFinal() - || ($resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal()); - } - - return $this->isFinal; - } - - public function isFinalByKeyword(): bool - { - return $this->reflection->isFinal(); - } - - public function isAttributeClass(): bool - { - return $this->findAttributeClass() !== null; - } - - private function findAttributeClass(): ?Attribute - { - if ($this->isInterface() || $this->isTrait()) { - return null; - } - - if ($this->reflection instanceof ReflectionClass) { - foreach ($this->reflection->getBetterReflection()->getAttributes() as $attribute) { - if ($attribute->getName() === \Attribute::class) { - /** @var \Attribute */ - return $attribute->newInstance(); - } - } - - return null; - } - - if (!method_exists($this->reflection, 'getAttributes')) { - return null; - } - - $nativeAttributes = $this->reflection->getAttributes(\Attribute::class); - if (count($nativeAttributes) === 1) { - /** @var Attribute */ - return $nativeAttributes[0]->newInstance(); - } - - return null; - } - - public function getAttributeClassFlags(): int - { - $attribute = $this->findAttributeClass(); - if ($attribute === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $attribute->flags; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - if ($this->templateTypeMap !== null) { - return $this->templateTypeMap; - } - - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - $this->templateTypeMap = TemplateTypeMap::createEmpty(); - return $this->templateTypeMap; - } - - $templateTypeScope = TemplateTypeScope::createWithClass($this->getName()); - - $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { - return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); - }, $this->getTemplateTags())); - - $this->templateTypeMap = $templateTypeMap; - - return $templateTypeMap; - } - - public function getActiveTemplateTypeMap(): TemplateTypeMap - { - return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap(); - } - - public function isGeneric(): bool - { - if ($this->isGeneric === null) { - $this->isGeneric = count($this->getTemplateTags()) > 0; - } - - return $this->isGeneric; - } - - /** - * @param array $types - * @return \PHPStan\Type\Generic\TemplateTypeMap - */ - public function typeMapFromList(array $types): TemplateTypeMap - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return TemplateTypeMap::createEmpty(); - } - - $map = []; - $i = 0; - foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $map[$tag->getName()] = $types[$i] ?? new ErrorType(); - $i++; - } - - return new TemplateTypeMap($map); - } - - /** @return array */ - public function typeMapToList(TemplateTypeMap $typeMap): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - $list = []; - foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $list[] = $typeMap->getType($tag->getName()) ?? $tag->getBound(); - } - - return $list; - } - - /** - * @param array $types - */ - public function withTypes(array $types): self - { - return new self( - $this->reflectionProvider, - $this->fileTypeMapper, - $this->phpVersion, - $this->propertiesClassReflectionExtensions, - $this->methodsClassReflectionExtensions, - $this->displayName, - $this->reflection, - $this->anonymousFilename, - $this->typeMapFromList($types), - $this->stubPhpDocBlock - ); - } - - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - if ($this->stubPhpDocBlock !== null) { - return $this->stubPhpDocBlock; - } - - $fileName = $this->getFileName(); - if ($fileName === false) { - return null; - } - - if ($this->reflectionDocComment === null) { - $this->reflectionDocComment = $this->reflection->getDocComment(); - } - - if ($this->reflectionDocComment === false) { - return null; - } - - return $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment); - } - - private function getFirstExtendsTag(): ?ExtendsTag - { - foreach ($this->getExtendsTags() as $tag) { - return $tag; - } - - return null; - } - - /** @return ExtendsTag[] */ - private function getExtendsTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getExtendsTags(); - } - - /** @return ImplementsTag[] */ - private function getImplementsTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getImplementsTags(); - } - - /** @return array */ - public function getTemplateTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getTemplateTags(); - } - - /** - * @return array - */ - public function getAncestors(): array - { - $ancestors = $this->ancestors; - - if ($ancestors === null) { - $ancestors = [ - $this->getName() => $this, - ]; - - $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void { - if (array_key_exists($name, $ancestors)) { - return; - } - - $ancestors[$name] = $classReflection; - }; - - foreach ($this->getInterfaces() as $interface) { - $addToAncestors($interface->getName(), $interface); - foreach ($interface->getAncestors() as $name => $ancestor) { - $addToAncestors($name, $ancestor); - } - } - - foreach ($this->getTraits() as $trait) { - $addToAncestors($trait->getName(), $trait); - foreach ($trait->getAncestors() as $name => $ancestor) { - $addToAncestors($name, $ancestor); - } - } - - $parent = $this->getParentClass(); - if ($parent !== false) { - $addToAncestors($parent->getName(), $parent); - foreach ($parent->getAncestors() as $name => $ancestor) { - $addToAncestors($name, $ancestor); - } - } - - $this->ancestors = $ancestors; - } - - return $ancestors; - } - - public function getAncestorWithClassName(string $className): ?self - { - return $this->getAncestors()[$className] ?? null; - } - - /** - * @param string[] $ancestorClasses - */ - private function isValidAncestorType(Type $type, array $ancestorClasses): bool - { - if (!$type instanceof GenericObjectType) { - return false; - } - - $reflection = $type->getClassReflection(); - if ($reflection === null) { - return false; - } - - return in_array($reflection->getName(), $ancestorClasses, true); - } - - /** - * @return array - */ - public function getMixinTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getMixinTags(); - } - - /** - * @return array - */ - public function getPropertyTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getPropertyTags(); - } - - /** - * @return array - */ - public function getMethodTags(): array - { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return []; - } - - return $resolvedPhpDoc->getMethodTags(); - } - - /** - * @return array - */ - public function getResolvedMixinTypes(): array - { - $types = []; - foreach ($this->getMixinTags() as $mixinTag) { - if (!$this->isGeneric()) { - $types[] = $mixinTag->getType(); - continue; - } - - $types[] = TemplateTypeHelper::resolveTemplateTypes( - $mixinTag->getType(), - $this->getActiveTemplateTypeMap() - ); - } - - return $types; - } + /** @var array */ + private static array $resolvingTypeAliasImports = []; + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param \PHPStan\Type\FileTypeMapper $fileTypeMapper + * @param \PHPStan\Reflection\PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions + * @param \PHPStan\Reflection\MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + * @param string $displayName + * @param \ReflectionClass $reflection + * @param string|null $anonymousFilename + * @param ResolvedPhpDocBlock|null $stubPhpDocBlock + * @param string|null $extraCacheKey + */ + public function __construct( + ReflectionProvider $reflectionProvider, + FileTypeMapper $fileTypeMapper, + PhpVersion $phpVersion, + array $propertiesClassReflectionExtensions, + array $methodsClassReflectionExtensions, + string $displayName, + \ReflectionClass $reflection, + ?string $anonymousFilename, + ?TemplateTypeMap $resolvedTemplateTypeMap, + ?ResolvedPhpDocBlock $stubPhpDocBlock, + ?string $extraCacheKey = null + ) { + $this->reflectionProvider = $reflectionProvider; + $this->fileTypeMapper = $fileTypeMapper; + $this->phpVersion = $phpVersion; + $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; + $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; + $this->displayName = $displayName; + $this->reflection = $reflection; + $this->anonymousFilename = $anonymousFilename; + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; + $this->stubPhpDocBlock = $stubPhpDocBlock; + $this->extraCacheKey = $extraCacheKey; + } + + public function getNativeReflection(): \ReflectionClass + { + return $this->reflection; + } + + /** + * @return string|false + */ + public function getFileName() + { + if (isset($this->filename)) { + return $this->filename; + } + + if ($this->anonymousFilename !== null) { + return $this->filename = $this->anonymousFilename; + } + $fileName = $this->reflection->getFileName(); + if ($fileName === false) { + return $this->filename = false; + } + + if (!file_exists($fileName)) { + return $this->filename = false; + } + + return $this->filename = $fileName; + } + + public function getFileNameWithPhpDocs(): ?string + { + if ($this->stubPhpDocBlock !== null) { + return $this->stubPhpDocBlock->getFilename(); + } + + $filename = $this->getFileName(); + if ($filename === false) { + return null; + } + + return $filename; + } + + /** + * @return false|\PHPStan\Reflection\ClassReflection + */ + public function getParentClass() + { + if ($this->cachedParentClass !== null) { + return $this->cachedParentClass; + } + + $parentClass = $this->reflection->getParentClass(); + + if ($parentClass === false) { + return $this->cachedParentClass = false; + } + + $extendsTag = $this->getFirstExtendsTag(); + + if ($extendsTag !== null && $this->isValidAncestorType($extendsTag->getType(), [$parentClass->getName()])) { + $extendedType = $extendsTag->getType(); + + if ($this->isGeneric()) { + $extendedType = TemplateTypeHelper::resolveTemplateTypes( + $extendedType, + $this->getActiveTemplateTypeMap() + ); + } + + if (!$extendedType instanceof GenericObjectType) { + return $this->reflectionProvider->getClass($parentClass->getName()); + } + + return $extendedType->getClassReflection() ?? $this->reflectionProvider->getClass($parentClass->getName()); + } + + $parentReflection = $this->reflectionProvider->getClass($parentClass->getName()); + if ($parentReflection->isGeneric()) { + return $parentReflection->withTypes( + array_values($parentReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()) + ); + } + + $this->cachedParentClass = $parentReflection; + + return $parentReflection; + } + + /** + * @return class-string + */ + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getDisplayName(bool $withTemplateTypes = true): string + { + $name = $this->displayName; + + if ( + $withTemplateTypes === false + || $this->resolvedTemplateTypeMap === null + || count($this->resolvedTemplateTypeMap->getTypes()) === 0 + ) { + return $name; + } + + return $name . '<' . implode(',', array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::typeOnly()); + }, $this->resolvedTemplateTypeMap->getTypes())) . '>'; + } + + public function getCacheKey(): string + { + $cacheKey = $this->cacheKey; + if ($cacheKey !== null) { + return $this->cacheKey; + } + + $cacheKey = $this->displayName; + + if ($this->resolvedTemplateTypeMap !== null) { + $cacheKey .= '<' . implode(',', array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::cache()); + }, $this->resolvedTemplateTypeMap->getTypes())) . '>'; + } + + if ($this->extraCacheKey !== null) { + $cacheKey .= '-' . $this->extraCacheKey; + } + + $this->cacheKey = $cacheKey; + + return $cacheKey; + } + + /** + * @return int[] + */ + public function getClassHierarchyDistances(): array + { + if ($this->classHierarchyDistances === null) { + $distance = 0; + $distances = [ + $this->getName() => $distance, + ]; + $currentClassReflection = $this->getNativeReflection(); + foreach ($this->collectTraits($this->getNativeReflection()) as $trait) { + $distance++; + if (array_key_exists($trait->getName(), $distances)) { + continue; + } + + $distances[$trait->getName()] = $distance; + } + + while ($currentClassReflection->getParentClass() !== false) { + $distance++; + $parentClassName = $currentClassReflection->getParentClass()->getName(); + if (!array_key_exists($parentClassName, $distances)) { + $distances[$parentClassName] = $distance; + } + $currentClassReflection = $currentClassReflection->getParentClass(); + foreach ($this->collectTraits($currentClassReflection) as $trait) { + $distance++; + if (array_key_exists($trait->getName(), $distances)) { + continue; + } + + $distances[$trait->getName()] = $distance; + } + } + foreach ($this->getNativeReflection()->getInterfaces() as $interface) { + $distance++; + if (array_key_exists($interface->getName(), $distances)) { + continue; + } + + $distances[$interface->getName()] = $distance; + } + + $this->classHierarchyDistances = $distances; + } + + return $this->classHierarchyDistances; + } + + /** + * @param \ReflectionClass $class + * @return \ReflectionClass[] + */ + private function collectTraits(\ReflectionClass $class): array + { + $traits = []; + $traitsLeftToAnalyze = $class->getTraits(); + + while (count($traitsLeftToAnalyze) !== 0) { + $trait = reset($traitsLeftToAnalyze); + $traits[] = $trait; + + foreach ($trait->getTraits() as $subTrait) { + if (in_array($subTrait, $traits, true)) { + continue; + } + + $traitsLeftToAnalyze[] = $subTrait; + } + + array_shift($traitsLeftToAnalyze); + } + + return $traits; + } + + public function hasProperty(string $propertyName): bool + { + foreach ($this->propertiesClassReflectionExtensions as $extension) { + if ($extension->hasProperty($this, $propertyName)) { + return true; + } + } + + return false; + } + + public function hasMethod(string $methodName): bool + { + foreach ($this->methodsClassReflectionExtensions as $extension) { + if ($extension->hasMethod($this, $methodName)) { + return true; + } + } + + return false; + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + $key = $methodName; + if ($scope->isInClass()) { + $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); + } + if (!isset($this->methods[$key])) { + foreach ($this->methodsClassReflectionExtensions as $extension) { + if (!$extension->hasMethod($this, $methodName)) { + continue; + } + + $method = $extension->getMethod($this, $methodName); + if ($scope->canCallMethod($method)) { + return $this->methods[$key] = $method; + } + $this->methods[$key] = $method; + } + } + + if (!isset($this->methods[$key])) { + throw new \PHPStan\Reflection\MissingMethodFromReflectionException($this->getName(), $methodName); + } + + return $this->methods[$key]; + } + + public function hasNativeMethod(string $methodName): bool + { + return $this->getPhpExtension()->hasNativeMethod($this, $methodName); + } + + public function getNativeMethod(string $methodName): MethodReflection + { + if (!$this->hasNativeMethod($methodName)) { + throw new \PHPStan\Reflection\MissingMethodFromReflectionException($this->getName(), $methodName); + } + return $this->getPhpExtension()->getNativeMethod($this, $methodName); + } + + /** + * @deprecated Use ClassReflection::getNativeReflection() instead. + * @return MethodReflection[] + */ + public function getNativeMethods(): array + { + $methods = []; + foreach ($this->reflection->getMethods() as $method) { + $methods[] = $this->getNativeMethod($method->getName()); + } + + return $methods; + } + + public function hasConstructor(): bool + { + return $this->findConstructor() !== null; + } + + public function getConstructor(): MethodReflection + { + $constructor = $this->findConstructor(); + if ($constructor === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + return $this->getNativeMethod($constructor->getName()); + } + + private function findConstructor(): ?ReflectionMethod + { + $constructor = $this->reflection->getConstructor(); + if ($constructor === null) { + return null; + } + + if ($this->phpVersion->supportsLegacyConstructor()) { + return $constructor; + } + + if (strtolower($constructor->getName()) !== '__construct') { + return null; + } + + return $constructor; + } + + private function getPhpExtension(): PhpClassReflectionExtension + { + $extension = $this->methodsClassReflectionExtensions[0]; + if (!$extension instanceof PhpClassReflectionExtension) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $extension; + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + $key = $propertyName; + if ($scope->isInClass()) { + $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); + } + if (!isset($this->properties[$key])) { + foreach ($this->propertiesClassReflectionExtensions as $extension) { + if (!$extension->hasProperty($this, $propertyName)) { + continue; + } + + $property = $extension->getProperty($this, $propertyName); + if ($scope->canAccessProperty($property)) { + return $this->properties[$key] = $property; + } + $this->properties[$key] = $property; + } + } + + if (!isset($this->properties[$key])) { + throw new \PHPStan\Reflection\MissingPropertyFromReflectionException($this->getName(), $propertyName); + } + + return $this->properties[$key]; + } + + public function hasNativeProperty(string $propertyName): bool + { + return $this->getPhpExtension()->hasProperty($this, $propertyName); + } + + public function getNativeProperty(string $propertyName): PhpPropertyReflection + { + if (!$this->hasNativeProperty($propertyName)) { + throw new \PHPStan\Reflection\MissingPropertyFromReflectionException($this->getName(), $propertyName); + } + + return $this->getPhpExtension()->getNativeProperty($this, $propertyName); + } + + public function isAbstract(): bool + { + return $this->reflection->isAbstract(); + } + + public function isInterface(): bool + { + return $this->reflection->isInterface(); + } + + public function isTrait(): bool + { + return $this->reflection->isTrait(); + } + + public function isClass(): bool + { + return !$this->isInterface() && !$this->isTrait(); + } + + public function isAnonymous(): bool + { + return $this->anonymousFilename !== null; + } + + public function isSubclassOf(string $className): bool + { + if (isset($this->subclasses[$className])) { + return $this->subclasses[$className]; + } + + if (!$this->reflectionProvider->hasClass($className)) { + return $this->subclasses[$className] = false; + } + + try { + return $this->subclasses[$className] = $this->reflection->isSubclassOf($className); + } catch (\ReflectionException $e) { + return $this->subclasses[$className] = false; + } + } + + public function implementsInterface(string $className): bool + { + try { + return $this->reflection->implementsInterface($className); + } catch (\ReflectionException $e) { + return false; + } + } + + /** + * @return \PHPStan\Reflection\ClassReflection[] + */ + public function getParents(): array + { + $parents = []; + $parent = $this->getParentClass(); + while ($parent !== false) { + $parents[] = $parent; + $parent = $parent->getParentClass(); + } + + return $parents; + } + + /** + * @return \PHPStan\Reflection\ClassReflection[] + */ + public function getInterfaces(): array + { + if ($this->cachedInterfaces !== null) { + return $this->cachedInterfaces; + } + + $interfaces = []; + + $parent = $this->getParentClass(); + if ($parent !== false) { + foreach ($parent->getInterfaces() as $interface) { + $interfaces[$interface->getName()] = $interface; + } + } + + if ($this->reflection->isInterface()) { + $implementsTags = $this->getExtendsTags(); + } else { + $implementsTags = $this->getImplementsTags(); + } + + $interfaceNames = $this->reflection->getInterfaceNames(); + $genericInterfaces = []; + + foreach ($implementsTags as $implementsTag) { + $implementedType = $implementsTag->getType(); + + if (!$this->isValidAncestorType($implementedType, $interfaceNames)) { + continue; + } + + if ($this->isGeneric()) { + $implementedType = TemplateTypeHelper::resolveTemplateTypes( + $implementedType, + $this->getActiveTemplateTypeMap() + ); + } + + if (!$implementedType instanceof GenericObjectType) { + continue; + } + + $reflectionIface = $implementedType->getClassReflection(); + if ($reflectionIface === null) { + continue; + } + + $genericInterfaces[] = $reflectionIface; + } + + foreach ($genericInterfaces as $genericInterface) { + $interfaces = array_merge($interfaces, $genericInterface->getInterfaces()); + } + + foreach ($genericInterfaces as $genericInterface) { + $interfaces[$genericInterface->getName()] = $genericInterface; + } + + foreach ($interfaceNames as $interfaceName) { + if (isset($interfaces[$interfaceName])) { + continue; + } + + $interfaceReflection = $this->reflectionProvider->getClass($interfaceName); + if (!$interfaceReflection->isGeneric()) { + $interfaces[$interfaceName] = $interfaceReflection; + continue; + } + + $interfaces[$interfaceName] = $interfaceReflection->withTypes( + array_values($interfaceReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()) + ); + } + + $this->cachedInterfaces = $interfaces; + + return $interfaces; + } + + /** + * @return \PHPStan\Reflection\ClassReflection[] + */ + public function getTraits(): array + { + return array_map(function (\ReflectionClass $trait): ClassReflection { + return $this->reflectionProvider->getClass($trait->getName()); + }, $this->getNativeReflection()->getTraits()); + } + + /** + * @return string[] + */ + public function getParentClassesNames(): array + { + $parentNames = []; + $currentClassReflection = $this; + while ($currentClassReflection->getParentClass() !== false) { + $parentNames[] = $currentClassReflection->getParentClass()->getName(); + $currentClassReflection = $currentClassReflection->getParentClass(); + } + + return $parentNames; + } + + public function hasConstant(string $name): bool + { + if (!$this->getNativeReflection()->hasConstant($name)) { + return false; + } + + $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name); + if ($reflectionConstant === false) { + return false; + } + + return $this->reflectionProvider->hasClass($reflectionConstant->getDeclaringClass()->getName()); + } + + public function getConstant(string $name): ConstantReflection + { + if (!isset($this->constants[$name])) { + $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name); + if ($reflectionConstant === false) { + throw new \PHPStan\Reflection\MissingConstantFromReflectionException($this->getName(), $name); + } + + $deprecatedDescription = null; + $isDeprecated = false; + $isInternal = false; + $declaringClass = $reflectionConstant->getDeclaringClass(); + $fileName = $declaringClass->getFileName(); + if ($reflectionConstant->getDocComment() !== false && $fileName !== false) { + $docComment = $reflectionConstant->getDocComment(); + $className = $declaringClass->getName(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $className, null, null, $docComment); + + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + } + + $this->constants[$name] = new ClassConstantReflection( + $this->reflectionProvider->getClass($declaringClass->getName()), + $reflectionConstant, + $deprecatedDescription, + $isDeprecated, + $isInternal + ); + } + return $this->constants[$name]; + } + + public function hasTraitUse(string $traitName): bool + { + return in_array($traitName, $this->getTraitNames(), true); + } + + /** + * @return string[] + */ + private function getTraitNames(): array + { + $class = $this->reflection; + $traitNames = $class->getTraitNames(); + while ($class->getParentClass() !== false) { + $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames()))); + $class = $class->getParentClass(); + } + + return $traitNames; + } + + /** + * @return array + */ + public function getTypeAliases(): array + { + if ($this->typeAliases === null) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return $this->typeAliases = []; + } + + $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags(); + $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags(); + + // prevent circular imports + if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) { + throw new \PHPStan\Type\CircularTypeAliasDefinitionException(); + } + + self::$resolvingTypeAliasImports[$this->getName()] = true; + + $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias { + $importedAlias = $typeAliasImportTag->getImportedAlias(); + $importedFromClassName = $typeAliasImportTag->getImportedFrom(); + + if (!$this->reflectionProvider->hasClass($importedFromClassName)) { + return null; + } + + $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName); + + try { + $typeAliases = $importedFromReflection->getTypeAliases(); + } catch (\PHPStan\Type\CircularTypeAliasDefinitionException $e) { + return TypeAlias::invalid(); + } + + if (!array_key_exists($importedAlias, $typeAliases)) { + return null; + } + + return $typeAliases[$importedAlias]; + }, $typeAliasImportTags); + + unset(self::$resolvingTypeAliasImports[$this->getName()]); + + $localAliases = array_map(static function (TypeAliasTag $typeAliasTag): TypeAlias { + return $typeAliasTag->getTypeAlias(); + }, $typeAliasTags); + + $this->typeAliases = array_filter( + array_merge($importedAliases, $localAliases), + static function (?TypeAlias $typeAlias): bool { + return $typeAlias !== null; + } + ); + } + + return $this->typeAliases; + } + + public function getDeprecatedDescription(): ?string + { + if ($this->deprecatedDescription === null && $this->isDeprecated()) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) { + $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage(); + } + } + + return $this->deprecatedDescription; + } + + public function isDeprecated(): bool + { + if ($this->isDeprecated === null) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + $this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated(); + } + + return $this->isDeprecated; + } + + public function isBuiltin(): bool + { + return $this->reflection->isInternal(); + } + + public function isInternal(): bool + { + if ($this->isInternal === null) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal(); + } + + return $this->isInternal; + } + + public function isFinal(): bool + { + if ($this->isFinal === null) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + $this->isFinal = $this->reflection->isFinal() + || ($resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal()); + } + + return $this->isFinal; + } + + public function isFinalByKeyword(): bool + { + return $this->reflection->isFinal(); + } + + public function isAttributeClass(): bool + { + return $this->findAttributeClass() !== null; + } + + private function findAttributeClass(): ?Attribute + { + if ($this->isInterface() || $this->isTrait()) { + return null; + } + + if ($this->reflection instanceof ReflectionClass) { + foreach ($this->reflection->getBetterReflection()->getAttributes() as $attribute) { + if ($attribute->getName() === \Attribute::class) { + /** @var \Attribute */ + return $attribute->newInstance(); + } + } + + return null; + } + + if (!method_exists($this->reflection, 'getAttributes')) { + return null; + } + + $nativeAttributes = $this->reflection->getAttributes(\Attribute::class); + if (count($nativeAttributes) === 1) { + /** @var Attribute */ + return $nativeAttributes[0]->newInstance(); + } + + return null; + } + + public function getAttributeClassFlags(): int + { + $attribute = $this->findAttributeClass(); + if ($attribute === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $attribute->flags; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + if ($this->templateTypeMap !== null) { + return $this->templateTypeMap; + } + + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + $this->templateTypeMap = TemplateTypeMap::createEmpty(); + return $this->templateTypeMap; + } + + $templateTypeScope = TemplateTypeScope::createWithClass($this->getName()); + + $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { + return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); + }, $this->getTemplateTags())); + + $this->templateTypeMap = $templateTypeMap; + + return $templateTypeMap; + } + + public function getActiveTemplateTypeMap(): TemplateTypeMap + { + return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap(); + } + + public function isGeneric(): bool + { + if ($this->isGeneric === null) { + $this->isGeneric = count($this->getTemplateTags()) > 0; + } + + return $this->isGeneric; + } + + /** + * @param array $types + * @return \PHPStan\Type\Generic\TemplateTypeMap + */ + public function typeMapFromList(array $types): TemplateTypeMap + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return TemplateTypeMap::createEmpty(); + } + + $map = []; + $i = 0; + foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { + $map[$tag->getName()] = $types[$i] ?? new ErrorType(); + $i++; + } + + return new TemplateTypeMap($map); + } + + /** @return array */ + public function typeMapToList(TemplateTypeMap $typeMap): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + $list = []; + foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { + $list[] = $typeMap->getType($tag->getName()) ?? $tag->getBound(); + } + + return $list; + } + + /** + * @param array $types + */ + public function withTypes(array $types): self + { + return new self( + $this->reflectionProvider, + $this->fileTypeMapper, + $this->phpVersion, + $this->propertiesClassReflectionExtensions, + $this->methodsClassReflectionExtensions, + $this->displayName, + $this->reflection, + $this->anonymousFilename, + $this->typeMapFromList($types), + $this->stubPhpDocBlock + ); + } + + public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock + { + if ($this->stubPhpDocBlock !== null) { + return $this->stubPhpDocBlock; + } + + $fileName = $this->getFileName(); + if ($fileName === false) { + return null; + } + + if ($this->reflectionDocComment === null) { + $this->reflectionDocComment = $this->reflection->getDocComment(); + } + + if ($this->reflectionDocComment === false) { + return null; + } + + return $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment); + } + + private function getFirstExtendsTag(): ?ExtendsTag + { + foreach ($this->getExtendsTags() as $tag) { + return $tag; + } + + return null; + } + + /** @return ExtendsTag[] */ + private function getExtendsTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getExtendsTags(); + } + + /** @return ImplementsTag[] */ + private function getImplementsTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getImplementsTags(); + } + + /** @return array */ + public function getTemplateTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getTemplateTags(); + } + + /** + * @return array + */ + public function getAncestors(): array + { + $ancestors = $this->ancestors; + + if ($ancestors === null) { + $ancestors = [ + $this->getName() => $this, + ]; + + $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void { + if (array_key_exists($name, $ancestors)) { + return; + } + + $ancestors[$name] = $classReflection; + }; + + foreach ($this->getInterfaces() as $interface) { + $addToAncestors($interface->getName(), $interface); + foreach ($interface->getAncestors() as $name => $ancestor) { + $addToAncestors($name, $ancestor); + } + } + + foreach ($this->getTraits() as $trait) { + $addToAncestors($trait->getName(), $trait); + foreach ($trait->getAncestors() as $name => $ancestor) { + $addToAncestors($name, $ancestor); + } + } + + $parent = $this->getParentClass(); + if ($parent !== false) { + $addToAncestors($parent->getName(), $parent); + foreach ($parent->getAncestors() as $name => $ancestor) { + $addToAncestors($name, $ancestor); + } + } + + $this->ancestors = $ancestors; + } + + return $ancestors; + } + + public function getAncestorWithClassName(string $className): ?self + { + return $this->getAncestors()[$className] ?? null; + } + + /** + * @param string[] $ancestorClasses + */ + private function isValidAncestorType(Type $type, array $ancestorClasses): bool + { + if (!$type instanceof GenericObjectType) { + return false; + } + + $reflection = $type->getClassReflection(); + if ($reflection === null) { + return false; + } + + return in_array($reflection->getName(), $ancestorClasses, true); + } + + /** + * @return array + */ + public function getMixinTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getMixinTags(); + } + + /** + * @return array + */ + public function getPropertyTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getPropertyTags(); + } + + /** + * @return array + */ + public function getMethodTags(): array + { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return []; + } + + return $resolvedPhpDoc->getMethodTags(); + } + + /** + * @return array + */ + public function getResolvedMixinTypes(): array + { + $types = []; + foreach ($this->getMixinTags() as $mixinTag) { + if (!$this->isGeneric()) { + $types[] = $mixinTag->getType(); + continue; + } + + $types[] = TemplateTypeHelper::resolveTemplateTypes( + $mixinTag->getType(), + $this->getActiveTemplateTypeMap() + ); + } + + return $types; + } } diff --git a/src/Reflection/ClassReflectionExtensionRegistry.php b/src/Reflection/ClassReflectionExtensionRegistry.php index 9acc50ae61..71967cb03a 100644 --- a/src/Reflection/ClassReflectionExtensionRegistry.php +++ b/src/Reflection/ClassReflectionExtensionRegistry.php @@ -1,4 +1,6 @@ -setBroker($broker); - } - $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; - $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; - } - - /** - * @return \PHPStan\Reflection\PropertiesClassReflectionExtension[] - */ - public function getPropertiesClassReflectionExtensions(): array - { - return $this->propertiesClassReflectionExtensions; - } - - /** - * @return \PHPStan\Reflection\MethodsClassReflectionExtension[] - */ - public function getMethodsClassReflectionExtensions(): array - { - return $this->methodsClassReflectionExtensions; - } - + /** @var \PHPStan\Reflection\PropertiesClassReflectionExtension[] */ + private array $propertiesClassReflectionExtensions; + + /** @var \PHPStan\Reflection\MethodsClassReflectionExtension[] */ + private array $methodsClassReflectionExtensions; + + /** + * @param \PHPStan\Broker\Broker $broker + * @param \PHPStan\Reflection\PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions + * @param \PHPStan\Reflection\MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + */ + public function __construct( + Broker $broker, + array $propertiesClassReflectionExtensions, + array $methodsClassReflectionExtensions + ) { + foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions) as $extension) { + if (!($extension instanceof BrokerAwareExtension)) { + continue; + } + + $extension->setBroker($broker); + } + $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; + $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; + } + + /** + * @return \PHPStan\Reflection\PropertiesClassReflectionExtension[] + */ + public function getPropertiesClassReflectionExtensions(): array + { + return $this->propertiesClassReflectionExtensions; + } + + /** + * @return \PHPStan\Reflection\MethodsClassReflectionExtension[] + */ + public function getMethodsClassReflectionExtensions(): array + { + return $this->methodsClassReflectionExtensions; + } } diff --git a/src/Reflection/Constant/RuntimeConstantReflection.php b/src/Reflection/Constant/RuntimeConstantReflection.php index a703fa4a34..a721eec1ae 100644 --- a/src/Reflection/Constant/RuntimeConstantReflection.php +++ b/src/Reflection/Constant/RuntimeConstantReflection.php @@ -1,4 +1,6 @@ -name = $name; - $this->valueType = $valueType; - $this->fileName = $fileName; - } - - public function getName(): string - { - return $this->name; - } - - public function getValueType(): Type - { - return $this->valueType; - } - - public function getFileName(): ?string - { - return $this->fileName; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - + private string $name; + + private Type $valueType; + + private ?string $fileName; + + public function __construct( + string $name, + Type $valueType, + ?string $fileName + ) { + $this->name = $name; + $this->valueType = $valueType; + $this->fileName = $fileName; + } + + public function getName(): string + { + return $this->name; + } + + public function getValueType(): Type + { + return $this->valueType; + } + + public function getFileName(): ?string + { + return $this->fileName; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } } diff --git a/src/Reflection/ConstantReflection.php b/src/Reflection/ConstantReflection.php index ada86cebfa..42c8d542de 100644 --- a/src/Reflection/ConstantReflection.php +++ b/src/Reflection/ConstantReflection.php @@ -1,13 +1,13 @@ -declaringClass = $declaringClass; - $this->reflection = $reflection; - $this->variants = $variants; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->reflection->getDocComment(); - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getPrototype(): ClassMemberReflection - { - return $this->reflection->getPrototype(); - } - - public function getVariants(): array - { - return $this->variants; - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->reflection->getDeprecatedDescription(); - } - - public function isFinal(): TrinaryLogic - { - return $this->reflection->isFinal(); - } - - public function isInternal(): TrinaryLogic - { - return $this->reflection->isInternal(); - } - - public function getThrowType(): ?Type - { - return $this->reflection->getThrowType(); - } - - public function hasSideEffects(): TrinaryLogic - { - return $this->reflection->hasSideEffects(); - } - + private ClassReflection $declaringClass; + + private MethodReflection $reflection; + + /** @var ParametersAcceptor[] */ + private array $variants; + + /** + * @param MethodReflection $reflection + * @param ParametersAcceptor[] $variants + */ + public function __construct(ClassReflection $declaringClass, MethodReflection $reflection, array $variants) + { + $this->declaringClass = $declaringClass; + $this->reflection = $reflection; + $this->variants = $variants; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->reflection->getPrototype(); + } + + public function getVariants(): array + { + return $this->variants; + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->reflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->reflection->hasSideEffects(); + } } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index e8217e91f4..9364d99c84 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->reflection = $reflection; - $this->readableType = $readableType; - $this->writableType = $writableType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->reflection->getDocComment(); - } - - public function getReadableType(): Type - { - return $this->readableType; - } - - public function getWritableType(): Type - { - return $this->writableType; - } - - public function canChangeTypeAfterAssignment(): bool - { - return $this->reflection->canChangeTypeAfterAssignment(); - } - - public function isReadable(): bool - { - return $this->reflection->isReadable(); - } - - public function isWritable(): bool - { - return $this->reflection->isWritable(); - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->reflection->getDeprecatedDescription(); - } - - public function isInternal(): TrinaryLogic - { - return $this->reflection->isInternal(); - } - - public function getOriginalReflection(): PropertyReflection - { - return $this->reflection; - } - + private ClassReflection $declaringClass; + + private PropertyReflection $reflection; + + private Type $readableType; + + private Type $writableType; + + public function __construct(ClassReflection $declaringClass, PropertyReflection $reflection, Type $readableType, Type $writableType) + { + $this->declaringClass = $declaringClass; + $this->reflection = $reflection; + $this->readableType = $readableType; + $this->writableType = $writableType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function getReadableType(): Type + { + return $this->readableType; + } + + public function getWritableType(): Type + { + return $this->writableType; + } + + public function canChangeTypeAfterAssignment(): bool + { + return $this->reflection->canChangeTypeAfterAssignment(); + } + + public function isReadable(): bool + { + return $this->reflection->isReadable(); + } + + public function isWritable(): bool + { + return $this->reflection->isWritable(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } + + public function getOriginalReflection(): PropertyReflection + { + return $this->reflection; + } } diff --git a/src/Reflection/Dummy/DummyConstantReflection.php b/src/Reflection/Dummy/DummyConstantReflection.php index 2137837e39..52ba63364d 100644 --- a/src/Reflection/Dummy/DummyConstantReflection.php +++ b/src/Reflection/Dummy/DummyConstantReflection.php @@ -1,4 +1,6 @@ -name = $name; - } - - public function getDeclaringClass(): ClassReflection - { - $broker = Broker::getInstance(); - - return $broker->getClass(\stdClass::class); - } - - public function getFileName(): ?string - { - return null; - } - - public function isStatic(): bool - { - return true; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return mixed - */ - public function getValue() - { - // so that Scope::getTypeFromValue() returns mixed - return new \stdClass(); - } - - public function getValueType(): Type - { - return new MixedType(); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDocComment(): ?string - { - return null; - } - + private string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function getDeclaringClass(): ClassReflection + { + $broker = Broker::getInstance(); + + return $broker->getClass(\stdClass::class); + } + + public function getFileName(): ?string + { + return null; + } + + public function isStatic(): bool + { + return true; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return mixed + */ + public function getValue() + { + // so that Scope::getTypeFromValue() returns mixed + return new \stdClass(); + } + + public function getValueType(): Type + { + return new MixedType(); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 18b75b19f4..9975961369 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getName(): string - { - return '__construct'; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - public function getVariants(): array - { - return [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [], - false, - new VoidType() - ), - ]; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getThrowType(): ?Type - { - return null; - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDocComment(): ?string - { - return null; - } - + private ClassReflection $declaringClass; + + public function __construct(ClassReflection $declaringClass) + { + $this->declaringClass = $declaringClass; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getName(): string + { + return '__construct'; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + public function getVariants(): array + { + return [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [], + false, + new VoidType() + ), + ]; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index bf12ffb38b..11caee0ed6 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -1,4 +1,6 @@ -name = $name; - } - - public function getDeclaringClass(): ClassReflection - { - $broker = Broker::getInstance(); - - return $broker->getClass(\stdClass::class); - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getName(): string - { - return $this->name; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getVariants(): array - { - return [ - new TrivialParametersAcceptor(), - ]; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getThrowType(): ?Type - { - return null; - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDocComment(): ?string - { - return null; - } - + private string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function getDeclaringClass(): ClassReflection + { + $broker = Broker::getInstance(); + + return $broker->getClass(\stdClass::class); + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getName(): string + { + return $this->name; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getVariants(): array + { + return [ + new TrivialParametersAcceptor(), + ]; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 1f5b97379e..198a3602da 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -1,4 +1,6 @@ -getClass(\stdClass::class); - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getReadableType(): Type - { - return new MixedType(); - } - - public function getWritableType(): Type - { - return new MixedType(); - } - - public function canChangeTypeAfterAssignment(): bool - { - return true; - } - - public function isReadable(): bool - { - return true; - } - - public function isWritable(): bool - { - return true; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getDocComment(): ?string - { - return null; - } - + public function getDeclaringClass(): ClassReflection + { + $broker = Broker::getInstance(); + + return $broker->getClass(\stdClass::class); + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getReadableType(): Type + { + return new MixedType(); + } + + public function getWritableType(): Type + { + return new MixedType(); + } + + public function canChangeTypeAfterAssignment(): bool + { + return true; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index b973ac5582..ae4db62078 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -1,4 +1,6 @@ - */ - private array $parameters; + private ?TemplateTypeMap $resolvedTemplateTypeMap; - private bool $isVariadic; + /** @var array */ + private array $parameters; - private Type $returnType; + private bool $isVariadic; - /** - * @param array $parameters - * @param bool $isVariadic - * @param Type $returnType - */ - public function __construct( - TemplateTypeMap $templateTypeMap, - ?TemplateTypeMap $resolvedTemplateTypeMap, - array $parameters, - bool $isVariadic, - Type $returnType - ) - { - $this->templateTypeMap = $templateTypeMap; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - $this->parameters = $parameters; - $this->isVariadic = $isVariadic; - $this->returnType = $returnType; - } + private Type $returnType; - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->templateTypeMap; - } + /** + * @param array $parameters + * @param bool $isVariadic + * @param Type $returnType + */ + public function __construct( + TemplateTypeMap $templateTypeMap, + ?TemplateTypeMap $resolvedTemplateTypeMap, + array $parameters, + bool $isVariadic, + Type $returnType + ) { + $this->templateTypeMap = $templateTypeMap; + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; + $this->parameters = $parameters; + $this->isVariadic = $isVariadic; + $this->returnType = $returnType; + } - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return $this->resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty(); - } + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->templateTypeMap; + } - /** - * @return array - */ - public function getParameters(): array - { - return $this->parameters; - } + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return $this->resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty(); + } - public function isVariadic(): bool - { - return $this->isVariadic; - } + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } - public function getReturnType(): Type - { - return $this->returnType; - } + public function isVariadic(): bool + { + return $this->isVariadic; + } + public function getReturnType(): Type + { + return $this->returnType; + } } diff --git a/src/Reflection/FunctionVariantWithPhpDocs.php b/src/Reflection/FunctionVariantWithPhpDocs.php index 73e0d6f1e1..7efd248d64 100644 --- a/src/Reflection/FunctionVariantWithPhpDocs.php +++ b/src/Reflection/FunctionVariantWithPhpDocs.php @@ -1,4 +1,6 @@ - $parameters - * @param bool $isVariadic - * @param Type $returnType - * @param Type $phpDocReturnType - * @param Type $nativeReturnType - */ - public function __construct( - TemplateTypeMap $templateTypeMap, - ?TemplateTypeMap $resolvedTemplateTypeMap, - array $parameters, - bool $isVariadic, - Type $returnType, - Type $phpDocReturnType, - Type $nativeReturnType - ) - { - parent::__construct( - $templateTypeMap, - $resolvedTemplateTypeMap, - $parameters, - $isVariadic, - $returnType - ); - $this->phpDocReturnType = $phpDocReturnType; - $this->nativeReturnType = $nativeReturnType; - } + private Type $nativeReturnType; - /** - * @return array - */ - public function getParameters(): array - { - /** @var \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parameters */ - $parameters = parent::getParameters(); + /** + * @param TemplateTypeMap $templateTypeMap + * @param array $parameters + * @param bool $isVariadic + * @param Type $returnType + * @param Type $phpDocReturnType + * @param Type $nativeReturnType + */ + public function __construct( + TemplateTypeMap $templateTypeMap, + ?TemplateTypeMap $resolvedTemplateTypeMap, + array $parameters, + bool $isVariadic, + Type $returnType, + Type $phpDocReturnType, + Type $nativeReturnType + ) { + parent::__construct( + $templateTypeMap, + $resolvedTemplateTypeMap, + $parameters, + $isVariadic, + $returnType + ); + $this->phpDocReturnType = $phpDocReturnType; + $this->nativeReturnType = $nativeReturnType; + } - return $parameters; - } + /** + * @return array + */ + public function getParameters(): array + { + /** @var \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parameters */ + $parameters = parent::getParameters(); - public function getPhpDocReturnType(): Type - { - return $this->phpDocReturnType; - } + return $parameters; + } - public function getNativeReturnType(): Type - { - return $this->nativeReturnType; - } + public function getPhpDocReturnType(): Type + { + return $this->phpDocReturnType; + } + public function getNativeReturnType(): Type + { + return $this->nativeReturnType; + } } diff --git a/src/Reflection/Generic/ResolvedFunctionVariant.php b/src/Reflection/Generic/ResolvedFunctionVariant.php index 81c76bc77f..d6f9488f74 100644 --- a/src/Reflection/Generic/ResolvedFunctionVariant.php +++ b/src/Reflection/Generic/ResolvedFunctionVariant.php @@ -1,4 +1,6 @@ -parametersAcceptor = $parametersAcceptor; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->parametersAcceptor->getTemplateTypeMap(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return $this->resolvedTemplateTypeMap; - } - - public function getParameters(): array - { - $parameters = $this->parameters; - - if ($parameters === null) { - $parameters = array_map(function (ParameterReflection $param): ParameterReflection { - return new DummyParameter( - $param->getName(), - TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap), - $param->isOptional(), - $param->passedByReference(), - $param->isVariadic(), - $param->getDefaultValue() - ); - }, $this->parametersAcceptor->getParameters()); - - $this->parameters = $parameters; - } - - return $parameters; - } - - public function isVariadic(): bool - { - return $this->parametersAcceptor->isVariadic(); - } - - public function getReturnType(): Type - { - $type = $this->returnType; - - if ($type === null) { - $type = TemplateTypeHelper::resolveTemplateTypes( - $this->parametersAcceptor->getReturnType(), - $this->resolvedTemplateTypeMap - ); - - $this->returnType = $type; - } - - return $type; - } - + private ParametersAcceptor $parametersAcceptor; + + private TemplateTypeMap $resolvedTemplateTypeMap; + + /** @var ParameterReflection[]|null */ + private ?array $parameters = null; + + private ?Type $returnType = null; + + public function __construct( + ParametersAcceptor $parametersAcceptor, + TemplateTypeMap $resolvedTemplateTypeMap + ) { + $this->parametersAcceptor = $parametersAcceptor; + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->parametersAcceptor->getTemplateTypeMap(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return $this->resolvedTemplateTypeMap; + } + + public function getParameters(): array + { + $parameters = $this->parameters; + + if ($parameters === null) { + $parameters = array_map(function (ParameterReflection $param): ParameterReflection { + return new DummyParameter( + $param->getName(), + TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap), + $param->isOptional(), + $param->passedByReference(), + $param->isVariadic(), + $param->getDefaultValue() + ); + }, $this->parametersAcceptor->getParameters()); + + $this->parameters = $parameters; + } + + return $parameters; + } + + public function isVariadic(): bool + { + return $this->parametersAcceptor->isVariadic(); + } + + public function getReturnType(): Type + { + $type = $this->returnType; + + if ($type === null) { + $type = TemplateTypeHelper::resolveTemplateTypes( + $this->parametersAcceptor->getReturnType(), + $this->resolvedTemplateTypeMap + ); + + $this->returnType = $type; + } + + return $type; + } } diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 3bc038aad8..0015199be0 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -1,4 +1,6 @@ -getParameters() as $i => $param) { - if (isset($argTypes[$i])) { - $argType = $argTypes[$i]; - } elseif ($param->getDefaultValue() !== null) { - $argType = $param->getDefaultValue(); - } else { - break; - } - - $paramType = $param->getType(); - $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); - } - - return new ResolvedFunctionVariant( - $parametersAcceptor, - new TemplateTypeMap(array_merge( - $parametersAcceptor->getTemplateTypeMap()->map(static function (string $name, Type $type): Type { - return new ErrorType(); - })->getTypes(), - $typeMap->getTypes() - )) - ); - } - + /** + * Resolve template types + * + * @param \PHPStan\Type\Type[] $argTypes Unpacked arguments + */ + public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ParametersAcceptor + { + $typeMap = TemplateTypeMap::createEmpty(); + + foreach ($parametersAcceptor->getParameters() as $i => $param) { + if (isset($argTypes[$i])) { + $argType = $argTypes[$i]; + } elseif ($param->getDefaultValue() !== null) { + $argType = $param->getDefaultValue(); + } else { + break; + } + + $paramType = $param->getType(); + $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); + } + + return new ResolvedFunctionVariant( + $parametersAcceptor, + new TemplateTypeMap(array_merge( + $parametersAcceptor->getTemplateTypeMap()->map(static function (string $name, Type $type): Type { + return new ErrorType(); + })->getTypes(), + $typeMap->getTypes() + )) + ); + } } diff --git a/src/Reflection/GlobalConstantReflection.php b/src/Reflection/GlobalConstantReflection.php index ad6d9d967e..8992a3ffdd 100644 --- a/src/Reflection/GlobalConstantReflection.php +++ b/src/Reflection/GlobalConstantReflection.php @@ -1,4 +1,6 @@ -methodReflection = $methodReflection; - } - - public function getMethod(): MethodReflection - { - return $this->methodReflection; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - /** - * @return array - */ - public function getParameters(): array - { - return []; - } - - public function isVariadic(): bool - { - return true; - } - - public function getReturnType(): Type - { - return new MixedType(); - } - + private MethodReflection $methodReflection; + + public function __construct(MethodReflection $methodReflection) + { + $this->methodReflection = $methodReflection; + } + + public function getMethod(): MethodReflection + { + return $this->methodReflection; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + /** + * @return array + */ + public function getParameters(): array + { + return []; + } + + public function isVariadic(): bool + { + return true; + } + + public function getReturnType(): Type + { + return new MixedType(); + } } diff --git a/src/Reflection/MethodPrototypeReflection.php b/src/Reflection/MethodPrototypeReflection.php index cb2ecabc58..fc2b8ba00d 100644 --- a/src/Reflection/MethodPrototypeReflection.php +++ b/src/Reflection/MethodPrototypeReflection.php @@ -1,104 +1,103 @@ -name = $name; - $this->declaringClass = $declaringClass; - $this->isStatic = $isStatic; - $this->isPrivate = $isPrivate; - $this->isPublic = $isPublic; - $this->isAbstract = $isAbstract; - $this->isFinal = $isFinal; - $this->variants = $variants; - } - - public function getName(): string - { - return $this->name; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return $this->isStatic; - } - - public function isPrivate(): bool - { - return $this->isPrivate; - } - - public function isPublic(): bool - { - return $this->isPublic; - } - - public function isAbstract(): bool - { - return $this->isAbstract; - } - - public function isFinal(): bool - { - return $this->isFinal; - } - - public function getDocComment(): ?string - { - return null; - } - - /** - * @return ParametersAcceptor[] - */ - public function getVariants(): array - { - return $this->variants; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private string $name; + + private bool $isStatic; + + private bool $isPrivate; + + private bool $isPublic; + + private bool $isAbstract; + + private bool $isFinal; + + /** @var ParametersAcceptor[] */ + private array $variants; + + /** + * @param string $name + * @param ClassReflection $declaringClass + * @param bool $isStatic + * @param bool $isPrivate + * @param bool $isPublic + * @param bool $isAbstract + * @param bool $isFinal + * @param ParametersAcceptor[] $variants + */ + public function __construct( + string $name, + ClassReflection $declaringClass, + bool $isStatic, + bool $isPrivate, + bool $isPublic, + bool $isAbstract, + bool $isFinal, + array $variants + ) { + $this->name = $name; + $this->declaringClass = $declaringClass; + $this->isStatic = $isStatic; + $this->isPrivate = $isPrivate; + $this->isPublic = $isPublic; + $this->isAbstract = $isAbstract; + $this->isFinal = $isFinal; + $this->variants = $variants; + } + + public function getName(): string + { + return $this->name; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->isStatic; + } + + public function isPrivate(): bool + { + return $this->isPrivate; + } + + public function isPublic(): bool + { + return $this->isPublic; + } + + public function isAbstract(): bool + { + return $this->isAbstract; + } + + public function isFinal(): bool + { + return $this->isFinal; + } + + public function getDocComment(): ?string + { + return null; + } + + /** + * @return ParametersAcceptor[] + */ + public function getVariants(): array + { + return $this->variants; + } } diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index a65c2016d7..98abb0bcc0 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - $this->static = $static; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->reflection->getDeclaringClass(); - } - - public function isStatic(): bool - { - return $this->static; - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->reflection->getDocComment(); - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getPrototype(): ClassMemberReflection - { - return $this->reflection->getPrototype(); - } - - public function getVariants(): array - { - return $this->reflection->getVariants(); - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->reflection->getDeprecatedDescription(); - } - - public function isFinal(): TrinaryLogic - { - return $this->reflection->isFinal(); - } - - public function isInternal(): TrinaryLogic - { - return $this->reflection->isInternal(); - } - - public function getThrowType(): ?Type - { - return $this->reflection->getThrowType(); - } - - public function hasSideEffects(): TrinaryLogic - { - return $this->reflection->hasSideEffects(); - } - + private MethodReflection $reflection; + + private bool $static; + + public function __construct(MethodReflection $reflection, bool $static) + { + $this->reflection = $reflection; + $this->static = $static; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->reflection->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->static; + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->reflection->getPrototype(); + } + + public function getVariants(): array + { + return $this->reflection->getVariants(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->reflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->reflection->hasSideEffects(); + } } diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index f5af02cc28..c6a2ea64f2 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -1,4 +1,6 @@ -mixinExcludeClasses = $mixinExcludeClasses; - } - - public function hasMethod(ClassReflection $classReflection, string $methodName): bool - { - return $this->findMethod($classReflection, $methodName) !== null; - } - - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - $method = $this->findMethod($classReflection, $methodName); - if ($method === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $method; - } - - private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection - { - $mixinTypes = $classReflection->getResolvedMixinTypes(); - foreach ($mixinTypes as $type) { - if (count(array_intersect(TypeUtils::getDirectClassNames($type), $this->mixinExcludeClasses)) > 0) { - continue; - } - - if (!$type->hasMethod($methodName)->yes()) { - continue; - } - - $method = $type->getMethod($methodName, new OutOfClassScope()); - $static = $method->isStatic(); - if ( - !$static - && $classReflection->hasNativeMethod('__callStatic') - && !$classReflection->hasNativeMethod('__call') - ) { - $static = true; - } - - return new MixinMethodReflection($method, $static); - } - - foreach ($classReflection->getParents() as $parentClass) { - $method = $this->findMethod($parentClass, $methodName); - if ($method === null) { - continue; - } - - return $method; - } - - return null; - } - + /** @var string[] */ + private array $mixinExcludeClasses; + + /** + * @param string[] $mixinExcludeClasses + */ + public function __construct(array $mixinExcludeClasses) + { + $this->mixinExcludeClasses = $mixinExcludeClasses; + } + + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $this->findMethod($classReflection, $methodName) !== null; + } + + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + $method = $this->findMethod($classReflection, $methodName); + if ($method === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $method; + } + + private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection + { + $mixinTypes = $classReflection->getResolvedMixinTypes(); + foreach ($mixinTypes as $type) { + if (count(array_intersect(TypeUtils::getDirectClassNames($type), $this->mixinExcludeClasses)) > 0) { + continue; + } + + if (!$type->hasMethod($methodName)->yes()) { + continue; + } + + $method = $type->getMethod($methodName, new OutOfClassScope()); + $static = $method->isStatic(); + if ( + !$static + && $classReflection->hasNativeMethod('__callStatic') + && !$classReflection->hasNativeMethod('__call') + ) { + $static = true; + } + + return new MixinMethodReflection($method, $static); + } + + foreach ($classReflection->getParents() as $parentClass) { + $method = $this->findMethod($parentClass, $methodName); + if ($method === null) { + continue; + } + + return $method; + } + + return null; + } } diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index 4ab4635111..0271d8b094 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -1,4 +1,6 @@ -mixinExcludeClasses = $mixinExcludeClasses; - } - - public function hasProperty(ClassReflection $classReflection, string $propertyName): bool - { - return $this->findProperty($classReflection, $propertyName) !== null; - } + /** + * @param string[] $mixinExcludeClasses + */ + public function __construct(array $mixinExcludeClasses) + { + $this->mixinExcludeClasses = $mixinExcludeClasses; + } - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection - { - $property = $this->findProperty($classReflection, $propertyName); - if ($property === null) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function hasProperty(ClassReflection $classReflection, string $propertyName): bool + { + return $this->findProperty($classReflection, $propertyName) !== null; + } - return $property; - } + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + $property = $this->findProperty($classReflection, $propertyName); + if ($property === null) { + throw new \PHPStan\ShouldNotHappenException(); + } - private function findProperty(ClassReflection $classReflection, string $propertyName): ?PropertyReflection - { - $mixinTypes = $classReflection->getResolvedMixinTypes(); - foreach ($mixinTypes as $type) { - if (count(array_intersect(TypeUtils::getDirectClassNames($type), $this->mixinExcludeClasses)) > 0) { - continue; - } + return $property; + } - if (!$type->hasProperty($propertyName)->yes()) { - continue; - } + private function findProperty(ClassReflection $classReflection, string $propertyName): ?PropertyReflection + { + $mixinTypes = $classReflection->getResolvedMixinTypes(); + foreach ($mixinTypes as $type) { + if (count(array_intersect(TypeUtils::getDirectClassNames($type), $this->mixinExcludeClasses)) > 0) { + continue; + } - return $type->getProperty($propertyName, new OutOfClassScope()); - } + if (!$type->hasProperty($propertyName)->yes()) { + continue; + } - foreach ($classReflection->getParents() as $parentClass) { - $property = $this->findProperty($parentClass, $propertyName); - if ($property === null) { - continue; - } + return $type->getProperty($propertyName, new OutOfClassScope()); + } - return $property; - } + foreach ($classReflection->getParents() as $parentClass) { + $property = $this->findProperty($parentClass, $propertyName); + if ($property === null) { + continue; + } - return null; - } + return $property; + } + return null; + } } diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 901d61e443..9d9b690a09 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -1,4 +1,6 @@ -name = $name; - $this->variants = $variants; - $this->throwType = $throwType; - $this->hasSideEffects = $hasSideEffects; - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getVariants(): array - { - return $this->variants; - } - - public function getThrowType(): ?Type - { - return $this->throwType; - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function hasSideEffects(): TrinaryLogic - { - if ($this->isVoid()) { - return TrinaryLogic::createYes(); - } - - return $this->hasSideEffects; - } - - private function isVoid(): bool - { - foreach ($this->variants as $variant) { - if (!$variant->getReturnType() instanceof VoidType) { - return false; - } - } - - return true; - } - - public function isBuiltin(): bool - { - return true; - } - + private string $name; + + /** @var \PHPStan\Reflection\ParametersAcceptor[] */ + private array $variants; + + private ?\PHPStan\Type\Type $throwType; + + private TrinaryLogic $hasSideEffects; + + /** + * @param string $name + * @param \PHPStan\Reflection\ParametersAcceptor[] $variants + * @param \PHPStan\Type\Type|null $throwType + * @param \PHPStan\TrinaryLogic $hasSideEffects + */ + public function __construct( + string $name, + array $variants, + ?Type $throwType, + TrinaryLogic $hasSideEffects + ) { + $this->name = $name; + $this->variants = $variants; + $this->throwType = $throwType; + $this->hasSideEffects = $hasSideEffects; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getVariants(): array + { + return $this->variants; + } + + public function getThrowType(): ?Type + { + return $this->throwType; + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasSideEffects(): TrinaryLogic + { + if ($this->isVoid()) { + return TrinaryLogic::createYes(); + } + + return $this->hasSideEffects; + } + + private function isVoid(): bool + { + foreach ($this->variants as $variant) { + if (!$variant->getReturnType() instanceof VoidType) { + return false; + } + } + + return true; + } + + public function isBuiltin(): bool + { + return true; + } } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 1410a99583..533d6b1040 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->declaringClass = $declaringClass; - $this->reflection = $reflection; - $this->variants = $variants; - $this->hasSideEffects = $hasSideEffects; - $this->stubPhpDocString = $stubPhpDocString; - $this->throwType = $throwType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function isAbstract(): bool - { - return $this->reflection->isAbstract(); - } - - public function getPrototype(): ClassMemberReflection - { - try { - $prototypeMethod = $this->reflection->getPrototype(); - $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); - - return new MethodPrototypeReflection( - $prototypeMethod->getName(), - $prototypeDeclaringClass, - $prototypeMethod->isStatic(), - $prototypeMethod->isPrivate(), - $prototypeMethod->isPublic(), - $prototypeMethod->isAbstract(), - $prototypeMethod->isFinal(), - $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() - ); - } catch (\ReflectionException $e) { - return $this; - } - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] - */ - public function getVariants(): array - { - return $this->variants; - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); - } - - public function getThrowType(): ?Type - { - return $this->throwType; - } - - public function hasSideEffects(): TrinaryLogic - { - $name = strtolower($this->getName()); - $isVoid = $this->isVoid(); - if ( - $name !== '__construct' - && $isVoid - ) { - return TrinaryLogic::createYes(); - } - - return $this->hasSideEffects; - } - - private function isVoid(): bool - { - foreach ($this->variants as $variant) { - if (!$variant->getReturnType() instanceof VoidType) { - return false; - } - } - - return true; - } - - public function getDocComment(): ?string - { - if ($this->stubPhpDocString !== null) { - return $this->stubPhpDocString; - } - - return $this->reflection->getDocComment(); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private BuiltinMethodReflection $reflection; + + /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] */ + private array $variants; + + private TrinaryLogic $hasSideEffects; + + private ?string $stubPhpDocString; + + private ?Type $throwType; + + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param \PHPStan\Reflection\ClassReflection $declaringClass + * @param BuiltinMethodReflection $reflection + * @param \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] $variants + * @param TrinaryLogic $hasSideEffects + * @param string|null $stubPhpDocString + * @param Type|null $throwType + */ + public function __construct( + ReflectionProvider $reflectionProvider, + ClassReflection $declaringClass, + BuiltinMethodReflection $reflection, + array $variants, + TrinaryLogic $hasSideEffects, + ?string $stubPhpDocString, + ?Type $throwType + ) { + $this->reflectionProvider = $reflectionProvider; + $this->declaringClass = $declaringClass; + $this->reflection = $reflection; + $this->variants = $variants; + $this->hasSideEffects = $hasSideEffects; + $this->stubPhpDocString = $stubPhpDocString; + $this->throwType = $throwType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function isAbstract(): bool + { + return $this->reflection->isAbstract(); + } + + public function getPrototype(): ClassMemberReflection + { + try { + $prototypeMethod = $this->reflection->getPrototype(); + $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); + + return new MethodPrototypeReflection( + $prototypeMethod->getName(), + $prototypeDeclaringClass, + $prototypeMethod->isStatic(), + $prototypeMethod->isPrivate(), + $prototypeMethod->isPublic(), + $prototypeMethod->isAbstract(), + $prototypeMethod->isFinal(), + $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() + ); + } catch (\ReflectionException $e) { + return $this; + } + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] + */ + public function getVariants(): array + { + return $this->variants; + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); + } + + public function getThrowType(): ?Type + { + return $this->throwType; + } + + public function hasSideEffects(): TrinaryLogic + { + $name = strtolower($this->getName()); + $isVoid = $this->isVoid(); + if ( + $name !== '__construct' + && $isVoid + ) { + return TrinaryLogic::createYes(); + } + + return $this->hasSideEffects; + } + + private function isVoid(): bool + { + foreach ($this->variants as $variant) { + if (!$variant->getReturnType() instanceof VoidType) { + return false; + } + } + + return true; + } + + public function getDocComment(): ?string + { + if ($this->stubPhpDocString !== null) { + return $this->stubPhpDocString; + } + + return $this->reflection->getDocComment(); + } } diff --git a/src/Reflection/Native/NativeParameterReflection.php b/src/Reflection/Native/NativeParameterReflection.php index 3d15d91052..c22dab4807 100644 --- a/src/Reflection/Native/NativeParameterReflection.php +++ b/src/Reflection/Native/NativeParameterReflection.php @@ -1,4 +1,6 @@ -name = $name; - $this->optional = $optional; - $this->type = $type; - $this->passedByReference = $passedByReference; - $this->variadic = $variadic; - $this->defaultValue = $defaultValue; - } - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - return $this->type; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['name'], - $properties['optional'], - $properties['type'], - $properties['passedByReference'], - $properties['variadic'], - $properties['defaultValue'] - ); - } - + private string $name; + + private bool $optional; + + private \PHPStan\Type\Type $type; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private bool $variadic; + + private ?\PHPStan\Type\Type $defaultValue; + + public function __construct( + string $name, + bool $optional, + Type $type, + PassedByReference $passedByReference, + bool $variadic, + ?Type $defaultValue + ) { + $this->name = $name; + $this->optional = $optional; + $this->type = $type; + $this->passedByReference = $passedByReference; + $this->variadic = $variadic; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function getType(): Type + { + return $this->type; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['name'], + $properties['optional'], + $properties['type'], + $properties['passedByReference'], + $properties['variadic'], + $properties['defaultValue'] + ); + } } diff --git a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php index f9f092fb4b..becc5451ba 100644 --- a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php +++ b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php @@ -1,4 +1,6 @@ -name = $name; - $this->optional = $optional; - $this->type = $type; - $this->phpDocType = $phpDocType; - $this->nativeType = $nativeType; - $this->passedByReference = $passedByReference; - $this->variadic = $variadic; - $this->defaultValue = $defaultValue; - } - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - return $this->type; - } - - public function getPhpDocType(): Type - { - return $this->phpDocType; - } - - public function getNativeType(): Type - { - return $this->nativeType; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['name'], - $properties['optional'], - $properties['type'], - $properties['phpDocType'], - $properties['nativeType'], - $properties['passedByReference'], - $properties['variadic'], - $properties['defaultValue'] - ); - } - + private string $name; + + private bool $optional; + + private \PHPStan\Type\Type $type; + + private \PHPStan\Type\Type $phpDocType; + + private \PHPStan\Type\Type $nativeType; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private bool $variadic; + + private ?\PHPStan\Type\Type $defaultValue; + + public function __construct( + string $name, + bool $optional, + Type $type, + Type $phpDocType, + Type $nativeType, + PassedByReference $passedByReference, + bool $variadic, + ?Type $defaultValue + ) { + $this->name = $name; + $this->optional = $optional; + $this->type = $type; + $this->phpDocType = $phpDocType; + $this->nativeType = $nativeType; + $this->passedByReference = $passedByReference; + $this->variadic = $variadic; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function getType(): Type + { + return $this->type; + } + + public function getPhpDocType(): Type + { + return $this->phpDocType; + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['name'], + $properties['optional'], + $properties['type'], + $properties['phpDocType'], + $properties['nativeType'], + $properties['passedByReference'], + $properties['variadic'], + $properties['defaultValue'] + ); + } } diff --git a/src/Reflection/ParameterReflection.php b/src/Reflection/ParameterReflection.php index 7f2b150bfe..5e0608783f 100644 --- a/src/Reflection/ParameterReflection.php +++ b/src/Reflection/ParameterReflection.php @@ -1,4 +1,6 @@ - - */ - public function getParameters(): array; + public function getResolvedTemplateTypeMap(): TemplateTypeMap; - public function isVariadic(): bool; + /** + * @return array + */ + public function getParameters(): array; - public function getReturnType(): Type; + public function isVariadic(): bool; + public function getReturnType(): Type; } diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index f335deb79d..d9fdcec6c7 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -1,4 +1,6 @@ -getType($arg->value); - if ($arg->unpack) { - $unpack = true; - $types[] = $type->getIterableValueType(); - } else { - $types[] = $type; - } - } - - return self::selectFromTypes($types, $parametersAcceptors, $unpack); - } - - /** - * @param \PHPStan\Type\Type[] $types - * @param ParametersAcceptor[] $parametersAcceptors - * @param bool $unpack - * @return ParametersAcceptor - */ - public static function selectFromTypes( - array $types, - array $parametersAcceptors, - bool $unpack - ): ParametersAcceptor - { - if (count($parametersAcceptors) === 1) { - return GenericParametersAcceptorResolver::resolve($types, $parametersAcceptors[0]); - } - - if (count($parametersAcceptors) === 0) { - throw new \PHPStan\ShouldNotHappenException( - 'getVariants() must return at least one variant.' - ); - } - - $typesCount = count($types); - $acceptableAcceptors = []; - - foreach ($parametersAcceptors as $parametersAcceptor) { - if ($unpack) { - $acceptableAcceptors[] = $parametersAcceptor; - continue; - } - - $functionParametersMinCount = 0; - $functionParametersMaxCount = 0; - foreach ($parametersAcceptor->getParameters() as $parameter) { - if (!$parameter->isOptional()) { - $functionParametersMinCount++; - } - - $functionParametersMaxCount++; - } - - if ($typesCount < $functionParametersMinCount) { - continue; - } - - if ( - !$parametersAcceptor->isVariadic() - && $typesCount > $functionParametersMaxCount - ) { - continue; - } - - $acceptableAcceptors[] = $parametersAcceptor; - } - - if (count($acceptableAcceptors) === 0) { - return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($parametersAcceptors)); - } - - if (count($acceptableAcceptors) === 1) { - return GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptors[0]); - } - - $winningAcceptors = []; - $winningCertainty = null; - foreach ($acceptableAcceptors as $acceptableAcceptor) { - $isSuperType = TrinaryLogic::createYes(); - $acceptableAcceptor = GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptor); - foreach ($acceptableAcceptor->getParameters() as $i => $parameter) { - if (!isset($types[$i])) { - if (!$unpack || count($types) <= 0) { - break; - } - - $type = $types[count($types) - 1]; - } else { - $type = $types[$i]; - } - - if ($parameter->getType() instanceof MixedType) { - $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); - } else { - $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)); - } - } - - if ($isSuperType->no()) { - continue; - } - - if ($winningCertainty === null) { - $winningAcceptors[] = $acceptableAcceptor; - $winningCertainty = $isSuperType; - } else { - $comparison = $winningCertainty->compareTo($isSuperType); - if ($comparison === $isSuperType) { - $winningAcceptors = [$acceptableAcceptor]; - $winningCertainty = $isSuperType; - } elseif ($comparison === null) { - $winningAcceptors[] = $acceptableAcceptor; - } - } - } - - if (count($winningAcceptors) === 0) { - return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($acceptableAcceptors)); - } - - return self::combineAcceptors($winningAcceptors); - } - - /** - * @param ParametersAcceptor[] $acceptors - * @return ParametersAcceptor - */ - public static function combineAcceptors(array $acceptors): ParametersAcceptor - { - if (count($acceptors) === 0) { - throw new \PHPStan\ShouldNotHappenException( - 'getVariants() must return at least one variant.' - ); - } - if (count($acceptors) === 1) { - return $acceptors[0]; - } - - $minimumNumberOfParameters = null; - foreach ($acceptors as $acceptor) { - $acceptorParametersMinCount = 0; - foreach ($acceptor->getParameters() as $parameter) { - if ($parameter->isOptional()) { - continue; - } - - $acceptorParametersMinCount++; - } - - if ($minimumNumberOfParameters !== null && $minimumNumberOfParameters <= $acceptorParametersMinCount) { - continue; - } - - $minimumNumberOfParameters = $acceptorParametersMinCount; - } - - $parameters = []; - $isVariadic = false; - $returnType = null; - - foreach ($acceptors as $acceptor) { - if ($returnType === null) { - $returnType = $acceptor->getReturnType(); - } else { - $returnType = TypeCombinator::union($returnType, $acceptor->getReturnType()); - } - $isVariadic = $isVariadic || $acceptor->isVariadic(); - - foreach ($acceptor->getParameters() as $i => $parameter) { - if (!isset($parameters[$i])) { - $parameters[$i] = new NativeParameterReflection( - $parameter->getName(), - $i + 1 > $minimumNumberOfParameters, - $parameter->getType(), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue() - ); - continue; - } - - $isVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic(); - $defaultValueLeft = $parameters[$i]->getDefaultValue(); - $defaultValueRight = $parameter->getDefaultValue(); - if ($defaultValueLeft !== null && $defaultValueRight !== null) { - $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight); - } else { - $defaultValue = null; - } - - $parameters[$i] = new NativeParameterReflection( - $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(), - $i + 1 > $minimumNumberOfParameters, - TypeCombinator::union($parameters[$i]->getType(), $parameter->getType()), - $parameters[$i]->passedByReference()->combine($parameter->passedByReference()), - $isVariadic, - $defaultValue - ); - - if ($isVariadic) { - $parameters = array_slice($parameters, 0, $i + 1); - break; - } - } - } - - return new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - $parameters, - $isVariadic, - $returnType - ); - } - + /** + * @template T of ParametersAcceptor + * @param T[] $parametersAcceptors + * @return T + */ + public static function selectSingle( + array $parametersAcceptors + ): ParametersAcceptor { + if (count($parametersAcceptors) !== 1) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $parametersAcceptors[0]; + } + + /** + * @param Scope $scope + * @param \PhpParser\Node\Arg[] $args + * @param ParametersAcceptor[] $parametersAcceptors + * @return ParametersAcceptor + */ + public static function selectFromArgs( + Scope $scope, + array $args, + array $parametersAcceptors + ): ParametersAcceptor { + $types = []; + $unpack = false; + foreach ($args as $arg) { + $type = $scope->getType($arg->value); + if ($arg->unpack) { + $unpack = true; + $types[] = $type->getIterableValueType(); + } else { + $types[] = $type; + } + } + + return self::selectFromTypes($types, $parametersAcceptors, $unpack); + } + + /** + * @param \PHPStan\Type\Type[] $types + * @param ParametersAcceptor[] $parametersAcceptors + * @param bool $unpack + * @return ParametersAcceptor + */ + public static function selectFromTypes( + array $types, + array $parametersAcceptors, + bool $unpack + ): ParametersAcceptor { + if (count($parametersAcceptors) === 1) { + return GenericParametersAcceptorResolver::resolve($types, $parametersAcceptors[0]); + } + + if (count($parametersAcceptors) === 0) { + throw new \PHPStan\ShouldNotHappenException( + 'getVariants() must return at least one variant.' + ); + } + + $typesCount = count($types); + $acceptableAcceptors = []; + + foreach ($parametersAcceptors as $parametersAcceptor) { + if ($unpack) { + $acceptableAcceptors[] = $parametersAcceptor; + continue; + } + + $functionParametersMinCount = 0; + $functionParametersMaxCount = 0; + foreach ($parametersAcceptor->getParameters() as $parameter) { + if (!$parameter->isOptional()) { + $functionParametersMinCount++; + } + + $functionParametersMaxCount++; + } + + if ($typesCount < $functionParametersMinCount) { + continue; + } + + if ( + !$parametersAcceptor->isVariadic() + && $typesCount > $functionParametersMaxCount + ) { + continue; + } + + $acceptableAcceptors[] = $parametersAcceptor; + } + + if (count($acceptableAcceptors) === 0) { + return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($parametersAcceptors)); + } + + if (count($acceptableAcceptors) === 1) { + return GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptors[0]); + } + + $winningAcceptors = []; + $winningCertainty = null; + foreach ($acceptableAcceptors as $acceptableAcceptor) { + $isSuperType = TrinaryLogic::createYes(); + $acceptableAcceptor = GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptor); + foreach ($acceptableAcceptor->getParameters() as $i => $parameter) { + if (!isset($types[$i])) { + if (!$unpack || count($types) <= 0) { + break; + } + + $type = $types[count($types) - 1]; + } else { + $type = $types[$i]; + } + + if ($parameter->getType() instanceof MixedType) { + $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); + } else { + $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)); + } + } + + if ($isSuperType->no()) { + continue; + } + + if ($winningCertainty === null) { + $winningAcceptors[] = $acceptableAcceptor; + $winningCertainty = $isSuperType; + } else { + $comparison = $winningCertainty->compareTo($isSuperType); + if ($comparison === $isSuperType) { + $winningAcceptors = [$acceptableAcceptor]; + $winningCertainty = $isSuperType; + } elseif ($comparison === null) { + $winningAcceptors[] = $acceptableAcceptor; + } + } + } + + if (count($winningAcceptors) === 0) { + return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($acceptableAcceptors)); + } + + return self::combineAcceptors($winningAcceptors); + } + + /** + * @param ParametersAcceptor[] $acceptors + * @return ParametersAcceptor + */ + public static function combineAcceptors(array $acceptors): ParametersAcceptor + { + if (count($acceptors) === 0) { + throw new \PHPStan\ShouldNotHappenException( + 'getVariants() must return at least one variant.' + ); + } + if (count($acceptors) === 1) { + return $acceptors[0]; + } + + $minimumNumberOfParameters = null; + foreach ($acceptors as $acceptor) { + $acceptorParametersMinCount = 0; + foreach ($acceptor->getParameters() as $parameter) { + if ($parameter->isOptional()) { + continue; + } + + $acceptorParametersMinCount++; + } + + if ($minimumNumberOfParameters !== null && $minimumNumberOfParameters <= $acceptorParametersMinCount) { + continue; + } + + $minimumNumberOfParameters = $acceptorParametersMinCount; + } + + $parameters = []; + $isVariadic = false; + $returnType = null; + + foreach ($acceptors as $acceptor) { + if ($returnType === null) { + $returnType = $acceptor->getReturnType(); + } else { + $returnType = TypeCombinator::union($returnType, $acceptor->getReturnType()); + } + $isVariadic = $isVariadic || $acceptor->isVariadic(); + + foreach ($acceptor->getParameters() as $i => $parameter) { + if (!isset($parameters[$i])) { + $parameters[$i] = new NativeParameterReflection( + $parameter->getName(), + $i + 1 > $minimumNumberOfParameters, + $parameter->getType(), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue() + ); + continue; + } + + $isVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic(); + $defaultValueLeft = $parameters[$i]->getDefaultValue(); + $defaultValueRight = $parameter->getDefaultValue(); + if ($defaultValueLeft !== null && $defaultValueRight !== null) { + $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight); + } else { + $defaultValue = null; + } + + $parameters[$i] = new NativeParameterReflection( + $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(), + $i + 1 > $minimumNumberOfParameters, + TypeCombinator::union($parameters[$i]->getType(), $parameter->getType()), + $parameters[$i]->passedByReference()->combine($parameter->passedByReference()), + $isVariadic, + $defaultValue + ); + + if ($isVariadic) { + $parameters = array_slice($parameters, 0, $i + 1); + break; + } + } + } + + return new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + $parameters, + $isVariadic, + $returnType + ); + } } diff --git a/src/Reflection/ParametersAcceptorWithPhpDocs.php b/src/Reflection/ParametersAcceptorWithPhpDocs.php index fbc22be4ea..0f39420ac6 100644 --- a/src/Reflection/ParametersAcceptorWithPhpDocs.php +++ b/src/Reflection/ParametersAcceptorWithPhpDocs.php @@ -1,4 +1,6 @@ - + */ + public function getParameters(): array; - /** - * @return array - */ - public function getParameters(): array; - - public function getPhpDocReturnType(): Type; - - public function getNativeReturnType(): Type; + public function getPhpDocReturnType(): Type; + public function getNativeReturnType(): Type; } diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index 17e233c124..74457b0ef8 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -1,86 +1,86 @@ -value = $value; - } - - private static function create(int $value): self - { - if (!array_key_exists($value, self::$registry)) { - self::$registry[$value] = new self($value); - } - - return self::$registry[$value]; - } - - public static function createNo(): self - { - return self::create(self::NO); - } - - public static function createCreatesNewVariable(): self - { - return self::create(self::CREATES_NEW_VARIABLE); - } - - public static function createReadsArgument(): self - { - return self::create(self::READS_ARGUMENT); - } - - public function no(): bool - { - return $this->value === self::NO; - } - - public function yes(): bool - { - return !$this->no(); - } - - public function equals(self $other): bool - { - return $this->value === $other->value; - } - - public function createsNewVariable(): bool - { - return $this->value === self::CREATES_NEW_VARIABLE; - } - - public function combine(self $other): self - { - if ($this->value > $other->value) { - return $this; - } elseif ($this->value < $other->value) { - return $other; - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self($properties['value']); - } - + private const NO = 1; + private const READS_ARGUMENT = 2; + private const CREATES_NEW_VARIABLE = 3; + + /** @var self[] */ + private static array $registry = []; + + private int $value; + + private function __construct(int $value) + { + $this->value = $value; + } + + private static function create(int $value): self + { + if (!array_key_exists($value, self::$registry)) { + self::$registry[$value] = new self($value); + } + + return self::$registry[$value]; + } + + public static function createNo(): self + { + return self::create(self::NO); + } + + public static function createCreatesNewVariable(): self + { + return self::create(self::CREATES_NEW_VARIABLE); + } + + public static function createReadsArgument(): self + { + return self::create(self::READS_ARGUMENT); + } + + public function no(): bool + { + return $this->value === self::NO; + } + + public function yes(): bool + { + return !$this->no(); + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } + + public function createsNewVariable(): bool + { + return $this->value === self::CREATES_NEW_VARIABLE; + } + + public function combine(self $other): self + { + if ($this->value > $other->value) { + return $this; + } elseif ($this->value < $other->value) { + return $other; + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self($properties['value']); + } } diff --git a/src/Reflection/Php/BuiltinMethodReflection.php b/src/Reflection/Php/BuiltinMethodReflection.php index efc2f4e0b4..3e4e9729e3 100644 --- a/src/Reflection/Php/BuiltinMethodReflection.php +++ b/src/Reflection/Php/BuiltinMethodReflection.php @@ -1,4 +1,6 @@ -nativeMethodReflection = $nativeMethodReflection; - $this->closureType = $closureType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->nativeMethodReflection->getDeclaringClass(); - } - - public function isStatic(): bool - { - return $this->nativeMethodReflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->nativeMethodReflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->nativeMethodReflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->nativeMethodReflection->getDocComment(); - } - - public function getName(): string - { - return $this->nativeMethodReflection->getName(); - } - - public function getPrototype(): ClassMemberReflection - { - return $this->nativeMethodReflection->getPrototype(); - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getVariants(): array - { - $parameters = $this->closureType->getParameters(); - $newThis = new NativeParameterReflection( - 'newThis', - false, - new ObjectWithoutClassType(), - PassedByReference::createNo(), - false, - null - ); - - array_unshift($parameters, $newThis); - - return [ - new FunctionVariant( - $this->closureType->getTemplateTypeMap(), - $this->closureType->getResolvedTemplateTypeMap(), - $parameters, - $this->closureType->isVariadic(), - $this->closureType->getReturnType() - ), - ]; - } - - public function isDeprecated(): TrinaryLogic - { - return $this->nativeMethodReflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->nativeMethodReflection->getDeprecatedDescription(); - } - - public function isFinal(): TrinaryLogic - { - return $this->nativeMethodReflection->isFinal(); - } - - public function isInternal(): TrinaryLogic - { - return $this->nativeMethodReflection->isInternal(); - } - - public function getThrowType(): ?Type - { - return $this->nativeMethodReflection->getThrowType(); - } - - public function hasSideEffects(): TrinaryLogic - { - return $this->nativeMethodReflection->hasSideEffects(); - } - + private MethodReflection $nativeMethodReflection; + + private ClosureType $closureType; + + public function __construct( + MethodReflection $nativeMethodReflection, + ClosureType $closureType + ) { + $this->nativeMethodReflection = $nativeMethodReflection; + $this->closureType = $closureType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->nativeMethodReflection->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->nativeMethodReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->nativeMethodReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->nativeMethodReflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->nativeMethodReflection->getDocComment(); + } + + public function getName(): string + { + return $this->nativeMethodReflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->nativeMethodReflection->getPrototype(); + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getVariants(): array + { + $parameters = $this->closureType->getParameters(); + $newThis = new NativeParameterReflection( + 'newThis', + false, + new ObjectWithoutClassType(), + PassedByReference::createNo(), + false, + null + ); + + array_unshift($parameters, $newThis); + + return [ + new FunctionVariant( + $this->closureType->getTemplateTypeMap(), + $this->closureType->getResolvedTemplateTypeMap(), + $parameters, + $this->closureType->isVariadic(), + $this->closureType->getReturnType() + ), + ]; + } + + public function isDeprecated(): TrinaryLogic + { + return $this->nativeMethodReflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->nativeMethodReflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->nativeMethodReflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->nativeMethodReflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->nativeMethodReflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->nativeMethodReflection->hasSideEffects(); + } } diff --git a/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php b/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php index 3ec871b59e..c99b95a85e 100644 --- a/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -prototype = $prototype; - $this->closure = $closure; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection - { - return new self($this->prototype->doNotResolveTemplateTypeMapToBounds(), $this->closure); - } - - public function getNakedMethod(): MethodReflection - { - return $this->getTransformedMethod(); - } - - public function getTransformedMethod(): MethodReflection - { - return new ClosureCallMethodReflection($this->prototype->getTransformedMethod(), $this->closure); - } - - public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection - { - return new self($this->prototype->withCalledOnType($type), $this->closure); - } - + private UnresolvedMethodPrototypeReflection $prototype; + + private ClosureType $closure; + + public function __construct(UnresolvedMethodPrototypeReflection $prototype, ClosureType $closure) + { + $this->prototype = $prototype; + $this->closure = $closure; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection + { + return new self($this->prototype->doNotResolveTemplateTypeMapToBounds(), $this->closure); + } + + public function getNakedMethod(): MethodReflection + { + return $this->getTransformedMethod(); + } + + public function getTransformedMethod(): MethodReflection + { + return new ClosureCallMethodReflection($this->prototype->getTransformedMethod(), $this->closure); + } + + public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection + { + return new self($this->prototype->withCalledOnType($type), $this->closure); + } } diff --git a/src/Reflection/Php/DummyParameter.php b/src/Reflection/Php/DummyParameter.php index 1420d4814f..107c9cc0fc 100644 --- a/src/Reflection/Php/DummyParameter.php +++ b/src/Reflection/Php/DummyParameter.php @@ -1,4 +1,6 @@ -name = $name; - $this->type = $type; - $this->optional = $optional; - $this->passedByReference = $passedByReference ?? PassedByReference::createNo(); - $this->variadic = $variadic; - $this->defaultValue = $defaultValue; - } - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - return $this->type; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } - + private string $name; + + private \PHPStan\Type\Type $type; + + private bool $optional; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private bool $variadic; + + /** @var ?\PHPStan\Type\Type */ + private ?\PHPStan\Type\Type $defaultValue; + + public function __construct(string $name, Type $type, bool $optional, ?PassedByReference $passedByReference, bool $variadic, ?Type $defaultValue) + { + $this->name = $name; + $this->type = $type; + $this->optional = $optional; + $this->passedByReference = $passedByReference ?? PassedByReference::createNo(); + $this->variadic = $variadic; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function getType(): Type + { + return $this->type; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } } diff --git a/src/Reflection/Php/FakeBuiltinMethodReflection.php b/src/Reflection/Php/FakeBuiltinMethodReflection.php index a1737af26c..26b4df7b16 100644 --- a/src/Reflection/Php/FakeBuiltinMethodReflection.php +++ b/src/Reflection/Php/FakeBuiltinMethodReflection.php @@ -1,4 +1,6 @@ -methodName = $methodName; - $this->declaringClass = $declaringClass; - } - - public function getName(): string - { - return $this->methodName; - } - - public function getReflection(): ?\ReflectionMethod - { - return null; - } - - /** - * @return string|false - */ - public function getFileName() - { - return false; - } - - public function getDeclaringClass(): \ReflectionClass - { - return $this->declaringClass; - } - - /** - * @return int|false - */ - public function getStartLine() - { - return false; - } - - /** - * @return int|false - */ - public function getEndLine() - { - return false; - } - - public function getDocComment(): ?string - { - return null; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getPrototype(): BuiltinMethodReflection - { - throw new \ReflectionException(); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isVariadic(): bool - { - return false; - } - - public function isFinal(): bool - { - return false; - } - - public function isInternal(): bool - { - return false; - } - - public function isAbstract(): bool - { - return false; - } - - public function getReturnType(): ?\ReflectionType - { - return null; - } - - /** - * @return \ReflectionParameter[] - */ - public function getParameters(): array - { - return []; - } - + private string $methodName; + + private \ReflectionClass $declaringClass; + + public function __construct( + string $methodName, + \ReflectionClass $declaringClass + ) { + $this->methodName = $methodName; + $this->declaringClass = $declaringClass; + } + + public function getName(): string + { + return $this->methodName; + } + + public function getReflection(): ?\ReflectionMethod + { + return null; + } + + /** + * @return string|false + */ + public function getFileName() + { + return false; + } + + public function getDeclaringClass(): \ReflectionClass + { + return $this->declaringClass; + } + + /** + * @return int|false + */ + public function getStartLine() + { + return false; + } + + /** + * @return int|false + */ + public function getEndLine() + { + return false; + } + + public function getDocComment(): ?string + { + return null; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getPrototype(): BuiltinMethodReflection + { + throw new \ReflectionException(); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVariadic(): bool + { + return false; + } + + public function isFinal(): bool + { + return false; + } + + public function isInternal(): bool + { + return false; + } + + public function isAbstract(): bool + { + return false; + } + + public function getReturnType(): ?\ReflectionType + { + return null; + } + + /** + * @return \ReflectionParameter[] + */ + public function getParameters(): array + { + return []; + } } diff --git a/src/Reflection/Php/NativeBuiltinMethodReflection.php b/src/Reflection/Php/NativeBuiltinMethodReflection.php index 8a59bb0237..ac424e91f3 100644 --- a/src/Reflection/Php/NativeBuiltinMethodReflection.php +++ b/src/Reflection/Php/NativeBuiltinMethodReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getReflection(): ?\ReflectionMethod - { - return $this->reflection; - } - - /** - * @return string|false - */ - public function getFileName() - { - return $this->reflection->getFileName(); - } - - public function getDeclaringClass(): \ReflectionClass - { - return $this->reflection->getDeclaringClass(); - } - - /** - * @return int|false - */ - public function getStartLine() - { - return $this->reflection->getStartLine(); - } - - /** - * @return int|false - */ - public function getEndLine() - { - return $this->reflection->getEndLine(); - } - - public function getDocComment(): ?string - { - $docComment = $this->reflection->getDocComment(); - if ($docComment === false) { - return null; - } - - return $docComment; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function isConstructor(): bool - { - return $this->reflection->isConstructor(); - } - - public function getPrototype(): BuiltinMethodReflection - { - return new self($this->reflection->getPrototype()); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); - } - - public function isFinal(): bool - { - return $this->reflection->isFinal(); - } - - public function isInternal(): bool - { - return $this->reflection->isInternal(); - } - - public function isAbstract(): bool - { - return $this->reflection->isAbstract(); - } - - public function isVariadic(): bool - { - return $this->reflection->isVariadic(); - } - - public function getReturnType(): ?\ReflectionType - { - return $this->reflection->getReturnType(); - } - - /** - * @return \ReflectionParameter[] - */ - public function getParameters(): array - { - return $this->reflection->getParameters(); - } - + private \ReflectionMethod $reflection; + + public function __construct(\ReflectionMethod $reflection) + { + $this->reflection = $reflection; + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getReflection(): ?\ReflectionMethod + { + return $this->reflection; + } + + /** + * @return string|false + */ + public function getFileName() + { + return $this->reflection->getFileName(); + } + + public function getDeclaringClass(): \ReflectionClass + { + return $this->reflection->getDeclaringClass(); + } + + /** + * @return int|false + */ + public function getStartLine() + { + return $this->reflection->getStartLine(); + } + + /** + * @return int|false + */ + public function getEndLine() + { + return $this->reflection->getEndLine(); + } + + public function getDocComment(): ?string + { + $docComment = $this->reflection->getDocComment(); + if ($docComment === false) { + return null; + } + + return $docComment; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function isConstructor(): bool + { + return $this->reflection->isConstructor(); + } + + public function getPrototype(): BuiltinMethodReflection + { + return new self($this->reflection->getPrototype()); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); + } + + public function isFinal(): bool + { + return $this->reflection->isFinal(); + } + + public function isInternal(): bool + { + return $this->reflection->isInternal(); + } + + public function isAbstract(): bool + { + return $this->reflection->isAbstract(); + } + + public function isVariadic(): bool + { + return $this->reflection->isVariadic(); + } + + public function getReturnType(): ?\ReflectionType + { + return $this->reflection->getReturnType(); + } + + /** + * @return \ReflectionParameter[] + */ + public function getParameters(): array + { + return $this->reflection->getParameters(); + } } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index e6cf66e61c..50a5b7887c 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -1,4 +1,6 @@ -> */ - private array $propertyTypesCache = []; - - /** @var array */ - private array $inferClassConstructorPropertyTypesInProcess = []; - - /** - * @param \PHPStan\Analyser\ScopeFactory $scopeFactory - * @param \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver - * @param \PHPStan\Reflection\Php\PhpMethodReflectionFactory $methodReflectionFactory - * @param \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver - * @param \PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension - * @param \PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension - * @param \PHPStan\Reflection\SignatureMap\SignatureMapProvider $signatureMapProvider - * @param \PHPStan\Parser\Parser $parser - * @param \PHPStan\PhpDoc\StubPhpDocProvider $stubPhpDocProvider - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param FileTypeMapper $fileTypeMapper - * @param bool $inferPrivatePropertyTypeFromConstructor - * @param string[] $universalObjectCratesClasses - */ - public function __construct( - ScopeFactory $scopeFactory, - NodeScopeResolver $nodeScopeResolver, - PhpMethodReflectionFactory $methodReflectionFactory, - PhpDocInheritanceResolver $phpDocInheritanceResolver, - AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension, - AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension, - SignatureMapProvider $signatureMapProvider, - Parser $parser, - StubPhpDocProvider $stubPhpDocProvider, - ReflectionProvider $reflectionProvider, - FileTypeMapper $fileTypeMapper, - bool $inferPrivatePropertyTypeFromConstructor, - array $universalObjectCratesClasses - ) - { - $this->scopeFactory = $scopeFactory; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->methodReflectionFactory = $methodReflectionFactory; - $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; - $this->annotationsMethodsClassReflectionExtension = $annotationsMethodsClassReflectionExtension; - $this->annotationsPropertiesClassReflectionExtension = $annotationsPropertiesClassReflectionExtension; - $this->signatureMapProvider = $signatureMapProvider; - $this->parser = $parser; - $this->stubPhpDocProvider = $stubPhpDocProvider; - $this->reflectionProvider = $reflectionProvider; - $this->fileTypeMapper = $fileTypeMapper; - $this->inferPrivatePropertyTypeFromConstructor = $inferPrivatePropertyTypeFromConstructor; - $this->universalObjectCratesClasses = $universalObjectCratesClasses; - } - - public function hasProperty(ClassReflection $classReflection, string $propertyName): bool - { - return $classReflection->getNativeReflection()->hasProperty($propertyName); - } - - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection - { - if (!isset($this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { - $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); - } - - return $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName]; - } - - public function getNativeProperty(ClassReflection $classReflection, string $propertyName): PhpPropertyReflection - { - if (!isset($this->nativeProperties[$classReflection->getCacheKey()][$propertyName])) { - /** @var \PHPStan\Reflection\Php\PhpPropertyReflection $property */ - $property = $this->createProperty($classReflection, $propertyName, false); - $this->nativeProperties[$classReflection->getCacheKey()][$propertyName] = $property; - } - - return $this->nativeProperties[$classReflection->getCacheKey()][$propertyName]; - } - - private function createProperty( - ClassReflection $classReflection, - string $propertyName, - bool $includingAnnotations - ): PropertyReflection - { - $propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName); - $propertyName = $propertyReflection->getName(); - $declaringClassName = $propertyReflection->getDeclaringClass()->getName(); - $declaringClassReflection = $classReflection->getAncestorWithClassName($declaringClassName); - if ($declaringClassReflection === null) { - throw new \PHPStan\ShouldNotHappenException(sprintf( - 'Internal error: Expected to find an ancestor with class name %s on %s, but none was found.', - $declaringClassName, - $classReflection->getName() - )); - } - - $deprecatedDescription = null; - $isDeprecated = false; - $isInternal = false; - - if ($includingAnnotations && $this->annotationsPropertiesClassReflectionExtension->hasProperty($classReflection, $propertyName)) { - $hierarchyDistances = $classReflection->getClassHierarchyDistances(); - $annotationProperty = $this->annotationsPropertiesClassReflectionExtension->getProperty($classReflection, $propertyName); - if (!isset($hierarchyDistances[$annotationProperty->getDeclaringClass()->getName()])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $distanceDeclaringClass = $propertyReflection->getDeclaringClass()->getName(); - $propertyTrait = $this->findPropertyTrait($propertyReflection); - if ($propertyTrait !== null) { - $distanceDeclaringClass = $propertyTrait; - } - if (!isset($hierarchyDistances[$distanceDeclaringClass])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($hierarchyDistances[$annotationProperty->getDeclaringClass()->getName()] < $hierarchyDistances[$distanceDeclaringClass]) { - return $annotationProperty; - } - } - - $docComment = $propertyReflection->getDocComment() !== false - ? $propertyReflection->getDocComment() - : null; - - $declaringTraitName = null; - $phpDocType = null; - $resolvedPhpDoc = $this->stubPhpDocProvider->findPropertyPhpDoc( - $declaringClassName, - $propertyReflection->getName() - ); - $stubPhpDocString = null; - if ($resolvedPhpDoc === null) { - if ($declaringClassReflection->getFileName() !== false) { - $declaringTraitName = $this->findPropertyTrait($propertyReflection); - $constructorName = null; - if (method_exists($propertyReflection, 'isPromoted') && $propertyReflection->isPromoted()) { - if ($declaringClassReflection->hasConstructor()) { - $constructorName = $declaringClassReflection->getConstructor()->getName(); - } - } - - if ($constructorName === null) { - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForProperty( - $docComment, - $declaringClassReflection, - $declaringClassReflection->getFileName(), - $declaringTraitName, - $propertyName - ); - } elseif ($docComment !== null) { - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $declaringClassReflection->getFileName(), - $declaringClassName, - $declaringTraitName, - $constructorName, - $docComment - ); - } - $phpDocBlockClassReflection = $declaringClassReflection; - } - } else { - $phpDocBlockClassReflection = $declaringClassReflection; - $stubPhpDocString = $resolvedPhpDoc->getPhpDocString(); - } - - if ($resolvedPhpDoc !== null) { - $varTags = $resolvedPhpDoc->getVarTags(); - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } elseif (isset($varTags[$propertyName])) { - $phpDocType = $varTags[$propertyName]->getType(); - } - } - - if ($phpDocType === null) { - if (isset($constructorName) && $declaringClassReflection->getFileName() !== false) { - $constructorDocComment = $declaringClassReflection->getConstructor()->getDocComment(); - $nativeClassReflection = $declaringClassReflection->getNativeReflection(); - $positionalParameterNames = []; - if ($nativeClassReflection->getConstructor() !== null) { - $positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string { - return $parameter->getName(); - }, $nativeClassReflection->getConstructor()->getParameters()); - } - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( - $constructorDocComment, - $declaringClassReflection->getFileName(), - $declaringClassReflection, - $declaringTraitName, - $constructorName, - $positionalParameterNames - ); - $paramTags = $resolvedPhpDoc->getParamTags(); - if (isset($paramTags[$propertyReflection->getName()])) { - $phpDocType = $paramTags[$propertyReflection->getName()]->getType(); - } - } - } - - if ($resolvedPhpDoc !== null) { - if (!isset($phpDocBlockClassReflection)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $phpDocType = $phpDocType !== null ? TemplateTypeHelper::resolveTemplateTypes( - $phpDocType, - $phpDocBlockClassReflection->getActiveTemplateTypeMap() - ) : null; - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - } - - if ( - $phpDocType === null - && $this->inferPrivatePropertyTypeFromConstructor - && $declaringClassReflection->getFileName() !== false - && $propertyReflection->isPrivate() - && (!method_exists($propertyReflection, 'hasType') || !$propertyReflection->hasType()) - && $declaringClassReflection->hasConstructor() - && $declaringClassReflection->getConstructor()->getDeclaringClass()->getName() === $declaringClassReflection->getName() - ) { - $phpDocType = $this->inferPrivatePropertyType( - $propertyReflection->getName(), - $declaringClassReflection->getConstructor() - ); - } - - $nativeType = null; - if (method_exists($propertyReflection, 'getType') && $propertyReflection->getType() !== null) { - $nativeType = $propertyReflection->getType(); - } - - $declaringTrait = null; - if ( - $declaringTraitName !== null && $this->reflectionProvider->hasClass($declaringTraitName) - ) { - $declaringTrait = $this->reflectionProvider->getClass($declaringTraitName); - } - - return new PhpPropertyReflection( - $declaringClassReflection, - $declaringTrait, - $nativeType, - $phpDocType, - $propertyReflection, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $stubPhpDocString - ); - } - - public function hasMethod(ClassReflection $classReflection, string $methodName): bool - { - return $classReflection->getNativeReflection()->hasMethod($methodName); - } - - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - if (isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName])) { - return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName]; - } - - $nativeMethodReflection = new NativeBuiltinMethodReflection($classReflection->getNativeReflection()->getMethod($methodName)); - if (!isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { - $method = $this->createMethod($classReflection, $nativeMethodReflection, true); - $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()] = $method; - if ($nativeMethodReflection->getName() !== $methodName) { - $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName] = $method; - } - } - - return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()]; - } - - public function hasNativeMethod(ClassReflection $classReflection, string $methodName): bool - { - $hasMethod = $this->hasMethod($classReflection, $methodName); - if ($hasMethod) { - return true; - } - - if ($methodName === '__get' && UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( - $this->reflectionProvider, - $this->universalObjectCratesClasses, - $classReflection - )) { - return true; - } - - return false; - } - - public function getNativeMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - if (isset($this->nativeMethods[$classReflection->getCacheKey()][$methodName])) { - return $this->nativeMethods[$classReflection->getCacheKey()][$methodName]; - } - - if ($classReflection->getNativeReflection()->hasMethod($methodName)) { - $nativeMethodReflection = new NativeBuiltinMethodReflection( - $classReflection->getNativeReflection()->getMethod($methodName) - ); - } else { - if ( - $methodName !== '__get' - || !UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( - $this->reflectionProvider, - $this->universalObjectCratesClasses, - $classReflection - )) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $nativeMethodReflection = new FakeBuiltinMethodReflection( - $methodName, - $classReflection->getNativeReflection() - ); - } - - if (!isset($this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { - $method = $this->createMethod($classReflection, $nativeMethodReflection, false); - $this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()] = $method; - } - - return $this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()]; - } - - private function createMethod( - ClassReflection $classReflection, - BuiltinMethodReflection $methodReflection, - bool $includingAnnotations - ): MethodReflection - { - if ($includingAnnotations && $this->annotationsMethodsClassReflectionExtension->hasMethod($classReflection, $methodReflection->getName())) { - $hierarchyDistances = $classReflection->getClassHierarchyDistances(); - $annotationMethod = $this->annotationsMethodsClassReflectionExtension->getMethod($classReflection, $methodReflection->getName()); - if (!isset($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $distanceDeclaringClass = $methodReflection->getDeclaringClass()->getName(); - $methodTrait = $this->findMethodTrait($methodReflection); - if ($methodTrait !== null) { - $distanceDeclaringClass = $methodTrait; - } - if (!isset($hierarchyDistances[$distanceDeclaringClass])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()] < $hierarchyDistances[$distanceDeclaringClass]) { - return $annotationMethod; - } - } - $declaringClassName = $methodReflection->getDeclaringClass()->getName(); - $declaringClass = $classReflection->getAncestorWithClassName($declaringClassName); - - if ($declaringClass === null) { - throw new \PHPStan\ShouldNotHappenException(sprintf( - 'Internal error: Expected to find an ancestor with class name %s on %s, but none was found.', - $declaringClassName, - $classReflection->getName() - )); - } - - if ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName())) { - $variantNumbers = []; - $i = 0; - while ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName(), $i)) { - $variantNumbers[] = $i; - $i++; - } - - $stubPhpDocString = null; - $variants = []; - $reflectionMethod = null; - $throwType = null; - if ($classReflection->getNativeReflection()->hasMethod($methodReflection->getName())) { - $reflectionMethod = $classReflection->getNativeReflection()->getMethod($methodReflection->getName()); - } elseif (class_exists($classReflection->getName(), false)) { - $reflectionClass = new \ReflectionClass($classReflection->getName()); - if ($reflectionClass->hasMethod($methodReflection->getName())) { - $reflectionMethod = $reflectionClass->getMethod($methodReflection->getName()); - } - } - foreach ($variantNumbers as $variantNumber) { - $methodSignature = $this->signatureMapProvider->getMethodSignature($declaringClassName, $methodReflection->getName(), $reflectionMethod, $variantNumber); - $phpDocParameterNameMapping = []; - foreach ($methodSignature->getParameters() as $parameter) { - $phpDocParameterNameMapping[$parameter->getName()] = $parameter->getName(); - } - $stubPhpDocReturnType = null; - $stubPhpDocParameterTypes = []; - $stubPhpDocParameterVariadicity = []; - $phpDocParameterTypes = []; - $phpDocReturnType = null; - $stubPhpDocPair = null; - if (count($variantNumbers) === 1) { - $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static function (ParameterSignature $parameterSignature): string { - return $parameterSignature->getName(); - }, $methodSignature->getParameters())); - if ($stubPhpDocPair !== null) { - [$stubPhpDoc, $stubDeclaringClass] = $stubPhpDocPair; - $stubPhpDocString = $stubPhpDoc->getPhpDocString(); - $templateTypeMap = $stubDeclaringClass->getActiveTemplateTypeMap(); - $returnTag = $stubPhpDoc->getReturnTag(); - if ($returnTag !== null) { - $stubPhpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( - $returnTag->getType(), - $templateTypeMap - ); - } - - foreach ($stubPhpDoc->getParamTags() as $name => $paramTag) { - $stubPhpDocParameterTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( - $paramTag->getType(), - $templateTypeMap - ); - $stubPhpDocParameterVariadicity[$name] = $paramTag->isVariadic(); - } - - $throwsTag = $stubPhpDoc->getThrowsTag(); - if ($throwsTag !== null) { - $throwType = $throwsTag->getType(); - } - } - } - if ($stubPhpDocPair === null && $reflectionMethod !== null && $reflectionMethod->getDocComment() !== false) { - $filename = $reflectionMethod->getFileName(); - if ($filename !== false) { - $phpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( - $filename, - $declaringClassName, - null, - $reflectionMethod->getName(), - $reflectionMethod->getDocComment() - ); - $throwsTag = $phpDocBlock->getThrowsTag(); - if ($throwsTag !== null) { - $throwType = $throwsTag->getType(); - } - $returnTag = $phpDocBlock->getReturnTag(); - if ($returnTag !== null) { - $phpDocReturnType = $returnTag->getType(); - } - foreach ($phpDocBlock->getParamTags() as $name => $paramTag) { - $phpDocParameterTypes[$name] = $paramTag->getType(); - } - - $signatureParameters = $methodSignature->getParameters(); - foreach ($reflectionMethod->getParameters() as $paramI => $reflectionParameter) { - if (!array_key_exists($paramI, $signatureParameters)) { - continue; - } - - $phpDocParameterNameMapping[$signatureParameters[$paramI]->getName()] = $reflectionParameter->getName(); - } - } - } - $variants[] = $this->createNativeMethodVariant($methodSignature, $stubPhpDocParameterTypes, $stubPhpDocParameterVariadicity, $stubPhpDocReturnType, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping); - } - - if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) { - $hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']); - } else { - $hasSideEffects = TrinaryLogic::createMaybe(); - } - return new NativeMethodReflection( - $this->reflectionProvider, - $declaringClass, - $methodReflection, - $variants, - $hasSideEffects, - $stubPhpDocString, - $throwType - ); - } - - $declaringTraitName = $this->findMethodTrait($methodReflection); - $resolvedPhpDoc = null; - $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static function (\ReflectionParameter $parameter): string { - return $parameter->getName(); - }, $methodReflection->getParameters())); - $phpDocBlockClassReflection = $declaringClass; - if ($stubPhpDocPair !== null) { - [$resolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; - } - $stubPhpDocString = null; - - if ($resolvedPhpDoc === null) { - if ($declaringClass->getFileName() !== false) { - $docComment = $methodReflection->getDocComment(); - $positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string { - return $parameter->getName(); - }, $methodReflection->getParameters()); - - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( - $docComment, - $declaringClass->getFileName(), - $declaringClass, - $declaringTraitName, - $methodReflection->getName(), - $positionalParameterNames - ); - $phpDocBlockClassReflection = $declaringClass; - } - } else { - $stubPhpDocString = $resolvedPhpDoc->getPhpDocString(); - } - - $declaringTrait = null; - if ( - $declaringTraitName !== null && $this->reflectionProvider->hasClass($declaringTraitName) - ) { - $declaringTrait = $this->reflectionProvider->getClass($declaringTraitName); - } - - $templateTypeMap = TemplateTypeMap::createEmpty(); - $phpDocParameterTypes = []; - $phpDocReturnType = null; - $phpDocThrowType = null; - $deprecatedDescription = null; - $isDeprecated = false; - $isInternal = false; - $isFinal = false; - $isPure = false; - if ( - $methodReflection instanceof NativeBuiltinMethodReflection - && $methodReflection->isConstructor() - && $declaringClass->getFileName() !== false - ) { - foreach ($methodReflection->getParameters() as $parameter) { - if (!method_exists($parameter, 'isPromoted') || !$parameter->isPromoted()) { - continue; - } - - if (!$methodReflection->getDeclaringClass()->hasProperty($parameter->getName())) { - continue; - } - - $parameterProperty = $methodReflection->getDeclaringClass()->getProperty($parameter->getName()); - if (!method_exists($parameterProperty, 'isPromoted') || !$parameterProperty->isPromoted()) { - continue; - } - if ($parameterProperty->getDocComment() === false) { - continue; - } - - $propertyDocblock = $this->fileTypeMapper->getResolvedPhpDoc( - $declaringClass->getFileName(), - $declaringClassName, - $declaringTraitName, - $methodReflection->getName(), - $parameterProperty->getDocComment() - ); - $varTags = $propertyDocblock->getVarTags(); - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } elseif (isset($varTags[$parameter->getName()])) { - $phpDocType = $varTags[$parameter->getName()]->getType(); - } else { - continue; - } - - $phpDocParameterTypes[$parameter->getName()] = $phpDocType; - } - } - if ($resolvedPhpDoc !== null) { - $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); - foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { - if (array_key_exists($paramName, $phpDocParameterTypes)) { - continue; - } - $phpDocParameterTypes[$paramName] = $paramTag->getType(); - } - foreach ($phpDocParameterTypes as $paramName => $paramType) { - $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( - $paramType, - $phpDocBlockClassReflection->getActiveTemplateTypeMap() - ); - } - $nativeReturnType = TypehintHelper::decideTypeFromReflection( - $methodReflection->getReturnType(), - null, - $declaringClass->getName() - ); - $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); - $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $isPure = $resolvedPhpDoc->isPure(); - } - - return $this->methodReflectionFactory->create( - $declaringClass, - $declaringTrait, - $methodReflection, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $stubPhpDocString, - $isPure - ); - } - - /** - * @param FunctionSignature $methodSignature - * @param array $stubPhpDocParameterTypes - * @param array $stubPhpDocParameterVariadicity - * @param Type|null $stubPhpDocReturnType - * @param array $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param array $phpDocParameterNameMapping - * @return FunctionVariantWithPhpDocs - */ - private function createNativeMethodVariant( - FunctionSignature $methodSignature, - array $stubPhpDocParameterTypes, - array $stubPhpDocParameterVariadicity, - ?Type $stubPhpDocReturnType, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - array $phpDocParameterNameMapping - ): FunctionVariantWithPhpDocs - { - $parameters = []; - foreach ($methodSignature->getParameters() as $parameterSignature) { - $type = null; - $phpDocType = null; - - $phpDocParameterName = $phpDocParameterNameMapping[$parameterSignature->getName()] ?? $parameterSignature->getName(); - - if (isset($stubPhpDocParameterTypes[$parameterSignature->getName()])) { - $type = $stubPhpDocParameterTypes[$parameterSignature->getName()]; - $phpDocType = $stubPhpDocParameterTypes[$parameterSignature->getName()]; - } elseif (isset($phpDocParameterTypes[$phpDocParameterName])) { - $phpDocType = $phpDocParameterTypes[$phpDocParameterName]; - } - - $parameters[] = new NativeParameterWithPhpDocsReflection( - $phpDocParameterName, - $parameterSignature->isOptional(), - $type ?? $parameterSignature->getType(), - $phpDocType ?? new MixedType(), - $parameterSignature->getNativeType(), - $parameterSignature->passedByReference(), - $stubPhpDocParameterVariadicity[$parameterSignature->getName()] ?? $parameterSignature->isVariadic(), - null - ); - } - - $returnType = null; - if ($stubPhpDocReturnType !== null) { - $returnType = $stubPhpDocReturnType; - $phpDocReturnType = $stubPhpDocReturnType; - } - - return new FunctionVariantWithPhpDocs( - TemplateTypeMap::createEmpty(), - null, - $parameters, - $methodSignature->isVariadic(), - $returnType ?? $methodSignature->getReturnType(), - $phpDocReturnType ?? new MixedType(), - $methodSignature->getNativeReturnType() - ); - } - - private function findPropertyTrait(\ReflectionProperty $propertyReflection): ?string - { - if ($propertyReflection instanceof ReflectionProperty) { - $declaringClass = $propertyReflection->getBetterReflection()->getDeclaringClass(); - if ($declaringClass->isTrait()) { - if ($propertyReflection->getDeclaringClass()->isTrait() && $propertyReflection->getDeclaringClass()->getName() === $declaringClass->getName()) { - return null; - } - - return $declaringClass->getName(); - } - - return null; - } - $declaringClass = $propertyReflection->getDeclaringClass(); - $trait = $this->deepScanTraitsForProperty($declaringClass->getTraits(), $propertyReflection); - if ($trait !== null) { - return $trait; - } - - return null; - } - - /** - * @param \ReflectionClass[] $traits - * @param \ReflectionProperty $propertyReflection - * @return string|null - */ - private function deepScanTraitsForProperty( - array $traits, - \ReflectionProperty $propertyReflection - ): ?string - { - foreach ($traits as $trait) { - $result = $this->deepScanTraitsForProperty($trait->getTraits(), $propertyReflection); - if ($result !== null) { - return $result; - } - - if (!$trait->hasProperty($propertyReflection->getName())) { - continue; - } - - $traitProperty = $trait->getProperty($propertyReflection->getName()); - if ($traitProperty->getDocComment() === $propertyReflection->getDocComment()) { - return $trait->getName(); - } - } - - return null; - } - - private function findMethodTrait( - BuiltinMethodReflection $methodReflection - ): ?string - { - if ($methodReflection->getReflection() instanceof ReflectionMethod) { - $declaringClass = $methodReflection->getReflection()->getBetterReflection()->getDeclaringClass(); - if ($declaringClass->isTrait()) { - if ($methodReflection->getDeclaringClass()->isTrait() && $declaringClass->getName() === $methodReflection->getDeclaringClass()->getName()) { - return null; - } - - return $declaringClass->getName(); - } - - return null; - } - - $declaringClass = $methodReflection->getDeclaringClass(); - if ( - $methodReflection->getFileName() === $declaringClass->getFileName() - && $methodReflection->getStartLine() >= $declaringClass->getStartLine() - && $methodReflection->getEndLine() <= $declaringClass->getEndLine() - ) { - return null; - } - - $declaringClass = $methodReflection->getDeclaringClass(); - $traitAliases = $declaringClass->getTraitAliases(); - if (array_key_exists($methodReflection->getName(), $traitAliases)) { - return explode('::', $traitAliases[$methodReflection->getName()])[0]; - } - - foreach ($this->collectTraits($declaringClass) as $traitReflection) { - if (!$traitReflection->hasMethod($methodReflection->getName())) { - continue; - } - - if ( - $methodReflection->getFileName() === $traitReflection->getFileName() - && $methodReflection->getStartLine() >= $traitReflection->getStartLine() - && $methodReflection->getEndLine() <= $traitReflection->getEndLine() - ) { - return $traitReflection->getName(); - } - } - - return null; - } - - /** - * @param \ReflectionClass $class - * @return \ReflectionClass[] - */ - private function collectTraits(\ReflectionClass $class): array - { - $traits = []; - $traitsLeftToAnalyze = $class->getTraits(); - - while (count($traitsLeftToAnalyze) !== 0) { - $trait = reset($traitsLeftToAnalyze); - $traits[] = $trait; - - foreach ($trait->getTraits() as $subTrait) { - if (in_array($subTrait, $traits, true)) { - continue; - } - - $traitsLeftToAnalyze[] = $subTrait; - } - - array_shift($traitsLeftToAnalyze); - } - - return $traits; - } - - private function inferPrivatePropertyType( - string $propertyName, - MethodReflection $constructor - ): ?Type - { - $declaringClassName = $constructor->getDeclaringClass()->getName(); - if (isset($this->inferClassConstructorPropertyTypesInProcess[$declaringClassName])) { - return null; - } - $this->inferClassConstructorPropertyTypesInProcess[$declaringClassName] = true; - $propertyTypes = $this->inferAndCachePropertyTypes($constructor); - unset($this->inferClassConstructorPropertyTypesInProcess[$declaringClassName]); - if (array_key_exists($propertyName, $propertyTypes)) { - return $propertyTypes[$propertyName]; - } - - return null; - } - - /** - * @param \PHPStan\Reflection\MethodReflection $constructor - * @return array - */ - private function inferAndCachePropertyTypes( - MethodReflection $constructor - ): array - { - $declaringClass = $constructor->getDeclaringClass(); - if (isset($this->propertyTypesCache[$declaringClass->getName()])) { - return $this->propertyTypesCache[$declaringClass->getName()]; - } - if ($declaringClass->getFileName() === false) { - return $this->propertyTypesCache[$declaringClass->getName()] = []; - } - - $fileName = $declaringClass->getFileName(); - $nodes = $this->parser->parseFile($fileName); - $classNode = $this->findClassNode($declaringClass->getName(), $nodes); - if ($classNode === null) { - return $this->propertyTypesCache[$declaringClass->getName()] = []; - } - - $methodNode = $this->findConstructorNode($constructor->getName(), $classNode->stmts); - if ($methodNode === null || $methodNode->stmts === null) { - return $this->propertyTypesCache[$declaringClass->getName()] = []; - } - - $classNameParts = explode('\\', $declaringClass->getName()); - $namespace = null; - if (count($classNameParts) > 1) { - $namespace = implode('\\', array_slice($classNameParts, 0, -1)); - } - - $classScope = $this->scopeFactory->create( - ScopeContext::create($fileName), - false, - [], - $constructor, - $namespace - )->enterClass($declaringClass); - [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode); - $methodScope = $classScope->enterClassMethod( - $methodNode, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure - ); - - $propertyTypes = []; - foreach ($methodNode->stmts as $statement) { - if (!$statement instanceof Node\Stmt\Expression) { - continue; - } - - $expr = $statement->expr; - if (!$expr instanceof Node\Expr\Assign) { - continue; - } - - if (!$expr->var instanceof Node\Expr\PropertyFetch) { - continue; - } - - $propertyFetch = $expr->var; - if ( - !$propertyFetch->var instanceof Node\Expr\Variable - || $propertyFetch->var->name !== 'this' - || !$propertyFetch->name instanceof Node\Identifier - ) { - continue; - } - - $propertyType = $methodScope->getType($expr->expr); - if ($propertyType instanceof ErrorType || $propertyType instanceof NeverType) { - continue; - } - - $propertyType = TypeUtils::generalizeType($propertyType); - if ($propertyType instanceof ConstantArrayType) { - $propertyType = new ArrayType(new MixedType(true), new MixedType(true)); - } - - $propertyTypes[$propertyFetch->name->toString()] = $propertyType; - } - - return $this->propertyTypesCache[$declaringClass->getName()] = $propertyTypes; - } - - /** - * @param string $className - * @param \PhpParser\Node[] $nodes - * @return \PhpParser\Node\Stmt\Class_|null - */ - private function findClassNode(string $className, array $nodes): ?Class_ - { - foreach ($nodes as $node) { - if ( - $node instanceof Class_ - && $node->namespacedName->toString() === $className - ) { - return $node; - } - if ( - !$node instanceof Namespace_ - && !$node instanceof Declare_ - ) { - continue; - } - $subNodeNames = $node->getSubNodeNames(); - foreach ($subNodeNames as $subNodeName) { - $subNode = $node->{$subNodeName}; - if (!is_array($subNode)) { - $subNode = [$subNode]; - } - $result = $this->findClassNode($className, $subNode); - if ($result === null) { - continue; - } - return $result; - } - } - return null; - } - - /** - * @param string $methodName - * @param \PhpParser\Node\Stmt[] $classStatements - * @return \PhpParser\Node\Stmt\ClassMethod|null - */ - private function findConstructorNode(string $methodName, array $classStatements): ?ClassMethod - { - foreach ($classStatements as $statement) { - if ( - $statement instanceof ClassMethod - && $statement->name->toString() === $methodName - ) { - return $statement; - } - } - return null; - } - - private function getPhpDocReturnType(ClassReflection $phpDocBlockClassReflection, ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type - { - $returnTag = $resolvedPhpDoc->getReturnTag(); - - if ($returnTag === null) { - return null; - } - - $phpDocReturnType = $returnTag->getType(); - $phpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( - $phpDocReturnType, - $phpDocBlockClassReflection->getActiveTemplateTypeMap() - ); - - if ($returnTag->isExplicit() || $nativeReturnType->isSuperTypeOf($phpDocReturnType)->yes()) { - return $phpDocReturnType; - } - - return null; - } - - /** - * @param ClassReflection $declaringClass - * @param string $methodName - * @param array $positionalParameterNames - * @return array{\PHPStan\PhpDoc\ResolvedPhpDocBlock, ClassReflection}|null - */ - private function findMethodPhpDocIncludingAncestors(ClassReflection $declaringClass, string $methodName, array $positionalParameterNames): ?array - { - $declaringClassName = $declaringClass->getName(); - $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodName, $positionalParameterNames); - if ($resolved !== null) { - return [$resolved, $declaringClass]; - } - if (!$this->stubPhpDocProvider->isKnownClass($declaringClassName)) { - return null; - } - - $ancestors = $declaringClass->getAncestors(); - foreach ($ancestors as $ancestor) { - if ($ancestor->getName() === $declaringClassName) { - continue; - } - if (!$ancestor->hasNativeMethod($methodName)) { - continue; - } - - $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($ancestor->getName(), $methodName, $positionalParameterNames); - if ($resolved === null) { - continue; - } - - return [$resolved, $ancestor]; - } - - return null; - } - + private ScopeFactory $scopeFactory; + + private NodeScopeResolver $nodeScopeResolver; + + private \PHPStan\Reflection\Php\PhpMethodReflectionFactory $methodReflectionFactory; + + private \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver; + + private \PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension; + + private \PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension; + + private \PHPStan\Reflection\SignatureMap\SignatureMapProvider $signatureMapProvider; + + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\PhpDoc\StubPhpDocProvider $stubPhpDocProvider; + + private bool $inferPrivatePropertyTypeFromConstructor; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private FileTypeMapper $fileTypeMapper; + + /** @var string[] */ + private array $universalObjectCratesClasses; + + /** @var \PHPStan\Reflection\PropertyReflection[][] */ + private array $propertiesIncludingAnnotations = []; + + /** @var \PHPStan\Reflection\Php\PhpPropertyReflection[][] */ + private array $nativeProperties = []; + + /** @var \PHPStan\Reflection\MethodReflection[][] */ + private array $methodsIncludingAnnotations = []; + + /** @var \PHPStan\Reflection\MethodReflection[][] */ + private array $nativeMethods = []; + + /** @var array> */ + private array $propertyTypesCache = []; + + /** @var array */ + private array $inferClassConstructorPropertyTypesInProcess = []; + + /** + * @param \PHPStan\Analyser\ScopeFactory $scopeFactory + * @param \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver + * @param \PHPStan\Reflection\Php\PhpMethodReflectionFactory $methodReflectionFactory + * @param \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver + * @param \PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension + * @param \PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension + * @param \PHPStan\Reflection\SignatureMap\SignatureMapProvider $signatureMapProvider + * @param \PHPStan\Parser\Parser $parser + * @param \PHPStan\PhpDoc\StubPhpDocProvider $stubPhpDocProvider + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param FileTypeMapper $fileTypeMapper + * @param bool $inferPrivatePropertyTypeFromConstructor + * @param string[] $universalObjectCratesClasses + */ + public function __construct( + ScopeFactory $scopeFactory, + NodeScopeResolver $nodeScopeResolver, + PhpMethodReflectionFactory $methodReflectionFactory, + PhpDocInheritanceResolver $phpDocInheritanceResolver, + AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension, + AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension, + SignatureMapProvider $signatureMapProvider, + Parser $parser, + StubPhpDocProvider $stubPhpDocProvider, + ReflectionProvider $reflectionProvider, + FileTypeMapper $fileTypeMapper, + bool $inferPrivatePropertyTypeFromConstructor, + array $universalObjectCratesClasses + ) { + $this->scopeFactory = $scopeFactory; + $this->nodeScopeResolver = $nodeScopeResolver; + $this->methodReflectionFactory = $methodReflectionFactory; + $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; + $this->annotationsMethodsClassReflectionExtension = $annotationsMethodsClassReflectionExtension; + $this->annotationsPropertiesClassReflectionExtension = $annotationsPropertiesClassReflectionExtension; + $this->signatureMapProvider = $signatureMapProvider; + $this->parser = $parser; + $this->stubPhpDocProvider = $stubPhpDocProvider; + $this->reflectionProvider = $reflectionProvider; + $this->fileTypeMapper = $fileTypeMapper; + $this->inferPrivatePropertyTypeFromConstructor = $inferPrivatePropertyTypeFromConstructor; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; + } + + public function hasProperty(ClassReflection $classReflection, string $propertyName): bool + { + return $classReflection->getNativeReflection()->hasProperty($propertyName); + } + + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + if (!isset($this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { + $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); + } + + return $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName]; + } + + public function getNativeProperty(ClassReflection $classReflection, string $propertyName): PhpPropertyReflection + { + if (!isset($this->nativeProperties[$classReflection->getCacheKey()][$propertyName])) { + /** @var \PHPStan\Reflection\Php\PhpPropertyReflection $property */ + $property = $this->createProperty($classReflection, $propertyName, false); + $this->nativeProperties[$classReflection->getCacheKey()][$propertyName] = $property; + } + + return $this->nativeProperties[$classReflection->getCacheKey()][$propertyName]; + } + + private function createProperty( + ClassReflection $classReflection, + string $propertyName, + bool $includingAnnotations + ): PropertyReflection { + $propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName); + $propertyName = $propertyReflection->getName(); + $declaringClassName = $propertyReflection->getDeclaringClass()->getName(); + $declaringClassReflection = $classReflection->getAncestorWithClassName($declaringClassName); + if ($declaringClassReflection === null) { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Internal error: Expected to find an ancestor with class name %s on %s, but none was found.', + $declaringClassName, + $classReflection->getName() + )); + } + + $deprecatedDescription = null; + $isDeprecated = false; + $isInternal = false; + + if ($includingAnnotations && $this->annotationsPropertiesClassReflectionExtension->hasProperty($classReflection, $propertyName)) { + $hierarchyDistances = $classReflection->getClassHierarchyDistances(); + $annotationProperty = $this->annotationsPropertiesClassReflectionExtension->getProperty($classReflection, $propertyName); + if (!isset($hierarchyDistances[$annotationProperty->getDeclaringClass()->getName()])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $distanceDeclaringClass = $propertyReflection->getDeclaringClass()->getName(); + $propertyTrait = $this->findPropertyTrait($propertyReflection); + if ($propertyTrait !== null) { + $distanceDeclaringClass = $propertyTrait; + } + if (!isset($hierarchyDistances[$distanceDeclaringClass])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($hierarchyDistances[$annotationProperty->getDeclaringClass()->getName()] < $hierarchyDistances[$distanceDeclaringClass]) { + return $annotationProperty; + } + } + + $docComment = $propertyReflection->getDocComment() !== false + ? $propertyReflection->getDocComment() + : null; + + $declaringTraitName = null; + $phpDocType = null; + $resolvedPhpDoc = $this->stubPhpDocProvider->findPropertyPhpDoc( + $declaringClassName, + $propertyReflection->getName() + ); + $stubPhpDocString = null; + if ($resolvedPhpDoc === null) { + if ($declaringClassReflection->getFileName() !== false) { + $declaringTraitName = $this->findPropertyTrait($propertyReflection); + $constructorName = null; + if (method_exists($propertyReflection, 'isPromoted') && $propertyReflection->isPromoted()) { + if ($declaringClassReflection->hasConstructor()) { + $constructorName = $declaringClassReflection->getConstructor()->getName(); + } + } + + if ($constructorName === null) { + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForProperty( + $docComment, + $declaringClassReflection, + $declaringClassReflection->getFileName(), + $declaringTraitName, + $propertyName + ); + } elseif ($docComment !== null) { + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $declaringClassReflection->getFileName(), + $declaringClassName, + $declaringTraitName, + $constructorName, + $docComment + ); + } + $phpDocBlockClassReflection = $declaringClassReflection; + } + } else { + $phpDocBlockClassReflection = $declaringClassReflection; + $stubPhpDocString = $resolvedPhpDoc->getPhpDocString(); + } + + if ($resolvedPhpDoc !== null) { + $varTags = $resolvedPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } elseif (isset($varTags[$propertyName])) { + $phpDocType = $varTags[$propertyName]->getType(); + } + } + + if ($phpDocType === null) { + if (isset($constructorName) && $declaringClassReflection->getFileName() !== false) { + $constructorDocComment = $declaringClassReflection->getConstructor()->getDocComment(); + $nativeClassReflection = $declaringClassReflection->getNativeReflection(); + $positionalParameterNames = []; + if ($nativeClassReflection->getConstructor() !== null) { + $positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string { + return $parameter->getName(); + }, $nativeClassReflection->getConstructor()->getParameters()); + } + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $constructorDocComment, + $declaringClassReflection->getFileName(), + $declaringClassReflection, + $declaringTraitName, + $constructorName, + $positionalParameterNames + ); + $paramTags = $resolvedPhpDoc->getParamTags(); + if (isset($paramTags[$propertyReflection->getName()])) { + $phpDocType = $paramTags[$propertyReflection->getName()]->getType(); + } + } + } + + if ($resolvedPhpDoc !== null) { + if (!isset($phpDocBlockClassReflection)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $phpDocType = $phpDocType !== null ? TemplateTypeHelper::resolveTemplateTypes( + $phpDocType, + $phpDocBlockClassReflection->getActiveTemplateTypeMap() + ) : null; + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + } + + if ( + $phpDocType === null + && $this->inferPrivatePropertyTypeFromConstructor + && $declaringClassReflection->getFileName() !== false + && $propertyReflection->isPrivate() + && (!method_exists($propertyReflection, 'hasType') || !$propertyReflection->hasType()) + && $declaringClassReflection->hasConstructor() + && $declaringClassReflection->getConstructor()->getDeclaringClass()->getName() === $declaringClassReflection->getName() + ) { + $phpDocType = $this->inferPrivatePropertyType( + $propertyReflection->getName(), + $declaringClassReflection->getConstructor() + ); + } + + $nativeType = null; + if (method_exists($propertyReflection, 'getType') && $propertyReflection->getType() !== null) { + $nativeType = $propertyReflection->getType(); + } + + $declaringTrait = null; + if ( + $declaringTraitName !== null && $this->reflectionProvider->hasClass($declaringTraitName) + ) { + $declaringTrait = $this->reflectionProvider->getClass($declaringTraitName); + } + + return new PhpPropertyReflection( + $declaringClassReflection, + $declaringTrait, + $nativeType, + $phpDocType, + $propertyReflection, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $stubPhpDocString + ); + } + + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $classReflection->getNativeReflection()->hasMethod($methodName); + } + + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + if (isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName])) { + return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName]; + } + + $nativeMethodReflection = new NativeBuiltinMethodReflection($classReflection->getNativeReflection()->getMethod($methodName)); + if (!isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { + $method = $this->createMethod($classReflection, $nativeMethodReflection, true); + $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()] = $method; + if ($nativeMethodReflection->getName() !== $methodName) { + $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName] = $method; + } + } + + return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()]; + } + + public function hasNativeMethod(ClassReflection $classReflection, string $methodName): bool + { + $hasMethod = $this->hasMethod($classReflection, $methodName); + if ($hasMethod) { + return true; + } + + if ($methodName === '__get' && UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( + $this->reflectionProvider, + $this->universalObjectCratesClasses, + $classReflection + )) { + return true; + } + + return false; + } + + public function getNativeMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + if (isset($this->nativeMethods[$classReflection->getCacheKey()][$methodName])) { + return $this->nativeMethods[$classReflection->getCacheKey()][$methodName]; + } + + if ($classReflection->getNativeReflection()->hasMethod($methodName)) { + $nativeMethodReflection = new NativeBuiltinMethodReflection( + $classReflection->getNativeReflection()->getMethod($methodName) + ); + } else { + if ( + $methodName !== '__get' + || !UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( + $this->reflectionProvider, + $this->universalObjectCratesClasses, + $classReflection + )) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $nativeMethodReflection = new FakeBuiltinMethodReflection( + $methodName, + $classReflection->getNativeReflection() + ); + } + + if (!isset($this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { + $method = $this->createMethod($classReflection, $nativeMethodReflection, false); + $this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()] = $method; + } + + return $this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()]; + } + + private function createMethod( + ClassReflection $classReflection, + BuiltinMethodReflection $methodReflection, + bool $includingAnnotations + ): MethodReflection { + if ($includingAnnotations && $this->annotationsMethodsClassReflectionExtension->hasMethod($classReflection, $methodReflection->getName())) { + $hierarchyDistances = $classReflection->getClassHierarchyDistances(); + $annotationMethod = $this->annotationsMethodsClassReflectionExtension->getMethod($classReflection, $methodReflection->getName()); + if (!isset($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $distanceDeclaringClass = $methodReflection->getDeclaringClass()->getName(); + $methodTrait = $this->findMethodTrait($methodReflection); + if ($methodTrait !== null) { + $distanceDeclaringClass = $methodTrait; + } + if (!isset($hierarchyDistances[$distanceDeclaringClass])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($hierarchyDistances[$annotationMethod->getDeclaringClass()->getName()] < $hierarchyDistances[$distanceDeclaringClass]) { + return $annotationMethod; + } + } + $declaringClassName = $methodReflection->getDeclaringClass()->getName(); + $declaringClass = $classReflection->getAncestorWithClassName($declaringClassName); + + if ($declaringClass === null) { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Internal error: Expected to find an ancestor with class name %s on %s, but none was found.', + $declaringClassName, + $classReflection->getName() + )); + } + + if ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName())) { + $variantNumbers = []; + $i = 0; + while ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName(), $i)) { + $variantNumbers[] = $i; + $i++; + } + + $stubPhpDocString = null; + $variants = []; + $reflectionMethod = null; + $throwType = null; + if ($classReflection->getNativeReflection()->hasMethod($methodReflection->getName())) { + $reflectionMethod = $classReflection->getNativeReflection()->getMethod($methodReflection->getName()); + } elseif (class_exists($classReflection->getName(), false)) { + $reflectionClass = new \ReflectionClass($classReflection->getName()); + if ($reflectionClass->hasMethod($methodReflection->getName())) { + $reflectionMethod = $reflectionClass->getMethod($methodReflection->getName()); + } + } + foreach ($variantNumbers as $variantNumber) { + $methodSignature = $this->signatureMapProvider->getMethodSignature($declaringClassName, $methodReflection->getName(), $reflectionMethod, $variantNumber); + $phpDocParameterNameMapping = []; + foreach ($methodSignature->getParameters() as $parameter) { + $phpDocParameterNameMapping[$parameter->getName()] = $parameter->getName(); + } + $stubPhpDocReturnType = null; + $stubPhpDocParameterTypes = []; + $stubPhpDocParameterVariadicity = []; + $phpDocParameterTypes = []; + $phpDocReturnType = null; + $stubPhpDocPair = null; + if (count($variantNumbers) === 1) { + $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static function (ParameterSignature $parameterSignature): string { + return $parameterSignature->getName(); + }, $methodSignature->getParameters())); + if ($stubPhpDocPair !== null) { + [$stubPhpDoc, $stubDeclaringClass] = $stubPhpDocPair; + $stubPhpDocString = $stubPhpDoc->getPhpDocString(); + $templateTypeMap = $stubDeclaringClass->getActiveTemplateTypeMap(); + $returnTag = $stubPhpDoc->getReturnTag(); + if ($returnTag !== null) { + $stubPhpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( + $returnTag->getType(), + $templateTypeMap + ); + } + + foreach ($stubPhpDoc->getParamTags() as $name => $paramTag) { + $stubPhpDocParameterTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( + $paramTag->getType(), + $templateTypeMap + ); + $stubPhpDocParameterVariadicity[$name] = $paramTag->isVariadic(); + } + + $throwsTag = $stubPhpDoc->getThrowsTag(); + if ($throwsTag !== null) { + $throwType = $throwsTag->getType(); + } + } + } + if ($stubPhpDocPair === null && $reflectionMethod !== null && $reflectionMethod->getDocComment() !== false) { + $filename = $reflectionMethod->getFileName(); + if ($filename !== false) { + $phpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( + $filename, + $declaringClassName, + null, + $reflectionMethod->getName(), + $reflectionMethod->getDocComment() + ); + $throwsTag = $phpDocBlock->getThrowsTag(); + if ($throwsTag !== null) { + $throwType = $throwsTag->getType(); + } + $returnTag = $phpDocBlock->getReturnTag(); + if ($returnTag !== null) { + $phpDocReturnType = $returnTag->getType(); + } + foreach ($phpDocBlock->getParamTags() as $name => $paramTag) { + $phpDocParameterTypes[$name] = $paramTag->getType(); + } + + $signatureParameters = $methodSignature->getParameters(); + foreach ($reflectionMethod->getParameters() as $paramI => $reflectionParameter) { + if (!array_key_exists($paramI, $signatureParameters)) { + continue; + } + + $phpDocParameterNameMapping[$signatureParameters[$paramI]->getName()] = $reflectionParameter->getName(); + } + } + } + $variants[] = $this->createNativeMethodVariant($methodSignature, $stubPhpDocParameterTypes, $stubPhpDocParameterVariadicity, $stubPhpDocReturnType, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping); + } + + if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) { + $hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']); + } else { + $hasSideEffects = TrinaryLogic::createMaybe(); + } + return new NativeMethodReflection( + $this->reflectionProvider, + $declaringClass, + $methodReflection, + $variants, + $hasSideEffects, + $stubPhpDocString, + $throwType + ); + } + + $declaringTraitName = $this->findMethodTrait($methodReflection); + $resolvedPhpDoc = null; + $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static function (\ReflectionParameter $parameter): string { + return $parameter->getName(); + }, $methodReflection->getParameters())); + $phpDocBlockClassReflection = $declaringClass; + if ($stubPhpDocPair !== null) { + [$resolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; + } + $stubPhpDocString = null; + + if ($resolvedPhpDoc === null) { + if ($declaringClass->getFileName() !== false) { + $docComment = $methodReflection->getDocComment(); + $positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string { + return $parameter->getName(); + }, $methodReflection->getParameters()); + + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $docComment, + $declaringClass->getFileName(), + $declaringClass, + $declaringTraitName, + $methodReflection->getName(), + $positionalParameterNames + ); + $phpDocBlockClassReflection = $declaringClass; + } + } else { + $stubPhpDocString = $resolvedPhpDoc->getPhpDocString(); + } + + $declaringTrait = null; + if ( + $declaringTraitName !== null && $this->reflectionProvider->hasClass($declaringTraitName) + ) { + $declaringTrait = $this->reflectionProvider->getClass($declaringTraitName); + } + + $templateTypeMap = TemplateTypeMap::createEmpty(); + $phpDocParameterTypes = []; + $phpDocReturnType = null; + $phpDocThrowType = null; + $deprecatedDescription = null; + $isDeprecated = false; + $isInternal = false; + $isFinal = false; + $isPure = false; + if ( + $methodReflection instanceof NativeBuiltinMethodReflection + && $methodReflection->isConstructor() + && $declaringClass->getFileName() !== false + ) { + foreach ($methodReflection->getParameters() as $parameter) { + if (!method_exists($parameter, 'isPromoted') || !$parameter->isPromoted()) { + continue; + } + + if (!$methodReflection->getDeclaringClass()->hasProperty($parameter->getName())) { + continue; + } + + $parameterProperty = $methodReflection->getDeclaringClass()->getProperty($parameter->getName()); + if (!method_exists($parameterProperty, 'isPromoted') || !$parameterProperty->isPromoted()) { + continue; + } + if ($parameterProperty->getDocComment() === false) { + continue; + } + + $propertyDocblock = $this->fileTypeMapper->getResolvedPhpDoc( + $declaringClass->getFileName(), + $declaringClassName, + $declaringTraitName, + $methodReflection->getName(), + $parameterProperty->getDocComment() + ); + $varTags = $propertyDocblock->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } elseif (isset($varTags[$parameter->getName()])) { + $phpDocType = $varTags[$parameter->getName()]->getType(); + } else { + continue; + } + + $phpDocParameterTypes[$parameter->getName()] = $phpDocType; + } + } + if ($resolvedPhpDoc !== null) { + $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); + foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { + if (array_key_exists($paramName, $phpDocParameterTypes)) { + continue; + } + $phpDocParameterTypes[$paramName] = $paramTag->getType(); + } + foreach ($phpDocParameterTypes as $paramName => $paramType) { + $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( + $paramType, + $phpDocBlockClassReflection->getActiveTemplateTypeMap() + ); + } + $nativeReturnType = TypehintHelper::decideTypeFromReflection( + $methodReflection->getReturnType(), + null, + $declaringClass->getName() + ); + $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); + $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); + $isPure = $resolvedPhpDoc->isPure(); + } + + return $this->methodReflectionFactory->create( + $declaringClass, + $declaringTrait, + $methodReflection, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $stubPhpDocString, + $isPure + ); + } + + /** + * @param FunctionSignature $methodSignature + * @param array $stubPhpDocParameterTypes + * @param array $stubPhpDocParameterVariadicity + * @param Type|null $stubPhpDocReturnType + * @param array $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param array $phpDocParameterNameMapping + * @return FunctionVariantWithPhpDocs + */ + private function createNativeMethodVariant( + FunctionSignature $methodSignature, + array $stubPhpDocParameterTypes, + array $stubPhpDocParameterVariadicity, + ?Type $stubPhpDocReturnType, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + array $phpDocParameterNameMapping + ): FunctionVariantWithPhpDocs { + $parameters = []; + foreach ($methodSignature->getParameters() as $parameterSignature) { + $type = null; + $phpDocType = null; + + $phpDocParameterName = $phpDocParameterNameMapping[$parameterSignature->getName()] ?? $parameterSignature->getName(); + + if (isset($stubPhpDocParameterTypes[$parameterSignature->getName()])) { + $type = $stubPhpDocParameterTypes[$parameterSignature->getName()]; + $phpDocType = $stubPhpDocParameterTypes[$parameterSignature->getName()]; + } elseif (isset($phpDocParameterTypes[$phpDocParameterName])) { + $phpDocType = $phpDocParameterTypes[$phpDocParameterName]; + } + + $parameters[] = new NativeParameterWithPhpDocsReflection( + $phpDocParameterName, + $parameterSignature->isOptional(), + $type ?? $parameterSignature->getType(), + $phpDocType ?? new MixedType(), + $parameterSignature->getNativeType(), + $parameterSignature->passedByReference(), + $stubPhpDocParameterVariadicity[$parameterSignature->getName()] ?? $parameterSignature->isVariadic(), + null + ); + } + + $returnType = null; + if ($stubPhpDocReturnType !== null) { + $returnType = $stubPhpDocReturnType; + $phpDocReturnType = $stubPhpDocReturnType; + } + + return new FunctionVariantWithPhpDocs( + TemplateTypeMap::createEmpty(), + null, + $parameters, + $methodSignature->isVariadic(), + $returnType ?? $methodSignature->getReturnType(), + $phpDocReturnType ?? new MixedType(), + $methodSignature->getNativeReturnType() + ); + } + + private function findPropertyTrait(\ReflectionProperty $propertyReflection): ?string + { + if ($propertyReflection instanceof ReflectionProperty) { + $declaringClass = $propertyReflection->getBetterReflection()->getDeclaringClass(); + if ($declaringClass->isTrait()) { + if ($propertyReflection->getDeclaringClass()->isTrait() && $propertyReflection->getDeclaringClass()->getName() === $declaringClass->getName()) { + return null; + } + + return $declaringClass->getName(); + } + + return null; + } + $declaringClass = $propertyReflection->getDeclaringClass(); + $trait = $this->deepScanTraitsForProperty($declaringClass->getTraits(), $propertyReflection); + if ($trait !== null) { + return $trait; + } + + return null; + } + + /** + * @param \ReflectionClass[] $traits + * @param \ReflectionProperty $propertyReflection + * @return string|null + */ + private function deepScanTraitsForProperty( + array $traits, + \ReflectionProperty $propertyReflection + ): ?string { + foreach ($traits as $trait) { + $result = $this->deepScanTraitsForProperty($trait->getTraits(), $propertyReflection); + if ($result !== null) { + return $result; + } + + if (!$trait->hasProperty($propertyReflection->getName())) { + continue; + } + + $traitProperty = $trait->getProperty($propertyReflection->getName()); + if ($traitProperty->getDocComment() === $propertyReflection->getDocComment()) { + return $trait->getName(); + } + } + + return null; + } + + private function findMethodTrait( + BuiltinMethodReflection $methodReflection + ): ?string { + if ($methodReflection->getReflection() instanceof ReflectionMethod) { + $declaringClass = $methodReflection->getReflection()->getBetterReflection()->getDeclaringClass(); + if ($declaringClass->isTrait()) { + if ($methodReflection->getDeclaringClass()->isTrait() && $declaringClass->getName() === $methodReflection->getDeclaringClass()->getName()) { + return null; + } + + return $declaringClass->getName(); + } + + return null; + } + + $declaringClass = $methodReflection->getDeclaringClass(); + if ( + $methodReflection->getFileName() === $declaringClass->getFileName() + && $methodReflection->getStartLine() >= $declaringClass->getStartLine() + && $methodReflection->getEndLine() <= $declaringClass->getEndLine() + ) { + return null; + } + + $declaringClass = $methodReflection->getDeclaringClass(); + $traitAliases = $declaringClass->getTraitAliases(); + if (array_key_exists($methodReflection->getName(), $traitAliases)) { + return explode('::', $traitAliases[$methodReflection->getName()])[0]; + } + + foreach ($this->collectTraits($declaringClass) as $traitReflection) { + if (!$traitReflection->hasMethod($methodReflection->getName())) { + continue; + } + + if ( + $methodReflection->getFileName() === $traitReflection->getFileName() + && $methodReflection->getStartLine() >= $traitReflection->getStartLine() + && $methodReflection->getEndLine() <= $traitReflection->getEndLine() + ) { + return $traitReflection->getName(); + } + } + + return null; + } + + /** + * @param \ReflectionClass $class + * @return \ReflectionClass[] + */ + private function collectTraits(\ReflectionClass $class): array + { + $traits = []; + $traitsLeftToAnalyze = $class->getTraits(); + + while (count($traitsLeftToAnalyze) !== 0) { + $trait = reset($traitsLeftToAnalyze); + $traits[] = $trait; + + foreach ($trait->getTraits() as $subTrait) { + if (in_array($subTrait, $traits, true)) { + continue; + } + + $traitsLeftToAnalyze[] = $subTrait; + } + + array_shift($traitsLeftToAnalyze); + } + + return $traits; + } + + private function inferPrivatePropertyType( + string $propertyName, + MethodReflection $constructor + ): ?Type { + $declaringClassName = $constructor->getDeclaringClass()->getName(); + if (isset($this->inferClassConstructorPropertyTypesInProcess[$declaringClassName])) { + return null; + } + $this->inferClassConstructorPropertyTypesInProcess[$declaringClassName] = true; + $propertyTypes = $this->inferAndCachePropertyTypes($constructor); + unset($this->inferClassConstructorPropertyTypesInProcess[$declaringClassName]); + if (array_key_exists($propertyName, $propertyTypes)) { + return $propertyTypes[$propertyName]; + } + + return null; + } + + /** + * @param \PHPStan\Reflection\MethodReflection $constructor + * @return array + */ + private function inferAndCachePropertyTypes( + MethodReflection $constructor + ): array { + $declaringClass = $constructor->getDeclaringClass(); + if (isset($this->propertyTypesCache[$declaringClass->getName()])) { + return $this->propertyTypesCache[$declaringClass->getName()]; + } + if ($declaringClass->getFileName() === false) { + return $this->propertyTypesCache[$declaringClass->getName()] = []; + } + + $fileName = $declaringClass->getFileName(); + $nodes = $this->parser->parseFile($fileName); + $classNode = $this->findClassNode($declaringClass->getName(), $nodes); + if ($classNode === null) { + return $this->propertyTypesCache[$declaringClass->getName()] = []; + } + + $methodNode = $this->findConstructorNode($constructor->getName(), $classNode->stmts); + if ($methodNode === null || $methodNode->stmts === null) { + return $this->propertyTypesCache[$declaringClass->getName()] = []; + } + + $classNameParts = explode('\\', $declaringClass->getName()); + $namespace = null; + if (count($classNameParts) > 1) { + $namespace = implode('\\', array_slice($classNameParts, 0, -1)); + } + + $classScope = $this->scopeFactory->create( + ScopeContext::create($fileName), + false, + [], + $constructor, + $namespace + )->enterClass($declaringClass); + [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode); + $methodScope = $classScope->enterClassMethod( + $methodNode, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $isPure + ); + + $propertyTypes = []; + foreach ($methodNode->stmts as $statement) { + if (!$statement instanceof Node\Stmt\Expression) { + continue; + } + + $expr = $statement->expr; + if (!$expr instanceof Node\Expr\Assign) { + continue; + } + + if (!$expr->var instanceof Node\Expr\PropertyFetch) { + continue; + } + + $propertyFetch = $expr->var; + if ( + !$propertyFetch->var instanceof Node\Expr\Variable + || $propertyFetch->var->name !== 'this' + || !$propertyFetch->name instanceof Node\Identifier + ) { + continue; + } + + $propertyType = $methodScope->getType($expr->expr); + if ($propertyType instanceof ErrorType || $propertyType instanceof NeverType) { + continue; + } + + $propertyType = TypeUtils::generalizeType($propertyType); + if ($propertyType instanceof ConstantArrayType) { + $propertyType = new ArrayType(new MixedType(true), new MixedType(true)); + } + + $propertyTypes[$propertyFetch->name->toString()] = $propertyType; + } + + return $this->propertyTypesCache[$declaringClass->getName()] = $propertyTypes; + } + + /** + * @param string $className + * @param \PhpParser\Node[] $nodes + * @return \PhpParser\Node\Stmt\Class_|null + */ + private function findClassNode(string $className, array $nodes): ?Class_ + { + foreach ($nodes as $node) { + if ( + $node instanceof Class_ + && $node->namespacedName->toString() === $className + ) { + return $node; + } + if ( + !$node instanceof Namespace_ + && !$node instanceof Declare_ + ) { + continue; + } + $subNodeNames = $node->getSubNodeNames(); + foreach ($subNodeNames as $subNodeName) { + $subNode = $node->{$subNodeName}; + if (!is_array($subNode)) { + $subNode = [$subNode]; + } + $result = $this->findClassNode($className, $subNode); + if ($result === null) { + continue; + } + return $result; + } + } + return null; + } + + /** + * @param string $methodName + * @param \PhpParser\Node\Stmt[] $classStatements + * @return \PhpParser\Node\Stmt\ClassMethod|null + */ + private function findConstructorNode(string $methodName, array $classStatements): ?ClassMethod + { + foreach ($classStatements as $statement) { + if ( + $statement instanceof ClassMethod + && $statement->name->toString() === $methodName + ) { + return $statement; + } + } + return null; + } + + private function getPhpDocReturnType(ClassReflection $phpDocBlockClassReflection, ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type + { + $returnTag = $resolvedPhpDoc->getReturnTag(); + + if ($returnTag === null) { + return null; + } + + $phpDocReturnType = $returnTag->getType(); + $phpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( + $phpDocReturnType, + $phpDocBlockClassReflection->getActiveTemplateTypeMap() + ); + + if ($returnTag->isExplicit() || $nativeReturnType->isSuperTypeOf($phpDocReturnType)->yes()) { + return $phpDocReturnType; + } + + return null; + } + + /** + * @param ClassReflection $declaringClass + * @param string $methodName + * @param array $positionalParameterNames + * @return array{\PHPStan\PhpDoc\ResolvedPhpDocBlock, ClassReflection}|null + */ + private function findMethodPhpDocIncludingAncestors(ClassReflection $declaringClass, string $methodName, array $positionalParameterNames): ?array + { + $declaringClassName = $declaringClass->getName(); + $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodName, $positionalParameterNames); + if ($resolved !== null) { + return [$resolved, $declaringClass]; + } + if (!$this->stubPhpDocProvider->isKnownClass($declaringClassName)) { + return null; + } + + $ancestors = $declaringClass->getAncestors(); + foreach ($ancestors as $ancestor) { + if ($ancestor->getName() === $declaringClassName) { + continue; + } + if (!$ancestor->hasNativeMethod($methodName)) { + continue; + } + + $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($ancestor->getName(), $methodName, $positionalParameterNames); + if ($resolved === null) { + continue; + } + + return [$resolved, $ancestor]; + } + + return null; + } } diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index d1c109af91..0a52f90536 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -1,4 +1,6 @@ -functionLike = $functionLike; - $this->templateTypeMap = $templateTypeMap; - $this->realParameterTypes = $realParameterTypes; - $this->phpDocParameterTypes = $phpDocParameterTypes; - $this->realParameterDefaultValues = $realParameterDefaultValues; - $this->realReturnType = $realReturnType; - $this->phpDocReturnType = $phpDocReturnType; - $this->throwType = $throwType; - $this->deprecatedDescription = $deprecatedDescription; - $this->isDeprecated = $isDeprecated; - $this->isInternal = $isInternal; - $this->isFinal = $isFinal; - $this->isPure = $isPure; - } - - protected function getFunctionLike(): FunctionLike - { - return $this->functionLike; - } - - public function getName(): string - { - if ($this->functionLike instanceof ClassMethod) { - return $this->functionLike->name->name; - } - - return (string) $this->functionLike->namespacedName; - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] - */ - public function getVariants(): array - { - if ($this->variants === null) { - $this->variants = [ - new FunctionVariantWithPhpDocs( - $this->templateTypeMap, - null, - $this->getParameters(), - $this->isVariadic(), - $this->getReturnType(), - $this->phpDocReturnType ?? new MixedType(), - $this->realReturnType - ), - ]; - } - - return $this->variants; - } - - /** - * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] - */ - private function getParameters(): array - { - $parameters = []; - $isOptional = true; - - /** @var \PhpParser\Node\Param $parameter */ - foreach (array_reverse($this->functionLike->getParams()) as $parameter) { - if ($parameter->default === null && !$parameter->variadic) { - $isOptional = false; - } - - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $parameters[] = new PhpParameterFromParserNodeReflection( - $parameter->var->name, - $isOptional, - $this->realParameterTypes[$parameter->var->name], - $this->phpDocParameterTypes[$parameter->var->name] ?? null, - $parameter->byRef - ? PassedByReference::createCreatesNewVariable() - : PassedByReference::createNo(), - $this->realParameterDefaultValues[$parameter->var->name] ?? null, - $parameter->variadic - ); - } - - return array_reverse($parameters); - } - - private function isVariadic(): bool - { - foreach ($this->functionLike->getParams() as $parameter) { - if ($parameter->variadic) { - return true; - } - } - - return false; - } - - private function getReturnType(): Type - { - return TypehintHelper::decideType($this->realReturnType, $this->phpDocReturnType); - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isDeprecated); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isInternal); - } - - public function isFinal(): TrinaryLogic - { - $finalMethod = false; - if ($this->functionLike instanceof ClassMethod) { - $finalMethod = $this->functionLike->isFinal(); - } - return TrinaryLogic::createFromBoolean($finalMethod || $this->isFinal); - } - - public function getThrowType(): ?Type - { - return $this->throwType; - } - - public function hasSideEffects(): TrinaryLogic - { - if ($this->getReturnType() instanceof VoidType) { - return TrinaryLogic::createYes(); - } - if ($this->isPure !== null) { - return TrinaryLogic::createFromBoolean(!$this->isPure); - } - - return TrinaryLogic::createMaybe(); - } - - public function isBuiltin(): bool - { - return false; - } - + private \PhpParser\Node\FunctionLike $functionLike; + + private \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap; + + /** @var \PHPStan\Type\Type[] */ + private array $realParameterTypes; + + /** @var \PHPStan\Type\Type[] */ + private array $phpDocParameterTypes; + + /** @var \PHPStan\Type\Type[] */ + private array $realParameterDefaultValues; + + private \PHPStan\Type\Type $realReturnType; + + private ?\PHPStan\Type\Type $phpDocReturnType; + + private ?\PHPStan\Type\Type $throwType; + + private ?string $deprecatedDescription; + + private bool $isDeprecated; + + private bool $isInternal; + + private bool $isFinal; + + private ?bool $isPure; + + /** @var FunctionVariantWithPhpDocs[]|null */ + private ?array $variants = null; + + /** + * @param FunctionLike $functionLike + * @param TemplateTypeMap $templateTypeMap + * @param \PHPStan\Type\Type[] $realParameterTypes + * @param \PHPStan\Type\Type[] $phpDocParameterTypes + * @param \PHPStan\Type\Type[] $realParameterDefaultValues + * @param Type $realReturnType + * @param Type|null $phpDocReturnType + * @param Type|null $throwType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param bool|null $isPure + */ + public function __construct( + FunctionLike $functionLike, + TemplateTypeMap $templateTypeMap, + array $realParameterTypes, + array $phpDocParameterTypes, + array $realParameterDefaultValues, + Type $realReturnType, + ?Type $phpDocReturnType = null, + ?Type $throwType = null, + ?string $deprecatedDescription = null, + bool $isDeprecated = false, + bool $isInternal = false, + bool $isFinal = false, + ?bool $isPure = null + ) { + $this->functionLike = $functionLike; + $this->templateTypeMap = $templateTypeMap; + $this->realParameterTypes = $realParameterTypes; + $this->phpDocParameterTypes = $phpDocParameterTypes; + $this->realParameterDefaultValues = $realParameterDefaultValues; + $this->realReturnType = $realReturnType; + $this->phpDocReturnType = $phpDocReturnType; + $this->throwType = $throwType; + $this->deprecatedDescription = $deprecatedDescription; + $this->isDeprecated = $isDeprecated; + $this->isInternal = $isInternal; + $this->isFinal = $isFinal; + $this->isPure = $isPure; + } + + protected function getFunctionLike(): FunctionLike + { + return $this->functionLike; + } + + public function getName(): string + { + if ($this->functionLike instanceof ClassMethod) { + return $this->functionLike->name->name; + } + + return (string) $this->functionLike->namespacedName; + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptorWithPhpDocs[] + */ + public function getVariants(): array + { + if ($this->variants === null) { + $this->variants = [ + new FunctionVariantWithPhpDocs( + $this->templateTypeMap, + null, + $this->getParameters(), + $this->isVariadic(), + $this->getReturnType(), + $this->phpDocReturnType ?? new MixedType(), + $this->realReturnType + ), + ]; + } + + return $this->variants; + } + + /** + * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] + */ + private function getParameters(): array + { + $parameters = []; + $isOptional = true; + + /** @var \PhpParser\Node\Param $parameter */ + foreach (array_reverse($this->functionLike->getParams()) as $parameter) { + if ($parameter->default === null && !$parameter->variadic) { + $isOptional = false; + } + + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $parameters[] = new PhpParameterFromParserNodeReflection( + $parameter->var->name, + $isOptional, + $this->realParameterTypes[$parameter->var->name], + $this->phpDocParameterTypes[$parameter->var->name] ?? null, + $parameter->byRef + ? PassedByReference::createCreatesNewVariable() + : PassedByReference::createNo(), + $this->realParameterDefaultValues[$parameter->var->name] ?? null, + $parameter->variadic + ); + } + + return array_reverse($parameters); + } + + private function isVariadic(): bool + { + foreach ($this->functionLike->getParams() as $parameter) { + if ($parameter->variadic) { + return true; + } + } + + return false; + } + + private function getReturnType(): Type + { + return TypehintHelper::decideType($this->realReturnType, $this->phpDocReturnType); + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isDeprecated); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function isFinal(): TrinaryLogic + { + $finalMethod = false; + if ($this->functionLike instanceof ClassMethod) { + $finalMethod = $this->functionLike->isFinal(); + } + return TrinaryLogic::createFromBoolean($finalMethod || $this->isFinal); + } + + public function getThrowType(): ?Type + { + return $this->throwType; + } + + public function hasSideEffects(): TrinaryLogic + { + if ($this->getReturnType() instanceof VoidType) { + return TrinaryLogic::createYes(); + } + if ($this->isPure !== null) { + return TrinaryLogic::createFromBoolean(!$this->isPure); + } + + return TrinaryLogic::createMaybe(); + } + + public function isBuiltin(): bool + { + return false; + } } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index f16327cfd5..1d1dd4b4f3 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - $this->templateTypeMap = $templateTypeMap; - $this->phpDocParameterTypes = $phpDocParameterTypes; - $this->phpDocReturnType = $phpDocReturnType; - $this->phpDocThrowType = $phpDocThrowType; - $this->isDeprecated = $isDeprecated; - $this->deprecatedDescription = $deprecatedDescription; - $this->isInternal = $isInternal; - $this->isFinal = $isFinal; - $this->filename = $filename; - $this->isPure = $isPure; - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - /** - * @return string|false - */ - public function getFileName() - { - if ($this->filename === false) { - return false; - } - - if (!file_exists($this->filename)) { - return false; - } - - return $this->filename; - } - - /** - * @return ParametersAcceptorWithPhpDocs[] - */ - public function getVariants(): array - { - if ($this->variants === null) { - $this->variants = [ - new FunctionVariantWithPhpDocs( - $this->templateTypeMap, - null, - $this->getParameters(), - $this->isVariadic(), - $this->getReturnType(), - $this->getPhpDocReturnType(), - $this->getNativeReturnType() - ), - ]; - } - - return $this->variants; - } - - /** - * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] - */ - private function getParameters(): array - { - return array_map(function (\ReflectionParameter $reflection): PhpParameterReflection { - return new PhpParameterReflection( - $reflection, - $this->phpDocParameterTypes[$reflection->getName()] ?? null, - null - ); - }, $this->reflection->getParameters()); - } - - private function isVariadic(): bool - { - $isNativelyVariadic = $this->reflection->isVariadic(); - if (!$isNativelyVariadic && $this->reflection->getFileName() !== false) { - $fileName = $this->reflection->getFileName(); - if (file_exists($fileName)) { - $functionName = $this->reflection->getName(); - $modifiedTime = filemtime($fileName); - if ($modifiedTime === false) { - $modifiedTime = time(); - } - $variableCacheKey = sprintf('%d-v1', $modifiedTime); - $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); - $cachedResult = $this->cache->load($key, $variableCacheKey); - if ($cachedResult === null) { - $nodes = $this->parser->parseFile($fileName); - $result = $this->callsFuncGetArgs($nodes); - $this->cache->save($key, $variableCacheKey, $result); - return $result; - } - - return $cachedResult; - } - } - - return $isNativelyVariadic; - } - - /** - * @param \PhpParser\Node[] $nodes - * @return bool - */ - private function callsFuncGetArgs(array $nodes): bool - { - foreach ($nodes as $node) { - if ($node instanceof Function_) { - $functionName = (string) $node->namespacedName; - - if ($functionName === $this->reflection->getName()) { - return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; - } - - continue; - } - - if ($node instanceof ClassLike) { - continue; - } - - if ($node instanceof Namespace_) { - if ($this->callsFuncGetArgs($node->stmts)) { - return true; - } - } - - if (!$node instanceof Declare_ || $node->stmts === null) { - continue; - } - - if ($this->callsFuncGetArgs($node->stmts)) { - return true; - } - } - - return false; - } - - private function getReturnType(): Type - { - return TypehintHelper::decideTypeFromReflection( - $this->reflection->getReturnType(), - $this->phpDocReturnType - ); - } - - private function getPhpDocReturnType(): Type - { - if ($this->phpDocReturnType !== null) { - return $this->phpDocReturnType; - } - - return new MixedType(); - } - - private function getNativeReturnType(): Type - { - return TypehintHelper::decideTypeFromReflection($this->reflection->getReturnType()); - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean( - $this->isDeprecated || $this->reflection->isDeprecated() - ); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isInternal); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isFinal); - } - - public function getThrowType(): ?Type - { - return $this->phpDocThrowType; - } - - public function hasSideEffects(): TrinaryLogic - { - if ($this->getReturnType() instanceof VoidType) { - return TrinaryLogic::createYes(); - } - if ($this->isPure !== null) { - return TrinaryLogic::createFromBoolean(!$this->isPure); - } - - return TrinaryLogic::createMaybe(); - } - - public function isBuiltin(): bool - { - return $this->reflection->isInternal(); - } - + private \ReflectionFunction $reflection; + + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; + + private \PHPStan\Cache\Cache $cache; + + private \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap; + + /** @var \PHPStan\Type\Type[] */ + private array $phpDocParameterTypes; + + private ?\PHPStan\Type\Type $phpDocReturnType; + + private ?\PHPStan\Type\Type $phpDocThrowType; + + private ?string $deprecatedDescription; + + private bool $isDeprecated; + + private bool $isInternal; + + private bool $isFinal; + + /** @var string|false */ + private $filename; + + private ?bool $isPure; + + /** @var FunctionVariantWithPhpDocs[]|null */ + private ?array $variants = null; + + /** + * @param \ReflectionFunction $reflection + * @param Parser $parser + * @param FunctionCallStatementFinder $functionCallStatementFinder + * @param Cache $cache + * @param TemplateTypeMap $templateTypeMap + * @param \PHPStan\Type\Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $phpDocThrowType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param string|false $filename + * @param bool|null $isPure + */ + public function __construct( + \ReflectionFunction $reflection, + Parser $parser, + FunctionCallStatementFinder $functionCallStatementFinder, + Cache $cache, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $phpDocThrowType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + $filename, + ?bool $isPure = null + ) { + $this->reflection = $reflection; + $this->parser = $parser; + $this->functionCallStatementFinder = $functionCallStatementFinder; + $this->cache = $cache; + $this->templateTypeMap = $templateTypeMap; + $this->phpDocParameterTypes = $phpDocParameterTypes; + $this->phpDocReturnType = $phpDocReturnType; + $this->phpDocThrowType = $phpDocThrowType; + $this->isDeprecated = $isDeprecated; + $this->deprecatedDescription = $deprecatedDescription; + $this->isInternal = $isInternal; + $this->isFinal = $isFinal; + $this->filename = $filename; + $this->isPure = $isPure; + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + /** + * @return string|false + */ + public function getFileName() + { + if ($this->filename === false) { + return false; + } + + if (!file_exists($this->filename)) { + return false; + } + + return $this->filename; + } + + /** + * @return ParametersAcceptorWithPhpDocs[] + */ + public function getVariants(): array + { + if ($this->variants === null) { + $this->variants = [ + new FunctionVariantWithPhpDocs( + $this->templateTypeMap, + null, + $this->getParameters(), + $this->isVariadic(), + $this->getReturnType(), + $this->getPhpDocReturnType(), + $this->getNativeReturnType() + ), + ]; + } + + return $this->variants; + } + + /** + * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] + */ + private function getParameters(): array + { + return array_map(function (\ReflectionParameter $reflection): PhpParameterReflection { + return new PhpParameterReflection( + $reflection, + $this->phpDocParameterTypes[$reflection->getName()] ?? null, + null + ); + }, $this->reflection->getParameters()); + } + + private function isVariadic(): bool + { + $isNativelyVariadic = $this->reflection->isVariadic(); + if (!$isNativelyVariadic && $this->reflection->getFileName() !== false) { + $fileName = $this->reflection->getFileName(); + if (file_exists($fileName)) { + $functionName = $this->reflection->getName(); + $modifiedTime = filemtime($fileName); + if ($modifiedTime === false) { + $modifiedTime = time(); + } + $variableCacheKey = sprintf('%d-v1', $modifiedTime); + $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); + $cachedResult = $this->cache->load($key, $variableCacheKey); + if ($cachedResult === null) { + $nodes = $this->parser->parseFile($fileName); + $result = $this->callsFuncGetArgs($nodes); + $this->cache->save($key, $variableCacheKey, $result); + return $result; + } + + return $cachedResult; + } + } + + return $isNativelyVariadic; + } + + /** + * @param \PhpParser\Node[] $nodes + * @return bool + */ + private function callsFuncGetArgs(array $nodes): bool + { + foreach ($nodes as $node) { + if ($node instanceof Function_) { + $functionName = (string) $node->namespacedName; + + if ($functionName === $this->reflection->getName()) { + return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; + } + + continue; + } + + if ($node instanceof ClassLike) { + continue; + } + + if ($node instanceof Namespace_) { + if ($this->callsFuncGetArgs($node->stmts)) { + return true; + } + } + + if (!$node instanceof Declare_ || $node->stmts === null) { + continue; + } + + if ($this->callsFuncGetArgs($node->stmts)) { + return true; + } + } + + return false; + } + + private function getReturnType(): Type + { + return TypehintHelper::decideTypeFromReflection( + $this->reflection->getReturnType(), + $this->phpDocReturnType + ); + } + + private function getPhpDocReturnType(): Type + { + if ($this->phpDocReturnType !== null) { + return $this->phpDocReturnType; + } + + return new MixedType(); + } + + private function getNativeReturnType(): Type + { + return TypehintHelper::decideTypeFromReflection($this->reflection->getReturnType()); + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->isDeprecated || $this->reflection->isDeprecated() + ); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isFinal); + } + + public function getThrowType(): ?Type + { + return $this->phpDocThrowType; + } + + public function hasSideEffects(): TrinaryLogic + { + if ($this->getReturnType() instanceof VoidType) { + return TrinaryLogic::createYes(); + } + if ($this->isPure !== null) { + return TrinaryLogic::createFromBoolean(!$this->isPure); + } + + return TrinaryLogic::createMaybe(); + } + + public function isBuiltin(): bool + { + return $this->reflection->isInternal(); + } } diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index ee5f5d6dd8..41294b3aa0 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -1,4 +1,6 @@ -name->name); - if ( - $name === '__construct' - || $name === '__destruct' - || $name === '__unset' - || $name === '__wakeup' - || $name === '__clone' - ) { - $realReturnType = new VoidType(); - } - if ($name === '__tostring') { - $realReturnType = new StringType(); - } - if ($name === '__isset') { - $realReturnType = new BooleanType(); - } - if ($name === '__sleep') { - $realReturnType = new ArrayType(new IntegerType(), new StringType()); - } - if ($name === '__set_state') { - $realReturnType = TypeCombinator::intersect(new ObjectWithoutClassType(), $realReturnType); - } - - parent::__construct( - $classMethod, - $templateTypeMap, - $realParameterTypes, - $phpDocParameterTypes, - $realParameterDefaultValues, - $realReturnType, - $phpDocReturnType, - $throwType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal || $classMethod->isFinal(), - $isPure - ); - $this->declaringClass = $declaringClass; - } + /** + * @param ClassReflection $declaringClass + * @param ClassMethod $classMethod + * @param TemplateTypeMap $templateTypeMap + * @param \PHPStan\Type\Type[] $realParameterTypes + * @param \PHPStan\Type\Type[] $phpDocParameterTypes + * @param \PHPStan\Type\Type[] $realParameterDefaultValues + * @param Type $realReturnType + * @param Type|null $phpDocReturnType + * @param Type|null $throwType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param bool|null $isPure + */ + public function __construct( + ClassReflection $declaringClass, + ClassMethod $classMethod, + TemplateTypeMap $templateTypeMap, + array $realParameterTypes, + array $phpDocParameterTypes, + array $realParameterDefaultValues, + Type $realReturnType, + ?Type $phpDocReturnType, + ?Type $throwType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?bool $isPure = null + ) { + $name = strtolower($classMethod->name->name); + if ( + $name === '__construct' + || $name === '__destruct' + || $name === '__unset' + || $name === '__wakeup' + || $name === '__clone' + ) { + $realReturnType = new VoidType(); + } + if ($name === '__tostring') { + $realReturnType = new StringType(); + } + if ($name === '__isset') { + $realReturnType = new BooleanType(); + } + if ($name === '__sleep') { + $realReturnType = new ArrayType(new IntegerType(), new StringType()); + } + if ($name === '__set_state') { + $realReturnType = TypeCombinator::intersect(new ObjectWithoutClassType(), $realReturnType); + } - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } + parent::__construct( + $classMethod, + $templateTypeMap, + $realParameterTypes, + $phpDocParameterTypes, + $realParameterDefaultValues, + $realReturnType, + $phpDocReturnType, + $throwType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal || $classMethod->isFinal(), + $isPure + ); + $this->declaringClass = $declaringClass; + } - public function getPrototype(): ClassMemberReflection - { - try { - return $this->declaringClass->getNativeMethod($this->getClassMethod()->name->name)->getPrototype(); - } catch (\PHPStan\Reflection\MissingMethodFromReflectionException $e) { - return $this; - } - } + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } - private function getClassMethod(): ClassMethod - { - /** @var \PhpParser\Node\Stmt\ClassMethod $functionLike */ - $functionLike = $this->getFunctionLike(); - return $functionLike; - } + public function getPrototype(): ClassMemberReflection + { + try { + return $this->declaringClass->getNativeMethod($this->getClassMethod()->name->name)->getPrototype(); + } catch (\PHPStan\Reflection\MissingMethodFromReflectionException $e) { + return $this; + } + } - public function isStatic(): bool - { - return $this->getClassMethod()->isStatic(); - } + private function getClassMethod(): ClassMethod + { + /** @var \PhpParser\Node\Stmt\ClassMethod $functionLike */ + $functionLike = $this->getFunctionLike(); + return $functionLike; + } - public function isPrivate(): bool - { - return $this->getClassMethod()->isPrivate(); - } + public function isStatic(): bool + { + return $this->getClassMethod()->isStatic(); + } - public function isPublic(): bool - { - return $this->getClassMethod()->isPublic(); - } + public function isPrivate(): bool + { + return $this->getClassMethod()->isPrivate(); + } - public function getDocComment(): ?string - { - return null; - } + public function isPublic(): bool + { + return $this->getClassMethod()->isPublic(); + } - public function isBuiltin(): bool - { - return false; - } + public function getDocComment(): ?string + { + return null; + } + public function isBuiltin(): bool + { + return false; + } } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 3db061d441..979a0b200a 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->declaringTrait = $declaringTrait; - $this->reflection = $reflection; - $this->reflectionProvider = $reflectionProvider; - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - $this->templateTypeMap = $templateTypeMap; - $this->phpDocParameterTypes = $phpDocParameterTypes; - $this->phpDocReturnType = $phpDocReturnType; - $this->phpDocThrowType = $phpDocThrowType; - $this->deprecatedDescription = $deprecatedDescription; - $this->isDeprecated = $isDeprecated; - $this->isInternal = $isInternal; - $this->isFinal = $isFinal; - $this->stubPhpDocString = $stubPhpDocString; - $this->isPure = $isPure; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function getDeclaringTrait(): ?ClassReflection - { - return $this->declaringTrait; - } - - public function getDocComment(): ?string - { - if ($this->stubPhpDocString !== null) { - return $this->stubPhpDocString; - } - - return $this->reflection->getDocComment(); - } - - /** - * @return self|\PHPStan\Reflection\MethodPrototypeReflection - */ - public function getPrototype(): ClassMemberReflection - { - try { - $prototypeMethod = $this->reflection->getPrototype(); - $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); - - return new MethodPrototypeReflection( - $prototypeMethod->getName(), - $prototypeDeclaringClass, - $prototypeMethod->isStatic(), - $prototypeMethod->isPrivate(), - $prototypeMethod->isPublic(), - $prototypeMethod->isAbstract(), - $prototypeMethod->isFinal(), - $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() - ); - } catch (\ReflectionException $e) { - return $this; - } - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function getName(): string - { - $name = $this->reflection->getName(); - $lowercaseName = strtolower($name); - if ($lowercaseName === $name) { - // fix for https://bugs.php.net/bug.php?id=74939 - foreach ($this->getDeclaringClass()->getNativeReflection()->getTraitAliases() as $traitTarget) { - $correctName = $this->getMethodNameWithCorrectCase($name, $traitTarget); - if ($correctName !== null) { - $name = $correctName; - break; - } - } - } - - return $name; - } - - private function getMethodNameWithCorrectCase(string $lowercaseMethodName, string $traitTarget): ?string - { - $trait = explode('::', $traitTarget)[0]; - $traitReflection = $this->reflectionProvider->getClass($trait)->getNativeReflection(); - foreach ($traitReflection->getTraitAliases() as $methodAlias => $aliasTraitTarget) { - if ($lowercaseMethodName === strtolower($methodAlias)) { - return $methodAlias; - } - - $correctName = $this->getMethodNameWithCorrectCase($lowercaseMethodName, $aliasTraitTarget); - if ($correctName !== null) { - return $correctName; - } - } - - return null; - } - - /** - * @return ParametersAcceptorWithPhpDocs[] - */ - public function getVariants(): array - { - if ($this->variants === null) { - $this->variants = [ - new FunctionVariantWithPhpDocs( - $this->templateTypeMap, - null, - $this->getParameters(), - $this->isVariadic(), - $this->getReturnType(), - $this->getPhpDocReturnType(), - $this->getNativeReturnType() - ), - ]; - } - - return $this->variants; - } - - /** - * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] - */ - private function getParameters(): array - { - if ($this->parameters === null) { - $this->parameters = array_map(function (\ReflectionParameter $reflection): PhpParameterReflection { - return new PhpParameterReflection( - $reflection, - $this->phpDocParameterTypes[$reflection->getName()] ?? null, - $this->getDeclaringClass()->getName() - ); - }, $this->reflection->getParameters()); - } - - return $this->parameters; - } - - private function isVariadic(): bool - { - $isNativelyVariadic = $this->reflection->isVariadic(); - $declaringClass = $this->declaringClass; - $filename = $this->declaringClass->getFileName(); - if ($this->declaringTrait !== null) { - $declaringClass = $this->declaringTrait; - $filename = $this->declaringTrait->getFileName(); - } - - if (!$isNativelyVariadic && $filename !== false && file_exists($filename)) { - $modifiedTime = filemtime($filename); - if ($modifiedTime === false) { - $modifiedTime = time(); - } - $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); - $variableCacheKey = sprintf('%d-v2', $modifiedTime); - $cachedResult = $this->cache->load($key, $variableCacheKey); - if ($cachedResult === null || !is_bool($cachedResult)) { - $nodes = $this->parser->parseFile($filename); - $result = $this->callsFuncGetArgs($declaringClass, $nodes); - $this->cache->save($key, $variableCacheKey, $result); - return $result; - } - - return $cachedResult; - } - - return $isNativelyVariadic; - } - - /** - * @param ClassReflection $declaringClass - * @param \PhpParser\Node[] $nodes - * @return bool - */ - private function callsFuncGetArgs(ClassReflection $declaringClass, array $nodes): bool - { - foreach ($nodes as $node) { - if ( - $node instanceof \PhpParser\Node\Stmt\ClassLike - ) { - if (!isset($node->namespacedName)) { - continue; - } - if ($declaringClass->getName() !== (string) $node->namespacedName) { - continue; - } - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; - } - continue; - } - - if ($node instanceof ClassMethod) { - if ($node->getStmts() === null) { - continue; // interface - } - - $methodName = $node->name->name; - if ($methodName === $this->reflection->getName()) { - return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; - } - - continue; - } - - if ($node instanceof Function_) { - continue; - } - - if ($node instanceof Namespace_) { - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; - } - continue; - } - - if (!$node instanceof Declare_ || $node->stmts === null) { - continue; - } - - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; - } - } - - return false; - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - private function getReturnType(): Type - { - if ($this->returnType === null) { - $name = strtolower($this->getName()); - if ( - $name === '__construct' - || $name === '__destruct' - || $name === '__unset' - || $name === '__wakeup' - || $name === '__clone' - ) { - return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType); - } - if ($name === '__tostring') { - return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType); - } - if ($name === '__isset') { - return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType); - } - if ($name === '__sleep') { - return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType); - } - if ($name === '__set_state') { - return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType); - } - - $this->returnType = TypehintHelper::decideTypeFromReflection( - $this->reflection->getReturnType(), - $this->phpDocReturnType, - $this->declaringClass->getName() - ); - } - - return $this->returnType; - } - - private function getPhpDocReturnType(): Type - { - if ($this->phpDocReturnType !== null) { - return $this->phpDocReturnType; - } - - return new MixedType(); - } - - private function getNativeReturnType(): Type - { - if ($this->nativeReturnType === null) { - $this->nativeReturnType = TypehintHelper::decideTypeFromReflection( - $this->reflection->getReturnType(), - null, - $this->declaringClass->getName() - ); - } - - return $this->nativeReturnType; - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated()->or(TrinaryLogic::createFromBoolean($this->isDeprecated)); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->isInternal() || $this->isInternal); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->isFinal() || $this->isFinal); - } - - public function isAbstract(): bool - { - return $this->reflection->isAbstract(); - } - - public function getThrowType(): ?Type - { - return $this->phpDocThrowType; - } - - public function hasSideEffects(): TrinaryLogic - { - $name = strtolower($this->getName()); - $isVoid = $this->getReturnType() instanceof VoidType; - - if ( - $name !== '__construct' - && $isVoid - ) { - return TrinaryLogic::createYes(); - } - if ($this->isPure !== null) { - return TrinaryLogic::createFromBoolean(!$this->isPure); - } - - if ($isVoid) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private ?ClassReflection $declaringTrait; + + private BuiltinMethodReflection $reflection; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; + + private \PHPStan\Cache\Cache $cache; + + private \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap; + + /** @var \PHPStan\Type\Type[] */ + private array $phpDocParameterTypes; + + private ?\PHPStan\Type\Type $phpDocReturnType; + + private ?\PHPStan\Type\Type $phpDocThrowType; + + /** @var \PHPStan\Reflection\Php\PhpParameterReflection[]|null */ + private ?array $parameters = null; + + private ?\PHPStan\Type\Type $returnType = null; + + private ?\PHPStan\Type\Type $nativeReturnType = null; + + private ?string $deprecatedDescription; + + private bool $isDeprecated; + + private bool $isInternal; + + private bool $isFinal; + + private ?bool $isPure; + + private ?string $stubPhpDocString; + + /** @var FunctionVariantWithPhpDocs[]|null */ + private ?array $variants = null; + + /** + * @param ClassReflection $declaringClass + * @param ClassReflection|null $declaringTrait + * @param BuiltinMethodReflection $reflection + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param Parser $parser + * @param FunctionCallStatementFinder $functionCallStatementFinder + * @param Cache $cache + * @param \PHPStan\Type\Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $phpDocThrowType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param string|null $stubPhpDocString + */ + public function __construct( + ClassReflection $declaringClass, + ?ClassReflection $declaringTrait, + BuiltinMethodReflection $reflection, + ReflectionProvider $reflectionProvider, + Parser $parser, + FunctionCallStatementFinder $functionCallStatementFinder, + Cache $cache, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $phpDocThrowType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?string $stubPhpDocString, + ?bool $isPure = null + ) { + $this->declaringClass = $declaringClass; + $this->declaringTrait = $declaringTrait; + $this->reflection = $reflection; + $this->reflectionProvider = $reflectionProvider; + $this->parser = $parser; + $this->functionCallStatementFinder = $functionCallStatementFinder; + $this->cache = $cache; + $this->templateTypeMap = $templateTypeMap; + $this->phpDocParameterTypes = $phpDocParameterTypes; + $this->phpDocReturnType = $phpDocReturnType; + $this->phpDocThrowType = $phpDocThrowType; + $this->deprecatedDescription = $deprecatedDescription; + $this->isDeprecated = $isDeprecated; + $this->isInternal = $isInternal; + $this->isFinal = $isFinal; + $this->stubPhpDocString = $stubPhpDocString; + $this->isPure = $isPure; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function getDeclaringTrait(): ?ClassReflection + { + return $this->declaringTrait; + } + + public function getDocComment(): ?string + { + if ($this->stubPhpDocString !== null) { + return $this->stubPhpDocString; + } + + return $this->reflection->getDocComment(); + } + + /** + * @return self|\PHPStan\Reflection\MethodPrototypeReflection + */ + public function getPrototype(): ClassMemberReflection + { + try { + $prototypeMethod = $this->reflection->getPrototype(); + $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); + + return new MethodPrototypeReflection( + $prototypeMethod->getName(), + $prototypeDeclaringClass, + $prototypeMethod->isStatic(), + $prototypeMethod->isPrivate(), + $prototypeMethod->isPublic(), + $prototypeMethod->isAbstract(), + $prototypeMethod->isFinal(), + $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() + ); + } catch (\ReflectionException $e) { + return $this; + } + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function getName(): string + { + $name = $this->reflection->getName(); + $lowercaseName = strtolower($name); + if ($lowercaseName === $name) { + // fix for https://bugs.php.net/bug.php?id=74939 + foreach ($this->getDeclaringClass()->getNativeReflection()->getTraitAliases() as $traitTarget) { + $correctName = $this->getMethodNameWithCorrectCase($name, $traitTarget); + if ($correctName !== null) { + $name = $correctName; + break; + } + } + } + + return $name; + } + + private function getMethodNameWithCorrectCase(string $lowercaseMethodName, string $traitTarget): ?string + { + $trait = explode('::', $traitTarget)[0]; + $traitReflection = $this->reflectionProvider->getClass($trait)->getNativeReflection(); + foreach ($traitReflection->getTraitAliases() as $methodAlias => $aliasTraitTarget) { + if ($lowercaseMethodName === strtolower($methodAlias)) { + return $methodAlias; + } + + $correctName = $this->getMethodNameWithCorrectCase($lowercaseMethodName, $aliasTraitTarget); + if ($correctName !== null) { + return $correctName; + } + } + + return null; + } + + /** + * @return ParametersAcceptorWithPhpDocs[] + */ + public function getVariants(): array + { + if ($this->variants === null) { + $this->variants = [ + new FunctionVariantWithPhpDocs( + $this->templateTypeMap, + null, + $this->getParameters(), + $this->isVariadic(), + $this->getReturnType(), + $this->getPhpDocReturnType(), + $this->getNativeReturnType() + ), + ]; + } + + return $this->variants; + } + + /** + * @return \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] + */ + private function getParameters(): array + { + if ($this->parameters === null) { + $this->parameters = array_map(function (\ReflectionParameter $reflection): PhpParameterReflection { + return new PhpParameterReflection( + $reflection, + $this->phpDocParameterTypes[$reflection->getName()] ?? null, + $this->getDeclaringClass()->getName() + ); + }, $this->reflection->getParameters()); + } + + return $this->parameters; + } + + private function isVariadic(): bool + { + $isNativelyVariadic = $this->reflection->isVariadic(); + $declaringClass = $this->declaringClass; + $filename = $this->declaringClass->getFileName(); + if ($this->declaringTrait !== null) { + $declaringClass = $this->declaringTrait; + $filename = $this->declaringTrait->getFileName(); + } + + if (!$isNativelyVariadic && $filename !== false && file_exists($filename)) { + $modifiedTime = filemtime($filename); + if ($modifiedTime === false) { + $modifiedTime = time(); + } + $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); + $variableCacheKey = sprintf('%d-v2', $modifiedTime); + $cachedResult = $this->cache->load($key, $variableCacheKey); + if ($cachedResult === null || !is_bool($cachedResult)) { + $nodes = $this->parser->parseFile($filename); + $result = $this->callsFuncGetArgs($declaringClass, $nodes); + $this->cache->save($key, $variableCacheKey, $result); + return $result; + } + + return $cachedResult; + } + + return $isNativelyVariadic; + } + + /** + * @param ClassReflection $declaringClass + * @param \PhpParser\Node[] $nodes + * @return bool + */ + private function callsFuncGetArgs(ClassReflection $declaringClass, array $nodes): bool + { + foreach ($nodes as $node) { + if ( + $node instanceof \PhpParser\Node\Stmt\ClassLike + ) { + if (!isset($node->namespacedName)) { + continue; + } + if ($declaringClass->getName() !== (string) $node->namespacedName) { + continue; + } + if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { + return true; + } + continue; + } + + if ($node instanceof ClassMethod) { + if ($node->getStmts() === null) { + continue; // interface + } + + $methodName = $node->name->name; + if ($methodName === $this->reflection->getName()) { + return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; + } + + continue; + } + + if ($node instanceof Function_) { + continue; + } + + if ($node instanceof Namespace_) { + if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { + return true; + } + continue; + } + + if (!$node instanceof Declare_ || $node->stmts === null) { + continue; + } + + if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { + return true; + } + } + + return false; + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + private function getReturnType(): Type + { + if ($this->returnType === null) { + $name = strtolower($this->getName()); + if ( + $name === '__construct' + || $name === '__destruct' + || $name === '__unset' + || $name === '__wakeup' + || $name === '__clone' + ) { + return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType); + } + if ($name === '__tostring') { + return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType); + } + if ($name === '__isset') { + return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType); + } + if ($name === '__sleep') { + return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType); + } + if ($name === '__set_state') { + return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType); + } + + $this->returnType = TypehintHelper::decideTypeFromReflection( + $this->reflection->getReturnType(), + $this->phpDocReturnType, + $this->declaringClass->getName() + ); + } + + return $this->returnType; + } + + private function getPhpDocReturnType(): Type + { + if ($this->phpDocReturnType !== null) { + return $this->phpDocReturnType; + } + + return new MixedType(); + } + + private function getNativeReturnType(): Type + { + if ($this->nativeReturnType === null) { + $this->nativeReturnType = TypehintHelper::decideTypeFromReflection( + $this->reflection->getReturnType(), + null, + $this->declaringClass->getName() + ); + } + + return $this->nativeReturnType; + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated()->or(TrinaryLogic::createFromBoolean($this->isDeprecated)); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isInternal() || $this->isInternal); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isFinal() || $this->isFinal); + } + + public function isAbstract(): bool + { + return $this->reflection->isAbstract(); + } + + public function getThrowType(): ?Type + { + return $this->phpDocThrowType; + } + + public function hasSideEffects(): TrinaryLogic + { + $name = strtolower($this->getName()); + $isVoid = $this->getReturnType() instanceof VoidType; + + if ( + $name !== '__construct' + && $isVoid + ) { + return TrinaryLogic::createYes(); + } + if ($this->isPure !== null) { + return TrinaryLogic::createFromBoolean(!$this->isPure); + } + + if ($isVoid) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } } diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 4d06c19570..22760b06fe 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -1,4 +1,6 @@ -name = $name; - $this->optional = $optional; - $this->realType = $realType; - $this->phpDocType = $phpDocType; - $this->passedByReference = $passedByReference; - $this->defaultValue = $defaultValue; - $this->variadic = $variadic; - } - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - if ($this->type === null) { - $phpDocType = $this->phpDocType; - if ($phpDocType !== null && $this->defaultValue !== null) { - if ($this->defaultValue instanceof NullType) { - $phpDocType = \PHPStan\Type\TypeCombinator::addNull($phpDocType); - } - } - $this->type = TypehintHelper::decideType($this->realType, $phpDocType); - } - - return $this->type; - } - - public function getPhpDocType(): Type - { - return $this->phpDocType ?? new MixedType(); - } - - public function getNativeType(): Type - { - return $this->realType; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getDefaultValue(): ?Type - { - return $this->defaultValue; - } - + private string $name; + + private bool $optional; + + private \PHPStan\Type\Type $realType; + + private ?\PHPStan\Type\Type $phpDocType; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private ?\PHPStan\Type\Type $defaultValue; + + private bool $variadic; + + private ?\PHPStan\Type\Type $type = null; + + public function __construct( + string $name, + bool $optional, + Type $realType, + ?Type $phpDocType, + PassedByReference $passedByReference, + ?Type $defaultValue, + bool $variadic + ) { + $this->name = $name; + $this->optional = $optional; + $this->realType = $realType; + $this->phpDocType = $phpDocType; + $this->passedByReference = $passedByReference; + $this->defaultValue = $defaultValue; + $this->variadic = $variadic; + } + + public function getName(): string + { + return $this->name; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function getType(): Type + { + if ($this->type === null) { + $phpDocType = $this->phpDocType; + if ($phpDocType !== null && $this->defaultValue !== null) { + if ($this->defaultValue instanceof NullType) { + $phpDocType = \PHPStan\Type\TypeCombinator::addNull($phpDocType); + } + } + $this->type = TypehintHelper::decideType($this->realType, $phpDocType); + } + + return $this->type; + } + + public function getPhpDocType(): Type + { + return $this->phpDocType ?? new MixedType(); + } + + public function getNativeType(): Type + { + return $this->realType; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getDefaultValue(): ?Type + { + return $this->defaultValue; + } } diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index c134d7a1b2..be1e0d8135 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - $this->phpDocType = $phpDocType; - $this->declaringClassName = $declaringClassName; - } - - public function isOptional(): bool - { - return $this->reflection->isOptional(); - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getType(): Type - { - if ($this->type === null) { - $phpDocType = $this->phpDocType; - if ($phpDocType !== null) { - try { - if ($this->reflection->isDefaultValueAvailable() && $this->reflection->getDefaultValue() === null) { - $phpDocType = \PHPStan\Type\TypeCombinator::addNull($phpDocType); - } - } catch (\Throwable $e) { - // pass - } - } - - $this->type = TypehintHelper::decideTypeFromReflection( - $this->reflection->getType(), - $phpDocType, - $this->declaringClassName, - $this->isVariadic() - ); - } - - return $this->type; - } - - public function passedByReference(): PassedByReference - { - return $this->reflection->isPassedByReference() - ? PassedByReference::createCreatesNewVariable() - : PassedByReference::createNo(); - } - - public function isVariadic(): bool - { - return $this->reflection->isVariadic(); - } - - public function getPhpDocType(): Type - { - if ($this->phpDocType !== null) { - return $this->phpDocType; - } - - return new MixedType(); - } - - public function getNativeType(): Type - { - if ($this->nativeType === null) { - $this->nativeType = TypehintHelper::decideTypeFromReflection( - $this->reflection->getType(), - null, - $this->declaringClassName, - $this->isVariadic() - ); - } - - return $this->nativeType; - } - - public function getDefaultValue(): ?Type - { - try { - if ($this->reflection->isDefaultValueAvailable()) { - $defaultValue = $this->reflection->getDefaultValue(); - return ConstantTypeHelper::getTypeFromValue($defaultValue); - } - } catch (\Throwable $e) { - return null; - } - - return null; - } - + private \ReflectionParameter $reflection; + + private ?\PHPStan\Type\Type $phpDocType; + + private ?\PHPStan\Type\Type $type = null; + + private ?\PHPStan\Type\Type $nativeType = null; + + private ?string $declaringClassName; + + public function __construct( + \ReflectionParameter $reflection, + ?Type $phpDocType, + ?string $declaringClassName + ) { + $this->reflection = $reflection; + $this->phpDocType = $phpDocType; + $this->declaringClassName = $declaringClassName; + } + + public function isOptional(): bool + { + return $this->reflection->isOptional(); + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getType(): Type + { + if ($this->type === null) { + $phpDocType = $this->phpDocType; + if ($phpDocType !== null) { + try { + if ($this->reflection->isDefaultValueAvailable() && $this->reflection->getDefaultValue() === null) { + $phpDocType = \PHPStan\Type\TypeCombinator::addNull($phpDocType); + } + } catch (\Throwable $e) { + // pass + } + } + + $this->type = TypehintHelper::decideTypeFromReflection( + $this->reflection->getType(), + $phpDocType, + $this->declaringClassName, + $this->isVariadic() + ); + } + + return $this->type; + } + + public function passedByReference(): PassedByReference + { + return $this->reflection->isPassedByReference() + ? PassedByReference::createCreatesNewVariable() + : PassedByReference::createNo(); + } + + public function isVariadic(): bool + { + return $this->reflection->isVariadic(); + } + + public function getPhpDocType(): Type + { + if ($this->phpDocType !== null) { + return $this->phpDocType; + } + + return new MixedType(); + } + + public function getNativeType(): Type + { + if ($this->nativeType === null) { + $this->nativeType = TypehintHelper::decideTypeFromReflection( + $this->reflection->getType(), + null, + $this->declaringClassName, + $this->isVariadic() + ); + } + + return $this->nativeType; + } + + public function getDefaultValue(): ?Type + { + try { + if ($this->reflection->isDefaultValueAvailable()) { + $defaultValue = $this->reflection->getDefaultValue(); + return ConstantTypeHelper::getTypeFromValue($defaultValue); + } + } catch (\Throwable $e) { + return null; + } + + return null; + } } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index c9c1625c60..b00caf59c1 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->declaringTrait = $declaringTrait; - $this->nativeType = $nativeType; - $this->phpDocType = $phpDocType; - $this->reflection = $reflection; - $this->deprecatedDescription = $deprecatedDescription; - $this->isDeprecated = $isDeprecated; - $this->isInternal = $isInternal; - $this->stubPhpDocString = $stubPhpDocString; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function getDeclaringTrait(): ?ClassReflection - { - return $this->declaringTrait; - } - - public function getDocComment(): ?string - { - if ($this->stubPhpDocString !== null) { - return $this->stubPhpDocString; - } - - $docComment = $this->reflection->getDocComment(); - if ($docComment === false) { - return null; - } - - return $docComment; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getReadableType(): Type - { - if ($this->type === null) { - $this->type = TypehintHelper::decideTypeFromReflection( - $this->nativeType, - $this->phpDocType, - $this->declaringClass->getName() - ); - } - - return $this->type; - } - - public function getWritableType(): Type - { - return $this->getReadableType(); - } - - public function canChangeTypeAfterAssignment(): bool - { - return true; - } - - public function isPromoted(): bool - { - if (!method_exists($this->reflection, 'isPromoted')) { - return false; - } - - return $this->reflection->isPromoted(); - } - - public function hasPhpDoc(): bool - { - return $this->phpDocType !== null; - } - - public function getPhpDocType(): Type - { - if ($this->phpDocType !== null) { - return $this->phpDocType; - } - - return new MixedType(); - } - - public function getNativeType(): Type - { - if ($this->finalNativeType === null) { - $this->finalNativeType = TypehintHelper::decideTypeFromReflection( - $this->nativeType, - null, - $this->declaringClass->getName() - ); - } - - return $this->finalNativeType; - } - - public function isReadable(): bool - { - return true; - } - - public function isWritable(): bool - { - return true; - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isDeprecated); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isInternal); - } - - public function getNativeReflection(): \ReflectionProperty - { - return $this->reflection; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private ?\PHPStan\Reflection\ClassReflection $declaringTrait; + + private ?\ReflectionType $nativeType; + + private ?\PHPStan\Type\Type $finalNativeType = null; + + private ?\PHPStan\Type\Type $phpDocType; + + private ?\PHPStan\Type\Type $type = null; + + private \ReflectionProperty $reflection; + + private ?string $deprecatedDescription; + + private bool $isDeprecated; + + private bool $isInternal; + + private ?string $stubPhpDocString; + + public function __construct( + ClassReflection $declaringClass, + ?ClassReflection $declaringTrait, + ?\ReflectionType $nativeType, + ?Type $phpDocType, + \ReflectionProperty $reflection, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + ?string $stubPhpDocString + ) { + $this->declaringClass = $declaringClass; + $this->declaringTrait = $declaringTrait; + $this->nativeType = $nativeType; + $this->phpDocType = $phpDocType; + $this->reflection = $reflection; + $this->deprecatedDescription = $deprecatedDescription; + $this->isDeprecated = $isDeprecated; + $this->isInternal = $isInternal; + $this->stubPhpDocString = $stubPhpDocString; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function getDeclaringTrait(): ?ClassReflection + { + return $this->declaringTrait; + } + + public function getDocComment(): ?string + { + if ($this->stubPhpDocString !== null) { + return $this->stubPhpDocString; + } + + $docComment = $this->reflection->getDocComment(); + if ($docComment === false) { + return null; + } + + return $docComment; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getReadableType(): Type + { + if ($this->type === null) { + $this->type = TypehintHelper::decideTypeFromReflection( + $this->nativeType, + $this->phpDocType, + $this->declaringClass->getName() + ); + } + + return $this->type; + } + + public function getWritableType(): Type + { + return $this->getReadableType(); + } + + public function canChangeTypeAfterAssignment(): bool + { + return true; + } + + public function isPromoted(): bool + { + if (!method_exists($this->reflection, 'isPromoted')) { + return false; + } + + return $this->reflection->isPromoted(); + } + + public function hasPhpDoc(): bool + { + return $this->phpDocType !== null; + } + + public function getPhpDocType(): Type + { + if ($this->phpDocType !== null) { + return $this->phpDocType; + } + + return new MixedType(); + } + + public function getNativeType(): Type + { + if ($this->finalNativeType === null) { + $this->finalNativeType = TypehintHelper::decideTypeFromReflection( + $this->nativeType, + null, + $this->declaringClass->getName() + ); + } + + return $this->finalNativeType; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isDeprecated); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function getNativeReflection(): \ReflectionProperty + { + return $this->reflection; + } } diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index 098e62a820..b2650674ba 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->type = $type; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getReadableType(): Type - { - return $this->type; - } - - public function getWritableType(): Type - { - return TypeCombinator::union( - $this->type, - new IntegerType(), - new FloatType(), - new StringType(), - new BooleanType() - ); - } - - public function isReadable(): bool - { - return true; - } - - public function isWritable(): bool - { - return true; - } - - public function canChangeTypeAfterAssignment(): bool - { - return false; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDocComment(): ?string - { - return null; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private \PHPStan\Type\Type $type; + + public function __construct( + ClassReflection $declaringClass, + Type $type + ) { + $this->declaringClass = $declaringClass; + $this->type = $type; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getReadableType(): Type + { + return $this->type; + } + + public function getWritableType(): Type + { + return TypeCombinator::union( + $this->type, + new IntegerType(), + new FloatType(), + new StringType(), + new BooleanType() + ); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function canChangeTypeAfterAssignment(): bool + { + return false; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Php/Soap/SoapClientMethodReflection.php b/src/Reflection/Php/Soap/SoapClientMethodReflection.php index 32701b305e..88d028fce2 100644 --- a/src/Reflection/Php/Soap/SoapClientMethodReflection.php +++ b/src/Reflection/Php/Soap/SoapClientMethodReflection.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->name = $name; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getDocComment(): ?string - { - return null; - } - - public function getName(): string - { - return $this->name; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - public function getVariants(): array - { - return [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - TemplateTypeMap::createEmpty(), - [], - true, - new MixedType(true) - ), - ]; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getThrowType(): ?Type - { - return new ObjectType('SoapFault'); - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - + private ClassReflection $declaringClass; + + private string $name; + + public function __construct(ClassReflection $declaringClass, string $name) + { + $this->declaringClass = $declaringClass; + $this->name = $name; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getDocComment(): ?string + { + return null; + } + + public function getName(): string + { + return $this->name; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + public function getVariants(): array + { + return [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + TemplateTypeMap::createEmpty(), + [], + true, + new MixedType(true) + ), + ]; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getThrowType(): ?Type + { + return new ObjectType('SoapFault'); + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } } diff --git a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php index 73924b6081..b838f816f7 100644 --- a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php +++ b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php @@ -1,4 +1,6 @@ -getName() === 'SoapClient' || $classReflection->isSubclassOf('SoapClient'); + } - public function hasMethod(ClassReflection $classReflection, string $methodName): bool - { - return $classReflection->getName() === 'SoapClient' || $classReflection->isSubclassOf('SoapClient'); - } - - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection - { - return new SoapClientMethodReflection($classReflection, $methodName); - } - + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + return new SoapClientMethodReflection($classReflection, $methodName); + } } diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 5dbad67143..8733b3df37 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -1,4 +1,6 @@ -declaringClass = $declaringClass; - $this->readableType = $readableType; - $this->writableType = $writableType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return false; - } - - public function isPrivate(): bool - { - return false; - } - - public function isPublic(): bool - { - return true; - } - - public function getReadableType(): Type - { - return $this->readableType; - } - - public function getWritableType(): Type - { - return $this->writableType; - } - - public function canChangeTypeAfterAssignment(): bool - { - return true; - } - - public function isReadable(): bool - { - return true; - } - - public function isWritable(): bool - { - return true; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDeprecatedDescription(): ?string - { - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getDocComment(): ?string - { - return null; - } - + private \PHPStan\Reflection\ClassReflection $declaringClass; + + private \PHPStan\Type\Type $readableType; + + private \PHPStan\Type\Type $writableType; + + public function __construct( + ClassReflection $declaringClass, + Type $readableType, + Type $writableType + ) { + $this->declaringClass = $declaringClass; + $this->readableType = $readableType; + $this->writableType = $writableType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return false; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getReadableType(): Type + { + return $this->readableType; + } + + public function getWritableType(): Type + { + return $this->writableType; + } + + public function canChangeTypeAfterAssignment(): bool + { + return true; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index 8c0ec93652..5efd43121a 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -1,4 +1,6 @@ -classes = $classes; - } + private \PHPStan\Broker\Broker $broker; - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } + /** + * @param string[] $classes + */ + public function __construct(array $classes) + { + $this->classes = $classes; + } - public function hasProperty(ClassReflection $classReflection, string $propertyName): bool - { - return self::isUniversalObjectCrate( - $this->broker, - $this->classes, - $classReflection - ); - } + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } - /** - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param string[] $classes - * @param \PHPStan\Reflection\ClassReflection $classReflection - * @return bool - */ - public static function isUniversalObjectCrate( - ReflectionProvider $reflectionProvider, - array $classes, - ClassReflection $classReflection - ): bool - { - foreach ($classes as $className) { - if (!$reflectionProvider->hasClass($className)) { - continue; - } + public function hasProperty(ClassReflection $classReflection, string $propertyName): bool + { + return self::isUniversalObjectCrate( + $this->broker, + $this->classes, + $classReflection + ); + } - if ( - $classReflection->getName() === $className - || $classReflection->isSubclassOf($className) - ) { - return true; - } - } + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param string[] $classes + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @return bool + */ + public static function isUniversalObjectCrate( + ReflectionProvider $reflectionProvider, + array $classes, + ClassReflection $classReflection + ): bool { + foreach ($classes as $className) { + if (!$reflectionProvider->hasClass($className)) { + continue; + } - return false; - } + if ( + $classReflection->getName() === $className + || $classReflection->isSubclassOf($className) + ) { + return true; + } + } - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection - { - if ($classReflection->hasNativeMethod('__get')) { - $readableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__get')->getVariants())->getReturnType(); - } else { - $readableType = new MixedType(); - } + return false; + } - if ($classReflection->hasNativeMethod('__set')) { - $writableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__set')->getVariants())->getParameters()[1]->getType(); - } else { - $writableType = new MixedType(); - } + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + if ($classReflection->hasNativeMethod('__get')) { + $readableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__get')->getVariants())->getReturnType(); + } else { + $readableType = new MixedType(); + } - return new UniversalObjectCrateProperty($classReflection, $readableType, $writableType); - } + if ($classReflection->hasNativeMethod('__set')) { + $writableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__set')->getVariants())->getParameters()[1]->getType(); + } else { + $writableType = new MixedType(); + } + return new UniversalObjectCrateProperty($classReflection, $readableType, $writableType); + } } diff --git a/src/Reflection/PropertiesClassReflectionExtension.php b/src/Reflection/PropertiesClassReflectionExtension.php index 7b8b6fcec2..adf9d4db59 100644 --- a/src/Reflection/PropertiesClassReflectionExtension.php +++ b/src/Reflection/PropertiesClassReflectionExtension.php @@ -1,12 +1,12 @@ -providers = $providers; - } - - public function hasClass(string $className): bool - { - foreach ($this->providers as $provider) { - if (!$provider->hasClass($className)) { - continue; - } - - return true; - } - - return false; - } - - public function getClass(string $className): ClassReflection - { - foreach ($this->providers as $provider) { - if (!$provider->hasClass($className)) { - continue; - } - - return $provider->getClass($className); - } - - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - public function getClassName(string $className): string - { - foreach ($this->providers as $provider) { - if (!$provider->hasClass($className)) { - continue; - } - - return $provider->getClassName($className); - } - - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - public function supportsAnonymousClasses(): bool - { - foreach ($this->providers as $provider) { - if (!$provider->supportsAnonymousClasses()) { - continue; - } - - return true; - } - - return false; - } - - public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - foreach ($this->providers as $provider) { - if (!$provider->supportsAnonymousClasses()) { - continue; - } - - return $provider->getAnonymousClassReflection($classNode, $scope); - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - foreach ($this->providers as $provider) { - if (!$provider->hasFunction($nameNode, $scope)) { - continue; - } - - return true; - } - - return false; - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection - { - foreach ($this->providers as $provider) { - if (!$provider->hasFunction($nameNode, $scope)) { - continue; - } - - return $provider->getFunction($nameNode, $scope); - } - - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - foreach ($this->providers as $provider) { - $resolvedName = $provider->resolveFunctionName($nameNode, $scope); - if ($resolvedName === null) { - continue; - } - - return $resolvedName; - } - - return null; - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - foreach ($this->providers as $provider) { - if (!$provider->hasConstant($nameNode, $scope)) { - continue; - } - - return true; - } - - return false; - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - foreach ($this->providers as $provider) { - if (!$provider->hasConstant($nameNode, $scope)) { - continue; - } - - return $provider->getConstant($nameNode, $scope); - } - - throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - foreach ($this->providers as $provider) { - $resolvedName = $provider->resolveConstantName($nameNode, $scope); - if ($resolvedName === null) { - continue; - } - - return $resolvedName; - } - - return null; - } - + /** @var \PHPStan\Reflection\ReflectionProvider[] */ + private array $providers; + + /** + * @param \PHPStan\Reflection\ReflectionProvider[] $providers + */ + public function __construct( + array $providers + ) { + $this->providers = $providers; + } + + public function hasClass(string $className): bool + { + foreach ($this->providers as $provider) { + if (!$provider->hasClass($className)) { + continue; + } + + return true; + } + + return false; + } + + public function getClass(string $className): ClassReflection + { + foreach ($this->providers as $provider) { + if (!$provider->hasClass($className)) { + continue; + } + + return $provider->getClass($className); + } + + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + public function getClassName(string $className): string + { + foreach ($this->providers as $provider) { + if (!$provider->hasClass($className)) { + continue; + } + + return $provider->getClassName($className); + } + + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + public function supportsAnonymousClasses(): bool + { + foreach ($this->providers as $provider) { + if (!$provider->supportsAnonymousClasses()) { + continue; + } + + return true; + } + + return false; + } + + public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection + { + foreach ($this->providers as $provider) { + if (!$provider->supportsAnonymousClasses()) { + continue; + } + + return $provider->getAnonymousClassReflection($classNode, $scope); + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + foreach ($this->providers as $provider) { + if (!$provider->hasFunction($nameNode, $scope)) { + continue; + } + + return true; + } + + return false; + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection + { + foreach ($this->providers as $provider) { + if (!$provider->hasFunction($nameNode, $scope)) { + continue; + } + + return $provider->getFunction($nameNode, $scope); + } + + throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + foreach ($this->providers as $provider) { + $resolvedName = $provider->resolveFunctionName($nameNode, $scope); + if ($resolvedName === null) { + continue; + } + + return $resolvedName; + } + + return null; + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + foreach ($this->providers as $provider) { + if (!$provider->hasConstant($nameNode, $scope)) { + continue; + } + + return true; + } + + return false; + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + foreach ($this->providers as $provider) { + if (!$provider->hasConstant($nameNode, $scope)) { + continue; + } + + return $provider->getConstant($nameNode, $scope); + } + + throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + foreach ($this->providers as $provider) { + $resolvedName = $provider->resolveConstantName($nameNode, $scope); + if ($resolvedName === null) { + continue; + } + + return $resolvedName; + } + + return null; + } } diff --git a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php index b3ace3ff3f..1749956d88 100644 --- a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; - $this->patterns = $patterns; - $this->singleReflectionInsteadOfFile = $singleReflectionInsteadOfFile; - } - - public function hasClass(string $className): bool - { - if ($this->isClassBlacklisted($className)) { - return false; - } - - $has = $this->reflectionProvider->hasClass($className); - if (!$has) { - return false; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ($this->singleReflectionInsteadOfFile !== null) { - if ($classReflection->getFileName() === $this->singleReflectionInsteadOfFile) { - return false; - } - } - - foreach ($classReflection->getParentClassesNames() as $parentClassName) { - if ($this->isClassBlacklisted($parentClassName)) { - return false; - } - } - - foreach ($classReflection->getNativeReflection()->getInterfaceNames() as $interfaceName) { - if ($this->isClassBlacklisted($interfaceName)) { - return false; - } - } - - return true; - } - - private function isClassBlacklisted(string $className): bool - { - if ($this->phpStormStubsSourceStubber->hasClass($className)) { - // check that userland class isn't aliased to the same name as a class from stubs - if (!class_exists($className, false)) { - return true; - } - if (in_array(strtolower($className), ['reflectionuniontype', 'attribute'], true)) { - return true; - } - $reflection = new \ReflectionClass($className); - if ($reflection->getFileName() === false) { - return true; - } - } - - foreach ($this->patterns as $pattern) { - if (Strings::match($className, $pattern) !== null) { - return true; - } - } - - return false; - } - - public function getClass(string $className): ClassReflection - { - if (!$this->hasClass($className)) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - return $this->reflectionProvider->getClass($className); - } - - public function getClassName(string $className): string - { - if (!$this->hasClass($className)) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - return $this->reflectionProvider->getClassName($className); - } - - public function supportsAnonymousClasses(): bool - { - return false; - } - - public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - $has = $this->reflectionProvider->hasFunction($nameNode, $scope); - if (!$has) { - return false; - } - - if ($this->singleReflectionInsteadOfFile === null) { - return true; - } - - $functionReflection = $this->reflectionProvider->getFunction($nameNode, $scope); - if (!$functionReflection instanceof ReflectionWithFilename) { - return true; - } - - return $functionReflection->getFileName() !== $this->singleReflectionInsteadOfFile; - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection - { - return $this->reflectionProvider->getFunction($nameNode, $scope); - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->reflectionProvider->resolveFunctionName($nameNode, $scope); - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->reflectionProvider->hasConstant($nameNode, $scope); - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - return $this->reflectionProvider->getConstant($nameNode, $scope); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->reflectionProvider->resolveConstantName($nameNode, $scope); - } - + private ReflectionProvider $reflectionProvider; + + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; + + /** @var string[] */ + private array $patterns; + + private ?string $singleReflectionInsteadOfFile; + + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param string[] $patterns + */ + public function __construct( + ReflectionProvider $reflectionProvider, + PhpStormStubsSourceStubber $phpStormStubsSourceStubber, + array $patterns, + ?string $singleReflectionInsteadOfFile + ) { + $this->reflectionProvider = $reflectionProvider; + $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; + $this->patterns = $patterns; + $this->singleReflectionInsteadOfFile = $singleReflectionInsteadOfFile; + } + + public function hasClass(string $className): bool + { + if ($this->isClassBlacklisted($className)) { + return false; + } + + $has = $this->reflectionProvider->hasClass($className); + if (!$has) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($this->singleReflectionInsteadOfFile !== null) { + if ($classReflection->getFileName() === $this->singleReflectionInsteadOfFile) { + return false; + } + } + + foreach ($classReflection->getParentClassesNames() as $parentClassName) { + if ($this->isClassBlacklisted($parentClassName)) { + return false; + } + } + + foreach ($classReflection->getNativeReflection()->getInterfaceNames() as $interfaceName) { + if ($this->isClassBlacklisted($interfaceName)) { + return false; + } + } + + return true; + } + + private function isClassBlacklisted(string $className): bool + { + if ($this->phpStormStubsSourceStubber->hasClass($className)) { + // check that userland class isn't aliased to the same name as a class from stubs + if (!class_exists($className, false)) { + return true; + } + if (in_array(strtolower($className), ['reflectionuniontype', 'attribute'], true)) { + return true; + } + $reflection = new \ReflectionClass($className); + if ($reflection->getFileName() === false) { + return true; + } + } + + foreach ($this->patterns as $pattern) { + if (Strings::match($className, $pattern) !== null) { + return true; + } + } + + return false; + } + + public function getClass(string $className): ClassReflection + { + if (!$this->hasClass($className)) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + return $this->reflectionProvider->getClass($className); + } + + public function getClassName(string $className): string + { + if (!$this->hasClass($className)) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + return $this->reflectionProvider->getClassName($className); + } + + public function supportsAnonymousClasses(): bool + { + return false; + } + + public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + $has = $this->reflectionProvider->hasFunction($nameNode, $scope); + if (!$has) { + return false; + } + + if ($this->singleReflectionInsteadOfFile === null) { + return true; + } + + $functionReflection = $this->reflectionProvider->getFunction($nameNode, $scope); + if (!$functionReflection instanceof ReflectionWithFilename) { + return true; + } + + return $functionReflection->getFileName() !== $this->singleReflectionInsteadOfFile; + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection + { + return $this->reflectionProvider->getFunction($nameNode, $scope); + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->reflectionProvider->resolveFunctionName($nameNode, $scope); + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->reflectionProvider->hasConstant($nameNode, $scope); + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + return $this->reflectionProvider->getConstant($nameNode, $scope); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->reflectionProvider->resolveConstantName($nameNode, $scope); + } } diff --git a/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php index 96b4dd2fe0..ed74da7bd2 100644 --- a/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getReflectionProvider(): ReflectionProvider - { - return $this->reflectionProvider; - } + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getReflectionProvider(): ReflectionProvider + { + return $this->reflectionProvider; + } } diff --git a/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php index b8c623995c..f4409503ed 100644 --- a/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php @@ -1,4 +1,6 @@ -container = $container; - } - - public function getReflectionProvider(): ReflectionProvider - { - return $this->container->getByType(ReflectionProvider::class); - } + public function __construct(Container $container) + { + $this->container = $container; + } + public function getReflectionProvider(): ReflectionProvider + { + return $this->container->getByType(ReflectionProvider::class); + } } diff --git a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php index 751cb5160d..252a5dcb33 100644 --- a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php @@ -1,4 +1,6 @@ - */ - private array $hasClasses = []; - - /** @var array */ - private array $classes = []; - - /** @var array */ - private array $classNames = []; - - public function __construct(ReflectionProvider $provider) - { - $this->provider = $provider; - } - - public function hasClass(string $className): bool - { - $lowerClassName = strtolower($className); - if (isset($this->hasClasses[$lowerClassName])) { - return $this->hasClasses[$lowerClassName]; - } - - return $this->hasClasses[$lowerClassName] = $this->provider->hasClass($className); - } - - public function getClass(string $className): ClassReflection - { - $lowerClassName = strtolower($className); - if (isset($this->classes[$lowerClassName])) { - return $this->classes[$lowerClassName]; - } - - return $this->classes[$lowerClassName] = $this->provider->getClass($className); - } - - public function getClassName(string $className): string - { - $lowerClassName = strtolower($className); - if (isset($this->classNames[$lowerClassName])) { - return $this->classNames[$lowerClassName]; - } - - return $this->classNames[$lowerClassName] = $this->provider->getClassName($className); - } - - public function supportsAnonymousClasses(): bool - { - return $this->provider->supportsAnonymousClasses(); - } - - public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - return $this->provider->getAnonymousClassReflection($classNode, $scope); - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->provider->hasFunction($nameNode, $scope); - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection - { - return $this->provider->getFunction($nameNode, $scope); - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->provider->resolveFunctionName($nameNode, $scope); - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->provider->hasConstant($nameNode, $scope); - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - return $this->provider->getConstant($nameNode, $scope); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->provider->resolveConstantName($nameNode, $scope); - } - + private \PHPStan\Reflection\ReflectionProvider $provider; + + /** @var array */ + private array $hasClasses = []; + + /** @var array */ + private array $classes = []; + + /** @var array */ + private array $classNames = []; + + public function __construct(ReflectionProvider $provider) + { + $this->provider = $provider; + } + + public function hasClass(string $className): bool + { + $lowerClassName = strtolower($className); + if (isset($this->hasClasses[$lowerClassName])) { + return $this->hasClasses[$lowerClassName]; + } + + return $this->hasClasses[$lowerClassName] = $this->provider->hasClass($className); + } + + public function getClass(string $className): ClassReflection + { + $lowerClassName = strtolower($className); + if (isset($this->classes[$lowerClassName])) { + return $this->classes[$lowerClassName]; + } + + return $this->classes[$lowerClassName] = $this->provider->getClass($className); + } + + public function getClassName(string $className): string + { + $lowerClassName = strtolower($className); + if (isset($this->classNames[$lowerClassName])) { + return $this->classNames[$lowerClassName]; + } + + return $this->classNames[$lowerClassName] = $this->provider->getClassName($className); + } + + public function supportsAnonymousClasses(): bool + { + return $this->provider->supportsAnonymousClasses(); + } + + public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection + { + return $this->provider->getAnonymousClassReflection($classNode, $scope); + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->provider->hasFunction($nameNode, $scope); + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection + { + return $this->provider->getFunction($nameNode, $scope); + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->provider->resolveFunctionName($nameNode, $scope); + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->provider->hasConstant($nameNode, $scope); + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + return $this->provider->getConstant($nameNode, $scope); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->provider->resolveConstantName($nameNode, $scope); + } } diff --git a/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php b/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php index cc0d3cb35e..78debd087d 100644 --- a/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php +++ b/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php @@ -1,4 +1,6 @@ -runtimeReflectionProvider = $runtimeReflectionProvider; - $this->staticReflectionProvider = $staticReflectionProvider; - $this->disableRuntimeReflectionProvider = $disableRuntimeReflectionProvider; - } + private bool $disableRuntimeReflectionProvider; - public function create(): ReflectionProvider - { - $providers = []; + public function __construct( + ReflectionProvider $runtimeReflectionProvider, + ReflectionProvider $staticReflectionProvider, + bool $disableRuntimeReflectionProvider + ) { + $this->runtimeReflectionProvider = $runtimeReflectionProvider; + $this->staticReflectionProvider = $staticReflectionProvider; + $this->disableRuntimeReflectionProvider = $disableRuntimeReflectionProvider; + } - if (!$this->disableRuntimeReflectionProvider) { - $providers[] = $this->runtimeReflectionProvider; - } + public function create(): ReflectionProvider + { + $providers = []; - $providers[] = $this->staticReflectionProvider; + if (!$this->disableRuntimeReflectionProvider) { + $providers[] = $this->runtimeReflectionProvider; + } - return new MemoizingReflectionProvider(count($providers) === 1 ? $providers[0] : new ChainReflectionProvider($providers)); - } + $providers[] = $this->staticReflectionProvider; + return new MemoizingReflectionProvider(count($providers) === 1 ? $providers[0] : new ChainReflectionProvider($providers)); + } } diff --git a/src/Reflection/ReflectionProvider/ReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/ReflectionProviderProvider.php index 059ff39320..b570759a75 100644 --- a/src/Reflection/ReflectionProvider/ReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/ReflectionProviderProvider.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getReflectionProvider(): ReflectionProvider - { - return $this->reflectionProvider; - } + public function setReflectionProvider(ReflectionProvider $reflectionProvider): void + { + $this->reflectionProvider = $reflectionProvider; + } + public function getReflectionProvider(): ReflectionProvider + { + return $this->reflectionProvider; + } } diff --git a/src/Reflection/ReflectionWithFilename.php b/src/Reflection/ReflectionWithFilename.php index 6971eb4873..705edcca01 100644 --- a/src/Reflection/ReflectionWithFilename.php +++ b/src/Reflection/ReflectionWithFilename.php @@ -1,13 +1,13 @@ -parametersAcceptor = $parametersAcceptor; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - } - - public function getOriginalParametersAcceptor(): ParametersAcceptor - { - return $this->parametersAcceptor; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->parametersAcceptor->getTemplateTypeMap(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return $this->resolvedTemplateTypeMap; - } - - public function getParameters(): array - { - $parameters = $this->parameters; - - if ($parameters === null) { - $parameters = array_map(function (ParameterReflection $param): ParameterReflection { - return new DummyParameter( - $param->getName(), - TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap), - $param->isOptional(), - $param->passedByReference(), - $param->isVariadic(), - $param->getDefaultValue() - ); - }, $this->parametersAcceptor->getParameters()); - - $this->parameters = $parameters; - } - - return $parameters; - } - - public function isVariadic(): bool - { - return $this->parametersAcceptor->isVariadic(); - } - - public function getReturnType(): Type - { - $type = $this->returnType; - - if ($type === null) { - $type = TemplateTypeHelper::resolveTemplateTypes( - $this->parametersAcceptor->getReturnType(), - $this->resolvedTemplateTypeMap - ); - - $this->returnType = $type; - } - - return $type; - } - + private ParametersAcceptor $parametersAcceptor; + + private TemplateTypeMap $resolvedTemplateTypeMap; + + /** @var ParameterReflection[]|null */ + private ?array $parameters = null; + + private ?Type $returnType = null; + + public function __construct( + ParametersAcceptor $parametersAcceptor, + TemplateTypeMap $resolvedTemplateTypeMap + ) { + $this->parametersAcceptor = $parametersAcceptor; + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; + } + + public function getOriginalParametersAcceptor(): ParametersAcceptor + { + return $this->parametersAcceptor; + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->parametersAcceptor->getTemplateTypeMap(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return $this->resolvedTemplateTypeMap; + } + + public function getParameters(): array + { + $parameters = $this->parameters; + + if ($parameters === null) { + $parameters = array_map(function (ParameterReflection $param): ParameterReflection { + return new DummyParameter( + $param->getName(), + TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap), + $param->isOptional(), + $param->passedByReference(), + $param->isVariadic(), + $param->getDefaultValue() + ); + }, $this->parametersAcceptor->getParameters()); + + $this->parameters = $parameters; + } + + return $parameters; + } + + public function isVariadic(): bool + { + return $this->parametersAcceptor->isVariadic(); + } + + public function getReturnType(): Type + { + $type = $this->returnType; + + if ($type === null) { + $type = TemplateTypeHelper::resolveTemplateTypes( + $this->parametersAcceptor->getReturnType(), + $this->resolvedTemplateTypeMap + ); + + $this->returnType = $type; + } + + return $type; + } } diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 77e03b7f4d..4ed425fc9a 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - } - - public function getName(): string - { - return $this->reflection->getName(); - } - - public function getPrototype(): ClassMemberReflection - { - return $this->reflection->getPrototype(); - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getVariants(): array - { - $variants = $this->variants; - if ($variants !== null) { - return $variants; - } - - $variants = []; - foreach ($this->reflection->getVariants() as $variant) { - $variants[] = new ResolvedFunctionVariant( - $variant, - $this->resolvedTemplateTypeMap - ); - } - - $this->variants = $variants; - - return $variants; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->reflection->getDeclaringClass(); - } - - public function getDeclaringTrait(): ?ClassReflection - { - if ($this->reflection instanceof PhpMethodReflection) { - return $this->reflection->getDeclaringTrait(); - } - - return null; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->reflection->getDocComment(); - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->reflection->getDeprecatedDescription(); - } - - public function isFinal(): TrinaryLogic - { - return $this->reflection->isFinal(); - } - - public function isInternal(): TrinaryLogic - { - return $this->reflection->isInternal(); - } - - public function getThrowType(): ?Type - { - return $this->reflection->getThrowType(); - } - - public function hasSideEffects(): TrinaryLogic - { - return $this->reflection->hasSideEffects(); - } - + private MethodReflection $reflection; + + private TemplateTypeMap $resolvedTemplateTypeMap; + + /** @var \PHPStan\Reflection\ParametersAcceptor[]|null */ + private ?array $variants = null; + + public function __construct(MethodReflection $reflection, TemplateTypeMap $resolvedTemplateTypeMap) + { + $this->reflection = $reflection; + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->reflection->getPrototype(); + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getVariants(): array + { + $variants = $this->variants; + if ($variants !== null) { + return $variants; + } + + $variants = []; + foreach ($this->reflection->getVariants() as $variant) { + $variants[] = new ResolvedFunctionVariant( + $variant, + $this->resolvedTemplateTypeMap + ); + } + + $this->variants = $variants; + + return $variants; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->reflection->getDeclaringClass(); + } + + public function getDeclaringTrait(): ?ClassReflection + { + if ($this->reflection instanceof PhpMethodReflection) { + return $this->reflection->getDeclaringTrait(); + } + + return null; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->reflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->reflection->hasSideEffects(); + } } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index f75c267964..8648e4e2c5 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -1,4 +1,6 @@ -reflection = $reflection; - $this->templateTypeMap = $templateTypeMap; - } - - public function getOriginalReflection(): PropertyReflection - { - return $this->reflection; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->reflection->getDeclaringClass(); - } - - public function getDeclaringTrait(): ?ClassReflection - { - if ($this->reflection instanceof PhpPropertyReflection) { - return $this->reflection->getDeclaringTrait(); - } - - return null; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function getReadableType(): Type - { - $type = $this->readableType; - if ($type !== null) { - return $type; - } - - $type = TemplateTypeHelper::resolveTemplateTypes( - $this->reflection->getReadableType(), - $this->templateTypeMap - ); - $type = TemplateTypeHelper::resolveTemplateTypes( - $type, - $this->templateTypeMap - ); - - $this->readableType = $type; - - return $type; - } - - public function getWritableType(): Type - { - $type = $this->writableType; - if ($type !== null) { - return $type; - } - - $type = TemplateTypeHelper::resolveTemplateTypes( - $this->reflection->getWritableType(), - $this->templateTypeMap - ); - $type = TemplateTypeHelper::resolveTemplateTypes( - $type, - $this->templateTypeMap - ); - - $this->writableType = $type; - - return $type; - } - - public function canChangeTypeAfterAssignment(): bool - { - return $this->reflection->canChangeTypeAfterAssignment(); - } - - public function isReadable(): bool - { - return $this->reflection->isReadable(); - } - - public function isWritable(): bool - { - return $this->reflection->isWritable(); - } - - public function getDocComment(): ?string - { - return $this->reflection->getDocComment(); - } - - public function isDeprecated(): TrinaryLogic - { - return $this->reflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->reflection->getDeprecatedDescription(); - } - - public function isInternal(): TrinaryLogic - { - return $this->reflection->isInternal(); - } - + private PropertyReflection $reflection; + + private TemplateTypeMap $templateTypeMap; + + private ?Type $readableType = null; + + private ?Type $writableType = null; + + public function __construct(PropertyReflection $reflection, TemplateTypeMap $templateTypeMap) + { + $this->reflection = $reflection; + $this->templateTypeMap = $templateTypeMap; + } + + public function getOriginalReflection(): PropertyReflection + { + return $this->reflection; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->reflection->getDeclaringClass(); + } + + public function getDeclaringTrait(): ?ClassReflection + { + if ($this->reflection instanceof PhpPropertyReflection) { + return $this->reflection->getDeclaringTrait(); + } + + return null; + } + + public function isStatic(): bool + { + return $this->reflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getReadableType(): Type + { + $type = $this->readableType; + if ($type !== null) { + return $type; + } + + $type = TemplateTypeHelper::resolveTemplateTypes( + $this->reflection->getReadableType(), + $this->templateTypeMap + ); + $type = TemplateTypeHelper::resolveTemplateTypes( + $type, + $this->templateTypeMap + ); + + $this->readableType = $type; + + return $type; + } + + public function getWritableType(): Type + { + $type = $this->writableType; + if ($type !== null) { + return $type; + } + + $type = TemplateTypeHelper::resolveTemplateTypes( + $this->reflection->getWritableType(), + $this->templateTypeMap + ); + $type = TemplateTypeHelper::resolveTemplateTypes( + $type, + $this->templateTypeMap + ); + + $this->writableType = $type; + + return $type; + } + + public function canChangeTypeAfterAssignment(): bool + { + return $this->reflection->canChangeTypeAfterAssignment(); + } + + public function isReadable(): bool + { + return $this->reflection->isReadable(); + } + + public function isWritable(): bool + { + return $this->reflection->isWritable(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } } diff --git a/src/Reflection/Runtime/RuntimeReflectionProvider.php b/src/Reflection/Runtime/RuntimeReflectionProvider.php index 41c57cd996..6028400631 100644 --- a/src/Reflection/Runtime/RuntimeReflectionProvider.php +++ b/src/Reflection/Runtime/RuntimeReflectionProvider.php @@ -1,4 +1,6 @@ -reflectionProviderProvider = $reflectionProviderProvider; - $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; - $this->functionReflectionFactory = $functionReflectionFactory; - $this->fileTypeMapper = $fileTypeMapper; - $this->phpVersion = $phpVersion; - $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; - $this->stubPhpDocProvider = $stubPhpDocProvider; - $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; - } - - public function getClass(string $className): \PHPStan\Reflection\ClassReflection - { - /** @var class-string $className */ - $className = $className; - if (!$this->hasClass($className)) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - if (isset(self::$anonymousClasses[$className])) { - return self::$anonymousClasses[$className]; - } - - if (!isset($this->classReflections[$className])) { - $reflectionClass = new ReflectionClass($className); - $filename = null; - if ($reflectionClass->getFileName() !== false) { - $filename = $reflectionClass->getFileName(); - } - - $classReflection = $this->getClassFromReflection( - $reflectionClass, - $reflectionClass->getName(), - $reflectionClass->isAnonymous() ? $filename : null - ); - $this->classReflections[$className] = $classReflection; - if ($className !== $reflectionClass->getName()) { - // class alias optimization - $this->classReflections[$reflectionClass->getName()] = $classReflection; - } - } - - return $this->classReflections[$className]; - } - - public function getClassName(string $className): string - { - if (!$this->hasClass($className)) { - throw new \PHPStan\Broker\ClassNotFoundException($className); - } - - /** @var class-string $className */ - $className = $className; - $reflectionClass = new ReflectionClass($className); - $realName = $reflectionClass->getName(); - - if (isset(self::$anonymousClasses[$realName])) { - return self::$anonymousClasses[$realName]->getDisplayName(); - } - - return $realName; - } - - public function supportsAnonymousClasses(): bool - { - return false; - } - - public function getAnonymousClassReflection( - \PhpParser\Node\Stmt\Class_ $classNode, - Scope $scope - ): ClassReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - /** - * @param \ReflectionClass $reflectionClass - * @param string $displayName - * @param string|null $anonymousFilename - */ - private function getClassFromReflection(\ReflectionClass $reflectionClass, string $displayName, ?string $anonymousFilename): ClassReflection - { - $className = $reflectionClass->getName(); - if (!isset($this->classReflections[$className])) { - $classReflection = new ClassReflection( - $this->reflectionProviderProvider->getReflectionProvider(), - $this->fileTypeMapper, - $this->phpVersion, - $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), - $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), - $displayName, - $reflectionClass, - $anonymousFilename, - null, - $this->stubPhpDocProvider->findClassPhpDoc($className) - ); - $this->classReflections[$className] = $classReflection; - } - - return $this->classReflections[$className]; - } - - public function hasClass(string $className): bool - { - $className = trim($className, '\\'); - if (isset($this->hasClassCache[$className])) { - return $this->hasClassCache[$className]; - } - - spl_autoload_register($autoloader = function (string $autoloadedClassName) use ($className): void { - $autoloadedClassName = trim($autoloadedClassName, '\\'); - if ($autoloadedClassName !== $className && !$this->isExistsCheckCall()) { - throw new \PHPStan\Broker\ClassAutoloadingException($autoloadedClassName); - } - }); - - try { - return $this->hasClassCache[$className] = class_exists($className) || interface_exists($className) || trait_exists($className); - } catch (\PHPStan\Broker\ClassAutoloadingException $e) { - throw $e; - } catch (\Throwable $t) { - throw new \PHPStan\Broker\ClassAutoloadingException( - $className, - $t - ); - } finally { - spl_autoload_unregister($autoloader); - } - } - - public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\FunctionReflection - { - $functionName = $this->resolveFunctionName($nameNode, $scope); - if ($functionName === null) { - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } - - $lowerCasedFunctionName = strtolower($functionName); - if (isset($this->functionReflections[$lowerCasedFunctionName])) { - return $this->functionReflections[$lowerCasedFunctionName]; - } - - $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); - if ($nativeFunctionReflection !== null) { - $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; - return $nativeFunctionReflection; - } - - $this->functionReflections[$lowerCasedFunctionName] = $this->getCustomFunction($nameNode, $scope); - - return $this->functionReflections[$lowerCasedFunctionName]; - } - - public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->resolveFunctionName($nameNode, $scope) !== null; - } - - private function hasCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - $functionName = $this->resolveFunctionName($nameNode, $scope); - if ($functionName === null) { - return false; - } - - return $this->nativeFunctionReflectionProvider->findFunctionReflection($functionName) === null; - } - - private function getCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\Php\PhpFunctionReflection - { - if (!$this->hasCustomFunction($nameNode, $scope)) { - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } - - /** @var string $functionName */ - $functionName = $this->resolveFunctionName($nameNode, $scope); - if (!function_exists($functionName)) { - throw new \PHPStan\Broker\FunctionNotFoundException($functionName); - } - $lowerCasedFunctionName = strtolower($functionName); - if (isset($this->customFunctionReflections[$lowerCasedFunctionName])) { - return $this->customFunctionReflections[$lowerCasedFunctionName]; - } - - $reflectionFunction = new \ReflectionFunction($functionName); - $templateTypeMap = TemplateTypeMap::createEmpty(); - $phpDocParameterTags = []; - $phpDocReturnTag = null; - $phpDocThrowsTag = null; - $deprecatedTag = null; - $isDeprecated = false; - $isInternal = false; - $isFinal = false; - $isPure = null; - $resolvedPhpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($reflectionFunction->getName()); - if ($resolvedPhpDoc === null && $reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) { - $fileName = $reflectionFunction->getFileName(); - $docComment = $reflectionFunction->getDocComment(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); - } - - if ($resolvedPhpDoc !== null) { - $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); - $phpDocParameterTags = $resolvedPhpDoc->getParamTags(); - $phpDocReturnTag = $resolvedPhpDoc->getReturnTag(); - $phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag(); - $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $isPure = $resolvedPhpDoc->isPure(); - } - - $functionReflection = $this->functionReflectionFactory->create( - $reflectionFunction, - $templateTypeMap, - array_map(static function (ParamTag $paramTag): Type { - return $paramTag->getType(); - }, $phpDocParameterTags), - $phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null, - $phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null, - $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, - $isDeprecated, - $isInternal, - $isFinal, - $reflectionFunction->getFileName(), - $isPure - ); - $this->customFunctionReflections[$lowerCasedFunctionName] = $functionReflection; - - return $functionReflection; - } - - public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->resolveName($nameNode, function (string $name): bool { - $exists = function_exists($name) || $this->nativeFunctionReflectionProvider->findFunctionReflection($name) !== null; - if ($exists) { - if ($this->phpStormStubsSourceStubber->isPresentFunction($name) === false) { - return false; - } - - return true; - } - - return false; - }, $scope); - } - - public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool - { - return $this->resolveConstantName($nameNode, $scope) !== null; - } - - public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection - { - $constantName = $this->resolveConstantName($nameNode, $scope); - if ($constantName === null) { - throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); - } - - return new RuntimeConstantReflection( - $constantName, - ConstantTypeHelper::getTypeFromValue(constant($constantName)), - null - ); - } - - public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string - { - return $this->resolveName($nameNode, static function (string $name): bool { - return defined($name); - }, $scope); - } - - /** - * @param Node\Name $nameNode - * @param \Closure(string $name): bool $existsCallback - * @param Scope|null $scope - * @return string|null - */ - private function resolveName( - \PhpParser\Node\Name $nameNode, - \Closure $existsCallback, - ?Scope $scope - ): ?string - { - $name = (string) $nameNode; - if ($scope !== null && $scope->getNamespace() !== null && !$nameNode->isFullyQualified()) { - $namespacedName = sprintf('%s\\%s', $scope->getNamespace(), $name); - if ($existsCallback($namespacedName)) { - return $namespacedName; - } - } - - if ($existsCallback($name)) { - return $name; - } - - return null; - } - - private function isExistsCheckCall(): bool - { - $debugBacktrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - $existsCallTypes = [ - 'class_exists' => true, - 'interface_exists' => true, - 'trait_exists' => true, - ]; - - foreach ($debugBacktrace as $traceStep) { - if ( - isset($traceStep['function']) - && isset($existsCallTypes[$traceStep['function']]) - // We must ignore the self::hasClass calls - && (!isset($traceStep['file']) || $traceStep['file'] !== __FILE__) - ) { - return true; - } - } - - return false; - } - + private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider; + + private ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider; + + /** @var \PHPStan\Reflection\ClassReflection[] */ + private array $classReflections = []; + + private \PHPStan\Reflection\FunctionReflectionFactory $functionReflectionFactory; + + private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + + private PhpVersion $phpVersion; + + private \PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider $nativeFunctionReflectionProvider; + + private StubPhpDocProvider $stubPhpDocProvider; + + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; + + /** @var \PHPStan\Reflection\FunctionReflection[] */ + private array $functionReflections = []; + + /** @var \PHPStan\Reflection\Php\PhpFunctionReflection[] */ + private array $customFunctionReflections = []; + + /** @var bool[] */ + private array $hasClassCache = []; + + /** @var \PHPStan\Reflection\ClassReflection[] */ + private static array $anonymousClasses = []; + + public function __construct( + ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, + ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, + FunctionReflectionFactory $functionReflectionFactory, + FileTypeMapper $fileTypeMapper, + PhpVersion $phpVersion, + NativeFunctionReflectionProvider $nativeFunctionReflectionProvider, + StubPhpDocProvider $stubPhpDocProvider, + PhpStormStubsSourceStubber $phpStormStubsSourceStubber + ) { + $this->reflectionProviderProvider = $reflectionProviderProvider; + $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; + $this->functionReflectionFactory = $functionReflectionFactory; + $this->fileTypeMapper = $fileTypeMapper; + $this->phpVersion = $phpVersion; + $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; + $this->stubPhpDocProvider = $stubPhpDocProvider; + $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; + } + + public function getClass(string $className): \PHPStan\Reflection\ClassReflection + { + /** @var class-string $className */ + $className = $className; + if (!$this->hasClass($className)) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + if (isset(self::$anonymousClasses[$className])) { + return self::$anonymousClasses[$className]; + } + + if (!isset($this->classReflections[$className])) { + $reflectionClass = new ReflectionClass($className); + $filename = null; + if ($reflectionClass->getFileName() !== false) { + $filename = $reflectionClass->getFileName(); + } + + $classReflection = $this->getClassFromReflection( + $reflectionClass, + $reflectionClass->getName(), + $reflectionClass->isAnonymous() ? $filename : null + ); + $this->classReflections[$className] = $classReflection; + if ($className !== $reflectionClass->getName()) { + // class alias optimization + $this->classReflections[$reflectionClass->getName()] = $classReflection; + } + } + + return $this->classReflections[$className]; + } + + public function getClassName(string $className): string + { + if (!$this->hasClass($className)) { + throw new \PHPStan\Broker\ClassNotFoundException($className); + } + + /** @var class-string $className */ + $className = $className; + $reflectionClass = new ReflectionClass($className); + $realName = $reflectionClass->getName(); + + if (isset(self::$anonymousClasses[$realName])) { + return self::$anonymousClasses[$realName]->getDisplayName(); + } + + return $realName; + } + + public function supportsAnonymousClasses(): bool + { + return false; + } + + public function getAnonymousClassReflection( + \PhpParser\Node\Stmt\Class_ $classNode, + Scope $scope + ): ClassReflection { + throw new \PHPStan\ShouldNotHappenException(); + } + + /** + * @param \ReflectionClass $reflectionClass + * @param string $displayName + * @param string|null $anonymousFilename + */ + private function getClassFromReflection(\ReflectionClass $reflectionClass, string $displayName, ?string $anonymousFilename): ClassReflection + { + $className = $reflectionClass->getName(); + if (!isset($this->classReflections[$className])) { + $classReflection = new ClassReflection( + $this->reflectionProviderProvider->getReflectionProvider(), + $this->fileTypeMapper, + $this->phpVersion, + $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $displayName, + $reflectionClass, + $anonymousFilename, + null, + $this->stubPhpDocProvider->findClassPhpDoc($className) + ); + $this->classReflections[$className] = $classReflection; + } + + return $this->classReflections[$className]; + } + + public function hasClass(string $className): bool + { + $className = trim($className, '\\'); + if (isset($this->hasClassCache[$className])) { + return $this->hasClassCache[$className]; + } + + spl_autoload_register($autoloader = function (string $autoloadedClassName) use ($className): void { + $autoloadedClassName = trim($autoloadedClassName, '\\'); + if ($autoloadedClassName !== $className && !$this->isExistsCheckCall()) { + throw new \PHPStan\Broker\ClassAutoloadingException($autoloadedClassName); + } + }); + + try { + return $this->hasClassCache[$className] = class_exists($className) || interface_exists($className) || trait_exists($className); + } catch (\PHPStan\Broker\ClassAutoloadingException $e) { + throw $e; + } catch (\Throwable $t) { + throw new \PHPStan\Broker\ClassAutoloadingException( + $className, + $t + ); + } finally { + spl_autoload_unregister($autoloader); + } + } + + public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\FunctionReflection + { + $functionName = $this->resolveFunctionName($nameNode, $scope); + if ($functionName === null) { + throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); + } + + $lowerCasedFunctionName = strtolower($functionName); + if (isset($this->functionReflections[$lowerCasedFunctionName])) { + return $this->functionReflections[$lowerCasedFunctionName]; + } + + $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); + if ($nativeFunctionReflection !== null) { + $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; + return $nativeFunctionReflection; + } + + $this->functionReflections[$lowerCasedFunctionName] = $this->getCustomFunction($nameNode, $scope); + + return $this->functionReflections[$lowerCasedFunctionName]; + } + + public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->resolveFunctionName($nameNode, $scope) !== null; + } + + private function hasCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + $functionName = $this->resolveFunctionName($nameNode, $scope); + if ($functionName === null) { + return false; + } + + return $this->nativeFunctionReflectionProvider->findFunctionReflection($functionName) === null; + } + + private function getCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\Php\PhpFunctionReflection + { + if (!$this->hasCustomFunction($nameNode, $scope)) { + throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); + } + + /** @var string $functionName */ + $functionName = $this->resolveFunctionName($nameNode, $scope); + if (!function_exists($functionName)) { + throw new \PHPStan\Broker\FunctionNotFoundException($functionName); + } + $lowerCasedFunctionName = strtolower($functionName); + if (isset($this->customFunctionReflections[$lowerCasedFunctionName])) { + return $this->customFunctionReflections[$lowerCasedFunctionName]; + } + + $reflectionFunction = new \ReflectionFunction($functionName); + $templateTypeMap = TemplateTypeMap::createEmpty(); + $phpDocParameterTags = []; + $phpDocReturnTag = null; + $phpDocThrowsTag = null; + $deprecatedTag = null; + $isDeprecated = false; + $isInternal = false; + $isFinal = false; + $isPure = null; + $resolvedPhpDoc = $this->stubPhpDocProvider->findFunctionPhpDoc($reflectionFunction->getName()); + if ($resolvedPhpDoc === null && $reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) { + $fileName = $reflectionFunction->getFileName(); + $docComment = $reflectionFunction->getDocComment(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); + } + + if ($resolvedPhpDoc !== null) { + $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); + $phpDocParameterTags = $resolvedPhpDoc->getParamTags(); + $phpDocReturnTag = $resolvedPhpDoc->getReturnTag(); + $phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag(); + $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); + $isPure = $resolvedPhpDoc->isPure(); + } + + $functionReflection = $this->functionReflectionFactory->create( + $reflectionFunction, + $templateTypeMap, + array_map(static function (ParamTag $paramTag): Type { + return $paramTag->getType(); + }, $phpDocParameterTags), + $phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null, + $phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null, + $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, + $isDeprecated, + $isInternal, + $isFinal, + $reflectionFunction->getFileName(), + $isPure + ); + $this->customFunctionReflections[$lowerCasedFunctionName] = $functionReflection; + + return $functionReflection; + } + + public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->resolveName($nameNode, function (string $name): bool { + $exists = function_exists($name) || $this->nativeFunctionReflectionProvider->findFunctionReflection($name) !== null; + if ($exists) { + if ($this->phpStormStubsSourceStubber->isPresentFunction($name) === false) { + return false; + } + + return true; + } + + return false; + }, $scope); + } + + public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool + { + return $this->resolveConstantName($nameNode, $scope) !== null; + } + + public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection + { + $constantName = $this->resolveConstantName($nameNode, $scope); + if ($constantName === null) { + throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); + } + + return new RuntimeConstantReflection( + $constantName, + ConstantTypeHelper::getTypeFromValue(constant($constantName)), + null + ); + } + + public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string + { + return $this->resolveName($nameNode, static function (string $name): bool { + return defined($name); + }, $scope); + } + + /** + * @param Node\Name $nameNode + * @param \Closure(string $name): bool $existsCallback + * @param Scope|null $scope + * @return string|null + */ + private function resolveName( + \PhpParser\Node\Name $nameNode, + \Closure $existsCallback, + ?Scope $scope + ): ?string { + $name = (string) $nameNode; + if ($scope !== null && $scope->getNamespace() !== null && !$nameNode->isFullyQualified()) { + $namespacedName = sprintf('%s\\%s', $scope->getNamespace(), $name); + if ($existsCallback($namespacedName)) { + return $namespacedName; + } + } + + if ($existsCallback($name)) { + return $name; + } + + return null; + } + + private function isExistsCheckCall(): bool + { + $debugBacktrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $existsCallTypes = [ + 'class_exists' => true, + 'interface_exists' => true, + 'trait_exists' => true, + ]; + + foreach ($debugBacktrace as $traceStep) { + if ( + isset($traceStep['function']) + && isset($existsCallTypes[$traceStep['function']]) + // We must ignore the self::hasClass calls + && (!isset($traceStep['file']) || $traceStep['file'] !== __FILE__) + ) { + return true; + } + } + + return false; + } } diff --git a/src/Reflection/SignatureMap/FunctionSignature.php b/src/Reflection/SignatureMap/FunctionSignature.php index 2068335c5f..1587bb9afa 100644 --- a/src/Reflection/SignatureMap/FunctionSignature.php +++ b/src/Reflection/SignatureMap/FunctionSignature.php @@ -1,4 +1,6 @@ - $parameters - * @param \PHPStan\Type\Type $returnType - * @param \PHPStan\Type\Type $nativeReturnType - * @param bool $variadic - */ - public function __construct( - array $parameters, - Type $returnType, - Type $nativeReturnType, - bool $variadic - ) - { - $this->parameters = $parameters; - $this->returnType = $returnType; - $this->nativeReturnType = $nativeReturnType; - $this->variadic = $variadic; - } - - /** - * @return array - */ - public function getParameters(): array - { - return $this->parameters; - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - public function getNativeReturnType(): Type - { - return $this->nativeReturnType; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - + /** @var \PHPStan\Reflection\SignatureMap\ParameterSignature[] */ + private array $parameters; + + private \PHPStan\Type\Type $returnType; + + private \PHPStan\Type\Type $nativeReturnType; + + private bool $variadic; + + /** + * @param array $parameters + * @param \PHPStan\Type\Type $returnType + * @param \PHPStan\Type\Type $nativeReturnType + * @param bool $variadic + */ + public function __construct( + array $parameters, + Type $returnType, + Type $nativeReturnType, + bool $variadic + ) { + $this->parameters = $parameters; + $this->returnType = $returnType; + $this->nativeReturnType = $nativeReturnType; + $this->variadic = $variadic; + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function getNativeReturnType(): Type + { + return $this->nativeReturnType; + } + + public function isVariadic(): bool + { + return $this->variadic; + } } diff --git a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php index 0cdb5b1a21..e334969715 100644 --- a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php +++ b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php @@ -1,4 +1,6 @@ -|null */ - private ?array $functionMetadata = null; - - public function __construct(SignatureMapParser $parser, PhpVersion $phpVersion) - { - $this->parser = $parser; - $this->phpVersion = $phpVersion; - } - - public function hasMethodSignature(string $className, string $methodName, int $variant = 0): bool - { - return $this->hasFunctionSignature(sprintf('%s::%s', $className, $methodName), $variant); - } - - public function hasFunctionSignature(string $name, int $variant = 0): bool - { - $signatureMap = $this->getSignatureMap(); - if ($variant > 0) { - $name .= '\'' . $variant; - } - return array_key_exists(strtolower($name), $signatureMap); - } - - public function getMethodSignature(string $className, string $methodName, ?\ReflectionMethod $reflectionMethod, int $variant = 0): FunctionSignature - { - $signature = $this->getFunctionSignature(sprintf('%s::%s', $className, $methodName), $className, $variant); - $parameters = []; - foreach ($signature->getParameters() as $i => $parameter) { - if ($reflectionMethod === null) { - $parameters[] = $parameter; - continue; - } - $nativeParameters = $reflectionMethod->getParameters(); - if (!array_key_exists($i, $nativeParameters)) { - $parameters[] = $parameter; - continue; - } - - $parameters[] = new ParameterSignature( - $parameter->getName(), - $parameter->isOptional(), - $parameter->getType(), - TypehintHelper::decideTypeFromReflection($nativeParameters[$i]->getType()), - $parameter->passedByReference(), - $parameter->isVariadic() - ); - } - - if ($reflectionMethod === null) { - $nativeReturnType = new MixedType(); - } else { - $nativeReturnType = TypehintHelper::decideTypeFromReflection($reflectionMethod->getReturnType()); - } - - return new FunctionSignature( - $parameters, - $signature->getReturnType(), - $nativeReturnType, - $signature->isVariadic() - ); - } - - public function getFunctionSignature(string $functionName, ?string $className, int $variant = 0): FunctionSignature - { - $functionName = strtolower($functionName); - if ($variant > 0) { - $functionName .= '\'' . $variant; - } - - if (!$this->hasFunctionSignature($functionName)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $signatureMap = self::getSignatureMap(); - - return $this->parser->getFunctionSignature( - $signatureMap[$functionName], - $className - ); - } - - public function hasMethodMetadata(string $className, string $methodName): bool - { - return $this->hasFunctionMetadata(sprintf('%s::%s', $className, $methodName)); - } - - public function hasFunctionMetadata(string $name): bool - { - $signatureMap = $this->getFunctionMetadataMap(); - return array_key_exists(strtolower($name), $signatureMap); - } - - /** - * @param string $className - * @param string $methodName - * @return array{hasSideEffects: bool} - */ - public function getMethodMetadata(string $className, string $methodName): array - { - return $this->getFunctionMetadata(sprintf('%s::%s', $className, $methodName)); - } - - /** - * @param string $functionName - * @return array{hasSideEffects: bool} - */ - public function getFunctionMetadata(string $functionName): array - { - $functionName = strtolower($functionName); - - if (!$this->hasFunctionMetadata($functionName)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->getFunctionMetadataMap()[$functionName]; - } - - /** - * @return array - */ - private function getFunctionMetadataMap(): array - { - if ($this->functionMetadata === null) { - /** @var array $metadata */ - $metadata = require __DIR__ . '/../../../resources/functionMetadata.php'; - $this->functionMetadata = array_change_key_case($metadata, CASE_LOWER); - } - - return $this->functionMetadata; - } - - /** - * @return mixed[] - */ - public function getSignatureMap(): array - { - if ($this->signatureMap === null) { - $signatureMap = require __DIR__ . '/../../../resources/functionMap.php'; - if (!is_array($signatureMap)) { - throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); - } - - $signatureMap = array_change_key_case($signatureMap, CASE_LOWER); - - if ($this->phpVersion->getVersionId() >= 70400) { - $php74MapDelta = require __DIR__ . '/../../../resources/functionMap_php74delta.php'; - if (!is_array($php74MapDelta)) { - throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); - } - - $signatureMap = $this->computeSignatureMap($signatureMap, $php74MapDelta); - } - - if ($this->phpVersion->getVersionId() >= 80000) { - $php80MapDelta = require __DIR__ . '/../../../resources/functionMap_php80delta.php'; - if (!is_array($php80MapDelta)) { - throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); - } - - $signatureMap = $this->computeSignatureMap($signatureMap, $php80MapDelta); - } - - $this->signatureMap = $signatureMap; - } - - return $this->signatureMap; - } - - /** - * @param array $signatureMap - * @param array> $delta - * @return array - */ - private function computeSignatureMap(array $signatureMap, array $delta): array - { - foreach (array_keys($delta['old']) as $key) { - unset($signatureMap[strtolower($key)]); - } - foreach ($delta['new'] as $key => $signature) { - $signatureMap[strtolower($key)] = $signature; - } - - return $signatureMap; - } - + private \PHPStan\Reflection\SignatureMap\SignatureMapParser $parser; + + private PhpVersion $phpVersion; + + /** @var mixed[]|null */ + private ?array $signatureMap = null; + + /** @var array|null */ + private ?array $functionMetadata = null; + + public function __construct(SignatureMapParser $parser, PhpVersion $phpVersion) + { + $this->parser = $parser; + $this->phpVersion = $phpVersion; + } + + public function hasMethodSignature(string $className, string $methodName, int $variant = 0): bool + { + return $this->hasFunctionSignature(sprintf('%s::%s', $className, $methodName), $variant); + } + + public function hasFunctionSignature(string $name, int $variant = 0): bool + { + $signatureMap = $this->getSignatureMap(); + if ($variant > 0) { + $name .= '\'' . $variant; + } + return array_key_exists(strtolower($name), $signatureMap); + } + + public function getMethodSignature(string $className, string $methodName, ?\ReflectionMethod $reflectionMethod, int $variant = 0): FunctionSignature + { + $signature = $this->getFunctionSignature(sprintf('%s::%s', $className, $methodName), $className, $variant); + $parameters = []; + foreach ($signature->getParameters() as $i => $parameter) { + if ($reflectionMethod === null) { + $parameters[] = $parameter; + continue; + } + $nativeParameters = $reflectionMethod->getParameters(); + if (!array_key_exists($i, $nativeParameters)) { + $parameters[] = $parameter; + continue; + } + + $parameters[] = new ParameterSignature( + $parameter->getName(), + $parameter->isOptional(), + $parameter->getType(), + TypehintHelper::decideTypeFromReflection($nativeParameters[$i]->getType()), + $parameter->passedByReference(), + $parameter->isVariadic() + ); + } + + if ($reflectionMethod === null) { + $nativeReturnType = new MixedType(); + } else { + $nativeReturnType = TypehintHelper::decideTypeFromReflection($reflectionMethod->getReturnType()); + } + + return new FunctionSignature( + $parameters, + $signature->getReturnType(), + $nativeReturnType, + $signature->isVariadic() + ); + } + + public function getFunctionSignature(string $functionName, ?string $className, int $variant = 0): FunctionSignature + { + $functionName = strtolower($functionName); + if ($variant > 0) { + $functionName .= '\'' . $variant; + } + + if (!$this->hasFunctionSignature($functionName)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $signatureMap = self::getSignatureMap(); + + return $this->parser->getFunctionSignature( + $signatureMap[$functionName], + $className + ); + } + + public function hasMethodMetadata(string $className, string $methodName): bool + { + return $this->hasFunctionMetadata(sprintf('%s::%s', $className, $methodName)); + } + + public function hasFunctionMetadata(string $name): bool + { + $signatureMap = $this->getFunctionMetadataMap(); + return array_key_exists(strtolower($name), $signatureMap); + } + + /** + * @param string $className + * @param string $methodName + * @return array{hasSideEffects: bool} + */ + public function getMethodMetadata(string $className, string $methodName): array + { + return $this->getFunctionMetadata(sprintf('%s::%s', $className, $methodName)); + } + + /** + * @param string $functionName + * @return array{hasSideEffects: bool} + */ + public function getFunctionMetadata(string $functionName): array + { + $functionName = strtolower($functionName); + + if (!$this->hasFunctionMetadata($functionName)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->getFunctionMetadataMap()[$functionName]; + } + + /** + * @return array + */ + private function getFunctionMetadataMap(): array + { + if ($this->functionMetadata === null) { + /** @var array $metadata */ + $metadata = require __DIR__ . '/../../../resources/functionMetadata.php'; + $this->functionMetadata = array_change_key_case($metadata, CASE_LOWER); + } + + return $this->functionMetadata; + } + + /** + * @return mixed[] + */ + public function getSignatureMap(): array + { + if ($this->signatureMap === null) { + $signatureMap = require __DIR__ . '/../../../resources/functionMap.php'; + if (!is_array($signatureMap)) { + throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); + } + + $signatureMap = array_change_key_case($signatureMap, CASE_LOWER); + + if ($this->phpVersion->getVersionId() >= 70400) { + $php74MapDelta = require __DIR__ . '/../../../resources/functionMap_php74delta.php'; + if (!is_array($php74MapDelta)) { + throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); + } + + $signatureMap = $this->computeSignatureMap($signatureMap, $php74MapDelta); + } + + if ($this->phpVersion->getVersionId() >= 80000) { + $php80MapDelta = require __DIR__ . '/../../../resources/functionMap_php80delta.php'; + if (!is_array($php80MapDelta)) { + throw new \PHPStan\ShouldNotHappenException('Signature map could not be loaded.'); + } + + $signatureMap = $this->computeSignatureMap($signatureMap, $php80MapDelta); + } + + $this->signatureMap = $signatureMap; + } + + return $this->signatureMap; + } + + /** + * @param array $signatureMap + * @param array> $delta + * @return array + */ + private function computeSignatureMap(array $signatureMap, array $delta): array + { + foreach (array_keys($delta['old']) as $key) { + unset($signatureMap[strtolower($key)]); + } + foreach ($delta['new'] as $key => $signature) { + $signatureMap[strtolower($key)] = $signature; + } + + return $signatureMap; + } } diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 7d0b57536b..5d3a23d31d 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -1,4 +1,6 @@ -signatureMapProvider = $signatureMapProvider; - $this->functionReflector = $functionReflector; - $this->fileTypeMapper = $fileTypeMapper; - } - - public function findFunctionReflection(string $functionName): ?NativeFunctionReflection - { - $lowerCasedFunctionName = strtolower($functionName); - if (isset(self::$functionMap[$lowerCasedFunctionName])) { - return self::$functionMap[$lowerCasedFunctionName]; - } - - if (!$this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName)) { - return null; - } - - $variants = []; - $i = 0; - while ($this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName, $i)) { - $functionSignature = $this->signatureMapProvider->getFunctionSignature($lowerCasedFunctionName, null, $i); - $returnType = $functionSignature->getReturnType(); - $variants[] = new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - array_map(static function (ParameterSignature $parameterSignature) use ($lowerCasedFunctionName): NativeParameterReflection { - $type = $parameterSignature->getType(); - if ( - $parameterSignature->getName() === 'values' - && ( - $lowerCasedFunctionName === 'printf' - || $lowerCasedFunctionName === 'sprintf' - ) - ) { - $type = new UnionType([ - new StringAlwaysAcceptingObjectWithToStringType(), - new IntegerType(), - new FloatType(), - new NullType(), - new BooleanType(), - ]); - } - - if ( - $parameterSignature->getName() === 'fields' - && $lowerCasedFunctionName === 'fputcsv' - ) { - $type = new ArrayType( - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new UnionType([ - new StringAlwaysAcceptingObjectWithToStringType(), - new IntegerType(), - new FloatType(), - new NullType(), - new BooleanType(), - ]) - ); - } - - return new NativeParameterReflection( - $parameterSignature->getName(), - $parameterSignature->isOptional(), - $type, - $parameterSignature->passedByReference(), - $parameterSignature->isVariadic(), - null - ); - }, $functionSignature->getParameters()), - $functionSignature->isVariadic(), - $returnType - ); - - $i++; - } - - if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) { - $hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects']); - } else { - $hasSideEffects = TrinaryLogic::createMaybe(); - } - - $throwType = null; - try { - $reflectionFunction = $this->functionReflector->reflect($functionName); - if ($reflectionFunction->getFileName() !== null) { - $fileName = $reflectionFunction->getFileName(); - $docComment = $reflectionFunction->getDocComment(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); - $throwsTag = $resolvedPhpDoc->getThrowsTag(); - if ($throwsTag !== null) { - $throwType = $throwsTag->getType(); - } - } - } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { - // pass - } catch (InvalidIdentifierName $e) { - // pass - } - - $functionReflection = new NativeFunctionReflection( - $lowerCasedFunctionName, - $variants, - $throwType, - $hasSideEffects - ); - self::$functionMap[$lowerCasedFunctionName] = $functionReflection; - - return $functionReflection; - } - + /** @var NativeFunctionReflection[] */ + private static array $functionMap = []; + + private \PHPStan\Reflection\SignatureMap\SignatureMapProvider $signatureMapProvider; + + private \PHPStan\BetterReflection\Reflector\FunctionReflector $functionReflector; + + private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + + public function __construct(SignatureMapProvider $signatureMapProvider, FunctionReflector $functionReflector, FileTypeMapper $fileTypeMapper) + { + $this->signatureMapProvider = $signatureMapProvider; + $this->functionReflector = $functionReflector; + $this->fileTypeMapper = $fileTypeMapper; + } + + public function findFunctionReflection(string $functionName): ?NativeFunctionReflection + { + $lowerCasedFunctionName = strtolower($functionName); + if (isset(self::$functionMap[$lowerCasedFunctionName])) { + return self::$functionMap[$lowerCasedFunctionName]; + } + + if (!$this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName)) { + return null; + } + + $variants = []; + $i = 0; + while ($this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName, $i)) { + $functionSignature = $this->signatureMapProvider->getFunctionSignature($lowerCasedFunctionName, null, $i); + $returnType = $functionSignature->getReturnType(); + $variants[] = new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + array_map(static function (ParameterSignature $parameterSignature) use ($lowerCasedFunctionName): NativeParameterReflection { + $type = $parameterSignature->getType(); + if ( + $parameterSignature->getName() === 'values' + && ( + $lowerCasedFunctionName === 'printf' + || $lowerCasedFunctionName === 'sprintf' + ) + ) { + $type = new UnionType([ + new StringAlwaysAcceptingObjectWithToStringType(), + new IntegerType(), + new FloatType(), + new NullType(), + new BooleanType(), + ]); + } + + if ( + $parameterSignature->getName() === 'fields' + && $lowerCasedFunctionName === 'fputcsv' + ) { + $type = new ArrayType( + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new UnionType([ + new StringAlwaysAcceptingObjectWithToStringType(), + new IntegerType(), + new FloatType(), + new NullType(), + new BooleanType(), + ]) + ); + } + + return new NativeParameterReflection( + $parameterSignature->getName(), + $parameterSignature->isOptional(), + $type, + $parameterSignature->passedByReference(), + $parameterSignature->isVariadic(), + null + ); + }, $functionSignature->getParameters()), + $functionSignature->isVariadic(), + $returnType + ); + + $i++; + } + + if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) { + $hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects']); + } else { + $hasSideEffects = TrinaryLogic::createMaybe(); + } + + $throwType = null; + try { + $reflectionFunction = $this->functionReflector->reflect($functionName); + if ($reflectionFunction->getFileName() !== null) { + $fileName = $reflectionFunction->getFileName(); + $docComment = $reflectionFunction->getDocComment(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); + $throwsTag = $resolvedPhpDoc->getThrowsTag(); + if ($throwsTag !== null) { + $throwType = $throwsTag->getType(); + } + } + } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { + // pass + } catch (InvalidIdentifierName $e) { + // pass + } + + $functionReflection = new NativeFunctionReflection( + $lowerCasedFunctionName, + $variants, + $throwType, + $hasSideEffects + ); + self::$functionMap[$lowerCasedFunctionName] = $functionReflection; + + return $functionReflection; + } } diff --git a/src/Reflection/SignatureMap/ParameterSignature.php b/src/Reflection/SignatureMap/ParameterSignature.php index d4fc818292..e48526ecdf 100644 --- a/src/Reflection/SignatureMap/ParameterSignature.php +++ b/src/Reflection/SignatureMap/ParameterSignature.php @@ -1,4 +1,6 @@ -name = $name; - $this->optional = $optional; - $this->type = $type; - $this->nativeType = $nativeType; - $this->passedByReference = $passedByReference; - $this->variadic = $variadic; - } - - public function getName(): string - { - return $this->name; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function getType(): Type - { - return $this->type; - } - - public function getNativeType(): Type - { - return $this->nativeType; - } - - public function passedByReference(): PassedByReference - { - return $this->passedByReference; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - + private string $name; + + private bool $optional; + + private \PHPStan\Type\Type $type; + + private \PHPStan\Type\Type $nativeType; + + private \PHPStan\Reflection\PassedByReference $passedByReference; + + private bool $variadic; + + public function __construct( + string $name, + bool $optional, + Type $type, + Type $nativeType, + PassedByReference $passedByReference, + bool $variadic + ) { + $this->name = $name; + $this->optional = $optional; + $this->type = $type; + $this->nativeType = $nativeType; + $this->passedByReference = $passedByReference; + $this->variadic = $variadic; + } + + public function getName(): string + { + return $this->name; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function getType(): Type + { + return $this->type; + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + + public function passedByReference(): PassedByReference + { + return $this->passedByReference; + } + + public function isVariadic(): bool + { + return $this->variadic; + } } diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 86be50426a..2de3b44b13 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -1,4 +1,6 @@ -> */ - private array $methodNodes = []; - - public function __construct( - FunctionSignatureMapProvider $functionSignatureMapProvider, - FileNodesFetcher $fileNodesFetcher, - FileTypeMapper $fileTypeMapper - ) - { - $this->functionSignatureMapProvider = $functionSignatureMapProvider; - $this->fileNodesFetcher = $fileNodesFetcher; - $this->fileTypeMapper = $fileTypeMapper; - } - - public function hasMethodSignature(string $className, string $methodName, int $variant = 0): bool - { - $lowerClassName = strtolower($className); - if (!array_key_exists($lowerClassName, Php8StubsMap::CLASSES)) { - return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); - } - - if ($variant > 0) { - return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); - } - - if ($this->findMethodNode($className, $methodName) === null) { - return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); - } - - return true; - } - - /** - * @param string $className - * @param string $methodName - * @return array{ClassMethod, string}|null - * @throws \PHPStan\ShouldNotHappenException - */ - private function findMethodNode(string $className, string $methodName): ?array - { - $lowerClassName = strtolower($className); - $lowerMethodName = strtolower($methodName); - if (isset($this->methodNodes[$lowerClassName][$lowerMethodName])) { - return $this->methodNodes[$lowerClassName][$lowerMethodName]; - } - - $stubFile = self::DIRECTORY . '/' . Php8StubsMap::CLASSES[$lowerClassName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $classes = $nodes->getClassNodes(); - if (count($classes) !== 1) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - $class = $classes[$lowerClassName]; - if (count($class) !== 1) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - foreach ($class[0]->getNode()->stmts as $stmt) { - if (!$stmt instanceof ClassMethod) { - continue; - } - - if ($stmt->name->toLowerString() === $lowerMethodName) { - return $this->methodNodes[$lowerClassName][$lowerMethodName] = [$stmt, $stubFile]; - } - } - - return null; - } - - public function hasFunctionSignature(string $name, int $variant = 0): bool - { - $lowerName = strtolower($name); - if (!array_key_exists($lowerName, Php8StubsMap::FUNCTIONS)) { - return $this->functionSignatureMapProvider->hasFunctionSignature($name, $variant); - } - - if ($variant > 0) { - return $this->functionSignatureMapProvider->hasFunctionSignature($name, $variant); - } - - return true; - } - - public function getMethodSignature(string $className, string $methodName, ?\ReflectionMethod $reflectionMethod, int $variant = 0): FunctionSignature - { - $lowerClassName = strtolower($className); - if (!array_key_exists($lowerClassName, Php8StubsMap::CLASSES)) { - return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); - } - - if ($variant > 0) { - return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); - } - - if ($this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, 1)) { - return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); - } - - $methodNode = $this->findMethodNode($className, $methodName); - if ($methodNode === null) { - return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); - } - - [$methodNode, $stubFile] = $methodNode; - - $signature = $this->getSignature($methodNode, $className, $stubFile); - if ($this->functionSignatureMapProvider->hasMethodSignature($className, $methodName)) { - return $this->mergeSignatures( - $signature, - $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant) - ); - } - - return $signature; - } - - public function getFunctionSignature(string $functionName, ?string $className, int $variant = 0): FunctionSignature - { - $lowerName = strtolower($functionName); - if (!array_key_exists($lowerName, Php8StubsMap::FUNCTIONS)) { - return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); - } - - if ($variant > 0) { - return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); - } - - if ($this->functionSignatureMapProvider->hasFunctionSignature($functionName, 1)) { - return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); - } - - $stubFile = self::DIRECTORY . '/' . Php8StubsMap::FUNCTIONS[$lowerName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $functions = $nodes->getFunctionNodes(); - if (count($functions) !== 1) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Function %s stub not found in %s.', $functionName, $stubFile)); - } - - $signature = $this->getSignature($functions[$lowerName]->getNode(), null, $stubFile); - if ($this->functionSignatureMapProvider->hasFunctionSignature($functionName)) { - return $this->mergeSignatures( - $signature, - $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className) - ); - } - - return $signature; - } - - private function mergeSignatures(FunctionSignature $nativeSignature, FunctionSignature $functionMapSignature): FunctionSignature - { - $parameters = []; - foreach ($nativeSignature->getParameters() as $i => $nativeParameter) { - if (!array_key_exists($i, $functionMapSignature->getParameters())) { - $parameters[] = $nativeParameter; - continue; - } - - $functionMapParameter = $functionMapSignature->getParameters()[$i]; - $nativeParameterType = $nativeParameter->getNativeType(); - $parameters[] = new ParameterSignature( - $nativeParameter->getName(), - $nativeParameter->isOptional(), - TypehintHelper::decideType( - $nativeParameterType, - TypehintHelper::decideType( - $nativeParameter->getType(), - $functionMapParameter->getType() - ) - ), - $nativeParameterType, - $nativeParameter->passedByReference()->yes() ? $functionMapParameter->passedByReference() : $nativeParameter->passedByReference(), - $nativeParameter->isVariadic() - ); - } - - $nativeReturnType = $nativeSignature->getNativeReturnType(); - if ($nativeReturnType instanceof MixedType && !$nativeReturnType->isExplicitMixed()) { - $returnType = $functionMapSignature->getReturnType(); - } else { - $returnType = TypehintHelper::decideType( - $nativeReturnType, - TypehintHelper::decideType( - $nativeSignature->getReturnType(), - $functionMapSignature->getReturnType() - ) - ); - } - - return new FunctionSignature( - $parameters, - $returnType, - $nativeReturnType, - $nativeSignature->isVariadic() - ); - } - - public function hasMethodMetadata(string $className, string $methodName): bool - { - return $this->functionSignatureMapProvider->hasMethodMetadata($className, $methodName); - } - - public function hasFunctionMetadata(string $name): bool - { - return $this->functionSignatureMapProvider->hasFunctionMetadata($name); - } - - /** - * @param string $className - * @param string $methodName - * @return array{hasSideEffects: bool} - */ - public function getMethodMetadata(string $className, string $methodName): array - { - return $this->functionSignatureMapProvider->getMethodMetadata($className, $methodName); - } - - /** - * @param string $functionName - * @return array{hasSideEffects: bool} - */ - public function getFunctionMetadata(string $functionName): array - { - return $this->functionSignatureMapProvider->getFunctionMetadata($functionName); - } - - /** - * @param ClassMethod|Function_ $function - * @param string $stubFile - * @return FunctionSignature - */ - private function getSignature( - FunctionLike $function, - ?string $className, - string $stubFile - ): FunctionSignature - { - $phpDocParameterTypes = null; - $phpDocReturnType = null; - if ($function->getDocComment() !== null) { - $phpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $stubFile, - $className, - null, - $function instanceof ClassMethod ? $function->name->toString() : $function->namespacedName->toString(), - $function->getDocComment()->getText() - ); - $phpDocParameterTypes = array_map(static function (ParamTag $param): Type { - return $param->getType(); - }, $phpDoc->getParamTags()); - if ($phpDoc->getReturnTag() !== null) { - $phpDocReturnType = $phpDoc->getReturnTag()->getType(); - } - } - $parameters = []; - $variadic = false; - foreach ($function->getParams() as $param) { - $name = $param->var; - if (!$name instanceof Variable || !is_string($name->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - if ($name->name === 'array') { - $parameterType = new ArrayType(new MixedType(), new MixedType()); - } else { - $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, null); - } - $parameters[] = new ParameterSignature( - $name->name, - $param->default !== null || $param->variadic, - TypehintHelper::decideType($parameterType, $phpDocParameterTypes[$name->name] ?? null), - $parameterType, - $param->byRef ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(), - $param->variadic - ); - - $variadic = $variadic || $param->variadic; - } - - $returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), null); - - return new FunctionSignature( - $parameters, - TypehintHelper::decideType($returnType, $phpDocReturnType ?? null), - $returnType, - $variadic - ); - } - + private const DIRECTORY = __DIR__ . '/../../../vendor/phpstan/php-8-stubs'; + + private FunctionSignatureMapProvider $functionSignatureMapProvider; + + private FileNodesFetcher $fileNodesFetcher; + + private FileTypeMapper $fileTypeMapper; + + /** @var array> */ + private array $methodNodes = []; + + public function __construct( + FunctionSignatureMapProvider $functionSignatureMapProvider, + FileNodesFetcher $fileNodesFetcher, + FileTypeMapper $fileTypeMapper + ) { + $this->functionSignatureMapProvider = $functionSignatureMapProvider; + $this->fileNodesFetcher = $fileNodesFetcher; + $this->fileTypeMapper = $fileTypeMapper; + } + + public function hasMethodSignature(string $className, string $methodName, int $variant = 0): bool + { + $lowerClassName = strtolower($className); + if (!array_key_exists($lowerClassName, Php8StubsMap::CLASSES)) { + return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); + } + + if ($variant > 0) { + return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); + } + + if ($this->findMethodNode($className, $methodName) === null) { + return $this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, $variant); + } + + return true; + } + + /** + * @param string $className + * @param string $methodName + * @return array{ClassMethod, string}|null + * @throws \PHPStan\ShouldNotHappenException + */ + private function findMethodNode(string $className, string $methodName): ?array + { + $lowerClassName = strtolower($className); + $lowerMethodName = strtolower($methodName); + if (isset($this->methodNodes[$lowerClassName][$lowerMethodName])) { + return $this->methodNodes[$lowerClassName][$lowerMethodName]; + } + + $stubFile = self::DIRECTORY . '/' . Php8StubsMap::CLASSES[$lowerClassName]; + $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); + $classes = $nodes->getClassNodes(); + if (count($classes) !== 1) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } + + $class = $classes[$lowerClassName]; + if (count($class) !== 1) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } + + foreach ($class[0]->getNode()->stmts as $stmt) { + if (!$stmt instanceof ClassMethod) { + continue; + } + + if ($stmt->name->toLowerString() === $lowerMethodName) { + return $this->methodNodes[$lowerClassName][$lowerMethodName] = [$stmt, $stubFile]; + } + } + + return null; + } + + public function hasFunctionSignature(string $name, int $variant = 0): bool + { + $lowerName = strtolower($name); + if (!array_key_exists($lowerName, Php8StubsMap::FUNCTIONS)) { + return $this->functionSignatureMapProvider->hasFunctionSignature($name, $variant); + } + + if ($variant > 0) { + return $this->functionSignatureMapProvider->hasFunctionSignature($name, $variant); + } + + return true; + } + + public function getMethodSignature(string $className, string $methodName, ?\ReflectionMethod $reflectionMethod, int $variant = 0): FunctionSignature + { + $lowerClassName = strtolower($className); + if (!array_key_exists($lowerClassName, Php8StubsMap::CLASSES)) { + return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); + } + + if ($variant > 0) { + return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); + } + + if ($this->functionSignatureMapProvider->hasMethodSignature($className, $methodName, 1)) { + return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); + } + + $methodNode = $this->findMethodNode($className, $methodName); + if ($methodNode === null) { + return $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant); + } + + [$methodNode, $stubFile] = $methodNode; + + $signature = $this->getSignature($methodNode, $className, $stubFile); + if ($this->functionSignatureMapProvider->hasMethodSignature($className, $methodName)) { + return $this->mergeSignatures( + $signature, + $this->functionSignatureMapProvider->getMethodSignature($className, $methodName, $reflectionMethod, $variant) + ); + } + + return $signature; + } + + public function getFunctionSignature(string $functionName, ?string $className, int $variant = 0): FunctionSignature + { + $lowerName = strtolower($functionName); + if (!array_key_exists($lowerName, Php8StubsMap::FUNCTIONS)) { + return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); + } + + if ($variant > 0) { + return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); + } + + if ($this->functionSignatureMapProvider->hasFunctionSignature($functionName, 1)) { + return $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className, $variant); + } + + $stubFile = self::DIRECTORY . '/' . Php8StubsMap::FUNCTIONS[$lowerName]; + $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); + $functions = $nodes->getFunctionNodes(); + if (count($functions) !== 1) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Function %s stub not found in %s.', $functionName, $stubFile)); + } + + $signature = $this->getSignature($functions[$lowerName]->getNode(), null, $stubFile); + if ($this->functionSignatureMapProvider->hasFunctionSignature($functionName)) { + return $this->mergeSignatures( + $signature, + $this->functionSignatureMapProvider->getFunctionSignature($functionName, $className) + ); + } + + return $signature; + } + + private function mergeSignatures(FunctionSignature $nativeSignature, FunctionSignature $functionMapSignature): FunctionSignature + { + $parameters = []; + foreach ($nativeSignature->getParameters() as $i => $nativeParameter) { + if (!array_key_exists($i, $functionMapSignature->getParameters())) { + $parameters[] = $nativeParameter; + continue; + } + + $functionMapParameter = $functionMapSignature->getParameters()[$i]; + $nativeParameterType = $nativeParameter->getNativeType(); + $parameters[] = new ParameterSignature( + $nativeParameter->getName(), + $nativeParameter->isOptional(), + TypehintHelper::decideType( + $nativeParameterType, + TypehintHelper::decideType( + $nativeParameter->getType(), + $functionMapParameter->getType() + ) + ), + $nativeParameterType, + $nativeParameter->passedByReference()->yes() ? $functionMapParameter->passedByReference() : $nativeParameter->passedByReference(), + $nativeParameter->isVariadic() + ); + } + + $nativeReturnType = $nativeSignature->getNativeReturnType(); + if ($nativeReturnType instanceof MixedType && !$nativeReturnType->isExplicitMixed()) { + $returnType = $functionMapSignature->getReturnType(); + } else { + $returnType = TypehintHelper::decideType( + $nativeReturnType, + TypehintHelper::decideType( + $nativeSignature->getReturnType(), + $functionMapSignature->getReturnType() + ) + ); + } + + return new FunctionSignature( + $parameters, + $returnType, + $nativeReturnType, + $nativeSignature->isVariadic() + ); + } + + public function hasMethodMetadata(string $className, string $methodName): bool + { + return $this->functionSignatureMapProvider->hasMethodMetadata($className, $methodName); + } + + public function hasFunctionMetadata(string $name): bool + { + return $this->functionSignatureMapProvider->hasFunctionMetadata($name); + } + + /** + * @param string $className + * @param string $methodName + * @return array{hasSideEffects: bool} + */ + public function getMethodMetadata(string $className, string $methodName): array + { + return $this->functionSignatureMapProvider->getMethodMetadata($className, $methodName); + } + + /** + * @param string $functionName + * @return array{hasSideEffects: bool} + */ + public function getFunctionMetadata(string $functionName): array + { + return $this->functionSignatureMapProvider->getFunctionMetadata($functionName); + } + + /** + * @param ClassMethod|Function_ $function + * @param string $stubFile + * @return FunctionSignature + */ + private function getSignature( + FunctionLike $function, + ?string $className, + string $stubFile + ): FunctionSignature { + $phpDocParameterTypes = null; + $phpDocReturnType = null; + if ($function->getDocComment() !== null) { + $phpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $stubFile, + $className, + null, + $function instanceof ClassMethod ? $function->name->toString() : $function->namespacedName->toString(), + $function->getDocComment()->getText() + ); + $phpDocParameterTypes = array_map(static function (ParamTag $param): Type { + return $param->getType(); + }, $phpDoc->getParamTags()); + if ($phpDoc->getReturnTag() !== null) { + $phpDocReturnType = $phpDoc->getReturnTag()->getType(); + } + } + $parameters = []; + $variadic = false; + foreach ($function->getParams() as $param) { + $name = $param->var; + if (!$name instanceof Variable || !is_string($name->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + if ($name->name === 'array') { + $parameterType = new ArrayType(new MixedType(), new MixedType()); + } else { + $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, null); + } + $parameters[] = new ParameterSignature( + $name->name, + $param->default !== null || $param->variadic, + TypehintHelper::decideType($parameterType, $phpDocParameterTypes[$name->name] ?? null), + $parameterType, + $param->byRef ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(), + $param->variadic + ); + + $variadic = $variadic || $param->variadic; + } + + $returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), null); + + return new FunctionSignature( + $parameters, + TypehintHelper::decideType($returnType, $phpDocReturnType ?? null), + $returnType, + $variadic + ); + } } diff --git a/src/Reflection/SignatureMap/SignatureMapParser.php b/src/Reflection/SignatureMap/SignatureMapParser.php index 2a4e203d85..86a7854484 100644 --- a/src/Reflection/SignatureMap/SignatureMapParser.php +++ b/src/Reflection/SignatureMap/SignatureMapParser.php @@ -1,4 +1,6 @@ -typeStringResolver = $typeNodeResolver; - } - - /** - * @param mixed[] $map - * @param string|null $className - * @return \PHPStan\Reflection\SignatureMap\FunctionSignature - */ - public function getFunctionSignature(array $map, ?string $className): FunctionSignature - { - $parameterSignatures = $this->getParameters(array_slice($map, 1)); - $hasVariadic = false; - foreach ($parameterSignatures as $parameterSignature) { - if ($parameterSignature->isVariadic()) { - $hasVariadic = true; - break; - } - } - return new FunctionSignature( - $parameterSignatures, - $this->getTypeFromString($map[0], $className), - new MixedType(), - $hasVariadic - ); - } - - private function getTypeFromString(string $typeString, ?string $className): Type - { - if ($typeString === '') { - return new MixedType(true); - } - - return $this->typeStringResolver->resolve($typeString, new NameScope(null, [], $className)); - } - - /** - * @param array $parameterMap - * @return array - */ - private function getParameters(array $parameterMap): array - { - $parameterSignatures = []; - foreach ($parameterMap as $parameterName => $typeString) { - [$name, $isOptional, $passedByReference, $isVariadic] = $this->getParameterInfoFromName($parameterName); - $parameterSignatures[] = new ParameterSignature( - $name, - $isOptional, - $this->getTypeFromString($typeString, null), - new MixedType(), - $passedByReference, - $isVariadic - ); - } - - return $parameterSignatures; - } - - /** - * @param string $parameterNameString - * @return mixed[] - */ - private function getParameterInfoFromName(string $parameterNameString): array - { - $matches = \Nette\Utils\Strings::match( - $parameterNameString, - '#^(?P&(?:\.\.\.)?r?w?_?)?(?P\.\.\.)?(?P[^=]+)?(?P=)?($)#' - ); - if ($matches === null || !isset($matches['optional'])) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $isVariadic = $matches['variadic'] !== ''; - - $reference = $matches['reference']; - if (strpos($reference, '&...') === 0) { - $reference = '&' . substr($reference, 4); - $isVariadic = true; - } - if (strpos($reference, '&rw') === 0) { - $passedByReference = PassedByReference::createReadsArgument(); - } elseif (strpos($reference, '&w') === 0) { - $passedByReference = PassedByReference::createCreatesNewVariable(); - } else { - $passedByReference = PassedByReference::createNo(); - } - - $isOptional = $isVariadic || $matches['optional'] !== ''; - - $name = $matches['name'] !== '' ? $matches['name'] : '...'; - - return [$name, $isOptional, $passedByReference, $isVariadic]; - } - + private \PHPStan\PhpDoc\TypeStringResolver $typeStringResolver; + + public function __construct( + TypeStringResolver $typeNodeResolver + ) { + $this->typeStringResolver = $typeNodeResolver; + } + + /** + * @param mixed[] $map + * @param string|null $className + * @return \PHPStan\Reflection\SignatureMap\FunctionSignature + */ + public function getFunctionSignature(array $map, ?string $className): FunctionSignature + { + $parameterSignatures = $this->getParameters(array_slice($map, 1)); + $hasVariadic = false; + foreach ($parameterSignatures as $parameterSignature) { + if ($parameterSignature->isVariadic()) { + $hasVariadic = true; + break; + } + } + return new FunctionSignature( + $parameterSignatures, + $this->getTypeFromString($map[0], $className), + new MixedType(), + $hasVariadic + ); + } + + private function getTypeFromString(string $typeString, ?string $className): Type + { + if ($typeString === '') { + return new MixedType(true); + } + + return $this->typeStringResolver->resolve($typeString, new NameScope(null, [], $className)); + } + + /** + * @param array $parameterMap + * @return array + */ + private function getParameters(array $parameterMap): array + { + $parameterSignatures = []; + foreach ($parameterMap as $parameterName => $typeString) { + [$name, $isOptional, $passedByReference, $isVariadic] = $this->getParameterInfoFromName($parameterName); + $parameterSignatures[] = new ParameterSignature( + $name, + $isOptional, + $this->getTypeFromString($typeString, null), + new MixedType(), + $passedByReference, + $isVariadic + ); + } + + return $parameterSignatures; + } + + /** + * @param string $parameterNameString + * @return mixed[] + */ + private function getParameterInfoFromName(string $parameterNameString): array + { + $matches = \Nette\Utils\Strings::match( + $parameterNameString, + '#^(?P&(?:\.\.\.)?r?w?_?)?(?P\.\.\.)?(?P[^=]+)?(?P=)?($)#' + ); + if ($matches === null || !isset($matches['optional'])) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $isVariadic = $matches['variadic'] !== ''; + + $reference = $matches['reference']; + if (strpos($reference, '&...') === 0) { + $reference = '&' . substr($reference, 4); + $isVariadic = true; + } + if (strpos($reference, '&rw') === 0) { + $passedByReference = PassedByReference::createReadsArgument(); + } elseif (strpos($reference, '&w') === 0) { + $passedByReference = PassedByReference::createCreatesNewVariable(); + } else { + $passedByReference = PassedByReference::createNo(); + } + + $isOptional = $isVariadic || $matches['optional'] !== ''; + + $name = $matches['name'] !== '' ? $matches['name'] : '...'; + + return [$name, $isOptional, $passedByReference, $isVariadic]; + } } diff --git a/src/Reflection/SignatureMap/SignatureMapProvider.php b/src/Reflection/SignatureMap/SignatureMapProvider.php index c1f49dbddb..e6f2d134ea 100644 --- a/src/Reflection/SignatureMap/SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/SignatureMapProvider.php @@ -1,33 +1,33 @@ -phpVersion = $phpVersion; - $this->functionSignatureMapProvider = $functionSignatureMapProvider; - $this->php8SignatureMapProvider = $php8SignatureMapProvider; - } - - public function create(): SignatureMapProvider - { - if ($this->phpVersion->getVersionId() < 80000) { - return $this->functionSignatureMapProvider; - } - - return $this->php8SignatureMapProvider; - } - + private PhpVersion $phpVersion; + + private FunctionSignatureMapProvider $functionSignatureMapProvider; + + private Php8SignatureMapProvider $php8SignatureMapProvider; + + public function __construct( + PhpVersion $phpVersion, + FunctionSignatureMapProvider $functionSignatureMapProvider, + Php8SignatureMapProvider $php8SignatureMapProvider + ) { + $this->phpVersion = $phpVersion; + $this->functionSignatureMapProvider = $functionSignatureMapProvider; + $this->php8SignatureMapProvider = $php8SignatureMapProvider; + } + + public function create(): SignatureMapProvider + { + if ($this->phpVersion->getVersionId() < 80000) { + return $this->functionSignatureMapProvider; + } + + return $this->php8SignatureMapProvider; + } } diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index 8da226e0fd..0211fa3b43 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -1,4 +1,6 @@ - - */ - public function getParameters(): array - { - return []; - } - - public function isVariadic(): bool - { - return true; - } - - public function getReturnType(): Type - { - return new MixedType(); - } - + public function getTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + /** + * @return array + */ + public function getParameters(): array + { + return []; + } + + public function isVariadic(): bool + { + return true; + } + + public function getReturnType(): Type + { + return new MixedType(); + } } diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index 70fae4e624..98cbcc489f 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -methodReflection = $methodReflection; - $this->resolvedDeclaringClass = $resolvedDeclaringClass; - $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; - $this->transformStaticTypeCallback = $transformStaticTypeCallback; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( - $this->methodReflection, - $this->resolvedDeclaringClass, - false, - $this->transformStaticTypeCallback - ); - } - - public function getNakedMethod(): MethodReflection - { - return $this->methodReflection; - } - - public function getTransformedMethod(): MethodReflection - { - if ($this->transformedMethod !== null) { - return $this->transformedMethod; - } - $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); - - return $this->transformedMethod = new ResolvedMethodReflection( - $this->transformMethodWithStaticType($this->resolvedDeclaringClass, $this->methodReflection), - $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap - ); - } - - public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection - { - return new CalledOnTypeUnresolvedMethodPrototypeReflection( - $this->methodReflection, - $this->resolvedDeclaringClass, - $this->resolveTemplateTypeMapToBounds, - $type - ); - } - - private function transformMethodWithStaticType(ClassReflection $declaringClass, MethodReflection $method): MethodReflection - { - $variants = array_map(function (ParametersAcceptor $acceptor): ParametersAcceptor { - return new FunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - array_map(function (ParameterReflection $parameter): ParameterReflection { - return new DummyParameter( - $parameter->getName(), - $this->transformStaticType($parameter->getType()), - $parameter->isOptional(), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue() - ); - }, $acceptor->getParameters()), - $acceptor->isVariadic(), - $this->transformStaticType($acceptor->getReturnType()) - ); - }, $method->getVariants()); - - return new ChangedTypeMethodReflection($declaringClass, $method, $variants); - } - - private function transformStaticType(Type $type): Type - { - $callback = $this->transformStaticTypeCallback; - return $callback($type); - } - + private MethodReflection $methodReflection; + + private ClassReflection $resolvedDeclaringClass; + + private bool $resolveTemplateTypeMapToBounds; + + /** @var callable(Type): Type */ + private $transformStaticTypeCallback; + + private ?MethodReflection $transformedMethod = null; + + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; + + /** + * @param MethodReflection $methodReflection + * @param ClassReflection $resolvedDeclaringClass + * @param bool $resolveTemplateTypeMapToBounds + * @param callable(Type): Type $transformStaticTypeCallback + */ + public function __construct( + MethodReflection $methodReflection, + ClassReflection $resolvedDeclaringClass, + bool $resolveTemplateTypeMapToBounds, + callable $transformStaticTypeCallback + ) { + $this->methodReflection = $methodReflection; + $this->resolvedDeclaringClass = $resolvedDeclaringClass; + $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; + $this->transformStaticTypeCallback = $transformStaticTypeCallback; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( + $this->methodReflection, + $this->resolvedDeclaringClass, + false, + $this->transformStaticTypeCallback + ); + } + + public function getNakedMethod(): MethodReflection + { + return $this->methodReflection; + } + + public function getTransformedMethod(): MethodReflection + { + if ($this->transformedMethod !== null) { + return $this->transformedMethod; + } + $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); + + return $this->transformedMethod = new ResolvedMethodReflection( + $this->transformMethodWithStaticType($this->resolvedDeclaringClass, $this->methodReflection), + $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap + ); + } + + public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection + { + return new CalledOnTypeUnresolvedMethodPrototypeReflection( + $this->methodReflection, + $this->resolvedDeclaringClass, + $this->resolveTemplateTypeMapToBounds, + $type + ); + } + + private function transformMethodWithStaticType(ClassReflection $declaringClass, MethodReflection $method): MethodReflection + { + $variants = array_map(function (ParametersAcceptor $acceptor): ParametersAcceptor { + return new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + array_map(function (ParameterReflection $parameter): ParameterReflection { + return new DummyParameter( + $parameter->getName(), + $this->transformStaticType($parameter->getType()), + $parameter->isOptional(), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue() + ); + }, $acceptor->getParameters()), + $acceptor->isVariadic(), + $this->transformStaticType($acceptor->getReturnType()) + ); + }, $method->getVariants()); + + return new ChangedTypeMethodReflection($declaringClass, $method, $variants); + } + + private function transformStaticType(Type $type): Type + { + $callback = $this->transformStaticTypeCallback; + return $callback($type); + } } diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 903b081c58..5487d0721d 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -1,4 +1,6 @@ -propertyReflection = $propertyReflection; - $this->resolvedDeclaringClass = $resolvedDeclaringClass; - $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; - $this->transformStaticTypeCallback = $transformStaticTypeCallback; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( - $this->propertyReflection, - $this->resolvedDeclaringClass, - false, - $this->transformStaticTypeCallback - ); - } - - public function getNakedProperty(): PropertyReflection - { - return $this->propertyReflection; - } - - public function getTransformedProperty(): PropertyReflection - { - if ($this->transformedProperty !== null) { - return $this->transformedProperty; - } - $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); - - return $this->transformedProperty = new ResolvedPropertyReflection( - $this->transformPropertyWithStaticType($this->resolvedDeclaringClass, $this->propertyReflection), - $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap - ); - } - - public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection - { - return new CalledOnTypeUnresolvedPropertyPrototypeReflection( - $this->propertyReflection, - $this->resolvedDeclaringClass, - $this->resolveTemplateTypeMapToBounds, - $type - ); - } - - private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection - { - $readableType = $this->transformStaticType($property->getReadableType()); - $writableType = $this->transformStaticType($property->getWritableType()); - - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); - } - - private function transformStaticType(Type $type): Type - { - $callback = $this->transformStaticTypeCallback; - return $callback($type); - } - + private PropertyReflection $propertyReflection; + + private ClassReflection $resolvedDeclaringClass; + + private bool $resolveTemplateTypeMapToBounds; + + /** @var callable(Type): Type */ + private $transformStaticTypeCallback; + + private ?PropertyReflection $transformedProperty = null; + + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; + + /** + * @param PropertyReflection $propertyReflection + * @param ClassReflection $resolvedDeclaringClass + * @param bool $resolveTemplateTypeMapToBounds + * @param callable(Type): Type $transformStaticTypeCallback + */ + public function __construct( + PropertyReflection $propertyReflection, + ClassReflection $resolvedDeclaringClass, + bool $resolveTemplateTypeMapToBounds, + callable $transformStaticTypeCallback + ) { + $this->propertyReflection = $propertyReflection; + $this->resolvedDeclaringClass = $resolvedDeclaringClass; + $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; + $this->transformStaticTypeCallback = $transformStaticTypeCallback; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( + $this->propertyReflection, + $this->resolvedDeclaringClass, + false, + $this->transformStaticTypeCallback + ); + } + + public function getNakedProperty(): PropertyReflection + { + return $this->propertyReflection; + } + + public function getTransformedProperty(): PropertyReflection + { + if ($this->transformedProperty !== null) { + return $this->transformedProperty; + } + $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); + + return $this->transformedProperty = new ResolvedPropertyReflection( + $this->transformPropertyWithStaticType($this->resolvedDeclaringClass, $this->propertyReflection), + $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap + ); + } + + public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection + { + return new CalledOnTypeUnresolvedPropertyPrototypeReflection( + $this->propertyReflection, + $this->resolvedDeclaringClass, + $this->resolveTemplateTypeMapToBounds, + $type + ); + } + + private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection + { + $readableType = $this->transformStaticType($property->getReadableType()); + $writableType = $this->transformStaticType($property->getWritableType()); + + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + } + + private function transformStaticType(Type $type): Type + { + $callback = $this->transformStaticTypeCallback; + return $callback($type); + } } diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 069592d59d..991f94f13e 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -methodReflection = $methodReflection; - $this->resolvedDeclaringClass = $resolvedDeclaringClass; - $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; - $this->calledOnType = $calledOnType; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( - $this->methodReflection, - $this->resolvedDeclaringClass, - false, - $this->calledOnType - ); - } - - public function getNakedMethod(): MethodReflection - { - return $this->methodReflection; - } - - public function getTransformedMethod(): MethodReflection - { - if ($this->transformedMethod !== null) { - return $this->transformedMethod; - } - $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); - - return $this->transformedMethod = new ResolvedMethodReflection( - $this->transformMethodWithStaticType($this->resolvedDeclaringClass, $this->methodReflection), - $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap - ); - } - - public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection - { - return new self( - $this->methodReflection, - $this->resolvedDeclaringClass, - $this->resolveTemplateTypeMapToBounds, - $type - ); - } - - private function transformMethodWithStaticType(ClassReflection $declaringClass, MethodReflection $method): MethodReflection - { - $variants = array_map(function (ParametersAcceptor $acceptor): ParametersAcceptor { - return new FunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - array_map(function (ParameterReflection $parameter): ParameterReflection { - return new DummyParameter( - $parameter->getName(), - $this->transformStaticType($parameter->getType()), - $parameter->isOptional(), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue() - ); - }, $acceptor->getParameters()), - $acceptor->isVariadic(), - $this->transformStaticType($acceptor->getReturnType()) - ); - }, $method->getVariants()); - - return new ChangedTypeMethodReflection($declaringClass, $method, $variants); - } - - private function transformStaticType(Type $type): Type - { - return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { - if ($type instanceof StaticType) { - return $this->calledOnType; - } - - return $traverse($type); - }); - } - + private MethodReflection $methodReflection; + + private ClassReflection $resolvedDeclaringClass; + + private bool $resolveTemplateTypeMapToBounds; + + private Type $calledOnType; + + private ?MethodReflection $transformedMethod = null; + + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; + + public function __construct( + MethodReflection $methodReflection, + ClassReflection $resolvedDeclaringClass, + bool $resolveTemplateTypeMapToBounds, + Type $calledOnType + ) { + $this->methodReflection = $methodReflection; + $this->resolvedDeclaringClass = $resolvedDeclaringClass; + $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; + $this->calledOnType = $calledOnType; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( + $this->methodReflection, + $this->resolvedDeclaringClass, + false, + $this->calledOnType + ); + } + + public function getNakedMethod(): MethodReflection + { + return $this->methodReflection; + } + + public function getTransformedMethod(): MethodReflection + { + if ($this->transformedMethod !== null) { + return $this->transformedMethod; + } + $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); + + return $this->transformedMethod = new ResolvedMethodReflection( + $this->transformMethodWithStaticType($this->resolvedDeclaringClass, $this->methodReflection), + $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap + ); + } + + public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection + { + return new self( + $this->methodReflection, + $this->resolvedDeclaringClass, + $this->resolveTemplateTypeMapToBounds, + $type + ); + } + + private function transformMethodWithStaticType(ClassReflection $declaringClass, MethodReflection $method): MethodReflection + { + $variants = array_map(function (ParametersAcceptor $acceptor): ParametersAcceptor { + return new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + array_map(function (ParameterReflection $parameter): ParameterReflection { + return new DummyParameter( + $parameter->getName(), + $this->transformStaticType($parameter->getType()), + $parameter->isOptional(), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue() + ); + }, $acceptor->getParameters()), + $acceptor->isVariadic(), + $this->transformStaticType($acceptor->getReturnType()) + ); + }, $method->getVariants()); + + return new ChangedTypeMethodReflection($declaringClass, $method, $variants); + } + + private function transformStaticType(Type $type): Type + { + return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { + if ($type instanceof StaticType) { + return $this->calledOnType; + } + + return $traverse($type); + }); + } } diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 59bd33d067..8ecfce1e6c 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -1,4 +1,6 @@ -propertyReflection = $propertyReflection; - $this->resolvedDeclaringClass = $resolvedDeclaringClass; - $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; - $this->fetchedOnType = $fetchedOnType; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( - $this->propertyReflection, - $this->resolvedDeclaringClass, - false, - $this->fetchedOnType - ); - } - - public function getNakedProperty(): PropertyReflection - { - return $this->propertyReflection; - } - - public function getTransformedProperty(): PropertyReflection - { - if ($this->transformedProperty !== null) { - return $this->transformedProperty; - } - $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); - - return $this->transformedProperty = new ResolvedPropertyReflection( - $this->transformPropertyWithStaticType($this->resolvedDeclaringClass, $this->propertyReflection), - $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap - ); - } - - public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection - { - return new self( - $this->propertyReflection, - $this->resolvedDeclaringClass, - $this->resolveTemplateTypeMapToBounds, - $type - ); - } - - private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection - { - $readableType = $this->transformStaticType($property->getReadableType()); - $writableType = $this->transformStaticType($property->getWritableType()); - - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); - } - - private function transformStaticType(Type $type): Type - { - return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { - if ($type instanceof StaticType) { - return $this->fetchedOnType; - } - - return $traverse($type); - }); - } - + private PropertyReflection $propertyReflection; + + private ClassReflection $resolvedDeclaringClass; + + private bool $resolveTemplateTypeMapToBounds; + + private Type $fetchedOnType; + + private ?PropertyReflection $transformedProperty = null; + + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; + + public function __construct( + PropertyReflection $propertyReflection, + ClassReflection $resolvedDeclaringClass, + bool $resolveTemplateTypeMapToBounds, + Type $fetchedOnType + ) { + $this->propertyReflection = $propertyReflection; + $this->resolvedDeclaringClass = $resolvedDeclaringClass; + $this->resolveTemplateTypeMapToBounds = $resolveTemplateTypeMapToBounds; + $this->fetchedOnType = $fetchedOnType; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self( + $this->propertyReflection, + $this->resolvedDeclaringClass, + false, + $this->fetchedOnType + ); + } + + public function getNakedProperty(): PropertyReflection + { + return $this->propertyReflection; + } + + public function getTransformedProperty(): PropertyReflection + { + if ($this->transformedProperty !== null) { + return $this->transformedProperty; + } + $templateTypeMap = $this->resolvedDeclaringClass->getActiveTemplateTypeMap(); + + return $this->transformedProperty = new ResolvedPropertyReflection( + $this->transformPropertyWithStaticType($this->resolvedDeclaringClass, $this->propertyReflection), + $this->resolveTemplateTypeMapToBounds ? $templateTypeMap->resolveToBounds() : $templateTypeMap + ); + } + + public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection + { + return new self( + $this->propertyReflection, + $this->resolvedDeclaringClass, + $this->resolveTemplateTypeMapToBounds, + $type + ); + } + + private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection + { + $readableType = $this->transformStaticType($property->getReadableType()); + $writableType = $this->transformStaticType($property->getWritableType()); + + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + } + + private function transformStaticType(Type $type): Type + { + return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { + if ($type instanceof StaticType) { + return $this->fetchedOnType; + } + + return $traverse($type); + }); + } } diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index ad136f3abf..587b818f44 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -1,4 +1,6 @@ -methodName = $methodName; - $this->methods = $methods; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->methods[0]->getDeclaringClass(); - } - - public function isStatic(): bool - { - foreach ($this->methods as $method) { - if ($method->isStatic()) { - return true; - } - } - - return false; - } - - public function isPrivate(): bool - { - foreach ($this->methods as $method) { - if (!$method->isPrivate()) { - return false; - } - } - - return true; - } - - public function isPublic(): bool - { - foreach ($this->methods as $method) { - if ($method->isPublic()) { - return true; - } - } - - return false; - } - - public function getName(): string - { - return $this->methodName; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - public function getVariants(): array - { - $returnType = TypeCombinator::intersect(...array_map(static function (MethodReflection $method): Type { - return TypeCombinator::intersect(...array_map(static function (ParametersAcceptor $acceptor): Type { - return $acceptor->getReturnType(); - }, $method->getVariants())); - }, $this->methods)); - - return array_map(static function (ParametersAcceptor $acceptor) use ($returnType): ParametersAcceptor { - return new FunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - $acceptor->getParameters(), - $acceptor->isVariadic(), - $returnType - ); - }, $this->methods[0]->getVariants()); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isDeprecated(); - }, $this->methods)); - } - - public function getDeprecatedDescription(): ?string - { - $descriptions = []; - foreach ($this->methods as $method) { - if (!$method->isDeprecated()->yes()) { - continue; - } - $description = $method->getDeprecatedDescription(); - if ($description === null) { - continue; - } - - $descriptions[] = $description; - } - - if (count($descriptions) === 0) { - return null; - } - - return implode(' ', $descriptions); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isFinal(); - }, $this->methods)); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isInternal(); - }, $this->methods)); - } - - public function getThrowType(): ?Type - { - $types = []; - - foreach ($this->methods as $method) { - $type = $method->getThrowType(); - if ($type === null) { - continue; - } - - $types[] = $type; - } - - if (count($types) === 0) { - return null; - } - - return TypeCombinator::intersect(...$types); - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->hasSideEffects(); - }, $this->methods)); - } - - public function getDocComment(): ?string - { - return null; - } - + private string $methodName; + + /** @var MethodReflection[] */ + private array $methods; + + /** + * @param string $methodName + * @param \PHPStan\Reflection\MethodReflection[] $methods + */ + public function __construct(string $methodName, array $methods) + { + $this->methodName = $methodName; + $this->methods = $methods; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->methods[0]->getDeclaringClass(); + } + + public function isStatic(): bool + { + foreach ($this->methods as $method) { + if ($method->isStatic()) { + return true; + } + } + + return false; + } + + public function isPrivate(): bool + { + foreach ($this->methods as $method) { + if (!$method->isPrivate()) { + return false; + } + } + + return true; + } + + public function isPublic(): bool + { + foreach ($this->methods as $method) { + if ($method->isPublic()) { + return true; + } + } + + return false; + } + + public function getName(): string + { + return $this->methodName; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + public function getVariants(): array + { + $returnType = TypeCombinator::intersect(...array_map(static function (MethodReflection $method): Type { + return TypeCombinator::intersect(...array_map(static function (ParametersAcceptor $acceptor): Type { + return $acceptor->getReturnType(); + }, $method->getVariants())); + }, $this->methods)); + + return array_map(static function (ParametersAcceptor $acceptor) use ($returnType): ParametersAcceptor { + return new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + $acceptor->getParameters(), + $acceptor->isVariadic(), + $returnType + ); + }, $this->methods[0]->getVariants()); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isDeprecated(); + }, $this->methods)); + } + + public function getDeprecatedDescription(): ?string + { + $descriptions = []; + foreach ($this->methods as $method) { + if (!$method->isDeprecated()->yes()) { + continue; + } + $description = $method->getDeprecatedDescription(); + if ($description === null) { + continue; + } + + $descriptions[] = $description; + } + + if (count($descriptions) === 0) { + return null; + } + + return implode(' ', $descriptions); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isFinal(); + }, $this->methods)); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isInternal(); + }, $this->methods)); + } + + public function getThrowType(): ?Type + { + $types = []; + + foreach ($this->methods as $method) { + $type = $method->getThrowType(); + if ($type === null) { + continue; + } + + $types[] = $type; + } + + if (count($types) === 0) { + return null; + } + + return TypeCombinator::intersect(...$types); + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->hasSideEffects(); + }, $this->methods)); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 9ecb2f7947..5844ebf2de 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -1,4 +1,6 @@ -properties = $properties; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->properties[0]->getDeclaringClass(); - } - - public function isStatic(): bool - { - foreach ($this->properties as $property) { - if ($property->isStatic()) { - return true; - } - } - - return false; - } - - public function isPrivate(): bool - { - foreach ($this->properties as $property) { - if (!$property->isPrivate()) { - return false; - } - } - - return true; - } - - public function isPublic(): bool - { - foreach ($this->properties as $property) { - if ($property->isPublic()) { - return true; - } - } - - return false; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (PropertyReflection $property): TrinaryLogic { - return $property->isDeprecated(); - }, $this->properties)); - } - - public function getDeprecatedDescription(): ?string - { - $descriptions = []; - foreach ($this->properties as $property) { - if (!$property->isDeprecated()->yes()) { - continue; - } - $description = $property->getDeprecatedDescription(); - if ($description === null) { - continue; - } - - $descriptions[] = $description; - } - - if (count($descriptions) === 0) { - return null; - } - - return implode(' ', $descriptions); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::maxMin(...array_map(static function (PropertyReflection $property): TrinaryLogic { - return $property->isInternal(); - }, $this->properties)); - } - - public function getDocComment(): ?string - { - return null; - } - - public function getReadableType(): Type - { - return TypeCombinator::intersect(...array_map(static function (PropertyReflection $property): Type { - return $property->getReadableType(); - }, $this->properties)); - } - - public function getWritableType(): Type - { - return TypeCombinator::intersect(...array_map(static function (PropertyReflection $property): Type { - return $property->getWritableType(); - }, $this->properties)); - } - - public function canChangeTypeAfterAssignment(): bool - { - foreach ($this->properties as $property) { - if (!$property->canChangeTypeAfterAssignment()) { - return false; - } - } - - return true; - } - - public function isReadable(): bool - { - foreach ($this->properties as $property) { - if (!$property->isReadable()) { - return false; - } - } - - return true; - } - - public function isWritable(): bool - { - foreach ($this->properties as $property) { - if (!$property->isWritable()) { - return false; - } - } - - return true; - } - + /** @var PropertyReflection[] */ + private array $properties; + + /** + * @param \PHPStan\Reflection\PropertyReflection[] $properties + */ + public function __construct(array $properties) + { + $this->properties = $properties; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->properties[0]->getDeclaringClass(); + } + + public function isStatic(): bool + { + foreach ($this->properties as $property) { + if ($property->isStatic()) { + return true; + } + } + + return false; + } + + public function isPrivate(): bool + { + foreach ($this->properties as $property) { + if (!$property->isPrivate()) { + return false; + } + } + + return true; + } + + public function isPublic(): bool + { + foreach ($this->properties as $property) { + if ($property->isPublic()) { + return true; + } + } + + return false; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (PropertyReflection $property): TrinaryLogic { + return $property->isDeprecated(); + }, $this->properties)); + } + + public function getDeprecatedDescription(): ?string + { + $descriptions = []; + foreach ($this->properties as $property) { + if (!$property->isDeprecated()->yes()) { + continue; + } + $description = $property->getDeprecatedDescription(); + if ($description === null) { + continue; + } + + $descriptions[] = $description; + } + + if (count($descriptions) === 0) { + return null; + } + + return implode(' ', $descriptions); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::maxMin(...array_map(static function (PropertyReflection $property): TrinaryLogic { + return $property->isInternal(); + }, $this->properties)); + } + + public function getDocComment(): ?string + { + return null; + } + + public function getReadableType(): Type + { + return TypeCombinator::intersect(...array_map(static function (PropertyReflection $property): Type { + return $property->getReadableType(); + }, $this->properties)); + } + + public function getWritableType(): Type + { + return TypeCombinator::intersect(...array_map(static function (PropertyReflection $property): Type { + return $property->getWritableType(); + }, $this->properties)); + } + + public function canChangeTypeAfterAssignment(): bool + { + foreach ($this->properties as $property) { + if (!$property->canChangeTypeAfterAssignment()) { + return false; + } + } + + return true; + } + + public function isReadable(): bool + { + foreach ($this->properties as $property) { + if (!$property->isReadable()) { + return false; + } + } + + return true; + } + + public function isWritable(): bool + { + foreach ($this->properties as $property) { + if (!$property->isWritable()) { + return false; + } + } + + return true; + } } diff --git a/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php index d8252da113..c8b9510ad9 100644 --- a/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -methodName = $methodName; - $this->methodPrototypes = $methodPrototypes; - } + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } + /** + * @param UnresolvedMethodPrototypeReflection[] $methodPrototypes + */ + public function __construct( + string $methodName, + array $methodPrototypes + ) { + $this->methodName = $methodName; + $this->methodPrototypes = $methodPrototypes; + } - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype): UnresolvedMethodPrototypeReflection { - return $prototype->doNotResolveTemplateTypeMapToBounds(); - }, $this->methodPrototypes)); - } + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } - public function getNakedMethod(): MethodReflection - { - return $this->getTransformedMethod(); - } + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype): UnresolvedMethodPrototypeReflection { + return $prototype->doNotResolveTemplateTypeMapToBounds(); + }, $this->methodPrototypes)); + } - public function getTransformedMethod(): MethodReflection - { - if ($this->transformedMethod !== null) { - return $this->transformedMethod; - } - $methods = array_map(static function (UnresolvedMethodPrototypeReflection $prototype): MethodReflection { - return $prototype->getTransformedMethod(); - }, $this->methodPrototypes); + public function getNakedMethod(): MethodReflection + { + return $this->getTransformedMethod(); + } - return $this->transformedMethod = new IntersectionTypeMethodReflection($this->methodName, $methods); - } + public function getTransformedMethod(): MethodReflection + { + if ($this->transformedMethod !== null) { + return $this->transformedMethod; + } + $methods = array_map(static function (UnresolvedMethodPrototypeReflection $prototype): MethodReflection { + return $prototype->getTransformedMethod(); + }, $this->methodPrototypes); - public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection - { - return new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype) use ($type): UnresolvedMethodPrototypeReflection { - return $prototype->withCalledOnType($type); - }, $this->methodPrototypes)); - } + return $this->transformedMethod = new IntersectionTypeMethodReflection($this->methodName, $methods); + } + public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection + { + return new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype) use ($type): UnresolvedMethodPrototypeReflection { + return $prototype->withCalledOnType($type); + }, $this->methodPrototypes)); + } } diff --git a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php index 4be1d50ea0..b1b70b2e4e 100644 --- a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php @@ -1,4 +1,6 @@ -propertyName = $propertyName; - $this->propertyPrototypes = $propertyPrototypes; - } + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } + /** + * @param UnresolvedPropertyPrototypeReflection[] $propertyPrototypes + */ + public function __construct( + string $propertyName, + array $propertyPrototypes + ) { + $this->propertyName = $propertyName; + $this->propertyPrototypes = $propertyPrototypes; + } - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection { - return $prototype->doNotResolveTemplateTypeMapToBounds(); - }, $this->propertyPrototypes)); - } + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } - public function getNakedProperty(): PropertyReflection - { - return $this->getTransformedProperty(); - } + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection { + return $prototype->doNotResolveTemplateTypeMapToBounds(); + }, $this->propertyPrototypes)); + } - public function getTransformedProperty(): PropertyReflection - { - if ($this->transformedProperty !== null) { - return $this->transformedProperty; - } - $properties = array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): PropertyReflection { - return $prototype->getTransformedProperty(); - }, $this->propertyPrototypes); + public function getNakedProperty(): PropertyReflection + { + return $this->getTransformedProperty(); + } - return $this->transformedProperty = new IntersectionTypePropertyReflection($properties); - } + public function getTransformedProperty(): PropertyReflection + { + if ($this->transformedProperty !== null) { + return $this->transformedProperty; + } + $properties = array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): PropertyReflection { + return $prototype->getTransformedProperty(); + }, $this->propertyPrototypes); - public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection - { - return new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype) use ($type): UnresolvedPropertyPrototypeReflection { - return $prototype->withFechedOnType($type); - }, $this->propertyPrototypes)); - } + return $this->transformedProperty = new IntersectionTypePropertyReflection($properties); + } + public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection + { + return new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype) use ($type): UnresolvedPropertyPrototypeReflection { + return $prototype->withFechedOnType($type); + }, $this->propertyPrototypes)); + } } diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 54ca673a7a..c343b6d218 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -1,4 +1,6 @@ -methodName = $methodName; - $this->methods = $methods; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->methods[0]->getDeclaringClass(); - } - - public function isStatic(): bool - { - foreach ($this->methods as $method) { - if (!$method->isStatic()) { - return false; - } - } - - return true; - } - - public function isPrivate(): bool - { - foreach ($this->methods as $method) { - if ($method->isPrivate()) { - return true; - } - } - - return false; - } - - public function isPublic(): bool - { - foreach ($this->methods as $method) { - if (!$method->isPublic()) { - return false; - } - } - - return true; - } - - public function getName(): string - { - return $this->methodName; - } - - public function getPrototype(): ClassMemberReflection - { - return $this; - } - - public function getVariants(): array - { - $variants = $this->methods[0]->getVariants(); - $returnType = TypeCombinator::union(...array_map(static function (MethodReflection $method): Type { - return TypeCombinator::union(...array_map(static function (ParametersAcceptor $acceptor): Type { - return $acceptor->getReturnType(); - }, $method->getVariants())); - }, $this->methods)); - - return array_map(static function (ParametersAcceptor $acceptor) use ($returnType): ParametersAcceptor { - return new FunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - $acceptor->getParameters(), - $acceptor->isVariadic(), - $returnType - ); - }, $variants); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isDeprecated(); - }, $this->methods)); - } - - public function getDeprecatedDescription(): ?string - { - $descriptions = []; - foreach ($this->methods as $method) { - if (!$method->isDeprecated()->yes()) { - continue; - } - $description = $method->getDeprecatedDescription(); - if ($description === null) { - continue; - } - - $descriptions[] = $description; - } - - if (count($descriptions) === 0) { - return null; - } - - return implode(' ', $descriptions); - } - - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isFinal(); - }, $this->methods)); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->isInternal(); - }, $this->methods)); - } - - public function getThrowType(): ?Type - { - $types = []; - - foreach ($this->methods as $method) { - $type = $method->getThrowType(); - if ($type === null) { - continue; - } - - $types[] = $type; - } - - if (count($types) === 0) { - return null; - } - - return TypeCombinator::union(...$types); - } - - public function hasSideEffects(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { - return $method->hasSideEffects(); - }, $this->methods)); - } - - public function getDocComment(): ?string - { - return null; - } - + private string $methodName; + + /** @var MethodReflection[] */ + private array $methods; + + /** + * @param string $methodName + * @param \PHPStan\Reflection\MethodReflection[] $methods + */ + public function __construct(string $methodName, array $methods) + { + $this->methodName = $methodName; + $this->methods = $methods; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->methods[0]->getDeclaringClass(); + } + + public function isStatic(): bool + { + foreach ($this->methods as $method) { + if (!$method->isStatic()) { + return false; + } + } + + return true; + } + + public function isPrivate(): bool + { + foreach ($this->methods as $method) { + if ($method->isPrivate()) { + return true; + } + } + + return false; + } + + public function isPublic(): bool + { + foreach ($this->methods as $method) { + if (!$method->isPublic()) { + return false; + } + } + + return true; + } + + public function getName(): string + { + return $this->methodName; + } + + public function getPrototype(): ClassMemberReflection + { + return $this; + } + + public function getVariants(): array + { + $variants = $this->methods[0]->getVariants(); + $returnType = TypeCombinator::union(...array_map(static function (MethodReflection $method): Type { + return TypeCombinator::union(...array_map(static function (ParametersAcceptor $acceptor): Type { + return $acceptor->getReturnType(); + }, $method->getVariants())); + }, $this->methods)); + + return array_map(static function (ParametersAcceptor $acceptor) use ($returnType): ParametersAcceptor { + return new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + $acceptor->getParameters(), + $acceptor->isVariadic(), + $returnType + ); + }, $variants); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isDeprecated(); + }, $this->methods)); + } + + public function getDeprecatedDescription(): ?string + { + $descriptions = []; + foreach ($this->methods as $method) { + if (!$method->isDeprecated()->yes()) { + continue; + } + $description = $method->getDeprecatedDescription(); + if ($description === null) { + continue; + } + + $descriptions[] = $description; + } + + if (count($descriptions) === 0) { + return null; + } + + return implode(' ', $descriptions); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isFinal(); + }, $this->methods)); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->isInternal(); + }, $this->methods)); + } + + public function getThrowType(): ?Type + { + $types = []; + + foreach ($this->methods as $method) { + $type = $method->getThrowType(); + if ($type === null) { + continue; + } + + $types[] = $type; + } + + if (count($types) === 0) { + return null; + } + + return TypeCombinator::union(...$types); + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (MethodReflection $method): TrinaryLogic { + return $method->hasSideEffects(); + }, $this->methods)); + } + + public function getDocComment(): ?string + { + return null; + } } diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 0936fff8d8..0a6653ed68 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -1,4 +1,6 @@ -properties = $properties; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->properties[0]->getDeclaringClass(); - } - - public function isStatic(): bool - { - foreach ($this->properties as $property) { - if (!$property->isStatic()) { - return false; - } - } - - return true; - } - - public function isPrivate(): bool - { - foreach ($this->properties as $property) { - if ($property->isPrivate()) { - return true; - } - } - - return false; - } - - public function isPublic(): bool - { - foreach ($this->properties as $property) { - if (!$property->isPublic()) { - return false; - } - } - - return true; - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (PropertyReflection $property): TrinaryLogic { - return $property->isDeprecated(); - }, $this->properties)); - } - - public function getDeprecatedDescription(): ?string - { - $descriptions = []; - foreach ($this->properties as $property) { - if (!$property->isDeprecated()->yes()) { - continue; - } - $description = $property->getDeprecatedDescription(); - if ($description === null) { - continue; - } - - $descriptions[] = $description; - } - - if (count($descriptions) === 0) { - return null; - } - - return implode(' ', $descriptions); - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map(static function (PropertyReflection $property): TrinaryLogic { - return $property->isInternal(); - }, $this->properties)); - } - - public function getDocComment(): ?string - { - return null; - } - - public function getReadableType(): Type - { - return TypeCombinator::union(...array_map(static function (PropertyReflection $property): Type { - return $property->getReadableType(); - }, $this->properties)); - } - - public function getWritableType(): Type - { - return TypeCombinator::union(...array_map(static function (PropertyReflection $property): Type { - return $property->getWritableType(); - }, $this->properties)); - } - - public function canChangeTypeAfterAssignment(): bool - { - foreach ($this->properties as $property) { - if (!$property->canChangeTypeAfterAssignment()) { - return false; - } - } - - return true; - } - - public function isReadable(): bool - { - foreach ($this->properties as $property) { - if (!$property->isReadable()) { - return false; - } - } - - return true; - } - - public function isWritable(): bool - { - foreach ($this->properties as $property) { - if (!$property->isWritable()) { - return false; - } - } - - return true; - } - + /** @var PropertyReflection[] */ + private array $properties; + + /** + * @param \PHPStan\Reflection\PropertyReflection[] $properties + */ + public function __construct(array $properties) + { + $this->properties = $properties; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->properties[0]->getDeclaringClass(); + } + + public function isStatic(): bool + { + foreach ($this->properties as $property) { + if (!$property->isStatic()) { + return false; + } + } + + return true; + } + + public function isPrivate(): bool + { + foreach ($this->properties as $property) { + if ($property->isPrivate()) { + return true; + } + } + + return false; + } + + public function isPublic(): bool + { + foreach ($this->properties as $property) { + if (!$property->isPublic()) { + return false; + } + } + + return true; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (PropertyReflection $property): TrinaryLogic { + return $property->isDeprecated(); + }, $this->properties)); + } + + public function getDeprecatedDescription(): ?string + { + $descriptions = []; + foreach ($this->properties as $property) { + if (!$property->isDeprecated()->yes()) { + continue; + } + $description = $property->getDeprecatedDescription(); + if ($description === null) { + continue; + } + + $descriptions[] = $description; + } + + if (count($descriptions) === 0) { + return null; + } + + return implode(' ', $descriptions); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map(static function (PropertyReflection $property): TrinaryLogic { + return $property->isInternal(); + }, $this->properties)); + } + + public function getDocComment(): ?string + { + return null; + } + + public function getReadableType(): Type + { + return TypeCombinator::union(...array_map(static function (PropertyReflection $property): Type { + return $property->getReadableType(); + }, $this->properties)); + } + + public function getWritableType(): Type + { + return TypeCombinator::union(...array_map(static function (PropertyReflection $property): Type { + return $property->getWritableType(); + }, $this->properties)); + } + + public function canChangeTypeAfterAssignment(): bool + { + foreach ($this->properties as $property) { + if (!$property->canChangeTypeAfterAssignment()) { + return false; + } + } + + return true; + } + + public function isReadable(): bool + { + foreach ($this->properties as $property) { + if (!$property->isReadable()) { + return false; + } + } + + return true; + } + + public function isWritable(): bool + { + foreach ($this->properties as $property) { + if (!$property->isWritable()) { + return false; + } + } + + return true; + } } diff --git a/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php index 3d8e295dc7..50b6b59c73 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -methodName = $methodName; - $this->methodPrototypes = $methodPrototypes; - } - - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype): UnresolvedMethodPrototypeReflection { - return $prototype->doNotResolveTemplateTypeMapToBounds(); - }, $this->methodPrototypes)); - } - - public function getNakedMethod(): MethodReflection - { - return $this->getTransformedMethod(); - } - - public function getTransformedMethod(): MethodReflection - { - if ($this->transformedMethod !== null) { - return $this->transformedMethod; - } - - $methods = array_map(static function (UnresolvedMethodPrototypeReflection $prototype): MethodReflection { - return $prototype->getTransformedMethod(); - }, $this->methodPrototypes); - - return $this->transformedMethod = new UnionTypeMethodReflection($this->methodName, $methods); - } - - public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection - { - return new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype) use ($type): UnresolvedMethodPrototypeReflection { - return $prototype->withCalledOnType($type); - }, $this->methodPrototypes)); - } - + private string $methodName; + + /** @var UnresolvedMethodPrototypeReflection[] */ + private array $methodPrototypes; + + private ?MethodReflection $transformedMethod = null; + + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; + + /** + * @param UnresolvedMethodPrototypeReflection[] $methodPrototypes + */ + public function __construct( + string $methodName, + array $methodPrototypes + ) { + $this->methodName = $methodName; + $this->methodPrototypes = $methodPrototypes; + } + + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedMethodPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype): UnresolvedMethodPrototypeReflection { + return $prototype->doNotResolveTemplateTypeMapToBounds(); + }, $this->methodPrototypes)); + } + + public function getNakedMethod(): MethodReflection + { + return $this->getTransformedMethod(); + } + + public function getTransformedMethod(): MethodReflection + { + if ($this->transformedMethod !== null) { + return $this->transformedMethod; + } + + $methods = array_map(static function (UnresolvedMethodPrototypeReflection $prototype): MethodReflection { + return $prototype->getTransformedMethod(); + }, $this->methodPrototypes); + + return $this->transformedMethod = new UnionTypeMethodReflection($this->methodName, $methods); + } + + public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflection + { + return new self($this->methodName, array_map(static function (UnresolvedMethodPrototypeReflection $prototype) use ($type): UnresolvedMethodPrototypeReflection { + return $prototype->withCalledOnType($type); + }, $this->methodPrototypes)); + } } diff --git a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php index ce86e932b2..67f22b4fc5 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php @@ -1,4 +1,6 @@ -propertyName = $methodName; - $this->propertyPrototypes = $propertyPrototypes; - } + private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; - public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection - { - if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { - return $this->cachedDoNotResolveTemplateTypeMapToBounds; - } - return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection { - return $prototype->doNotResolveTemplateTypeMapToBounds(); - }, $this->propertyPrototypes)); - } + /** + * @param UnresolvedPropertyPrototypeReflection[] $propertyPrototypes + */ + public function __construct( + string $methodName, + array $propertyPrototypes + ) { + $this->propertyName = $methodName; + $this->propertyPrototypes = $propertyPrototypes; + } - public function getNakedProperty(): PropertyReflection - { - return $this->getTransformedProperty(); - } + public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection + { + if ($this->cachedDoNotResolveTemplateTypeMapToBounds !== null) { + return $this->cachedDoNotResolveTemplateTypeMapToBounds; + } + return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection { + return $prototype->doNotResolveTemplateTypeMapToBounds(); + }, $this->propertyPrototypes)); + } - public function getTransformedProperty(): PropertyReflection - { - if ($this->transformedProperty !== null) { - return $this->transformedProperty; - } + public function getNakedProperty(): PropertyReflection + { + return $this->getTransformedProperty(); + } - $methods = array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): PropertyReflection { - return $prototype->getTransformedProperty(); - }, $this->propertyPrototypes); + public function getTransformedProperty(): PropertyReflection + { + if ($this->transformedProperty !== null) { + return $this->transformedProperty; + } - return $this->transformedProperty = new UnionTypePropertyReflection($methods); - } + $methods = array_map(static function (UnresolvedPropertyPrototypeReflection $prototype): PropertyReflection { + return $prototype->getTransformedProperty(); + }, $this->propertyPrototypes); - public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection - { - return new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype) use ($type): UnresolvedPropertyPrototypeReflection { - return $prototype->withFechedOnType($type); - }, $this->propertyPrototypes)); - } + return $this->transformedProperty = new UnionTypePropertyReflection($methods); + } + public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection + { + return new self($this->propertyName, array_map(static function (UnresolvedPropertyPrototypeReflection $prototype) use ($type): UnresolvedPropertyPrototypeReflection { + return $prototype->withFechedOnType($type); + }, $this->propertyPrototypes)); + } } diff --git a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php index 27f36dfaa3..79681f6913 100644 --- a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php @@ -1,4 +1,6 @@ -propertyReflectionFinder = $propertyReflectionFinder; - $this->ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ( - !$node instanceof Assign - && !$node instanceof AssignOp - && !$node instanceof AssignRef - ) { - return []; - } - - if (!($node->var instanceof ArrayDimFetch)) { - return []; - } - - if ( - !$node->var->var instanceof \PhpParser\Node\Expr\PropertyFetch - && !$node->var->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch - ) { - return []; - } - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); - if ($propertyReflection === null) { - return []; - } - - $assignedToType = $propertyReflection->getWritableType(); - if (!($assignedToType instanceof ArrayType)) { - return []; - } - - if ($node instanceof Assign || $node instanceof AssignRef) { - $assignedValueType = $scope->getType($node->expr); - } else { - $assignedValueType = $scope->getType($node); - } - - $itemType = $assignedToType->getItemType(); - if (!$this->ruleLevelHelper->accepts($itemType, $assignedValueType, $scope->isDeclareStrictTypes())) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($itemType, $assignedValueType); - return [ - RuleErrorBuilder::message(sprintf( - 'Array (%s) does not accept %s.', - $assignedToType->describe($verbosityLevel), - $assignedValueType->describe($verbosityLevel) - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + public function __construct( + PropertyReflectionFinder $propertyReflectionFinder, + RuleLevelHelper $ruleLevelHelper + ) { + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ( + !$node instanceof Assign + && !$node instanceof AssignOp + && !$node instanceof AssignRef + ) { + return []; + } + + if (!($node->var instanceof ArrayDimFetch)) { + return []; + } + + if ( + !$node->var->var instanceof \PhpParser\Node\Expr\PropertyFetch + && !$node->var->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch + ) { + return []; + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); + if ($propertyReflection === null) { + return []; + } + + $assignedToType = $propertyReflection->getWritableType(); + if (!($assignedToType instanceof ArrayType)) { + return []; + } + + if ($node instanceof Assign || $node instanceof AssignRef) { + $assignedValueType = $scope->getType($node->expr); + } else { + $assignedValueType = $scope->getType($node); + } + + $itemType = $assignedToType->getItemType(); + if (!$this->ruleLevelHelper->accepts($itemType, $assignedValueType, $scope->isDeclareStrictTypes())) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($itemType, $assignedValueType); + return [ + RuleErrorBuilder::message(sprintf( + 'Array (%s) does not accept %s.', + $assignedToType->describe($verbosityLevel), + $assignedValueType->describe($verbosityLevel) + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php index 1dd50f00c1..ff84831fe3 100644 --- a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php +++ b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php @@ -1,4 +1,6 @@ -propertyReflectionFinder = $propertyReflectionFinder; - $this->checkUnionTypes = $checkUnionTypes; - } - - public function getNodeType(): string - { - return Assign::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if (!($node->var instanceof ArrayDimFetch)) { - return []; - } - - if ( - !$node->var->var instanceof \PhpParser\Node\Expr\PropertyFetch - && !$node->var->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch - ) { - return []; - } - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); - if ($propertyReflection === null) { - return []; - } - - $arrayType = $propertyReflection->getReadableType(); - if (!$arrayType instanceof ArrayType) { - return []; - } - - if ($node->var->dim !== null) { - $dimensionType = $scope->getType($node->var->dim); - $isValidKey = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); - if (!$isValidKey->yes()) { - // already handled by InvalidKeyInArrayDimFetchRule - return []; - } - - $keyType = ArrayType::castToArrayKeyType($dimensionType); - if (!$this->checkUnionTypes && $keyType instanceof UnionType) { - return []; - } - } else { - $keyType = new IntegerType(); - } - - if (!$arrayType->getIterableKeyType()->isSuperTypeOf($keyType)->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Array (%s) does not accept key %s.', - $arrayType->describe(VerbosityLevel::typeOnly()), - $keyType->describe(VerbosityLevel::value()) - ))->build(), - ]; - } - - return []; - } - + private PropertyReflectionFinder $propertyReflectionFinder; + + private bool $checkUnionTypes; + + public function __construct( + PropertyReflectionFinder $propertyReflectionFinder, + bool $checkUnionTypes + ) { + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->checkUnionTypes = $checkUnionTypes; + } + + public function getNodeType(): string + { + return Assign::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if (!($node->var instanceof ArrayDimFetch)) { + return []; + } + + if ( + !$node->var->var instanceof \PhpParser\Node\Expr\PropertyFetch + && !$node->var->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch + ) { + return []; + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); + if ($propertyReflection === null) { + return []; + } + + $arrayType = $propertyReflection->getReadableType(); + if (!$arrayType instanceof ArrayType) { + return []; + } + + if ($node->var->dim !== null) { + $dimensionType = $scope->getType($node->var->dim); + $isValidKey = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); + if (!$isValidKey->yes()) { + // already handled by InvalidKeyInArrayDimFetchRule + return []; + } + + $keyType = ArrayType::castToArrayKeyType($dimensionType); + if (!$this->checkUnionTypes && $keyType instanceof UnionType) { + return []; + } + } else { + $keyType = new IntegerType(); + } + + if (!$arrayType->getIterableKeyType()->isSuperTypeOf($keyType)->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Array (%s) does not accept key %s.', + $arrayType->describe(VerbosityLevel::typeOnly()), + $keyType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Arrays/ArrayDestructuringRule.php b/src/Rules/Arrays/ArrayDestructuringRule.php index de523a58c3..6ca64e3c85 100644 --- a/src/Rules/Arrays/ArrayDestructuringRule.php +++ b/src/Rules/Arrays/ArrayDestructuringRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->nonexistentOffsetInArrayDimFetchCheck = $nonexistentOffsetInArrayDimFetchCheck; - } - - public function getNodeType(): string - { - return Assign::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->var instanceof Node\Expr\List_ && !$node->var instanceof Node\Expr\Array_) { - return []; - } - - return $this->getErrors( - $scope, - $node->var, - $node->expr - ); - } - - /** - * @param Node\Expr\List_|Node\Expr\Array_ $var - * @return RuleError[] - */ - private function getErrors(Scope $scope, Expr $var, Expr $expr): array - { - $exprTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $expr, - '', - static function (Type $varType): bool { - return $varType->isArray()->yes(); - } - ); - $exprType = $exprTypeResult->getType(); - if ($exprType instanceof ErrorType) { - return []; - } - if (!$exprType->isArray()->yes()) { - return [ - RuleErrorBuilder::message(sprintf('Cannot use array destructuring on %s.', $exprType->describe(VerbosityLevel::typeOnly())))->build(), - ]; - } - - $errors = []; - $i = 0; - foreach ($var->items as $item) { - if ($item === null) { - $i++; - continue; - } - - $keyExpr = null; - if ($item->key === null) { - $keyType = new ConstantIntegerType($i); - $keyExpr = new Node\Scalar\LNumber($i); - } else { - $keyType = $scope->getType($item->key); - if ($keyType instanceof ConstantIntegerType) { - $keyExpr = new LNumber($keyType->getValue()); - } elseif ($keyType instanceof ConstantStringType) { - $keyExpr = new Node\Scalar\String_($keyType->getValue()); - } - } - - $itemErrors = $this->nonexistentOffsetInArrayDimFetchCheck->check( - $scope, - $expr, - '', - $keyType - ); - $errors = array_merge($errors, $itemErrors); - - if ($keyExpr === null) { - $i++; - continue; - } - - if (!$item->value instanceof Node\Expr\List_ && !$item->value instanceof Node\Expr\Array_) { - $i++; - continue; - } - - $errors = array_merge($errors, $this->getErrors( - $scope, - $item->value, - new Expr\ArrayDimFetch($expr, $keyExpr) - )); - } - - return $errors; - } - + private RuleLevelHelper $ruleLevelHelper; + + private NonexistentOffsetInArrayDimFetchCheck $nonexistentOffsetInArrayDimFetchCheck; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + NonexistentOffsetInArrayDimFetchCheck $nonexistentOffsetInArrayDimFetchCheck + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->nonexistentOffsetInArrayDimFetchCheck = $nonexistentOffsetInArrayDimFetchCheck; + } + + public function getNodeType(): string + { + return Assign::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->var instanceof Node\Expr\List_ && !$node->var instanceof Node\Expr\Array_) { + return []; + } + + return $this->getErrors( + $scope, + $node->var, + $node->expr + ); + } + + /** + * @param Node\Expr\List_|Node\Expr\Array_ $var + * @return RuleError[] + */ + private function getErrors(Scope $scope, Expr $var, Expr $expr): array + { + $exprTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static function (Type $varType): bool { + return $varType->isArray()->yes(); + } + ); + $exprType = $exprTypeResult->getType(); + if ($exprType instanceof ErrorType) { + return []; + } + if (!$exprType->isArray()->yes()) { + return [ + RuleErrorBuilder::message(sprintf('Cannot use array destructuring on %s.', $exprType->describe(VerbosityLevel::typeOnly())))->build(), + ]; + } + + $errors = []; + $i = 0; + foreach ($var->items as $item) { + if ($item === null) { + $i++; + continue; + } + + $keyExpr = null; + if ($item->key === null) { + $keyType = new ConstantIntegerType($i); + $keyExpr = new Node\Scalar\LNumber($i); + } else { + $keyType = $scope->getType($item->key); + if ($keyType instanceof ConstantIntegerType) { + $keyExpr = new LNumber($keyType->getValue()); + } elseif ($keyType instanceof ConstantStringType) { + $keyExpr = new Node\Scalar\String_($keyType->getValue()); + } + } + + $itemErrors = $this->nonexistentOffsetInArrayDimFetchCheck->check( + $scope, + $expr, + '', + $keyType + ); + $errors = array_merge($errors, $itemErrors); + + if ($keyExpr === null) { + $i++; + continue; + } + + if (!$item->value instanceof Node\Expr\List_ && !$item->value instanceof Node\Expr\Array_) { + $i++; + continue; + } + + $errors = array_merge($errors, $this->getErrors( + $scope, + $item->value, + new Expr\ArrayDimFetch($expr, $keyExpr) + )); + } + + return $errors; + } } diff --git a/src/Rules/Arrays/DeadForeachRule.php b/src/Rules/Arrays/DeadForeachRule.php index f24d930be3..d43d0aa869 100644 --- a/src/Rules/Arrays/DeadForeachRule.php +++ b/src/Rules/Arrays/DeadForeachRule.php @@ -1,4 +1,6 @@ -getType($node->expr); - if ($iterableType->isIterable()->no()) { - return []; - } - - if (!$iterableType->isIterableAtLeastOnce()->no()) { - return []; - } - - return [ - RuleErrorBuilder::message('Empty array passed to foreach.')->build(), - ]; - } - + public function getNodeType(): string + { + return Node\Stmt\Foreach_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $iterableType = $scope->getType($node->expr); + if ($iterableType->isIterable()->no()) { + return []; + } + + if (!$iterableType->isIterableAtLeastOnce()->no()) { + return []; + } + + return [ + RuleErrorBuilder::message('Empty array passed to foreach.')->build(), + ]; + } } diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 72e17c0525..60b069c9f3 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -1,4 +1,6 @@ -printer = $printer; - } - - public function getNodeType(): string - { - return LiteralArrayNode::class; - } + public function __construct( + \PhpParser\PrettyPrinter\Standard $printer + ) { + $this->printer = $printer; + } - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - $values = []; - $duplicateKeys = []; - $printedValues = []; - $valueLines = []; - foreach ($node->getItemNodes() as $itemNode) { - $item = $itemNode->getArrayItem(); - if ($item === null) { - continue; - } - if ($item->key === null) { - continue; - } + public function getNodeType(): string + { + return LiteralArrayNode::class; + } - $key = $item->key; - $keyType = $itemNode->getScope()->getType($key); - if ( - !$keyType instanceof ConstantScalarType - ) { - continue; - } + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + $values = []; + $duplicateKeys = []; + $printedValues = []; + $valueLines = []; + foreach ($node->getItemNodes() as $itemNode) { + $item = $itemNode->getArrayItem(); + if ($item === null) { + continue; + } + if ($item->key === null) { + continue; + } - $printedValue = $this->printer->prettyPrintExpr($key); - $value = $keyType->getValue(); - $printedValues[$value][] = $printedValue; + $key = $item->key; + $keyType = $itemNode->getScope()->getType($key); + if ( + !$keyType instanceof ConstantScalarType + ) { + continue; + } - if (!isset($valueLines[$value])) { - $valueLines[$value] = $item->getLine(); - } + $printedValue = $this->printer->prettyPrintExpr($key); + $value = $keyType->getValue(); + $printedValues[$value][] = $printedValue; - $previousCount = count($values); - $values[$value] = $printedValue; - if ($previousCount !== count($values)) { - continue; - } + if (!isset($valueLines[$value])) { + $valueLines[$value] = $item->getLine(); + } - $duplicateKeys[$value] = true; - } + $previousCount = count($values); + $values[$value] = $printedValue; + if ($previousCount !== count($values)) { + continue; + } - $messages = []; - foreach (array_keys($duplicateKeys) as $value) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Array has %d %s with value %s (%s).', - count($printedValues[$value]), - count($printedValues[$value]) === 1 ? 'duplicate key' : 'duplicate keys', - var_export($value, true), - implode(', ', $printedValues[$value]) - ))->line($valueLines[$value])->build(); - } + $duplicateKeys[$value] = true; + } - return $messages; - } + $messages = []; + foreach (array_keys($duplicateKeys) as $value) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Array has %d %s with value %s (%s).', + count($printedValues[$value]), + count($printedValues[$value]) === 1 ? 'duplicate key' : 'duplicate keys', + var_export($value, true), + implode(', ', $printedValues[$value]) + ))->line($valueLines[$value])->build(); + } + return $messages; + } } diff --git a/src/Rules/Arrays/EmptyArrayItemRule.php b/src/Rules/Arrays/EmptyArrayItemRule.php index fa55e6a1f9..b25d0f6858 100644 --- a/src/Rules/Arrays/EmptyArrayItemRule.php +++ b/src/Rules/Arrays/EmptyArrayItemRule.php @@ -1,4 +1,6 @@ -getItemNodes() as $itemNode) { - $item = $itemNode->getArrayItem(); - if ($item !== null) { - continue; - } - - return [ - RuleErrorBuilder::message('Literal array contains empty item.') - ->nonIgnorable() - ->build(), - ]; - } - - return []; - } - + public function getNodeType(): string + { + return LiteralArrayNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + foreach ($node->getItemNodes() as $itemNode) { + $item = $itemNode->getArrayItem(); + if ($item !== null) { + continue; + } + + return [ + RuleErrorBuilder::message('Literal array contains empty item.') + ->nonIgnorable() + ->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index 18b771b859..5066fd3dc0 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -1,4 +1,6 @@ -reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\ArrayDimFetch::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ($node->dim === null) { - return []; - } - - $varType = $scope->getType($node->var); - if (count(TypeUtils::getArrays($varType)) === 0) { - return []; - } - - $dimensionType = $scope->getType($node->dim); - $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); - if ($isSuperType->no()) { - return [ - RuleErrorBuilder::message( - sprintf('Invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) - )->build(), - ]; - } elseif ($this->reportMaybes && $isSuperType->maybe() && !$dimensionType instanceof MixedType) { - return [ - RuleErrorBuilder::message( - sprintf('Possibly invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) - )->build(), - ]; - } - - return []; - } - + private bool $reportMaybes; + + public function __construct(bool $reportMaybes) + { + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\ArrayDimFetch::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ($node->dim === null) { + return []; + } + + $varType = $scope->getType($node->var); + if (count(TypeUtils::getArrays($varType)) === 0) { + return []; + } + + $dimensionType = $scope->getType($node->dim); + $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); + if ($isSuperType->no()) { + return [ + RuleErrorBuilder::message( + sprintf('Invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) + )->build(), + ]; + } elseif ($this->reportMaybes && $isSuperType->maybe() && !$dimensionType instanceof MixedType) { + return [ + RuleErrorBuilder::message( + sprintf('Possibly invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) + )->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 1356bc501c..96b4fb2e53 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -1,4 +1,6 @@ -reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\ArrayItem::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ($node->key === null) { - return []; - } - - $dimensionType = $scope->getType($node->key); - $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); - if ($isSuperType->no()) { - return [ - RuleErrorBuilder::message( - sprintf('Invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) - )->build(), - ]; - } elseif ($this->reportMaybes && $isSuperType->maybe() && !$dimensionType instanceof MixedType) { - return [ - RuleErrorBuilder::message( - sprintf('Possibly invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) - )->build(), - ]; - } - - return []; - } - + private bool $reportMaybes; + + public function __construct(bool $reportMaybes) + { + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\ArrayItem::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ($node->key === null) { + return []; + } + + $dimensionType = $scope->getType($node->key); + $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); + if ($isSuperType->no()) { + return [ + RuleErrorBuilder::message( + sprintf('Invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) + )->build(), + ]; + } elseif ($this->reportMaybes && $isSuperType->maybe() && !$dimensionType instanceof MixedType) { + return [ + RuleErrorBuilder::message( + sprintf('Possibly invalid array key type %s.', $dimensionType->describe(VerbosityLevel::typeOnly())) + )->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Arrays/IterableInForeachRule.php b/src/Rules/Arrays/IterableInForeachRule.php index dafc1c56d7..eeedac7c04 100644 --- a/src/Rules/Arrays/IterableInForeachRule.php +++ b/src/Rules/Arrays/IterableInForeachRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\Foreach_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - 'Iterating over an object of an unknown class %s.', - static function (Type $type): bool { - return $type->isIterable()->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - if ($type->isIterable()->yes()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Argument of an invalid type %s supplied for foreach, only iterables are supported.', - $type->describe(VerbosityLevel::typeOnly()) - ))->line($node->expr->getLine())->build(), - ]; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\Foreach_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + 'Iterating over an object of an unknown class %s.', + static function (Type $type): bool { + return $type->isIterable()->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + if ($type->isIterable()->yes()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Argument of an invalid type %s supplied for foreach, only iterables are supported.', + $type->describe(VerbosityLevel::typeOnly()) + ))->line($node->expr->getLine())->build(), + ]; + } } diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 58eb5e955b..01fc42c0a7 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->reportMaybes = $reportMaybes; - } + private bool $reportMaybes; - /** - * @param Scope $scope - * @param Expr $var - * @param string $unknownClassPattern - * @param Type $dimType - * @return RuleError[] - */ - public function check( - Scope $scope, - Expr $var, - string $unknownClassPattern, - Type $dimType - ): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $var, - $unknownClassPattern, - static function (Type $type) use ($dimType): bool { - return $type->hasOffsetValueType($dimType)->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } + public function __construct(RuleLevelHelper $ruleLevelHelper, bool $reportMaybes) + { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->reportMaybes = $reportMaybes; + } - $hasOffsetValueType = $type->hasOffsetValueType($dimType); - $report = $hasOffsetValueType->no(); - if ($hasOffsetValueType->maybe()) { - $constantArrays = TypeUtils::getOldConstantArrays($type); - if (count($constantArrays) > 0) { - foreach ($constantArrays as $constantArray) { - if ($constantArray->hasOffsetValueType($dimType)->no()) { - $report = true; - break; - } - } - } - } + /** + * @param Scope $scope + * @param Expr $var + * @param string $unknownClassPattern + * @param Type $dimType + * @return RuleError[] + */ + public function check( + Scope $scope, + Expr $var, + string $unknownClassPattern, + Type $dimType + ): array { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $var, + $unknownClassPattern, + static function (Type $type) use ($dimType): bool { + return $type->hasOffsetValueType($dimType)->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } - if (!$report && $this->reportMaybes) { - foreach (TypeUtils::flattenTypes($type) as $innerType) { - if ($dimType instanceof UnionType) { - if ($innerType->hasOffsetValueType($dimType)->no()) { - $report = true; - break; - } - continue; - } - foreach (TypeUtils::flattenTypes($dimType) as $innerDimType) { - if ($innerType->hasOffsetValueType($innerDimType)->no()) { - $report = true; - break; - } - } - } - } + $hasOffsetValueType = $type->hasOffsetValueType($dimType); + $report = $hasOffsetValueType->no(); + if ($hasOffsetValueType->maybe()) { + $constantArrays = TypeUtils::getOldConstantArrays($type); + if (count($constantArrays) > 0) { + foreach ($constantArrays as $constantArray) { + if ($constantArray->hasOffsetValueType($dimType)->no()) { + $report = true; + break; + } + } + } + } - if ($report) { - if ($scope->isInExpressionAssign($var)) { - return []; - } + if (!$report && $this->reportMaybes) { + foreach (TypeUtils::flattenTypes($type) as $innerType) { + if ($dimType instanceof UnionType) { + if ($innerType->hasOffsetValueType($dimType)->no()) { + $report = true; + break; + } + continue; + } + foreach (TypeUtils::flattenTypes($dimType) as $innerDimType) { + if ($innerType->hasOffsetValueType($innerDimType)->no()) { + $report = true; + break; + } + } + } + } - return [ - RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value())))->build(), - ]; - } + if ($report) { + if ($scope->isInExpressionAssign($var)) { + return []; + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value())))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index e5f4e9dd8b..a0b6d801c9 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->nonexistentOffsetInArrayDimFetchCheck = $nonexistentOffsetInArrayDimFetchCheck; - $this->reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\ArrayDimFetch::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ($node->dim !== null) { - $dimType = $scope->getType($node->dim); - $unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', $dimType->describe(VerbosityLevel::value())); - } else { - $dimType = null; - $unknownClassPattern = 'Access to an offset on an unknown class %s.'; - } - - $isOffsetAccessibleTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->var, - $unknownClassPattern, - static function (Type $type): bool { - return $type->isOffsetAccessible()->yes(); - } - ); - $isOffsetAccessibleType = $isOffsetAccessibleTypeResult->getType(); - if ($isOffsetAccessibleType instanceof ErrorType) { - return $isOffsetAccessibleTypeResult->getUnknownClassErrors(); - } - - $isOffsetAccessible = $isOffsetAccessibleType->isOffsetAccessible(); - - if ($scope->isInExpressionAssign($node) && $isOffsetAccessible->yes()) { - return []; - } - - if (!$isOffsetAccessible->yes()) { - if ($isOffsetAccessible->no() || $this->reportMaybes) { - if ($dimType !== null) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot access offset %s on %s.', - $dimType->describe(VerbosityLevel::value()), - $isOffsetAccessibleType->describe(VerbosityLevel::value()) - ))->build(), - ]; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot access an offset on %s.', - $isOffsetAccessibleType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - - return []; - } - - if ($dimType === null) { - return []; - } - - return $this->nonexistentOffsetInArrayDimFetchCheck->check( - $scope, - $node->var, - $unknownClassPattern, - $dimType - ); - } - + private RuleLevelHelper $ruleLevelHelper; + + private NonexistentOffsetInArrayDimFetchCheck $nonexistentOffsetInArrayDimFetchCheck; + + private bool $reportMaybes; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + NonexistentOffsetInArrayDimFetchCheck $nonexistentOffsetInArrayDimFetchCheck, + bool $reportMaybes + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->nonexistentOffsetInArrayDimFetchCheck = $nonexistentOffsetInArrayDimFetchCheck; + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\ArrayDimFetch::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ($node->dim !== null) { + $dimType = $scope->getType($node->dim); + $unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', $dimType->describe(VerbosityLevel::value())); + } else { + $dimType = null; + $unknownClassPattern = 'Access to an offset on an unknown class %s.'; + } + + $isOffsetAccessibleTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + $unknownClassPattern, + static function (Type $type): bool { + return $type->isOffsetAccessible()->yes(); + } + ); + $isOffsetAccessibleType = $isOffsetAccessibleTypeResult->getType(); + if ($isOffsetAccessibleType instanceof ErrorType) { + return $isOffsetAccessibleTypeResult->getUnknownClassErrors(); + } + + $isOffsetAccessible = $isOffsetAccessibleType->isOffsetAccessible(); + + if ($scope->isInExpressionAssign($node) && $isOffsetAccessible->yes()) { + return []; + } + + if (!$isOffsetAccessible->yes()) { + if ($isOffsetAccessible->no() || $this->reportMaybes) { + if ($dimType !== null) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot access offset %s on %s.', + $dimType->describe(VerbosityLevel::value()), + $isOffsetAccessibleType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot access an offset on %s.', + $isOffsetAccessibleType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + + return []; + } + + if ($dimType === null) { + return []; + } + + return $this->nonexistentOffsetInArrayDimFetchCheck->check( + $scope, + $node->var, + $unknownClassPattern, + $dimType + ); + } } diff --git a/src/Rules/Arrays/OffsetAccessAssignOpRule.php b/src/Rules/Arrays/OffsetAccessAssignOpRule.php index 054d988fbd..5a17ec8328 100644 --- a/src/Rules/Arrays/OffsetAccessAssignOpRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignOpRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\AssignOp::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if (!$node->var instanceof ArrayDimFetch) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node\Expr\AssignOp::class; + } - $arrayDimFetch = $node->var; + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if (!$node->var instanceof ArrayDimFetch) { + return []; + } - $potentialDimType = null; - if ($arrayDimFetch->dim !== null) { - $potentialDimType = $scope->getType($arrayDimFetch->dim); - } + $arrayDimFetch = $node->var; - $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $arrayDimFetch->var, - '', - static function (Type $varType) use ($potentialDimType): bool { - $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); - return !($arrayDimType instanceof ErrorType); - } - ); - $varType = $varTypeResult->getType(); + $potentialDimType = null; + if ($arrayDimFetch->dim !== null) { + $potentialDimType = $scope->getType($arrayDimFetch->dim); + } - if ($arrayDimFetch->dim !== null) { - $dimTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $arrayDimFetch->dim, - '', - static function (Type $dimType) use ($varType): bool { - $arrayDimType = $varType->setOffsetValueType($dimType, new MixedType()); - return !($arrayDimType instanceof ErrorType); - } - ); - $dimType = $dimTypeResult->getType(); - if ($varType->hasOffsetValueType($dimType)->no()) { - return []; - } - } else { - $dimType = $potentialDimType; - } + $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $arrayDimFetch->var, + '', + static function (Type $varType) use ($potentialDimType): bool { + $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); + return !($arrayDimType instanceof ErrorType); + } + ); + $varType = $varTypeResult->getType(); - $resultType = $varType->setOffsetValueType($dimType, new MixedType()); - if (!($resultType instanceof ErrorType)) { - return []; - } + if ($arrayDimFetch->dim !== null) { + $dimTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $arrayDimFetch->dim, + '', + static function (Type $dimType) use ($varType): bool { + $arrayDimType = $varType->setOffsetValueType($dimType, new MixedType()); + return !($arrayDimType instanceof ErrorType); + } + ); + $dimType = $dimTypeResult->getType(); + if ($varType->hasOffsetValueType($dimType)->no()) { + return []; + } + } else { + $dimType = $potentialDimType; + } - if ($dimType === null) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot assign new offset to %s.', - $varType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + $resultType = $varType->setOffsetValueType($dimType, new MixedType()); + if (!($resultType instanceof ErrorType)) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot assign offset %s to %s.', - $dimType->describe(VerbosityLevel::value()), - $varType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + if ($dimType === null) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot assign new offset to %s.', + $varType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot assign offset %s to %s.', + $dimType->describe(VerbosityLevel::value()), + $varType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/Arrays/OffsetAccessAssignmentRule.php b/src/Rules/Arrays/OffsetAccessAssignmentRule.php index e111fca026..39d011d690 100644 --- a/src/Rules/Arrays/OffsetAccessAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignmentRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\ArrayDimFetch::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if (!$scope->isInExpressionAssign($node)) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node\Expr\ArrayDimFetch::class; + } - $potentialDimType = null; - if ($node->dim !== null) { - $potentialDimType = $scope->getType($node->dim); - } + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if (!$scope->isInExpressionAssign($node)) { + return []; + } - $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->var, - '', - static function (Type $varType) use ($potentialDimType): bool { - $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); - return !($arrayDimType instanceof ErrorType); - } - ); - $varType = $varTypeResult->getType(); - if ($varType instanceof ErrorType) { - return []; - } - if (!$varType->isOffsetAccessible()->yes()) { - return []; - } + $potentialDimType = null; + if ($node->dim !== null) { + $potentialDimType = $scope->getType($node->dim); + } - if ($node->dim !== null) { - $dimTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->dim, - '', - static function (Type $dimType) use ($varType): bool { - $arrayDimType = $varType->setOffsetValueType($dimType, new MixedType()); - return !($arrayDimType instanceof ErrorType); - } - ); - $dimType = $dimTypeResult->getType(); - } else { - $dimType = $potentialDimType; - } + $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + '', + static function (Type $varType) use ($potentialDimType): bool { + $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); + return !($arrayDimType instanceof ErrorType); + } + ); + $varType = $varTypeResult->getType(); + if ($varType instanceof ErrorType) { + return []; + } + if (!$varType->isOffsetAccessible()->yes()) { + return []; + } - $resultType = $varType->setOffsetValueType($dimType, new MixedType()); - if (!($resultType instanceof ErrorType)) { - return []; - } + if ($node->dim !== null) { + $dimTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->dim, + '', + static function (Type $dimType) use ($varType): bool { + $arrayDimType = $varType->setOffsetValueType($dimType, new MixedType()); + return !($arrayDimType instanceof ErrorType); + } + ); + $dimType = $dimTypeResult->getType(); + } else { + $dimType = $potentialDimType; + } - if ($dimType === null) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot assign new offset to %s.', - $varType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + $resultType = $varType->setOffsetValueType($dimType, new MixedType()); + if (!($resultType instanceof ErrorType)) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot assign offset %s to %s.', - $dimType->describe(VerbosityLevel::value()), - $varType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + if ($dimType === null) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot assign new offset to %s.', + $varType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot assign offset %s to %s.', + $dimType->describe(VerbosityLevel::value()), + $varType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php index ea571f6499..55f75e1a86 100644 --- a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Expr::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ( - !$node instanceof Assign - && !$node instanceof AssignOp - && !$node instanceof Expr\AssignRef - ) { - return []; - } + public function getNodeType(): string + { + return Expr::class; + } - if (!$node->var instanceof Expr\ArrayDimFetch) { - return []; - } + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ( + !$node instanceof Assign + && !$node instanceof AssignOp + && !$node instanceof Expr\AssignRef + ) { + return []; + } - $arrayDimFetch = $node->var; + if (!$node->var instanceof Expr\ArrayDimFetch) { + return []; + } - if ($node instanceof Assign || $node instanceof Expr\AssignRef) { - $assignedValueType = $scope->getType($node->expr); - } else { - $assignedValueType = $scope->getType($node); - } + $arrayDimFetch = $node->var; - $originalArrayType = $scope->getType($arrayDimFetch->var); - $arrayTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $arrayDimFetch->var, - '', - static function (Type $varType) use ($assignedValueType): bool { - $result = $varType->setOffsetValueType(new MixedType(), $assignedValueType); - return !($result instanceof ErrorType); - } - ); - $arrayType = $arrayTypeResult->getType(); - if ($arrayType instanceof ErrorType) { - return []; - } - $isOffsetAccessible = $arrayType->isOffsetAccessible(); - if (!$isOffsetAccessible->yes()) { - return []; - } - $resultType = $arrayType->setOffsetValueType(new MixedType(), $assignedValueType); - if (!$resultType instanceof ErrorType) { - return []; - } + if ($node instanceof Assign || $node instanceof Expr\AssignRef) { + $assignedValueType = $scope->getType($node->expr); + } else { + $assignedValueType = $scope->getType($node); + } - return [ - RuleErrorBuilder::message(sprintf( - '%s does not accept %s.', - $originalArrayType->describe(VerbosityLevel::value()), - $assignedValueType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + $originalArrayType = $scope->getType($arrayDimFetch->var); + $arrayTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $arrayDimFetch->var, + '', + static function (Type $varType) use ($assignedValueType): bool { + $result = $varType->setOffsetValueType(new MixedType(), $assignedValueType); + return !($result instanceof ErrorType); + } + ); + $arrayType = $arrayTypeResult->getType(); + if ($arrayType instanceof ErrorType) { + return []; + } + $isOffsetAccessible = $arrayType->isOffsetAccessible(); + if (!$isOffsetAccessible->yes()) { + return []; + } + $resultType = $arrayType->setOffsetValueType(new MixedType(), $assignedValueType); + if (!$resultType instanceof ErrorType) { + return []; + } + return [ + RuleErrorBuilder::message(sprintf( + '%s does not accept %s.', + $originalArrayType->describe(VerbosityLevel::value()), + $assignedValueType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php b/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php index cfc5fdb66c..22da3b4740 100644 --- a/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php +++ b/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php @@ -1,4 +1,6 @@ -isInExpressionAssign($node)) { - return []; - } - - if ($node->dim !== null) { - return []; - } - - return [ - RuleErrorBuilder::message('Cannot use [] for reading.')->nonIgnorable()->build(), - ]; - } - + public function getNodeType(): string + { + return \PhpParser\Node\Expr\ArrayDimFetch::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ($scope->isInExpressionAssign($node)) { + return []; + } + + if ($node->dim !== null) { + return []; + } + + return [ + RuleErrorBuilder::message('Cannot use [] for reading.')->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Arrays/UnpackIterableInArrayRule.php b/src/Rules/Arrays/UnpackIterableInArrayRule.php index a061c670a2..a5a3951886 100644 --- a/src/Rules/Arrays/UnpackIterableInArrayRule.php +++ b/src/Rules/Arrays/UnpackIterableInArrayRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return LiteralArrayNode::class; - } + public function __construct( + RuleLevelHelper $ruleLevelHelper + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - $errors = []; - foreach ($node->getItemNodes() as $itemNode) { - $item = $itemNode->getArrayItem(); - if ($item === null) { - continue; - } - if (!$item->unpack) { - continue; - } + public function getNodeType(): string + { + return LiteralArrayNode::class; + } - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $item->value, - '', - static function (Type $type): bool { - return $type->isIterable()->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - continue; - } + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->getItemNodes() as $itemNode) { + $item = $itemNode->getArrayItem(); + if ($item === null) { + continue; + } + if (!$item->unpack) { + continue; + } - if ($type->isIterable()->yes()) { - continue; - } + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $item->value, + '', + static function (Type $type): bool { + return $type->isIterable()->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + continue; + } - $errors[] = RuleErrorBuilder::message(sprintf( - 'Only iterables can be unpacked, %s given.', - $type->describe(VerbosityLevel::typeOnly()) - ))->line($item->getLine())->build(); - } + if ($type->isIterable()->yes()) { + continue; + } - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Only iterables can be unpacked, %s given.', + $type->describe(VerbosityLevel::typeOnly()) + ))->line($item->getLine())->build(); + } + return $errors; + } } diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 563a50e275..ebe316f47a 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->functionCallParametersCheck = $functionCallParametersCheck; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - } - - /** - * @param AttributeGroup[] $attrGroups - * @param \Attribute::TARGET_* $requiredTarget - * @return RuleError[] - */ - public function check( - Scope $scope, - array $attrGroups, - int $requiredTarget, - string $targetName - ): array - { - $errors = []; - $alreadyPresent = []; - foreach ($attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attribute) { - $name = $attribute->name->toString(); - if (!$this->reflectionProvider->hasClass($name)) { - $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not exist.', $name))->line($attribute->getLine())->build(); - continue; - } - - $attributeClass = $this->reflectionProvider->getClass($name); - if (!$attributeClass->isAttributeClass()) { - $errors[] = RuleErrorBuilder::message(sprintf('Class %s is not an Attribute class.', $attributeClass->getDisplayName()))->line($attribute->getLine())->build(); - continue; - } - - if ($attributeClass->isAbstract()) { - $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s is abstract.', $name))->line($attribute->getLine())->build(); - } - - foreach ($this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($name, $attribute)]) as $caseSensitivityError) { - $errors[] = $caseSensitivityError; - } - - $flags = $attributeClass->getAttributeClassFlags(); - if (($flags & $requiredTarget) === 0) { - $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not have the %s target.', $name, $targetName))->line($attribute->getLine())->build(); - } - - if (($flags & \Attribute::IS_REPEATABLE) === 0) { - $loweredName = strtolower($name); - if (array_key_exists($loweredName, $alreadyPresent)) { - $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s is not repeatable but is already present above the %s.', $name, $targetName))->line($attribute->getLine())->build(); - } - - $alreadyPresent[$loweredName] = true; - } - - if (!$attributeClass->hasConstructor()) { - if (count($attribute->args) > 0) { - $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not have a constructor and must be instantiated without any parameters.', $name))->line($attribute->getLine())->build(); - } - continue; - } - - $attributeConstructor = $attributeClass->getConstructor(); - if (!$attributeConstructor->isPublic()) { - $errors[] = RuleErrorBuilder::message(sprintf('Constructor of attribute class %s is not public.', $name))->line($attribute->getLine())->build(); - } - - $parameterErrors = $this->functionCallParametersCheck->check( - ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants()), - $scope, - $attributeConstructor->getDeclaringClass()->isBuiltin(), - new New_($attribute->name, $attribute->args, $attribute->getAttributes()), - [ - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor expects %s, %s given.', - '', // constructor does not have a return type - 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClass->getDisplayName(), - 'Missing parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', - 'Unknown parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', - ] - ); - - foreach ($parameterErrors as $error) { - $errors[] = $error; - } - } - } - - return $errors; - } - + private ReflectionProvider $reflectionProvider; + + private FunctionCallParametersCheck $functionCallParametersCheck; + + private ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + public function __construct( + ReflectionProvider $reflectionProvider, + FunctionCallParametersCheck $functionCallParametersCheck, + ClassCaseSensitivityCheck $classCaseSensitivityCheck + ) { + $this->reflectionProvider = $reflectionProvider; + $this->functionCallParametersCheck = $functionCallParametersCheck; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + } + + /** + * @param AttributeGroup[] $attrGroups + * @param \Attribute::TARGET_* $requiredTarget + * @return RuleError[] + */ + public function check( + Scope $scope, + array $attrGroups, + int $requiredTarget, + string $targetName + ): array { + $errors = []; + $alreadyPresent = []; + foreach ($attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + $name = $attribute->name->toString(); + if (!$this->reflectionProvider->hasClass($name)) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not exist.', $name))->line($attribute->getLine())->build(); + continue; + } + + $attributeClass = $this->reflectionProvider->getClass($name); + if (!$attributeClass->isAttributeClass()) { + $errors[] = RuleErrorBuilder::message(sprintf('Class %s is not an Attribute class.', $attributeClass->getDisplayName()))->line($attribute->getLine())->build(); + continue; + } + + if ($attributeClass->isAbstract()) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s is abstract.', $name))->line($attribute->getLine())->build(); + } + + foreach ($this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($name, $attribute)]) as $caseSensitivityError) { + $errors[] = $caseSensitivityError; + } + + $flags = $attributeClass->getAttributeClassFlags(); + if (($flags & $requiredTarget) === 0) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not have the %s target.', $name, $targetName))->line($attribute->getLine())->build(); + } + + if (($flags & \Attribute::IS_REPEATABLE) === 0) { + $loweredName = strtolower($name); + if (array_key_exists($loweredName, $alreadyPresent)) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s is not repeatable but is already present above the %s.', $name, $targetName))->line($attribute->getLine())->build(); + } + + $alreadyPresent[$loweredName] = true; + } + + if (!$attributeClass->hasConstructor()) { + if (count($attribute->args) > 0) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s does not have a constructor and must be instantiated without any parameters.', $name))->line($attribute->getLine())->build(); + } + continue; + } + + $attributeConstructor = $attributeClass->getConstructor(); + if (!$attributeConstructor->isPublic()) { + $errors[] = RuleErrorBuilder::message(sprintf('Constructor of attribute class %s is not public.', $name))->line($attribute->getLine())->build(); + } + + $parameterErrors = $this->functionCallParametersCheck->check( + ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants()), + $scope, + $attributeConstructor->getDeclaringClass()->isBuiltin(), + new New_($attribute->name, $attribute->args, $attribute->getAttributes()), + [ + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d required.', + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d required.', + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', + 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor expects %s, %s given.', + '', // constructor does not have a return type + 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClass->getDisplayName(), + 'Missing parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', + 'Unknown parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', + ] + ); + + foreach ($parameterErrors as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } } diff --git a/src/Rules/Cast/EchoRule.php b/src/Rules/Cast/EchoRule.php index b0ede225b3..8e5790f83a 100644 --- a/src/Rules/Cast/EchoRule.php +++ b/src/Rules/Cast/EchoRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Stmt\Echo_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $messages = []; - - foreach ($node->exprs as $key => $expr) { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $expr, - '', - static function (Type $type): bool { - return !$type->toString() instanceof ErrorType; - } - ); - - if ($typeResult->getType() instanceof ErrorType - || !$typeResult->getType()->toString() instanceof ErrorType - ) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d (%s) of echo cannot be converted to string.', - $key + 1, - $typeResult->getType()->describe(VerbosityLevel::value()) - ))->line($expr->getLine())->build(); - } - return $messages; - } - + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Stmt\Echo_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + + foreach ($node->exprs as $key => $expr) { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static function (Type $type): bool { + return !$type->toString() instanceof ErrorType; + } + ); + + if ($typeResult->getType() instanceof ErrorType + || !$typeResult->getType()->toString() instanceof ErrorType + ) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d (%s) of echo cannot be converted to string.', + $key + 1, + $typeResult->getType()->describe(VerbosityLevel::value()) + ))->line($expr->getLine())->build(); + } + return $messages; + } } diff --git a/src/Rules/Cast/InvalidCastRule.php b/src/Rules/Cast/InvalidCastRule.php index 7a2e63be5f..7509a1df8f 100644 --- a/src/Rules/Cast/InvalidCastRule.php +++ b/src/Rules/Cast/InvalidCastRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->ruleLevelHelper = $ruleLevelHelper; - } + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; - public function getNodeType(): string - { - return \PhpParser\Node\Expr\Cast::class; - } + public function __construct( + ReflectionProvider $reflectionProvider, + RuleLevelHelper $ruleLevelHelper + ) { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - $castTypeCallback = static function (Type $type) use ($node): ?Type { - if ($node instanceof \PhpParser\Node\Expr\Cast\Int_) { - return $type->toInteger(); - } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Bool_) { - return $type->toBoolean(); - } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Double) { - return $type->toFloat(); - } elseif ($node instanceof \PhpParser\Node\Expr\Cast\String_) { - return $type->toString(); - } + public function getNodeType(): string + { + return \PhpParser\Node\Expr\Cast::class; + } - return null; - }; + public function processNode(Node $node, Scope $scope): array + { + $castTypeCallback = static function (Type $type) use ($node): ?Type { + if ($node instanceof \PhpParser\Node\Expr\Cast\Int_) { + return $type->toInteger(); + } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Bool_) { + return $type->toBoolean(); + } elseif ($node instanceof \PhpParser\Node\Expr\Cast\Double) { + return $type->toFloat(); + } elseif ($node instanceof \PhpParser\Node\Expr\Cast\String_) { + return $type->toString(); + } - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - '', - static function (Type $type) use ($castTypeCallback): bool { - $castType = $castTypeCallback($type); - if ($castType === null) { - return true; - } + return null; + }; - return !$castType instanceof ErrorType; - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return []; - } + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + '', + static function (Type $type) use ($castTypeCallback): bool { + $castType = $castTypeCallback($type); + if ($castType === null) { + return true; + } - $castType = $castTypeCallback($type); - if ($castType instanceof ErrorType) { - $classReflection = $this->reflectionProvider->getClass(get_class($node)); - $shortName = $classReflection->getNativeReflection()->getShortName(); - $shortName = strtolower($shortName); - if ($shortName === 'double') { - $shortName = 'float'; - } else { - $shortName = substr($shortName, 0, -1); - } + return !$castType instanceof ErrorType; + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot cast %s to %s.', - $scope->getType($node->expr)->describe(VerbosityLevel::value()), - $shortName - ))->line($node->getLine())->build(), - ]; - } + $castType = $castTypeCallback($type); + if ($castType instanceof ErrorType) { + $classReflection = $this->reflectionProvider->getClass(get_class($node)); + $shortName = $classReflection->getNativeReflection()->getShortName(); + $shortName = strtolower($shortName); + if ($shortName === 'double') { + $shortName = 'float'; + } else { + $shortName = substr($shortName, 0, -1); + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot cast %s to %s.', + $scope->getType($node->expr)->describe(VerbosityLevel::value()), + $shortName + ))->line($node->getLine())->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php index 54bb38829a..95f1d3d5d9 100644 --- a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php +++ b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php @@ -1,4 +1,6 @@ -printer = $printer; - $this->ruleLevelHelper = $ruleLevelHelper; - } + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; - public function getNodeType(): string - { - return \PhpParser\Node\Scalar\Encapsed::class; - } + public function __construct( + \PhpParser\PrettyPrinter\Standard $printer, + RuleLevelHelper $ruleLevelHelper + ) { + $this->printer = $printer; + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - $messages = []; - foreach ($node->parts as $part) { - if ($part instanceof Node\Scalar\EncapsedStringPart) { - continue; - } + public function getNodeType(): string + { + return \PhpParser\Node\Scalar\Encapsed::class; + } - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $part, - '', - static function (Type $type): bool { - return !$type->toString() instanceof ErrorType; - } - ); - $partType = $typeResult->getType(); - if ($partType instanceof ErrorType) { - continue; - } + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + foreach ($node->parts as $part) { + if ($part instanceof Node\Scalar\EncapsedStringPart) { + continue; + } - $stringPartType = $partType->toString(); - if (!$stringPartType instanceof ErrorType) { - continue; - } - $messages[] = RuleErrorBuilder::message(sprintf( - 'Part %s (%s) of encapsed string cannot be cast to string.', - $this->printer->prettyPrintExpr($part), - $partType->describe(VerbosityLevel::value()) - ))->line($part->getLine())->build(); - } + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $part, + '', + static function (Type $type): bool { + return !$type->toString() instanceof ErrorType; + } + ); + $partType = $typeResult->getType(); + if ($partType instanceof ErrorType) { + continue; + } - return $messages; - } + $stringPartType = $partType->toString(); + if (!$stringPartType instanceof ErrorType) { + continue; + } + $messages[] = RuleErrorBuilder::message(sprintf( + 'Part %s (%s) of encapsed string cannot be cast to string.', + $this->printer->prettyPrintExpr($part), + $partType->describe(VerbosityLevel::value()) + ))->line($part->getLine())->build(); + } + return $messages; + } } diff --git a/src/Rules/Cast/PrintRule.php b/src/Rules/Cast/PrintRule.php index 3e719af4b5..3c49fdcb6d 100644 --- a/src/Rules/Cast/PrintRule.php +++ b/src/Rules/Cast/PrintRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Expr\Print_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - '', - static function (Type $type): bool { - return !$type->toString() instanceof ErrorType; - } - ); - - if (!$typeResult->getType() instanceof ErrorType - && $typeResult->getType()->toString() instanceof ErrorType - ) { - return [RuleErrorBuilder::message(sprintf( - 'Parameter %s of print cannot be converted to string.', - $typeResult->getType()->describe(VerbosityLevel::value()) - ))->line($node->expr->getLine())->build()]; - } - - return []; - } - + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr\Print_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + '', + static function (Type $type): bool { + return !$type->toString() instanceof ErrorType; + } + ); + + if (!$typeResult->getType() instanceof ErrorType + && $typeResult->getType()->toString() instanceof ErrorType + ) { + return [RuleErrorBuilder::message(sprintf( + 'Parameter %s of print cannot be converted to string.', + $typeResult->getType()->describe(VerbosityLevel::value()) + ))->line($node->expr->getLine())->build()]; + } + + return []; + } } diff --git a/src/Rules/Cast/UnsetCastRule.php b/src/Rules/Cast/UnsetCastRule.php index c50aca654a..6616e0d1ee 100644 --- a/src/Rules/Cast/UnsetCastRule.php +++ b/src/Rules/Cast/UnsetCastRule.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function getNodeType(): string - { - return Node\Expr\Cast\Unset_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->phpVersion->supportsUnsetCast()) { - return []; - } - - return [ - RuleErrorBuilder::message('The (unset) cast is no longer supported in PHP 8.0 and later.')->nonIgnorable()->build(), - ]; - } - + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return Node\Expr\Cast\Unset_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->phpVersion->supportsUnsetCast()) { + return []; + } + + return [ + RuleErrorBuilder::message('The (unset) cast is no longer supported in PHP 8.0 and later.')->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/ClassCaseSensitivityCheck.php b/src/Rules/ClassCaseSensitivityCheck.php index e3e1e67cda..aa6bcc4daa 100644 --- a/src/Rules/ClassCaseSensitivityCheck.php +++ b/src/Rules/ClassCaseSensitivityCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->checkInternalClassCaseSensitivity = $checkInternalClassCaseSensitivity; - } + private bool $checkInternalClassCaseSensitivity; - /** - * @param ClassNameNodePair[] $pairs - * @return RuleError[] - */ - public function checkClassNames(array $pairs): array - { - $errors = []; - foreach ($pairs as $pair) { - $className = $pair->getClassName(); - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - $classReflection = $this->reflectionProvider->getClass($className); - if (!$this->checkInternalClassCaseSensitivity && $classReflection->isBuiltin()) { - continue; // skip built-in classes - } - $realClassName = $classReflection->getName(); - if (strtolower($realClassName) !== strtolower($className)) { - continue; // skip class alias - } - if ($realClassName === $className) { - continue; - } + public function __construct(ReflectionProvider $reflectionProvider, bool $checkInternalClassCaseSensitivity = false) + { + $this->reflectionProvider = $reflectionProvider; + $this->checkInternalClassCaseSensitivity = $checkInternalClassCaseSensitivity; + } - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s referenced with incorrect case: %s.', - $this->getTypeName($classReflection), - $realClassName, - $className - ))->line($pair->getNode()->getLine())->build(); - } + /** + * @param ClassNameNodePair[] $pairs + * @return RuleError[] + */ + public function checkClassNames(array $pairs): array + { + $errors = []; + foreach ($pairs as $pair) { + $className = $pair->getClassName(); + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + $classReflection = $this->reflectionProvider->getClass($className); + if (!$this->checkInternalClassCaseSensitivity && $classReflection->isBuiltin()) { + continue; // skip built-in classes + } + $realClassName = $classReflection->getName(); + if (strtolower($realClassName) !== strtolower($className)) { + continue; // skip class alias + } + if ($realClassName === $className) { + continue; + } - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s referenced with incorrect case: %s.', + $this->getTypeName($classReflection), + $realClassName, + $className + ))->line($pair->getNode()->getLine())->build(); + } - private function getTypeName(ClassReflection $classReflection): string - { - if ($classReflection->isInterface()) { - return 'Interface'; - } elseif ($classReflection->isTrait()) { - return 'Trait'; - } + return $errors; + } - return 'Class'; - } + private function getTypeName(ClassReflection $classReflection): string + { + if ($classReflection->isInterface()) { + return 'Interface'; + } elseif ($classReflection->isTrait()) { + return 'Trait'; + } + return 'Class'; + } } diff --git a/src/Rules/ClassNameNodePair.php b/src/Rules/ClassNameNodePair.php index a92f539071..805acd8a83 100644 --- a/src/Rules/ClassNameNodePair.php +++ b/src/Rules/ClassNameNodePair.php @@ -1,4 +1,6 @@ -className = $className; - $this->node = $node; - } + private Node $node; - public function getClassName(): string - { - return $this->className; - } + public function __construct(string $className, Node $node) + { + $this->className = $className; + $this->node = $node; + } - public function getNode(): Node - { - return $this->node; - } + public function getClassName(): string + { + return $this->className; + } + public function getNode(): Node + { + return $this->node; + } } diff --git a/src/Rules/Classes/ClassAttributesRule.php b/src/Rules/Classes/ClassAttributesRule.php index 839a8fd112..27c5440f5a 100644 --- a/src/Rules/Classes/ClassAttributesRule.php +++ b/src/Rules/Classes/ClassAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Stmt\ClassLike::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_CLASS, - 'class' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassLike::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_CLASS, + 'class' + ); + } } diff --git a/src/Rules/Classes/ClassConstantAttributesRule.php b/src/Rules/Classes/ClassConstantAttributesRule.php index ded689c6ee..7ba70e436c 100644 --- a/src/Rules/Classes/ClassConstantAttributesRule.php +++ b/src/Rules/Classes/ClassConstantAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Stmt\ClassConst::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_CLASS_CONSTANT, - 'class constant' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_CLASS_CONSTANT, + 'class constant' + ); + } } diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 64d4e11bec..335defbcf4 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->phpVersion = $phpVersion; - } - - public function getNodeType(): string - { - return ClassConstFetch::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier) { - return []; - } - $constantName = $node->name->name; - - $class = $node->class; - $messages = []; - if ($class instanceof \PhpParser\Node\Name) { - $className = (string) $class; - $lowercasedClassName = strtolower($className); - if (in_array($lowercasedClassName, ['self', 'static'], true)) { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className))->build(), - ]; - } - - $classType = $scope->resolveTypeByName($class); - } elseif ($lowercasedClassName === 'parent') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className))->build(), - ]; - } - $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === false) { - return [ - RuleErrorBuilder::message(sprintf( - 'Access to parent::%s but %s does not extend any class.', - $constantName, - $currentClassReflection->getDisplayName() - ))->build(), - ]; - } - $classType = $scope->resolveTypeByName($class); - } else { - if (!$this->reflectionProvider->hasClass($className)) { - if ($scope->isInClassExists($className)) { - return []; - } - - if (strtolower($constantName) === 'class') { - return [ - RuleErrorBuilder::message(sprintf('Class %s not found.', $className))->discoveringSymbolsTip()->build(), - ]; - } - - return [ - RuleErrorBuilder::message( - sprintf('Access to constant %s on an unknown class %s.', $constantName, $className) - )->discoveringSymbolsTip()->build(), - ]; - } else { - $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); - } - - $classType = $scope->resolveTypeByName($class); - } - - if (strtolower($constantName) === 'class') { - return $messages; - } - } else { - $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $class, - sprintf('Access to constant %s on an unknown class %%s.', $constantName), - static function (Type $type) use ($constantName): bool { - return $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(); - } - ); - $classType = $classTypeResult->getType(); - if ($classType instanceof ErrorType) { - return $classTypeResult->getUnknownClassErrors(); - } - - if (strtolower($constantName) === 'class') { - if (!$this->phpVersion->supportsClassConstantOnExpression()) { - return [ - RuleErrorBuilder::message('Accessing ::class constant on an expression is supported only on PHP 8.0 and later.') - ->nonIgnorable() - ->build(), - ]; - } - - if ((new StringType())->isSuperTypeOf($classType)->yes()) { - return [ - RuleErrorBuilder::message('Accessing ::class constant on a dynamic string is not supported in PHP.') - ->nonIgnorable() - ->build(), - ]; - } - } - } - - if ((new StringType())->isSuperTypeOf($classType)->yes()) { - return $messages; - } - - $typeForDescribe = $classType; - if ($classType instanceof ThisType) { - $typeForDescribe = $classType->getStaticObjectType(); - } - $classType = TypeCombinator::remove($classType, new StringType()); - - if (!$classType->canAccessConstants()->yes()) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Cannot access constant %s on %s.', - $constantName, - $typeForDescribe->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]); - } - - if (strtolower($constantName) === 'class') { - return $messages; - } - - if (!$classType->hasConstant($constantName)->yes()) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Access to undefined constant %s::%s.', - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - $constantName - ))->build(), - ]); - } - - $constantReflection = $classType->getConstant($constantName); - if (!$scope->canAccessConstant($constantReflection)) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Access to %s constant %s of class %s.', - $constantReflection->isPrivate() ? 'private' : 'protected', - $constantName, - $constantReflection->getDeclaringClass()->getDisplayName() - ))->build(), - ]); - } - - return $messages; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private PhpVersion $phpVersion; + + public function __construct( + ReflectionProvider $reflectionProvider, + RuleLevelHelper $ruleLevelHelper, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + PhpVersion $phpVersion + ) { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return ClassConstFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + $constantName = $node->name->name; + + $class = $node->class; + $messages = []; + if ($class instanceof \PhpParser\Node\Name) { + $className = (string) $class; + $lowercasedClassName = strtolower($className); + if (in_array($lowercasedClassName, ['self', 'static'], true)) { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className))->build(), + ]; + } + + $classType = $scope->resolveTypeByName($class); + } elseif ($lowercasedClassName === 'parent') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $className))->build(), + ]; + } + $currentClassReflection = $scope->getClassReflection(); + if ($currentClassReflection->getParentClass() === false) { + return [ + RuleErrorBuilder::message(sprintf( + 'Access to parent::%s but %s does not extend any class.', + $constantName, + $currentClassReflection->getDisplayName() + ))->build(), + ]; + } + $classType = $scope->resolveTypeByName($class); + } else { + if (!$this->reflectionProvider->hasClass($className)) { + if ($scope->isInClassExists($className)) { + return []; + } + + if (strtolower($constantName) === 'class') { + return [ + RuleErrorBuilder::message(sprintf('Class %s not found.', $className))->discoveringSymbolsTip()->build(), + ]; + } + + return [ + RuleErrorBuilder::message( + sprintf('Access to constant %s on an unknown class %s.', $constantName, $className) + )->discoveringSymbolsTip()->build(), + ]; + } else { + $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); + } + + $classType = $scope->resolveTypeByName($class); + } + + if (strtolower($constantName) === 'class') { + return $messages; + } + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $class, + sprintf('Access to constant %s on an unknown class %%s.', $constantName), + static function (Type $type) use ($constantName): bool { + return $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(); + } + ); + $classType = $classTypeResult->getType(); + if ($classType instanceof ErrorType) { + return $classTypeResult->getUnknownClassErrors(); + } + + if (strtolower($constantName) === 'class') { + if (!$this->phpVersion->supportsClassConstantOnExpression()) { + return [ + RuleErrorBuilder::message('Accessing ::class constant on an expression is supported only on PHP 8.0 and later.') + ->nonIgnorable() + ->build(), + ]; + } + + if ((new StringType())->isSuperTypeOf($classType)->yes()) { + return [ + RuleErrorBuilder::message('Accessing ::class constant on a dynamic string is not supported in PHP.') + ->nonIgnorable() + ->build(), + ]; + } + } + } + + if ((new StringType())->isSuperTypeOf($classType)->yes()) { + return $messages; + } + + $typeForDescribe = $classType; + if ($classType instanceof ThisType) { + $typeForDescribe = $classType->getStaticObjectType(); + } + $classType = TypeCombinator::remove($classType, new StringType()); + + if (!$classType->canAccessConstants()->yes()) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Cannot access constant %s on %s.', + $constantName, + $typeForDescribe->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]); + } + + if (strtolower($constantName) === 'class') { + return $messages; + } + + if (!$classType->hasConstant($constantName)->yes()) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Access to undefined constant %s::%s.', + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + $constantName + ))->build(), + ]); + } + + $constantReflection = $classType->getConstant($constantName); + if (!$scope->canAccessConstant($constantReflection)) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Access to %s constant %s of class %s.', + $constantReflection->isPrivate() ? 'private' : 'protected', + $constantName, + $constantReflection->getDeclaringClass()->getDisplayName() + ))->build(), + ]); + } + + return $messages; + } } diff --git a/src/Rules/Classes/DuplicateDeclarationRule.php b/src/Rules/Classes/DuplicateDeclarationRule.php index eaee0ead9f..95d53ed4eb 100644 --- a/src/Rules/Classes/DuplicateDeclarationRule.php +++ b/src/Rules/Classes/DuplicateDeclarationRule.php @@ -1,4 +1,6 @@ -getClassReflection(); - if ($classReflection === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $errors = []; + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $scope->getClassReflection(); + if ($classReflection === null) { + throw new \PHPStan\ShouldNotHappenException(); + } - $declaredClassConstants = []; - foreach ($node->getOriginalNode()->getConstants() as $constDecl) { - foreach ($constDecl->consts as $const) { - if (array_key_exists($const->name->name, $declaredClassConstants)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare constant %s::%s.', - $classReflection->getDisplayName(), - $const->name->name - ))->line($const->getLine())->nonIgnorable()->build(); - } else { - $declaredClassConstants[$const->name->name] = true; - } - } - } + $errors = []; - $declaredProperties = []; - foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) { - foreach ($propertyDecl->props as $property) { - if (array_key_exists($property->name->name, $declaredProperties)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare property %s::$%s.', - $classReflection->getDisplayName(), - $property->name->name - ))->line($property->getLine())->nonIgnorable()->build(); - } else { - $declaredProperties[$property->name->name] = true; - } - } - } + $declaredClassConstants = []; + foreach ($node->getOriginalNode()->getConstants() as $constDecl) { + foreach ($constDecl->consts as $const) { + if (array_key_exists($const->name->name, $declaredClassConstants)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare constant %s::%s.', + $classReflection->getDisplayName(), + $const->name->name + ))->line($const->getLine())->nonIgnorable()->build(); + } else { + $declaredClassConstants[$const->name->name] = true; + } + } + } - $declaredFunctions = []; - foreach ($node->getOriginalNode()->getMethods() as $method) { - if ($method->name->toLowerString() === '__construct') { - foreach ($method->params as $param) { - if ($param->flags === 0) { - continue; - } + $declaredProperties = []; + foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) { + foreach ($propertyDecl->props as $property) { + if (array_key_exists($property->name->name, $declaredProperties)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare property %s::$%s.', + $classReflection->getDisplayName(), + $property->name->name + ))->line($property->getLine())->nonIgnorable()->build(); + } else { + $declaredProperties[$property->name->name] = true; + } + } + } - if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } + $declaredFunctions = []; + foreach ($node->getOriginalNode()->getMethods() as $method) { + if ($method->name->toLowerString() === '__construct') { + foreach ($method->params as $param) { + if ($param->flags === 0) { + continue; + } - $propertyName = $param->var->name; + if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } - if (array_key_exists($propertyName, $declaredProperties)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare property %s::$%s.', - $classReflection->getDisplayName(), - $propertyName - ))->line($param->getLine())->nonIgnorable()->build(); - } else { - $declaredProperties[$propertyName] = true; - } - } - } - if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare method %s::%s().', - $classReflection->getDisplayName(), - $method->name->name - ))->line($method->getStartLine())->nonIgnorable()->build(); - } else { - $declaredFunctions[strtolower($method->name->name)] = true; - } - } + $propertyName = $param->var->name; - return $errors; - } + if (array_key_exists($propertyName, $declaredProperties)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare property %s::$%s.', + $classReflection->getDisplayName(), + $propertyName + ))->line($param->getLine())->nonIgnorable()->build(); + } else { + $declaredProperties[$propertyName] = true; + } + } + } + if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare method %s::%s().', + $classReflection->getDisplayName(), + $method->name->name + ))->line($method->getStartLine())->nonIgnorable()->build(); + } else { + $declaredFunctions[strtolower($method->name->name)] = true; + } + } + return $errors; + } } diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index 53e049e9d1..a8b4a251b5 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -1,4 +1,6 @@ -classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->reflectionProvider = $reflectionProvider; - } + private ReflectionProvider $reflectionProvider; - public function getNodeType(): string - { - return Node\Stmt\Class_::class; - } + public function __construct( + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + ReflectionProvider $reflectionProvider + ) { + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - if ($node->extends === null) { - return []; - } - $extendedClassName = (string) $node->extends; - $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($extendedClassName, $node->extends)]); - $currentClassName = null; - if (isset($node->namespacedName)) { - $currentClassName = (string) $node->namespacedName; - } - if (!$this->reflectionProvider->hasClass($extendedClassName)) { - if (!$scope->isInClassExists($extendedClassName)) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s extends unknown class %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $extendedClassName - ))->nonIgnorable()->discoveringSymbolsTip()->build(); - } - } else { - $reflection = $this->reflectionProvider->getClass($extendedClassName); - if ($reflection->isInterface()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s extends interface %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $extendedClassName - ))->nonIgnorable()->build(); - } elseif ($reflection->isTrait()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s extends trait %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $extendedClassName - ))->nonIgnorable()->build(); - } elseif ($reflection->isFinalByKeyword()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s extends final class %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $extendedClassName - ))->nonIgnorable()->build(); - } - } + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } - return $messages; - } + public function processNode(Node $node, Scope $scope): array + { + if ($node->extends === null) { + return []; + } + $extendedClassName = (string) $node->extends; + $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($extendedClassName, $node->extends)]); + $currentClassName = null; + if (isset($node->namespacedName)) { + $currentClassName = (string) $node->namespacedName; + } + if (!$this->reflectionProvider->hasClass($extendedClassName)) { + if (!$scope->isInClassExists($extendedClassName)) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s extends unknown class %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $extendedClassName + ))->nonIgnorable()->discoveringSymbolsTip()->build(); + } + } else { + $reflection = $this->reflectionProvider->getClass($extendedClassName); + if ($reflection->isInterface()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s extends interface %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $extendedClassName + ))->nonIgnorable()->build(); + } elseif ($reflection->isTrait()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s extends trait %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $extendedClassName + ))->nonIgnorable()->build(); + } elseif ($reflection->isFinalByKeyword()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s extends final class %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $extendedClassName + ))->nonIgnorable()->build(); + } + } + return $messages; + } } diff --git a/src/Rules/Classes/ExistingClassInInstanceOfRule.php b/src/Rules/Classes/ExistingClassInInstanceOfRule.php index c0b83b1e4b..6032b13966 100644 --- a/src/Rules/Classes/ExistingClassInInstanceOfRule.php +++ b/src/Rules/Classes/ExistingClassInInstanceOfRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - } - - public function getNodeType(): string - { - return Instanceof_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $class = $node->class; - if (!($class instanceof \PhpParser\Node\Name)) { - return []; - } - - $name = (string) $class; - $lowercaseName = strtolower($name); - - if (in_array($lowercaseName, [ - 'self', - 'static', - 'parent', - ], true)) { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $lowercaseName))->line($class->getLine())->build(), - ]; - } - - return []; - } - - if (!$this->reflectionProvider->hasClass($name)) { - if ($scope->isInClassExists($name)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Class %s not found.', $name))->line($class->getLine())->discoveringSymbolsTip()->build(), - ]; - } elseif ($this->checkClassCaseSensitivity) { - return $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($name, $class)]); - } - - return []; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private bool $checkClassCaseSensitivity; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkClassCaseSensitivity + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + } + + public function getNodeType(): string + { + return Instanceof_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $class = $node->class; + if (!($class instanceof \PhpParser\Node\Name)) { + return []; + } + + $name = (string) $class; + $lowercaseName = strtolower($name); + + if (in_array($lowercaseName, [ + 'self', + 'static', + 'parent', + ], true)) { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $lowercaseName))->line($class->getLine())->build(), + ]; + } + + return []; + } + + if (!$this->reflectionProvider->hasClass($name)) { + if ($scope->isInClassExists($name)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Class %s not found.', $name))->line($class->getLine())->discoveringSymbolsTip()->build(), + ]; + } elseif ($this->checkClassCaseSensitivity) { + return $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($name, $class)]); + } + + return []; + } } diff --git a/src/Rules/Classes/ExistingClassInTraitUseRule.php b/src/Rules/Classes/ExistingClassInTraitUseRule.php index a26b0b6387..10467d4f91 100644 --- a/src/Rules/Classes/ExistingClassInTraitUseRule.php +++ b/src/Rules/Classes/ExistingClassInTraitUseRule.php @@ -1,4 +1,6 @@ -classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->reflectionProvider = $reflectionProvider; - } + private ReflectionProvider $reflectionProvider; - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\TraitUse::class; - } + public function __construct( + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + ReflectionProvider $reflectionProvider + ) { + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - $messages = $this->classCaseSensitivityCheck->checkClassNames( - array_map(static function (Node\Name $traitName): ClassNameNodePair { - return new ClassNameNodePair((string) $traitName, $traitName); - }, $node->traits) - ); + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\TraitUse::class; + } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $messages = $this->classCaseSensitivityCheck->checkClassNames( + array_map(static function (Node\Name $traitName): ClassNameNodePair { + return new ClassNameNodePair((string) $traitName, $traitName); + }, $node->traits) + ); - $classReflection = $scope->getClassReflection(); - if ($classReflection->isInterface()) { - if (!$scope->isInTrait()) { - foreach ($node->traits as $trait) { - $messages[] = RuleErrorBuilder::message(sprintf('Interface %s uses trait %s.', $classReflection->getName(), (string) $trait))->nonIgnorable()->build(); - } - } - } else { - if ($scope->isInTrait()) { - $currentName = sprintf('Trait %s', $scope->getTraitReflection()->getName()); - } else { - if ($classReflection->isAnonymous()) { - $currentName = 'Anonymous class'; - } else { - $currentName = sprintf('Class %s', $classReflection->getName()); - } - } - foreach ($node->traits as $trait) { - $traitName = (string) $trait; - if (!$this->reflectionProvider->hasClass($traitName)) { - $messages[] = RuleErrorBuilder::message(sprintf('%s uses unknown trait %s.', $currentName, $traitName))->nonIgnorable()->discoveringSymbolsTip()->build(); - } else { - $reflection = $this->reflectionProvider->getClass($traitName); - if ($reflection->isClass()) { - $messages[] = RuleErrorBuilder::message(sprintf('%s uses class %s.', $currentName, $traitName))->nonIgnorable()->build(); - } elseif ($reflection->isInterface()) { - $messages[] = RuleErrorBuilder::message(sprintf('%s uses interface %s.', $currentName, $traitName))->nonIgnorable()->build(); - } - } - } - } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - return $messages; - } + $classReflection = $scope->getClassReflection(); + if ($classReflection->isInterface()) { + if (!$scope->isInTrait()) { + foreach ($node->traits as $trait) { + $messages[] = RuleErrorBuilder::message(sprintf('Interface %s uses trait %s.', $classReflection->getName(), (string) $trait))->nonIgnorable()->build(); + } + } + } else { + if ($scope->isInTrait()) { + $currentName = sprintf('Trait %s', $scope->getTraitReflection()->getName()); + } else { + if ($classReflection->isAnonymous()) { + $currentName = 'Anonymous class'; + } else { + $currentName = sprintf('Class %s', $classReflection->getName()); + } + } + foreach ($node->traits as $trait) { + $traitName = (string) $trait; + if (!$this->reflectionProvider->hasClass($traitName)) { + $messages[] = RuleErrorBuilder::message(sprintf('%s uses unknown trait %s.', $currentName, $traitName))->nonIgnorable()->discoveringSymbolsTip()->build(); + } else { + $reflection = $this->reflectionProvider->getClass($traitName); + if ($reflection->isClass()) { + $messages[] = RuleErrorBuilder::message(sprintf('%s uses class %s.', $currentName, $traitName))->nonIgnorable()->build(); + } elseif ($reflection->isInterface()) { + $messages[] = RuleErrorBuilder::message(sprintf('%s uses interface %s.', $currentName, $traitName))->nonIgnorable()->build(); + } + } + } + } + return $messages; + } } diff --git a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php index 5e3bef9fb3..bee707f5ea 100644 --- a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php @@ -1,4 +1,6 @@ -classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->reflectionProvider = $reflectionProvider; - } + private ReflectionProvider $reflectionProvider; - public function getNodeType(): string - { - return Node\Stmt\Class_::class; - } + public function __construct( + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + ReflectionProvider $reflectionProvider + ) { + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - $messages = $this->classCaseSensitivityCheck->checkClassNames( - array_map(static function (Node\Name $interfaceName): ClassNameNodePair { - return new ClassNameNodePair((string) $interfaceName, $interfaceName); - }, $node->implements) - ); + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } - $currentClassName = null; - if (isset($node->namespacedName)) { - $currentClassName = (string) $node->namespacedName; - } + public function processNode(Node $node, Scope $scope): array + { + $messages = $this->classCaseSensitivityCheck->checkClassNames( + array_map(static function (Node\Name $interfaceName): ClassNameNodePair { + return new ClassNameNodePair((string) $interfaceName, $interfaceName); + }, $node->implements) + ); - foreach ($node->implements as $implements) { - $implementedClassName = (string) $implements; - if (!$this->reflectionProvider->hasClass($implementedClassName)) { - if (!$scope->isInClassExists($implementedClassName)) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s implements unknown interface %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $implementedClassName - ))->nonIgnorable()->discoveringSymbolsTip()->build(); - } - } else { - $reflection = $this->reflectionProvider->getClass($implementedClassName); - if ($reflection->isClass()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s implements class %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $implementedClassName - ))->nonIgnorable()->build(); - } elseif ($reflection->isTrait()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s implements trait %s.', - $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', - $implementedClassName - ))->nonIgnorable()->build(); - } - } - } + $currentClassName = null; + if (isset($node->namespacedName)) { + $currentClassName = (string) $node->namespacedName; + } - return $messages; - } + foreach ($node->implements as $implements) { + $implementedClassName = (string) $implements; + if (!$this->reflectionProvider->hasClass($implementedClassName)) { + if (!$scope->isInClassExists($implementedClassName)) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s implements unknown interface %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $implementedClassName + ))->nonIgnorable()->discoveringSymbolsTip()->build(); + } + } else { + $reflection = $this->reflectionProvider->getClass($implementedClassName); + if ($reflection->isClass()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s implements class %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $implementedClassName + ))->nonIgnorable()->build(); + } elseif ($reflection->isTrait()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s implements trait %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $implementedClassName + ))->nonIgnorable()->build(); + } + } + } + return $messages; + } } diff --git a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php index 0dfc51bf64..191858ba18 100644 --- a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php +++ b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php @@ -1,4 +1,6 @@ -classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->reflectionProvider = $reflectionProvider; - } + private ReflectionProvider $reflectionProvider; - public function getNodeType(): string - { - return Node\Stmt\Interface_::class; - } + public function __construct( + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + ReflectionProvider $reflectionProvider + ) { + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - $messages = $this->classCaseSensitivityCheck->checkClassNames( - array_map(static function (Node\Name $interfaceName): ClassNameNodePair { - return new ClassNameNodePair((string) $interfaceName, $interfaceName); - }, $node->extends) - ); + public function getNodeType(): string + { + return Node\Stmt\Interface_::class; + } - $currentInterfaceName = (string) $node->namespacedName; - foreach ($node->extends as $extends) { - $extendedInterfaceName = (string) $extends; - if (!$this->reflectionProvider->hasClass($extendedInterfaceName)) { - if (!$scope->isInClassExists($extendedInterfaceName)) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Interface %s extends unknown interface %s.', - $currentInterfaceName, - $extendedInterfaceName - ))->nonIgnorable()->discoveringSymbolsTip()->build(); - } - } else { - $reflection = $this->reflectionProvider->getClass($extendedInterfaceName); - if ($reflection->isClass()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Interface %s extends class %s.', - $currentInterfaceName, - $extendedInterfaceName - ))->nonIgnorable()->build(); - } elseif ($reflection->isTrait()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Interface %s extends trait %s.', - $currentInterfaceName, - $extendedInterfaceName - ))->nonIgnorable()->build(); - } - } + public function processNode(Node $node, Scope $scope): array + { + $messages = $this->classCaseSensitivityCheck->checkClassNames( + array_map(static function (Node\Name $interfaceName): ClassNameNodePair { + return new ClassNameNodePair((string) $interfaceName, $interfaceName); + }, $node->extends) + ); - return $messages; - } + $currentInterfaceName = (string) $node->namespacedName; + foreach ($node->extends as $extends) { + $extendedInterfaceName = (string) $extends; + if (!$this->reflectionProvider->hasClass($extendedInterfaceName)) { + if (!$scope->isInClassExists($extendedInterfaceName)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Interface %s extends unknown interface %s.', + $currentInterfaceName, + $extendedInterfaceName + ))->nonIgnorable()->discoveringSymbolsTip()->build(); + } + } else { + $reflection = $this->reflectionProvider->getClass($extendedInterfaceName); + if ($reflection->isClass()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Interface %s extends class %s.', + $currentInterfaceName, + $extendedInterfaceName + ))->nonIgnorable()->build(); + } elseif ($reflection->isTrait()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Interface %s extends trait %s.', + $currentInterfaceName, + $extendedInterfaceName + ))->nonIgnorable()->build(); + } + } - return $messages; - } + return $messages; + } + return $messages; + } } diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index 02e3e4c63f..94d4d5a65d 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -1,4 +1,6 @@ -checkAlwaysTrueInstanceof = $checkAlwaysTrueInstanceof; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function getNodeType(): string - { - return Node\Expr\Instanceof_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $instanceofType = $scope->getType($node); - $expressionType = $scope->getType($node->expr); - - if ($node->class instanceof Node\Name) { - $className = $scope->resolveName($node->class); - $classType = new ObjectType($className); - } else { - $classType = $scope->getType($node->class); - $allowed = TypeCombinator::union( - new StringType(), - new ObjectWithoutClassType() - ); - if (!$allowed->accepts($classType, true)->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Instanceof between %s and %s results in an error.', - $expressionType->describe(VerbosityLevel::typeOnly()), - $classType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - } - - if (!$instanceofType instanceof ConstantBooleanType) { - return []; - } - - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $instanceofTypeWithoutPhpDocs = $scope->doNotTreatPhpDocTypesAsCertain()->getType($node); - if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - - if (!$instanceofType->getValue()) { - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Instanceof between %s and %s will always evaluate to false.', - $expressionType->describe(VerbosityLevel::typeOnly()), - $classType->describe(VerbosityLevel::typeOnly()) - )))->build(), - ]; - } elseif ($this->checkAlwaysTrueInstanceof) { - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Instanceof between %s and %s will always evaluate to true.', - $expressionType->describe(VerbosityLevel::typeOnly()), - $classType->describe(VerbosityLevel::typeOnly()) - )))->build(), - ]; - } - - return []; - } - + private bool $checkAlwaysTrueInstanceof; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + bool $checkAlwaysTrueInstanceof, + bool $treatPhpDocTypesAsCertain + ) { + $this->checkAlwaysTrueInstanceof = $checkAlwaysTrueInstanceof; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return Node\Expr\Instanceof_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $instanceofType = $scope->getType($node); + $expressionType = $scope->getType($node->expr); + + if ($node->class instanceof Node\Name) { + $className = $scope->resolveName($node->class); + $classType = new ObjectType($className); + } else { + $classType = $scope->getType($node->class); + $allowed = TypeCombinator::union( + new StringType(), + new ObjectWithoutClassType() + ); + if (!$allowed->accepts($classType, true)->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Instanceof between %s and %s results in an error.', + $expressionType->describe(VerbosityLevel::typeOnly()), + $classType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + } + + if (!$instanceofType instanceof ConstantBooleanType) { + return []; + } + + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $instanceofTypeWithoutPhpDocs = $scope->doNotTreatPhpDocTypesAsCertain()->getType($node); + if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + if (!$instanceofType->getValue()) { + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Instanceof between %s and %s will always evaluate to false.', + $expressionType->describe(VerbosityLevel::typeOnly()), + $classType->describe(VerbosityLevel::typeOnly()) + )))->build(), + ]; + } elseif ($this->checkAlwaysTrueInstanceof) { + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Instanceof between %s and %s will always evaluate to true.', + $expressionType->describe(VerbosityLevel::typeOnly()), + $classType->describe(VerbosityLevel::typeOnly()) + )))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 17b4413331..b61e33d019 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->check = $check; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - } - - public function getNodeType(): string - { - return New_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $errors = []; - foreach ($this->getClassNames($node, $scope) as [$class, $isName]) { - $errors = array_merge($errors, $this->checkClassName($class, $isName, $node, $scope)); - } - return $errors; - } - - /** - * @param string $class - * @param \PhpParser\Node\Expr\New_ $node - * @param Scope $scope - * @return RuleError[] - */ - private function checkClassName(string $class, bool $isName, Node $node, Scope $scope): array - { - $lowercasedClass = strtolower($class); - $messages = []; - $isStatic = false; - if ($lowercasedClass === 'static') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), - ]; - } - - $isStatic = true; - $classReflection = $scope->getClassReflection(); - if (!$classReflection->isFinal()) { - if (!$classReflection->hasConstructor()) { - return []; - } - - $constructor = $classReflection->getConstructor(); - if ( - !$constructor->getPrototype()->getDeclaringClass()->isInterface() - && $constructor instanceof PhpMethodReflection - && !$constructor->isFinal()->yes() - && !$constructor->getPrototype()->isAbstract() - ) { - return []; - } - } - } elseif ($lowercasedClass === 'self') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), - ]; - } - $classReflection = $scope->getClassReflection(); - } elseif ($lowercasedClass === 'parent') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), - ]; - } - if ($scope->getClassReflection()->getParentClass() === false) { - return [ - RuleErrorBuilder::message(sprintf( - '%s::%s() calls new parent but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), - $scope->getFunctionName(), - $scope->getClassReflection()->getDisplayName() - ))->build(), - ]; - } - $classReflection = $scope->getClassReflection()->getParentClass(); - } else { - if (!$this->reflectionProvider->hasClass($class)) { - if ($scope->isInClassExists($class)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Instantiated class %s not found.', $class))->discoveringSymbolsTip()->build(), - ]; - } else { - $messages = $this->classCaseSensitivityCheck->checkClassNames([ - new ClassNameNodePair($class, $node->class), - ]); - } - - $classReflection = $this->reflectionProvider->getClass($class); - } - - if (!$isStatic && $classReflection->isInterface() && $isName) { - return [ - RuleErrorBuilder::message( - sprintf('Cannot instantiate interface %s.', $classReflection->getDisplayName()) - )->build(), - ]; - } - - if (!$isStatic && $classReflection->isAbstract() && $isName) { - return [ - RuleErrorBuilder::message( - sprintf('Instantiated class %s is abstract.', $classReflection->getDisplayName()) - )->build(), - ]; - } - - if (!$isName) { - return []; - } - - if (!$classReflection->hasConstructor()) { - if (count($node->args) > 0) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Class %s does not have a constructor and must be instantiated without any parameters.', - $classReflection->getDisplayName() - ))->build(), - ]); - } - - return $messages; - } - - $constructorReflection = $classReflection->getConstructor(); - if (!$scope->canCallMethod($constructorReflection)) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Cannot instantiate class %s via %s constructor %s::%s().', - $classReflection->getDisplayName(), - $constructorReflection->isPrivate() ? 'private' : 'protected', - $constructorReflection->getDeclaringClass()->getDisplayName(), - $constructorReflection->getName() - ))->build(); - } - - return array_merge($messages, $this->check->check( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $node->args, - $constructorReflection->getVariants() - ), - $scope, - $constructorReflection->getDeclaringClass()->isBuiltin(), - $node, - [ - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor expects %s, %s given.', - '', // constructor does not have a return type - 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of class ' . $classReflection->getDisplayName(), - 'Missing parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', - 'Unknown parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', - ] - )); - } - - /** - * @param \PhpParser\Node\Expr\New_ $node $node - * @param Scope $scope - * @return array - */ - private function getClassNames(Node $node, Scope $scope): array - { - if ($node->class instanceof \PhpParser\Node\Name) { - return [[(string) $node->class, true]]; - } - - if ($node->class instanceof Node\Stmt\Class_) { - $anonymousClassType = $scope->getType($node); - if (!$anonymousClassType instanceof TypeWithClassName) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return [[$anonymousClassType->getClassName(), true]]; - } - - $type = $scope->getType($node->class); - - return array_merge( - array_map( - static function (ConstantStringType $type): array { - return [$type->getValue(), true]; - }, - TypeUtils::getConstantStrings($type) - ), - array_map( - static function (string $name): array { - return [$name, false]; - }, - TypeUtils::getDirectClassNames($type) - ) - ); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\FunctionCallParametersCheck $check; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + public function __construct( + ReflectionProvider $reflectionProvider, + FunctionCallParametersCheck $check, + ClassCaseSensitivityCheck $classCaseSensitivityCheck + ) { + $this->reflectionProvider = $reflectionProvider; + $this->check = $check; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + } + + public function getNodeType(): string + { + return New_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($this->getClassNames($node, $scope) as [$class, $isName]) { + $errors = array_merge($errors, $this->checkClassName($class, $isName, $node, $scope)); + } + return $errors; + } + + /** + * @param string $class + * @param \PhpParser\Node\Expr\New_ $node + * @param Scope $scope + * @return RuleError[] + */ + private function checkClassName(string $class, bool $isName, Node $node, Scope $scope): array + { + $lowercasedClass = strtolower($class); + $messages = []; + $isStatic = false; + if ($lowercasedClass === 'static') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), + ]; + } + + $isStatic = true; + $classReflection = $scope->getClassReflection(); + if (!$classReflection->isFinal()) { + if (!$classReflection->hasConstructor()) { + return []; + } + + $constructor = $classReflection->getConstructor(); + if ( + !$constructor->getPrototype()->getDeclaringClass()->isInterface() + && $constructor instanceof PhpMethodReflection + && !$constructor->isFinal()->yes() + && !$constructor->getPrototype()->isAbstract() + ) { + return []; + } + } + } elseif ($lowercasedClass === 'self') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), + ]; + } + $classReflection = $scope->getClassReflection(); + } elseif ($lowercasedClass === 'parent') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), + ]; + } + if ($scope->getClassReflection()->getParentClass() === false) { + return [ + RuleErrorBuilder::message(sprintf( + '%s::%s() calls new parent but %s does not extend any class.', + $scope->getClassReflection()->getDisplayName(), + $scope->getFunctionName(), + $scope->getClassReflection()->getDisplayName() + ))->build(), + ]; + } + $classReflection = $scope->getClassReflection()->getParentClass(); + } else { + if (!$this->reflectionProvider->hasClass($class)) { + if ($scope->isInClassExists($class)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Instantiated class %s not found.', $class))->discoveringSymbolsTip()->build(), + ]; + } else { + $messages = $this->classCaseSensitivityCheck->checkClassNames([ + new ClassNameNodePair($class, $node->class), + ]); + } + + $classReflection = $this->reflectionProvider->getClass($class); + } + + if (!$isStatic && $classReflection->isInterface() && $isName) { + return [ + RuleErrorBuilder::message( + sprintf('Cannot instantiate interface %s.', $classReflection->getDisplayName()) + )->build(), + ]; + } + + if (!$isStatic && $classReflection->isAbstract() && $isName) { + return [ + RuleErrorBuilder::message( + sprintf('Instantiated class %s is abstract.', $classReflection->getDisplayName()) + )->build(), + ]; + } + + if (!$isName) { + return []; + } + + if (!$classReflection->hasConstructor()) { + if (count($node->args) > 0) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Class %s does not have a constructor and must be instantiated without any parameters.', + $classReflection->getDisplayName() + ))->build(), + ]); + } + + return $messages; + } + + $constructorReflection = $classReflection->getConstructor(); + if (!$scope->canCallMethod($constructorReflection)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Cannot instantiate class %s via %s constructor %s::%s().', + $classReflection->getDisplayName(), + $constructorReflection->isPrivate() ? 'private' : 'protected', + $constructorReflection->getDeclaringClass()->getDisplayName(), + $constructorReflection->getName() + ))->build(); + } + + return array_merge($messages, $this->check->check( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->args, + $constructorReflection->getVariants() + ), + $scope, + $constructorReflection->getDeclaringClass()->isBuiltin(), + $node, + [ + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d required.', + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d required.', + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', + 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor expects %s, %s given.', + '', // constructor does not have a return type + 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of class ' . $classReflection->getDisplayName(), + 'Missing parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', + 'Unknown parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', + ] + )); + } + + /** + * @param \PhpParser\Node\Expr\New_ $node $node + * @param Scope $scope + * @return array + */ + private function getClassNames(Node $node, Scope $scope): array + { + if ($node->class instanceof \PhpParser\Node\Name) { + return [[(string) $node->class, true]]; + } + + if ($node->class instanceof Node\Stmt\Class_) { + $anonymousClassType = $scope->getType($node); + if (!$anonymousClassType instanceof TypeWithClassName) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return [[$anonymousClassType->getClassName(), true]]; + } + + $type = $scope->getType($node->class); + + return array_merge( + array_map( + static function (ConstantStringType $type): array { + return [$type->getValue(), true]; + }, + TypeUtils::getConstantStrings($type) + ), + array_map( + static function (string $name): array { + return [$name, false]; + }, + TypeUtils::getDirectClassNames($type) + ) + ); + } } diff --git a/src/Rules/Classes/InvalidPromotedPropertiesRule.php b/src/Rules/Classes/InvalidPromotedPropertiesRule.php index 0e57df511f..0d3b23014d 100644 --- a/src/Rules/Classes/InvalidPromotedPropertiesRule.php +++ b/src/Rules/Classes/InvalidPromotedPropertiesRule.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function getNodeType(): string - { - return Node::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Expr\ArrowFunction - && !$node instanceof Node\Stmt\ClassMethod - && !$node instanceof Node\Expr\Closure - && !$node instanceof Node\Stmt\Function_ - ) { - return []; - } - - $hasPromotedProperties = false; - foreach ($node->params as $param) { - if ($param->flags === 0) { - continue; - } - - $hasPromotedProperties = true; - break; - } - - if (!$hasPromotedProperties) { - return []; - } - - if (!$this->phpVersion->supportsPromotedProperties()) { - return [ - RuleErrorBuilder::message( - 'Promoted properties are supported only on PHP 8.0 and later.' - )->nonIgnorable()->build(), - ]; - } - - if ( - !$node instanceof Node\Stmt\ClassMethod - || $node->name->toLowerString() !== '__construct' - ) { - return [ - RuleErrorBuilder::message( - 'Promoted properties can be in constructor only.' - )->nonIgnorable()->build(), - ]; - } - - if ($node->stmts === null) { - return [ - RuleErrorBuilder::message( - 'Promoted properties are not allowed in abstract constructors.' - )->nonIgnorable()->build(), - ]; - } - - $errors = []; - foreach ($node->params as $param) { - if ($param->flags === 0) { - continue; - } - - if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!$param->variadic) { - continue; - } - - $propertyName = $param->var->name; - $errors[] = RuleErrorBuilder::message( - sprintf('Promoted property parameter $%s can not be variadic.', $propertyName) - )->nonIgnorable()->line($param->getLine())->build(); - continue; - } - - return $errors; - } - + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Expr\ArrowFunction + && !$node instanceof Node\Stmt\ClassMethod + && !$node instanceof Node\Expr\Closure + && !$node instanceof Node\Stmt\Function_ + ) { + return []; + } + + $hasPromotedProperties = false; + foreach ($node->params as $param) { + if ($param->flags === 0) { + continue; + } + + $hasPromotedProperties = true; + break; + } + + if (!$hasPromotedProperties) { + return []; + } + + if (!$this->phpVersion->supportsPromotedProperties()) { + return [ + RuleErrorBuilder::message( + 'Promoted properties are supported only on PHP 8.0 and later.' + )->nonIgnorable()->build(), + ]; + } + + if ( + !$node instanceof Node\Stmt\ClassMethod + || $node->name->toLowerString() !== '__construct' + ) { + return [ + RuleErrorBuilder::message( + 'Promoted properties can be in constructor only.' + )->nonIgnorable()->build(), + ]; + } + + if ($node->stmts === null) { + return [ + RuleErrorBuilder::message( + 'Promoted properties are not allowed in abstract constructors.' + )->nonIgnorable()->build(), + ]; + } + + $errors = []; + foreach ($node->params as $param) { + if ($param->flags === 0) { + continue; + } + + if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!$param->variadic) { + continue; + } + + $propertyName = $param->var->name; + $errors[] = RuleErrorBuilder::message( + sprintf('Promoted property parameter $%s can not be variadic.', $propertyName) + )->nonIgnorable()->line($param->getLine())->build(); + continue; + } + + return $errors; + } } diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index 9773b79dc6..a4d500f593 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -1,4 +1,6 @@ - */ - private array $globalTypeAliases; - - private ReflectionProvider $reflectionProvider; - - private TypeNodeResolver $typeNodeResolver; - - /** - * @param array $globalTypeAliases - */ - public function __construct( - array $globalTypeAliases, - ReflectionProvider $reflectionProvider, - TypeNodeResolver $typeNodeResolver - ) - { - $this->globalTypeAliases = $globalTypeAliases; - $this->reflectionProvider = $reflectionProvider; - $this->typeNodeResolver = $typeNodeResolver; - } - - public function getNodeType(): string - { - return InClassNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $reflection = $node->getClassReflection(); - $phpDoc = $reflection->getResolvedPhpDoc(); - if ($phpDoc === null) { - return []; - } - - $nameScope = $phpDoc->getNullableNameScope(); - $resolveName = static function (string $name) use ($nameScope): string { - if ($nameScope === null) { - return $name; - } - - return $nameScope->resolveStringName($name); - }; - - $errors = []; - $className = $reflection->getName(); - - $importedAliases = []; - - foreach ($phpDoc->getTypeAliasImportTags() as $typeAliasImportTag) { - $aliasName = $typeAliasImportTag->getImportedAs() ?? $typeAliasImportTag->getImportedAlias(); - $importedAlias = $typeAliasImportTag->getImportedAlias(); - $importedFromClassName = $typeAliasImportTag->getImportedFrom(); - - if (!$this->reflectionProvider->hasClass($importedFromClassName)) { - $errors[] = RuleErrorBuilder::message(sprintf('Cannot import type alias %s: class %s does not exist.', $importedAlias, $importedFromClassName))->build(); - continue; - } - - $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName); - $typeAliases = $importedFromReflection->getTypeAliases(); - - if (!array_key_exists($importedAlias, $typeAliases)) { - $errors[] = RuleErrorBuilder::message(sprintf('Cannot import type alias %s: type alias does not exist in %s.', $importedAlias, $importedFromClassName))->build(); - continue; - } - - if ($this->reflectionProvider->hasClass($resolveName($aliasName))) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a class in scope of %s.', $aliasName, $className))->build(); - continue; - } - - if (array_key_exists($aliasName, $this->globalTypeAliases)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a global type alias.', $aliasName))->build(); - continue; - } - - $importedAs = $typeAliasImportTag->getImportedAs(); - if ($importedAs !== null && !$this->isAliasNameValid($importedAs, $nameScope)) { - $errors[] = RuleErrorBuilder::message(sprintf('Imported type alias %s has an invalid name: %s.', $importedAlias, $importedAs))->build(); - continue; - } - - $importedAliases[] = $aliasName; - } - - foreach ($phpDoc->getTypeAliasTags() as $typeAliasTag) { - $aliasName = $typeAliasTag->getAliasName(); - - if (in_array($aliasName, $importedAliases, true)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s overwrites an imported type alias of the same name.', $aliasName))->build(); - continue; - } - - if ($this->reflectionProvider->hasClass($resolveName($aliasName))) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a class in scope of %s.', $aliasName, $className))->build(); - continue; - } - - if (array_key_exists($aliasName, $this->globalTypeAliases)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a global type alias.', $aliasName))->build(); - continue; - } - - if (!$this->isAliasNameValid($aliasName, $nameScope)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias has an invalid name: %s.', $aliasName))->build(); - continue; - } - - $resolvedType = $typeAliasTag->getTypeAlias()->resolve($this->typeNodeResolver); - $foundError = false; - TypeTraverser::map($resolvedType, static function (\PHPStan\Type\Type $type, callable $traverse) use (&$errors, &$foundError, $aliasName): \PHPStan\Type\Type { - if ($foundError) { - return $type; - } - - if ($type instanceof ErrorType) { - $errors[] = RuleErrorBuilder::message(sprintf('Circular definition detected in type alias %s.', $aliasName))->build(); - $foundError = true; - return $type; - } - - return $traverse($type); - }); - } - - return $errors; - } - - private function isAliasNameValid(string $aliasName, ?NameScope $nameScope): bool - { - if ($nameScope === null) { - return true; - } - - $aliasNameResolvedType = $this->typeNodeResolver->resolve(new IdentifierTypeNode($aliasName), $nameScope->bypassTypeAliases()); - return ($aliasNameResolvedType instanceof ObjectType && !in_array($aliasName, ['self', 'parent'], true)) - || $aliasNameResolvedType instanceof TemplateType; // aliases take precedence over type parameters, this is reported by other rules using TemplateTypeCheck - } - + /** @var array */ + private array $globalTypeAliases; + + private ReflectionProvider $reflectionProvider; + + private TypeNodeResolver $typeNodeResolver; + + /** + * @param array $globalTypeAliases + */ + public function __construct( + array $globalTypeAliases, + ReflectionProvider $reflectionProvider, + TypeNodeResolver $typeNodeResolver + ) { + $this->globalTypeAliases = $globalTypeAliases; + $this->reflectionProvider = $reflectionProvider; + $this->typeNodeResolver = $typeNodeResolver; + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $reflection = $node->getClassReflection(); + $phpDoc = $reflection->getResolvedPhpDoc(); + if ($phpDoc === null) { + return []; + } + + $nameScope = $phpDoc->getNullableNameScope(); + $resolveName = static function (string $name) use ($nameScope): string { + if ($nameScope === null) { + return $name; + } + + return $nameScope->resolveStringName($name); + }; + + $errors = []; + $className = $reflection->getName(); + + $importedAliases = []; + + foreach ($phpDoc->getTypeAliasImportTags() as $typeAliasImportTag) { + $aliasName = $typeAliasImportTag->getImportedAs() ?? $typeAliasImportTag->getImportedAlias(); + $importedAlias = $typeAliasImportTag->getImportedAlias(); + $importedFromClassName = $typeAliasImportTag->getImportedFrom(); + + if (!$this->reflectionProvider->hasClass($importedFromClassName)) { + $errors[] = RuleErrorBuilder::message(sprintf('Cannot import type alias %s: class %s does not exist.', $importedAlias, $importedFromClassName))->build(); + continue; + } + + $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName); + $typeAliases = $importedFromReflection->getTypeAliases(); + + if (!array_key_exists($importedAlias, $typeAliases)) { + $errors[] = RuleErrorBuilder::message(sprintf('Cannot import type alias %s: type alias does not exist in %s.', $importedAlias, $importedFromClassName))->build(); + continue; + } + + if ($this->reflectionProvider->hasClass($resolveName($aliasName))) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a class in scope of %s.', $aliasName, $className))->build(); + continue; + } + + if (array_key_exists($aliasName, $this->globalTypeAliases)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a global type alias.', $aliasName))->build(); + continue; + } + + $importedAs = $typeAliasImportTag->getImportedAs(); + if ($importedAs !== null && !$this->isAliasNameValid($importedAs, $nameScope)) { + $errors[] = RuleErrorBuilder::message(sprintf('Imported type alias %s has an invalid name: %s.', $importedAlias, $importedAs))->build(); + continue; + } + + $importedAliases[] = $aliasName; + } + + foreach ($phpDoc->getTypeAliasTags() as $typeAliasTag) { + $aliasName = $typeAliasTag->getAliasName(); + + if (in_array($aliasName, $importedAliases, true)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s overwrites an imported type alias of the same name.', $aliasName))->build(); + continue; + } + + if ($this->reflectionProvider->hasClass($resolveName($aliasName))) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a class in scope of %s.', $aliasName, $className))->build(); + continue; + } + + if (array_key_exists($aliasName, $this->globalTypeAliases)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s already exists as a global type alias.', $aliasName))->build(); + continue; + } + + if (!$this->isAliasNameValid($aliasName, $nameScope)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias has an invalid name: %s.', $aliasName))->build(); + continue; + } + + $resolvedType = $typeAliasTag->getTypeAlias()->resolve($this->typeNodeResolver); + $foundError = false; + TypeTraverser::map($resolvedType, static function (\PHPStan\Type\Type $type, callable $traverse) use (&$errors, &$foundError, $aliasName): \PHPStan\Type\Type { + if ($foundError) { + return $type; + } + + if ($type instanceof ErrorType) { + $errors[] = RuleErrorBuilder::message(sprintf('Circular definition detected in type alias %s.', $aliasName))->build(); + $foundError = true; + return $type; + } + + return $traverse($type); + }); + } + + return $errors; + } + + private function isAliasNameValid(string $aliasName, ?NameScope $nameScope): bool + { + if ($nameScope === null) { + return true; + } + + $aliasNameResolvedType = $this->typeNodeResolver->resolve(new IdentifierTypeNode($aliasName), $nameScope->bypassTypeAliases()); + return ($aliasNameResolvedType instanceof ObjectType && !in_array($aliasName, ['self', 'parent'], true)) + || $aliasNameResolvedType instanceof TemplateType; // aliases take precedence over type parameters, this is reported by other rules using TemplateTypeCheck + } } diff --git a/src/Rules/Classes/MixinRule.php b/src/Rules/Classes/MixinRule.php index 12a0a8fdd6..7ccba7b221 100644 --- a/src/Rules/Classes/MixinRule.php +++ b/src/Rules/Classes/MixinRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->genericObjectTypeCheck = $genericObjectTypeCheck; - $this->missingTypehintCheck = $missingTypehintCheck; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - } - - public function getNodeType(): string - { - return Node\Stmt\Class_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!isset($node->namespacedName)) { - // anonymous class - return []; - } - - $className = (string) $node->namespacedName; - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $className, - null, - null, - $docComment->getText() - ); - $mixinTags = $resolvedPhpDoc->getMixinTags(); - $errors = []; - foreach ($mixinTags as $mixinTag) { - $type = $mixinTag->getType(); - if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly())))->build(); - continue; - } - - if ( - $type instanceof ErrorType - || ($type instanceof NeverType && !$type->isExplicit()) - ) { - $errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.')->build(); - continue; - } - - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $type, - 'PHPDoc tag @mixin contains generic type %s but class %s is not generic.', - 'Generic type %s in PHPDoc tag @mixin does not specify all template types of class %s: %s', - 'Generic type %s in PHPDoc tag @mixin specifies %d template types, but class %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of class %s.' - )); - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', - $innerName, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } - - foreach ($type->getReferencedClasses() as $class) { - if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class))->discoveringSymbolsTip()->build(); - } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class))->build(); - } elseif ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames([ - new ClassNameNodePair($class, $node), - ]) - ); - } - } - } - - return $errors; - } - + private FileTypeMapper $fileTypeMapper; + + private ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + private MissingTypehintCheck $missingTypehintCheck; + + private bool $checkClassCaseSensitivity; + + public function __construct( + FileTypeMapper $fileTypeMapper, + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + GenericObjectTypeCheck $genericObjectTypeCheck, + MissingTypehintCheck $missingTypehintCheck, + bool $checkClassCaseSensitivity + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + $this->missingTypehintCheck = $missingTypehintCheck; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!isset($node->namespacedName)) { + // anonymous class + return []; + } + + $className = (string) $node->namespacedName; + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $className, + null, + null, + $docComment->getText() + ); + $mixinTags = $resolvedPhpDoc->getMixinTags(); + $errors = []; + foreach ($mixinTags as $mixinTag) { + $type = $mixinTag->getType(); + if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly())))->build(); + continue; + } + + if ( + $type instanceof ErrorType + || ($type instanceof NeverType && !$type->isExplicit()) + ) { + $errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.')->build(); + continue; + } + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $type, + 'PHPDoc tag @mixin contains generic type %s but class %s is not generic.', + 'Generic type %s in PHPDoc tag @mixin does not specify all template types of class %s: %s', + 'Generic type %s in PHPDoc tag @mixin specifies %d template types, but class %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of class %s.' + )); + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', + $innerName, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + + foreach ($type->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class))->discoveringSymbolsTip()->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class))->build(); + } elseif ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ]) + ); + } + } + } + + return $errors; + } } diff --git a/src/Rules/Classes/NewStaticRule.php b/src/Rules/Classes/NewStaticRule.php index e93c93f68c..094fb50e31 100644 --- a/src/Rules/Classes/NewStaticRule.php +++ b/src/Rules/Classes/NewStaticRule.php @@ -1,4 +1,6 @@ -class instanceof Node\Name) { - return []; - } - - if (!$scope->isInClass()) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node->class instanceof Node\Name) { + return []; + } - if (strtolower($node->class->toString()) !== 'static') { - return []; - } + if (!$scope->isInClass()) { + return []; + } - $classReflection = $scope->getClassReflection(); - if ($classReflection->isFinal()) { - return []; - } + if (strtolower($node->class->toString()) !== 'static') { + return []; + } - $messages = [ - RuleErrorBuilder::message('Unsafe usage of new static().') - ->tip('See: https://phpstan.org/blog/solving-phpstan-error-unsafe-usage-of-new-static') - ->build(), - ]; - if (!$classReflection->hasConstructor()) { - return $messages; - } + $classReflection = $scope->getClassReflection(); + if ($classReflection->isFinal()) { + return []; + } - $constructor = $classReflection->getConstructor(); - if ($constructor->getPrototype()->getDeclaringClass()->isInterface()) { - return []; - } + $messages = [ + RuleErrorBuilder::message('Unsafe usage of new static().') + ->tip('See: https://phpstan.org/blog/solving-phpstan-error-unsafe-usage-of-new-static') + ->build(), + ]; + if (!$classReflection->hasConstructor()) { + return $messages; + } - if ($constructor instanceof PhpMethodReflection) { - if ($constructor->isFinal()->yes()) { - return []; - } + $constructor = $classReflection->getConstructor(); + if ($constructor->getPrototype()->getDeclaringClass()->isInterface()) { + return []; + } - $prototype = $constructor->getPrototype(); - if ($prototype->isAbstract()) { - return []; - } - } + if ($constructor instanceof PhpMethodReflection) { + if ($constructor->isFinal()->yes()) { + return []; + } - return $messages; - } + $prototype = $constructor->getPrototype(); + if ($prototype->isAbstract()) { + return []; + } + } + return $messages; + } } diff --git a/src/Rules/Classes/NonClassAttributeClassRule.php b/src/Rules/Classes/NonClassAttributeClassRule.php index 3a8bcedbbf..7497a6dd2f 100644 --- a/src/Rules/Classes/NonClassAttributeClassRule.php +++ b/src/Rules/Classes/NonClassAttributeClassRule.php @@ -1,4 +1,6 @@ -getOriginalNode(); - foreach ($originalNode->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - $name = $attr->name->toLowerString(); - if ($name === 'attribute') { - return $this->check($scope); - } - } - } - - return []; - } + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + foreach ($originalNode->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $name = $attr->name->toLowerString(); + if ($name === 'attribute') { + return $this->check($scope); + } + } + } - /** - * @param Scope $scope - * @return RuleError[] - */ - private function check(Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $classReflection = $scope->getClassReflection(); - if (!$classReflection->isClass()) { - return [ - RuleErrorBuilder::message('Interface cannot be an Attribute class.')->build(), - ]; - } - if ($classReflection->isAbstract()) { - return [ - RuleErrorBuilder::message(sprintf('Abstract class %s cannot be an Attribute class.', $classReflection->getDisplayName()))->build(), - ]; - } + return []; + } - if (!$classReflection->hasConstructor()) { - return []; - } + /** + * @param Scope $scope + * @return RuleError[] + */ + private function check(Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + if (!$classReflection->isClass()) { + return [ + RuleErrorBuilder::message('Interface cannot be an Attribute class.')->build(), + ]; + } + if ($classReflection->isAbstract()) { + return [ + RuleErrorBuilder::message(sprintf('Abstract class %s cannot be an Attribute class.', $classReflection->getDisplayName()))->build(), + ]; + } - if (!$classReflection->getConstructor()->isPublic()) { - return [ - RuleErrorBuilder::message(sprintf('Attribute class %s constructor must be public.', $classReflection->getDisplayName()))->build(), - ]; - } + if (!$classReflection->hasConstructor()) { + return []; + } - return []; - } + if (!$classReflection->getConstructor()->isPublic()) { + return [ + RuleErrorBuilder::message(sprintf('Attribute class %s constructor must be public.', $classReflection->getDisplayName()))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Classes/TraitAttributeClassRule.php b/src/Rules/Classes/TraitAttributeClassRule.php index 454d063a3a..344f8e3b33 100644 --- a/src/Rules/Classes/TraitAttributeClassRule.php +++ b/src/Rules/Classes/TraitAttributeClassRule.php @@ -1,4 +1,6 @@ -attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - $name = $attr->name->toLowerString(); - if ($name === 'attribute') { - return [ - RuleErrorBuilder::message('Trait cannot be an Attribute class.')->build(), - ]; - } - } - } - - return []; - } + public function processNode(Node $node, Scope $scope): array + { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $name = $attr->name->toLowerString(); + if ($name === 'attribute') { + return [ + RuleErrorBuilder::message('Trait cannot be an Attribute class.')->build(), + ]; + } + } + } + return []; + } } diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index 1d27034d71..3646bb874d 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } + public function __construct(UnusedFunctionParametersCheck $check) + { + $this->check = $check; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return InClassMethodNode::class; + } - $method = $scope->getFunction(); - if (!$method instanceof MethodReflection) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $originalNode = $node->getOriginalNode(); - if (strtolower($method->getName()) !== '__construct' || $originalNode->stmts === null) { - return []; - } + $method = $scope->getFunction(); + if (!$method instanceof MethodReflection) { + return []; + } - if (count($originalNode->params) === 0) { - return []; - } + $originalNode = $node->getOriginalNode(); + if (strtolower($method->getName()) !== '__construct' || $originalNode->stmts === null) { + return []; + } - $message = sprintf( - 'Constructor of class %s has an unused parameter $%%s.', - $scope->getClassReflection()->getDisplayName() - ); - if ($scope->getClassReflection()->isAnonymous()) { - $message = 'Constructor of an anonymous class has an unused parameter $%s.'; - } + if (count($originalNode->params) === 0) { + return []; + } - return $this->check->getUnusedParameters( - $scope, - array_map(static function (Param $parameter): string { - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - return $parameter->var->name; - }, array_values(array_filter($originalNode->params, static function (Param $parameter): bool { - return $parameter->flags === 0; - }))), - $originalNode->stmts, - $message, - 'constructor.unusedParameter', - [] - ); - } + $message = sprintf( + 'Constructor of class %s has an unused parameter $%%s.', + $scope->getClassReflection()->getDisplayName() + ); + if ($scope->getClassReflection()->isAnonymous()) { + $message = 'Constructor of an anonymous class has an unused parameter $%s.'; + } + return $this->check->getUnusedParameters( + $scope, + array_map(static function (Param $parameter): string { + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + return $parameter->var->name; + }, array_values(array_filter($originalNode->params, static function (Param $parameter): bool { + return $parameter->flags === 0; + }))), + $originalNode->stmts, + $message, + 'constructor.unusedParameter', + [] + ); + } } diff --git a/src/Rules/Comparison/BooleanAndConstantConditionRule.php b/src/Rules/Comparison/BooleanAndConstantConditionRule.php index 9feac9d37c..aafad9d8c6 100644 --- a/src/Rules/Comparison/BooleanAndConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanAndConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->checkLogicalAndConstantCondition = $checkLogicalAndConstantCondition; - } - - public function getNodeType(): string - { - return BooleanAndNode::class; - } - - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $errors = []; - - /** @var BooleanAnd|LogicalAnd $originalNode */ - $originalNode = $node->getOriginalNode(); - if (!$originalNode instanceof BooleanAnd && !$this->checkLogicalAndConstantCondition) { - return []; - } - - $leftType = $this->helper->getBooleanType($scope, $originalNode->left); - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - if ($leftType instanceof ConstantBooleanType) { - $addTipLeft = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $tipText, $originalNode): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $originalNode->left); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - $errors[] = $addTipLeft(RuleErrorBuilder::message(sprintf( - 'Left side of && is always %s.', - $leftType->getValue() ? 'true' : 'false' - )))->line($originalNode->left->getLine())->build(); - } - - $rightScope = $node->getRightScope(); - $rightType = $this->helper->getBooleanType( - $rightScope, - $originalNode->right - ); - if ($rightType instanceof ConstantBooleanType) { - $addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $this->helper->getNativeBooleanType( - $rightScope->doNotTreatPhpDocTypesAsCertain(), - $originalNode->right - ); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - $errors[] = $addTipRight(RuleErrorBuilder::message(sprintf( - 'Right side of && is always %s.', - $rightType->getValue() ? 'true' : 'false' - )))->line($originalNode->right->getLine())->build(); - } - - if (count($errors) === 0) { - $nodeType = $scope->getType($originalNode); - if ($nodeType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($originalNode); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - - $errors[] = $addTip(RuleErrorBuilder::message(sprintf( - 'Result of && is always %s.', - $nodeType->getValue() ? 'true' : 'false' - )))->build(); - } - } - - return $errors; - } - + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + private bool $checkLogicalAndConstantCondition; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain, + bool $checkLogicalAndConstantCondition + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->checkLogicalAndConstantCondition = $checkLogicalAndConstantCondition; + } + + public function getNodeType(): string + { + return BooleanAndNode::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $errors = []; + + /** @var BooleanAnd|LogicalAnd $originalNode */ + $originalNode = $node->getOriginalNode(); + if (!$originalNode instanceof BooleanAnd && !$this->checkLogicalAndConstantCondition) { + return []; + } + + $leftType = $this->helper->getBooleanType($scope, $originalNode->left); + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + if ($leftType instanceof ConstantBooleanType) { + $addTipLeft = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $tipText, $originalNode): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $originalNode->left); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + $errors[] = $addTipLeft(RuleErrorBuilder::message(sprintf( + 'Left side of && is always %s.', + $leftType->getValue() ? 'true' : 'false' + )))->line($originalNode->left->getLine())->build(); + } + + $rightScope = $node->getRightScope(); + $rightType = $this->helper->getBooleanType( + $rightScope, + $originalNode->right + ); + if ($rightType instanceof ConstantBooleanType) { + $addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType( + $rightScope->doNotTreatPhpDocTypesAsCertain(), + $originalNode->right + ); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + $errors[] = $addTipRight(RuleErrorBuilder::message(sprintf( + 'Right side of && is always %s.', + $rightType->getValue() ? 'true' : 'false' + )))->line($originalNode->right->getLine())->build(); + } + + if (count($errors) === 0) { + $nodeType = $scope->getType($originalNode); + if ($nodeType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($originalNode); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + + $errors[] = $addTip(RuleErrorBuilder::message(sprintf( + 'Result of && is always %s.', + $nodeType->getValue() ? 'true' : 'false' + )))->build(); + } + } + + return $errors; + } } diff --git a/src/Rules/Comparison/BooleanNotConstantConditionRule.php b/src/Rules/Comparison/BooleanNotConstantConditionRule.php index f87f3d342b..89515bc08a 100644 --- a/src/Rules/Comparison/BooleanNotConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanNotConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\BooleanNot::class; - } - - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $exprType = $this->helper->getBooleanType($scope, $node->expr); - if ($exprType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->expr); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Negated boolean expression is always %s.', - $exprType->getValue() ? 'false' : 'true' - )))->line($node->expr->getLine())->build(), - ]; - } - - return []; - } - + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\BooleanNot::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $exprType = $this->helper->getBooleanType($scope, $node->expr); + if ($exprType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->expr); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Negated boolean expression is always %s.', + $exprType->getValue() ? 'false' : 'true' + )))->line($node->expr->getLine())->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Comparison/BooleanOrConstantConditionRule.php b/src/Rules/Comparison/BooleanOrConstantConditionRule.php index 4b29a14d4d..0b2c31a013 100644 --- a/src/Rules/Comparison/BooleanOrConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanOrConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->checkLogicalOrConstantCondition = $checkLogicalOrConstantCondition; - } - - public function getNodeType(): string - { - return BooleanOrNode::class; - } - - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $originalNode = $node->getOriginalNode(); - if (!$originalNode instanceof BooleanOr && !$this->checkLogicalOrConstantCondition) { - return []; - } - - $messages = []; - $leftType = $this->helper->getBooleanType($scope, $originalNode->left); - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - if ($leftType instanceof ConstantBooleanType) { - $addTipLeft = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $originalNode->left); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - $messages[] = $addTipLeft(RuleErrorBuilder::message(sprintf( - 'Left side of || is always %s.', - $leftType->getValue() ? 'true' : 'false' - )))->line($originalNode->left->getLine())->build(); - } - - $rightScope = $node->getRightScope(); - $rightType = $this->helper->getBooleanType( - $rightScope, - $originalNode->right - ); - if ($rightType instanceof ConstantBooleanType) { - $addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $this->helper->getNativeBooleanType( - $rightScope->doNotTreatPhpDocTypesAsCertain(), - $originalNode->right - ); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - $messages[] = $addTipRight(RuleErrorBuilder::message(sprintf( - 'Right side of || is always %s.', - $rightType->getValue() ? 'true' : 'false' - )))->line($originalNode->right->getLine())->build(); - } - - if (count($messages) === 0) { - $nodeType = $scope->getType($originalNode); - if ($nodeType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($originalNode); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip($tipText); - }; - $messages[] = $addTip(RuleErrorBuilder::message(sprintf( - 'Result of || is always %s.', - $nodeType->getValue() ? 'true' : 'false' - )))->build(); - } - } - - return $messages; - } - + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + private bool $checkLogicalOrConstantCondition; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain, + bool $checkLogicalOrConstantCondition + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->checkLogicalOrConstantCondition = $checkLogicalOrConstantCondition; + } + + public function getNodeType(): string + { + return BooleanOrNode::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $originalNode = $node->getOriginalNode(); + if (!$originalNode instanceof BooleanOr && !$this->checkLogicalOrConstantCondition) { + return []; + } + + $messages = []; + $leftType = $this->helper->getBooleanType($scope, $originalNode->left); + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + if ($leftType instanceof ConstantBooleanType) { + $addTipLeft = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $originalNode->left); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + $messages[] = $addTipLeft(RuleErrorBuilder::message(sprintf( + 'Left side of || is always %s.', + $leftType->getValue() ? 'true' : 'false' + )))->line($originalNode->left->getLine())->build(); + } + + $rightScope = $node->getRightScope(); + $rightType = $this->helper->getBooleanType( + $rightScope, + $originalNode->right + ); + if ($rightType instanceof ConstantBooleanType) { + $addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType( + $rightScope->doNotTreatPhpDocTypesAsCertain(), + $originalNode->right + ); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + $messages[] = $addTipRight(RuleErrorBuilder::message(sprintf( + 'Right side of || is always %s.', + $rightType->getValue() ? 'true' : 'false' + )))->line($originalNode->right->getLine())->build(); + } + + if (count($messages) === 0) { + $nodeType = $scope->getType($originalNode); + if ($nodeType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($originalNode); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip($tipText); + }; + $messages[] = $addTip(RuleErrorBuilder::message(sprintf( + 'Result of || is always %s.', + $nodeType->getValue() ? 'true' : 'false' + )))->build(); + } + } + + return $messages; + } } diff --git a/src/Rules/Comparison/ConstantConditionRuleHelper.php b/src/Rules/Comparison/ConstantConditionRuleHelper.php index 18de49a6c7..4e7213298d 100644 --- a/src/Rules/Comparison/ConstantConditionRuleHelper.php +++ b/src/Rules/Comparison/ConstantConditionRuleHelper.php @@ -1,4 +1,6 @@ -impossibleCheckTypeHelper = $impossibleCheckTypeHelper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function shouldReportAlwaysTrueByDefault(Expr $expr): bool - { - return $expr instanceof Expr\BooleanNot - || $expr instanceof Expr\BinaryOp\BooleanOr - || $expr instanceof Expr\BinaryOp\BooleanAnd - || $expr instanceof Expr\Ternary - || $expr instanceof Expr\Isset_; - } - - public function shouldSkip(Scope $scope, Expr $expr): bool - { - if ( - $expr instanceof Expr\Instanceof_ - || $expr instanceof Expr\BinaryOp\Identical - || $expr instanceof Expr\BinaryOp\NotIdentical - || $expr instanceof Expr\BooleanNot - || $expr instanceof Expr\BinaryOp\BooleanOr - || $expr instanceof Expr\BinaryOp\BooleanAnd - || $expr instanceof Expr\Ternary - || $expr instanceof Expr\Isset_ - || $expr instanceof Expr\BinaryOp\Greater - || $expr instanceof Expr\BinaryOp\GreaterOrEqual - || $expr instanceof Expr\BinaryOp\Smaller - || $expr instanceof Expr\BinaryOp\SmallerOrEqual - ) { - // already checked by different rules - return true; - } - - if ( - $expr instanceof FuncCall - || $expr instanceof MethodCall - || $expr instanceof Expr\StaticCall - ) { - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr); - if ($isAlways !== null) { - return true; - } - } - - return false; - } - - public function getBooleanType(Scope $scope, Expr $expr): BooleanType - { - if ($this->shouldSkip($scope, $expr)) { - return new BooleanType(); - } - - if ($this->treatPhpDocTypesAsCertain) { - return $scope->getType($expr)->toBoolean(); - } - - return $scope->getNativeType($expr)->toBoolean(); - } - - public function getNativeBooleanType(Scope $scope, Expr $expr): BooleanType - { - if ($this->shouldSkip($scope, $expr)) { - return new BooleanType(); - } - - return $scope->getNativeType($expr)->toBoolean(); - } - + private ImpossibleCheckTypeHelper $impossibleCheckTypeHelper; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, + bool $treatPhpDocTypesAsCertain + ) { + $this->impossibleCheckTypeHelper = $impossibleCheckTypeHelper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function shouldReportAlwaysTrueByDefault(Expr $expr): bool + { + return $expr instanceof Expr\BooleanNot + || $expr instanceof Expr\BinaryOp\BooleanOr + || $expr instanceof Expr\BinaryOp\BooleanAnd + || $expr instanceof Expr\Ternary + || $expr instanceof Expr\Isset_; + } + + public function shouldSkip(Scope $scope, Expr $expr): bool + { + if ( + $expr instanceof Expr\Instanceof_ + || $expr instanceof Expr\BinaryOp\Identical + || $expr instanceof Expr\BinaryOp\NotIdentical + || $expr instanceof Expr\BooleanNot + || $expr instanceof Expr\BinaryOp\BooleanOr + || $expr instanceof Expr\BinaryOp\BooleanAnd + || $expr instanceof Expr\Ternary + || $expr instanceof Expr\Isset_ + || $expr instanceof Expr\BinaryOp\Greater + || $expr instanceof Expr\BinaryOp\GreaterOrEqual + || $expr instanceof Expr\BinaryOp\Smaller + || $expr instanceof Expr\BinaryOp\SmallerOrEqual + ) { + // already checked by different rules + return true; + } + + if ( + $expr instanceof FuncCall + || $expr instanceof MethodCall + || $expr instanceof Expr\StaticCall + ) { + $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr); + if ($isAlways !== null) { + return true; + } + } + + return false; + } + + public function getBooleanType(Scope $scope, Expr $expr): BooleanType + { + if ($this->shouldSkip($scope, $expr)) { + return new BooleanType(); + } + + if ($this->treatPhpDocTypesAsCertain) { + return $scope->getType($expr)->toBoolean(); + } + + return $scope->getNativeType($expr)->toBoolean(); + } + + public function getNativeBooleanType(Scope $scope, Expr $expr): BooleanType + { + if ($this->shouldSkip($scope, $expr)) { + return new BooleanType(); + } + + return $scope->getNativeType($expr)->toBoolean(); + } } diff --git a/src/Rules/Comparison/ElseIfConstantConditionRule.php b/src/Rules/Comparison/ElseIfConstantConditionRule.php index 734ccabf49..36b42fd333 100644 --- a/src/Rules/Comparison/ElseIfConstantConditionRule.php +++ b/src/Rules/Comparison/ElseIfConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } + private bool $treatPhpDocTypesAsCertain; - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\ElseIf_::class; - } + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $exprType = $this->helper->getBooleanType($scope, $node->cond); - if ($exprType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\ElseIf_::class; + } - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $exprType = $this->helper->getBooleanType($scope, $node->cond); + if ($exprType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Elseif condition is always %s.', - $exprType->getValue() ? 'true' : 'false' - )))->line($node->cond->getLine()) - ->identifier('deadCode.elseifConstantCondition') - ->metadata([ - 'depth' => $node->getAttribute('statementDepth'), - 'order' => $node->getAttribute('statementOrder'), - 'value' => $exprType->getValue(), - ]) - ->build(), - ]; - } + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } - return []; - } + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Elseif condition is always %s.', + $exprType->getValue() ? 'true' : 'false' + )))->line($node->cond->getLine()) + ->identifier('deadCode.elseifConstantCondition') + ->metadata([ + 'depth' => $node->getAttribute('statementDepth'), + 'order' => $node->getAttribute('statementOrder'), + 'value' => $exprType->getValue(), + ]) + ->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Comparison/IfConstantConditionRule.php b/src/Rules/Comparison/IfConstantConditionRule.php index f597146def..624405e599 100644 --- a/src/Rules/Comparison/IfConstantConditionRule.php +++ b/src/Rules/Comparison/IfConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } + private bool $treatPhpDocTypesAsCertain; - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\If_::class; - } + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $exprType = $this->helper->getBooleanType($scope, $node->cond); - if ($exprType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\If_::class; + } - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $exprType = $this->helper->getBooleanType($scope, $node->cond); + if ($exprType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'If condition is always %s.', - $exprType->getValue() ? 'true' : 'false' - )))->line($node->cond->getLine()) - ->identifier('deadCode.ifConstantCondition') - ->metadata([ - 'depth' => $node->getAttribute('statementDepth'), - 'order' => $node->getAttribute('statementOrder'), - 'value' => $exprType->getValue(), - ]) - ->build(), - ]; - } + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; - return []; - } + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'If condition is always %s.', + $exprType->getValue() ? 'true' : 'false' + )))->line($node->cond->getLine()) + ->identifier('deadCode.ifConstantCondition') + ->metadata([ + 'depth' => $node->getAttribute('statementDepth'), + 'order' => $node->getAttribute('statementOrder'), + 'value' => $exprType->getValue(), + ]) + ->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index f873a37332..a4806d5e88 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -1,4 +1,6 @@ -impossibleCheckTypeHelper = $impossibleCheckTypeHelper; - $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Name) { - return []; - } - - $functionName = (string) $node->name; - if (strtolower($functionName) === 'is_a') { - return []; - } - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); - if ($isAlways === null) { - return []; - } - - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); - if ($isAlways !== null) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - - if (!$isAlways) { - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to function %s()%s will always evaluate to false.', - $functionName, - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to function %s()%s will always evaluate to true.', - $functionName, - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $impossibleCheckTypeHelper; + + private bool $checkAlwaysTrueCheckTypeFunctionCall; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, + bool $checkAlwaysTrueCheckTypeFunctionCall, + bool $treatPhpDocTypesAsCertain + ) { + $this->impossibleCheckTypeHelper = $impossibleCheckTypeHelper; + $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = (string) $node->name; + if (strtolower($functionName) === 'is_a') { + return []; + } + $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + if ($isAlways === null) { + return []; + } + + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + if ($isAlways !== null) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + if (!$isAlways) { + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to function %s()%s will always evaluate to false.', + $functionName, + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to function %s()%s will always evaluate to true.', + $functionName, + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 43d2532c98..bb1b9c5d22 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->typeSpecifier = $typeSpecifier; - $this->universalObjectCratesClasses = $universalObjectCratesClasses; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function findSpecifiedType( - Scope $scope, - Expr $node - ): ?bool - { - if ( - $node instanceof FuncCall - && count($node->args) > 0 - ) { - if ($node->name instanceof \PhpParser\Node\Name) { - $functionName = strtolower((string) $node->name); - if ($functionName === 'assert') { - $assertValue = $scope->getType($node->args[0]->value)->toBoolean(); - if (!$assertValue instanceof ConstantBooleanType) { - return null; - } - - return $assertValue->getValue(); - } - if (in_array($functionName, [ - 'class_exists', - 'interface_exists', - 'trait_exists', - ], true)) { - return null; - } - if ($functionName === 'count') { - return null; - } elseif ($functionName === 'defined') { - return null; - } elseif ( - $functionName === 'in_array' - && count($node->args) >= 3 - ) { - $haystackType = $scope->getType($node->args[1]->value); - if ($haystackType instanceof MixedType) { - return null; - } - - if (!$haystackType->isArray()->yes()) { - return null; - } - - if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) { - $needleType = $scope->getType($node->args[0]->value); - - $haystackArrayTypes = TypeUtils::getArrays($haystackType); - if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) { - return null; - } - - $valueType = $haystackType->getIterableValueType(); - $isNeedleSupertype = $needleType->isSuperTypeOf($valueType); - - if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) { - foreach ($haystackArrayTypes as $haystackArrayType) { - foreach (TypeUtils::getConstantScalars($haystackArrayType->getIterableValueType()) as $constantScalarType) { - if ($needleType->isSuperTypeOf($constantScalarType)->yes()) { - continue 2; - } - } - - return null; - } - } - - if ($isNeedleSupertype->yes()) { - $hasConstantNeedleTypes = count(TypeUtils::getConstantScalars($needleType)) > 0; - $hasConstantHaystackTypes = count(TypeUtils::getConstantScalars($valueType)) > 0; - if ( - ( - !$hasConstantNeedleTypes - && !$hasConstantHaystackTypes - ) - || $hasConstantNeedleTypes !== $hasConstantHaystackTypes - ) { - return null; - } - } - } - } elseif ($functionName === 'method_exists' && count($node->args) >= 2) { - $objectType = $scope->getType($node->args[0]->value); - $methodType = $scope->getType($node->args[1]->value); - - if ($objectType instanceof ConstantStringType - && !$this->reflectionProvider->hasClass($objectType->getValue()) - ) { - return false; - } - - if ($methodType instanceof ConstantStringType) { - if ($objectType instanceof ConstantStringType) { - $objectType = new ObjectType($objectType->getValue()); - } - - if ($objectType instanceof TypeWithClassName) { - if ($objectType->hasMethod($methodType->getValue())->yes()) { - return true; - } - - if ($objectType->hasMethod($methodType->getValue())->no()) { - return false; - } - } - } - } - } - } - - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $node, TypeSpecifierContext::createTruthy()); - $sureTypes = $specifiedTypes->getSureTypes(); - $sureNotTypes = $specifiedTypes->getSureNotTypes(); - - $isSpecified = static function (Expr $expr) use ($scope, $node): bool { - if ($expr === $node) { - return true; - } - - return ( - $node instanceof FuncCall - || $node instanceof MethodCall - || $node instanceof Expr\StaticCall - ) && $scope->isSpecified($expr); - }; - - if (count($sureTypes) === 1 && count($sureNotTypes) === 0) { - $sureType = reset($sureTypes); - if ($isSpecified($sureType[0])) { - return null; - } - - if ($this->treatPhpDocTypesAsCertain) { - $argumentType = $scope->getType($sureType[0]); - } else { - $argumentType = $scope->getNativeType($sureType[0]); - } - - /** @var \PHPStan\Type\Type $resultType */ - $resultType = $sureType[1]; - - $isSuperType = $resultType->isSuperTypeOf($argumentType); - if ($isSuperType->yes()) { - return true; - } elseif ($isSuperType->no()) { - return false; - } - - return null; - } elseif (count($sureNotTypes) === 1 && count($sureTypes) === 0) { - $sureNotType = reset($sureNotTypes); - if ($isSpecified($sureNotType[0])) { - return null; - } - - if ($this->treatPhpDocTypesAsCertain) { - $argumentType = $scope->getType($sureNotType[0]); - } else { - $argumentType = $scope->getNativeType($sureNotType[0]); - } - - /** @var \PHPStan\Type\Type $resultType */ - $resultType = $sureNotType[1]; - - $isSuperType = $resultType->isSuperTypeOf($argumentType); - if ($isSuperType->yes()) { - return false; - } elseif ($isSuperType->no()) { - return true; - } - - return null; - } - - if (count($sureTypes) > 0) { - foreach ($sureTypes as $sureType) { - if ($isSpecified($sureType[0])) { - return null; - } - } - $types = TypeCombinator::union( - ...array_column($sureTypes, 1) - ); - if ($types instanceof NeverType) { - return false; - } - } - - if (count($sureNotTypes) > 0) { - foreach ($sureNotTypes as $sureNotType) { - if ($isSpecified($sureNotType[0])) { - return null; - } - } - $types = TypeCombinator::union( - ...array_column($sureNotTypes, 1) - ); - if ($types instanceof NeverType) { - return true; - } - } - - return null; - } - - /** - * @param Scope $scope - * @param \PhpParser\Node\Arg[] $args - * @return string - */ - public function getArgumentsDescription( - Scope $scope, - array $args - ): string - { - if (count($args) === 0) { - return ''; - } - - $descriptions = array_map(static function (Arg $arg) use ($scope): string { - return $scope->getType($arg->value)->describe(VerbosityLevel::value()); - }, $args); - - if (count($descriptions) < 3) { - return sprintf(' with %s', implode(' and ', $descriptions)); - } - - $lastDescription = array_pop($descriptions); - - return sprintf( - ' with arguments %s and %s', - implode(', ', $descriptions), - $lastDescription - ); - } - - public function doNotTreatPhpDocTypesAsCertain(): self - { - if (!$this->treatPhpDocTypesAsCertain) { - return $this; - } - - return new self( - $this->reflectionProvider, - $this->typeSpecifier, - $this->universalObjectCratesClasses, - false - ); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + /** @var string[] */ + private array $universalObjectCratesClasses; + + private bool $treatPhpDocTypesAsCertain; + + /** + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param \PHPStan\Analyser\TypeSpecifier $typeSpecifier + * @param string[] $universalObjectCratesClasses + * @param bool $treatPhpDocTypesAsCertain + */ + public function __construct( + ReflectionProvider $reflectionProvider, + TypeSpecifier $typeSpecifier, + array $universalObjectCratesClasses, + bool $treatPhpDocTypesAsCertain + ) { + $this->reflectionProvider = $reflectionProvider; + $this->typeSpecifier = $typeSpecifier; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function findSpecifiedType( + Scope $scope, + Expr $node + ): ?bool { + if ( + $node instanceof FuncCall + && count($node->args) > 0 + ) { + if ($node->name instanceof \PhpParser\Node\Name) { + $functionName = strtolower((string) $node->name); + if ($functionName === 'assert') { + $assertValue = $scope->getType($node->args[0]->value)->toBoolean(); + if (!$assertValue instanceof ConstantBooleanType) { + return null; + } + + return $assertValue->getValue(); + } + if (in_array($functionName, [ + 'class_exists', + 'interface_exists', + 'trait_exists', + ], true)) { + return null; + } + if ($functionName === 'count') { + return null; + } elseif ($functionName === 'defined') { + return null; + } elseif ( + $functionName === 'in_array' + && count($node->args) >= 3 + ) { + $haystackType = $scope->getType($node->args[1]->value); + if ($haystackType instanceof MixedType) { + return null; + } + + if (!$haystackType->isArray()->yes()) { + return null; + } + + if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) { + $needleType = $scope->getType($node->args[0]->value); + + $haystackArrayTypes = TypeUtils::getArrays($haystackType); + if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) { + return null; + } + + $valueType = $haystackType->getIterableValueType(); + $isNeedleSupertype = $needleType->isSuperTypeOf($valueType); + + if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) { + foreach ($haystackArrayTypes as $haystackArrayType) { + foreach (TypeUtils::getConstantScalars($haystackArrayType->getIterableValueType()) as $constantScalarType) { + if ($needleType->isSuperTypeOf($constantScalarType)->yes()) { + continue 2; + } + } + + return null; + } + } + + if ($isNeedleSupertype->yes()) { + $hasConstantNeedleTypes = count(TypeUtils::getConstantScalars($needleType)) > 0; + $hasConstantHaystackTypes = count(TypeUtils::getConstantScalars($valueType)) > 0; + if ( + ( + !$hasConstantNeedleTypes + && !$hasConstantHaystackTypes + ) + || $hasConstantNeedleTypes !== $hasConstantHaystackTypes + ) { + return null; + } + } + } + } elseif ($functionName === 'method_exists' && count($node->args) >= 2) { + $objectType = $scope->getType($node->args[0]->value); + $methodType = $scope->getType($node->args[1]->value); + + if ($objectType instanceof ConstantStringType + && !$this->reflectionProvider->hasClass($objectType->getValue()) + ) { + return false; + } + + if ($methodType instanceof ConstantStringType) { + if ($objectType instanceof ConstantStringType) { + $objectType = new ObjectType($objectType->getValue()); + } + + if ($objectType instanceof TypeWithClassName) { + if ($objectType->hasMethod($methodType->getValue())->yes()) { + return true; + } + + if ($objectType->hasMethod($methodType->getValue())->no()) { + return false; + } + } + } + } + } + } + + $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $node, TypeSpecifierContext::createTruthy()); + $sureTypes = $specifiedTypes->getSureTypes(); + $sureNotTypes = $specifiedTypes->getSureNotTypes(); + + $isSpecified = static function (Expr $expr) use ($scope, $node): bool { + if ($expr === $node) { + return true; + } + + return ( + $node instanceof FuncCall + || $node instanceof MethodCall + || $node instanceof Expr\StaticCall + ) && $scope->isSpecified($expr); + }; + + if (count($sureTypes) === 1 && count($sureNotTypes) === 0) { + $sureType = reset($sureTypes); + if ($isSpecified($sureType[0])) { + return null; + } + + if ($this->treatPhpDocTypesAsCertain) { + $argumentType = $scope->getType($sureType[0]); + } else { + $argumentType = $scope->getNativeType($sureType[0]); + } + + /** @var \PHPStan\Type\Type $resultType */ + $resultType = $sureType[1]; + + $isSuperType = $resultType->isSuperTypeOf($argumentType); + if ($isSuperType->yes()) { + return true; + } elseif ($isSuperType->no()) { + return false; + } + + return null; + } elseif (count($sureNotTypes) === 1 && count($sureTypes) === 0) { + $sureNotType = reset($sureNotTypes); + if ($isSpecified($sureNotType[0])) { + return null; + } + + if ($this->treatPhpDocTypesAsCertain) { + $argumentType = $scope->getType($sureNotType[0]); + } else { + $argumentType = $scope->getNativeType($sureNotType[0]); + } + + /** @var \PHPStan\Type\Type $resultType */ + $resultType = $sureNotType[1]; + + $isSuperType = $resultType->isSuperTypeOf($argumentType); + if ($isSuperType->yes()) { + return false; + } elseif ($isSuperType->no()) { + return true; + } + + return null; + } + + if (count($sureTypes) > 0) { + foreach ($sureTypes as $sureType) { + if ($isSpecified($sureType[0])) { + return null; + } + } + $types = TypeCombinator::union( + ...array_column($sureTypes, 1) + ); + if ($types instanceof NeverType) { + return false; + } + } + + if (count($sureNotTypes) > 0) { + foreach ($sureNotTypes as $sureNotType) { + if ($isSpecified($sureNotType[0])) { + return null; + } + } + $types = TypeCombinator::union( + ...array_column($sureNotTypes, 1) + ); + if ($types instanceof NeverType) { + return true; + } + } + + return null; + } + + /** + * @param Scope $scope + * @param \PhpParser\Node\Arg[] $args + * @return string + */ + public function getArgumentsDescription( + Scope $scope, + array $args + ): string { + if (count($args) === 0) { + return ''; + } + + $descriptions = array_map(static function (Arg $arg) use ($scope): string { + return $scope->getType($arg->value)->describe(VerbosityLevel::value()); + }, $args); + + if (count($descriptions) < 3) { + return sprintf(' with %s', implode(' and ', $descriptions)); + } + + $lastDescription = array_pop($descriptions); + + return sprintf( + ' with arguments %s and %s', + implode(', ', $descriptions), + $lastDescription + ); + } + + public function doNotTreatPhpDocTypesAsCertain(): self + { + if (!$this->treatPhpDocTypesAsCertain) { + return $this; + } + + return new self( + $this->reflectionProvider, + $this->typeSpecifier, + $this->universalObjectCratesClasses, + false + ); + } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index c7df381e21..bebb6c672c 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -1,4 +1,6 @@ -impossibleCheckTypeHelper = $impossibleCheckTypeHelper; - $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\MethodCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier) { - return []; - } - - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); - if ($isAlways === null) { - return []; - } - - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); - if ($isAlways !== null) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - - if (!$isAlways) { - $method = $this->getMethod($node->var, $node->name->name, $scope); - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to method %s::%s()%s will always evaluate to false.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - $method = $this->getMethod($node->var, $node->name->name, $scope); - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to method %s::%s()%s will always evaluate to true.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } - - return []; - } - - private function getMethod( - Expr $var, - string $methodName, - Scope $scope - ): MethodReflection - { - $calledOnType = $scope->getType($var); - $method = $scope->getMethodReflection($calledOnType, $methodName); - if ($method === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $method; - } - + private \PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $impossibleCheckTypeHelper; + + private bool $checkAlwaysTrueCheckTypeFunctionCall; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, + bool $checkAlwaysTrueCheckTypeFunctionCall, + bool $treatPhpDocTypesAsCertain + ) { + $this->impossibleCheckTypeHelper = $impossibleCheckTypeHelper; + $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + if ($isAlways === null) { + return []; + } + + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + if ($isAlways !== null) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + if (!$isAlways) { + $method = $this->getMethod($node->var, $node->name->name, $scope); + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to method %s::%s()%s will always evaluate to false.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { + $method = $this->getMethod($node->var, $node->name->name, $scope); + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to method %s::%s()%s will always evaluate to true.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } + + return []; + } + + private function getMethod( + Expr $var, + string $methodName, + Scope $scope + ): MethodReflection { + $calledOnType = $scope->getType($var); + $method = $scope->getMethodReflection($calledOnType, $methodName); + if ($method === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $method; + } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index 7284114a6a..719637949d 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -1,4 +1,6 @@ -impossibleCheckTypeHelper = $impossibleCheckTypeHelper; - $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\StaticCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier) { - return []; - } - - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); - if ($isAlways === null) { - return []; - } - - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); - if ($isAlways !== null) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - - if (!$isAlways) { - $method = $this->getMethod($node->class, $node->name->name, $scope); - - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to static method %s::%s()%s will always evaluate to false.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - $method = $this->getMethod($node->class, $node->name->name, $scope); - - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Call to static method %s::%s()%s will always evaluate to true.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) - )))->build(), - ]; - } - - return []; - } - - /** - * @param Node\Name|Expr $class - * @param string $methodName - * @param Scope $scope - * @return MethodReflection - * @throws \PHPStan\ShouldNotHappenException - */ - private function getMethod( - $class, - string $methodName, - Scope $scope - ): MethodReflection - { - if ($class instanceof Node\Name) { - $calledOnType = $scope->resolveTypeByName($class); - } else { - $calledOnType = $scope->getType($class); - } - - $method = $scope->getMethodReflection($calledOnType, $methodName); - if ($method === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $method; - } - + private \PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $impossibleCheckTypeHelper; + + private bool $checkAlwaysTrueCheckTypeFunctionCall; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, + bool $checkAlwaysTrueCheckTypeFunctionCall, + bool $treatPhpDocTypesAsCertain + ) { + $this->impossibleCheckTypeHelper = $impossibleCheckTypeHelper; + $this->checkAlwaysTrueCheckTypeFunctionCall = $checkAlwaysTrueCheckTypeFunctionCall; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + if ($isAlways === null) { + return []; + } + + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + if ($isAlways !== null) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + if (!$isAlways) { + $method = $this->getMethod($node->class, $node->name->name, $scope); + + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to static method %s::%s()%s will always evaluate to false.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { + $method = $this->getMethod($node->class, $node->name->name, $scope); + + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Call to static method %s::%s()%s will always evaluate to true.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + )))->build(), + ]; + } + + return []; + } + + /** + * @param Node\Name|Expr $class + * @param string $methodName + * @param Scope $scope + * @return MethodReflection + * @throws \PHPStan\ShouldNotHappenException + */ + private function getMethod( + $class, + string $methodName, + Scope $scope + ): MethodReflection { + if ($class instanceof Node\Name) { + $calledOnType = $scope->resolveTypeByName($class); + } else { + $calledOnType = $scope->getType($class); + } + + $method = $scope->getMethodReflection($calledOnType, $methodName); + if ($method === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $method; + } } diff --git a/src/Rules/Comparison/MatchExpressionRule.php b/src/Rules/Comparison/MatchExpressionRule.php index 51db3018db..3502add638 100644 --- a/src/Rules/Comparison/MatchExpressionRule.php +++ b/src/Rules/Comparison/MatchExpressionRule.php @@ -1,4 +1,6 @@ -checkAlwaysTrueStrictComparison = $checkAlwaysTrueStrictComparison; - } - - public function getNodeType(): string - { - return MatchExpressionNode::class; - } + public function __construct(bool $checkAlwaysTrueStrictComparison) + { + $this->checkAlwaysTrueStrictComparison = $checkAlwaysTrueStrictComparison; + } - public function processNode(Node $node, Scope $scope): array - { - $matchCondition = $node->getCondition(); - $nextArmIsDead = false; - $errors = []; - $armsCount = count($node->getArms()); - $hasDefault = false; - foreach ($node->getArms() as $i => $arm) { - if ($nextArmIsDead) { - $errors[] = RuleErrorBuilder::message('Match arm is unreachable because previous comparison is always true.')->line($arm->getLine())->build(); - continue; - } - $armConditions = $arm->getConditions(); - if (count($armConditions) === 0) { - $hasDefault = true; - } - foreach ($armConditions as $armCondition) { - $armConditionScope = $armCondition->getScope(); - $armConditionExpr = new Node\Expr\BinaryOp\Identical( - $matchCondition, - $armCondition->getCondition() - ); - $armConditionResult = $armConditionScope->getType($armConditionExpr); - if (!$armConditionResult instanceof ConstantBooleanType) { - continue; - } + public function getNodeType(): string + { + return MatchExpressionNode::class; + } - $armLine = $armCondition->getLine(); - if (!$armConditionResult->getValue()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Match arm comparison between %s and %s is always false.', - $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), - $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()) - ))->line($armLine)->build(); - } else { - $nextArmIsDead = true; - if ( - $this->checkAlwaysTrueStrictComparison - && ($i !== $armsCount - 1 || $i === 0) - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Match arm comparison between %s and %s is always true.', - $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), - $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()) - ))->line($armLine)->build(); - } - } - } - } + public function processNode(Node $node, Scope $scope): array + { + $matchCondition = $node->getCondition(); + $nextArmIsDead = false; + $errors = []; + $armsCount = count($node->getArms()); + $hasDefault = false; + foreach ($node->getArms() as $i => $arm) { + if ($nextArmIsDead) { + $errors[] = RuleErrorBuilder::message('Match arm is unreachable because previous comparison is always true.')->line($arm->getLine())->build(); + continue; + } + $armConditions = $arm->getConditions(); + if (count($armConditions) === 0) { + $hasDefault = true; + } + foreach ($armConditions as $armCondition) { + $armConditionScope = $armCondition->getScope(); + $armConditionExpr = new Node\Expr\BinaryOp\Identical( + $matchCondition, + $armCondition->getCondition() + ); + $armConditionResult = $armConditionScope->getType($armConditionExpr); + if (!$armConditionResult instanceof ConstantBooleanType) { + continue; + } - if (!$hasDefault && !$nextArmIsDead) { - $remainingType = $node->getEndScope()->getType($matchCondition); - if (!$remainingType instanceof NeverType) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Match expression does not handle remaining %s: %s', - $remainingType instanceof UnionType ? 'values' : 'value', - $remainingType->describe(VerbosityLevel::value()) - ))->build(); - } - } + $armLine = $armCondition->getLine(); + if (!$armConditionResult->getValue()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Match arm comparison between %s and %s is always false.', + $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), + $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()) + ))->line($armLine)->build(); + } else { + $nextArmIsDead = true; + if ( + $this->checkAlwaysTrueStrictComparison + && ($i !== $armsCount - 1 || $i === 0) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Match arm comparison between %s and %s is always true.', + $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), + $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()) + ))->line($armLine)->build(); + } + } + } + } - return $errors; - } + if (!$hasDefault && !$nextArmIsDead) { + $remainingType = $node->getEndScope()->getType($matchCondition); + if (!$remainingType instanceof NeverType) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Match expression does not handle remaining %s: %s', + $remainingType instanceof UnionType ? 'values' : 'value', + $remainingType->describe(VerbosityLevel::value()) + ))->build(); + } + } + return $errors; + } } diff --git a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php index de0ebaf218..b999117664 100644 --- a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php +++ b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php @@ -1,4 +1,6 @@ -getType($node); - if ($exprType instanceof ConstantBooleanType) { - return [ - RuleErrorBuilder::message(sprintf( - 'Comparison operation "%s" between %s and %s is always %s.', - $node->getOperatorSigil(), - $scope->getType($node->left)->describe(VerbosityLevel::value()), - $scope->getType($node->right)->describe(VerbosityLevel::value()), - $exprType->getValue() ? 'true' : 'false' - ))->build(), - ]; - } - - return []; - } - + public function getNodeType(): string + { + return BinaryOp::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + if ( + !$node instanceof BinaryOp\Greater + && !$node instanceof BinaryOp\GreaterOrEqual + && !$node instanceof BinaryOp\Smaller + && !$node instanceof BinaryOp\SmallerOrEqual + ) { + return []; + } + + $exprType = $scope->getType($node); + if ($exprType instanceof ConstantBooleanType) { + return [ + RuleErrorBuilder::message(sprintf( + 'Comparison operation "%s" between %s and %s is always %s.', + $node->getOperatorSigil(), + $scope->getType($node->left)->describe(VerbosityLevel::value()), + $scope->getType($node->right)->describe(VerbosityLevel::value()), + $exprType->getValue() ? 'true' : 'false' + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index e09838544a..54b34b66ae 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -1,4 +1,6 @@ -checkAlwaysTrueStrictComparison = $checkAlwaysTrueStrictComparison; - } - - public function getNodeType(): string - { - return Node\Expr\BinaryOp::class; - } + public function __construct(bool $checkAlwaysTrueStrictComparison) + { + $this->checkAlwaysTrueStrictComparison = $checkAlwaysTrueStrictComparison; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$node instanceof Node\Expr\BinaryOp\Identical && !$node instanceof Node\Expr\BinaryOp\NotIdentical) { - return []; - } + public function getNodeType(): string + { + return Node\Expr\BinaryOp::class; + } - $nodeType = $scope->getType($node); - if (!$nodeType instanceof ConstantBooleanType) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof Node\Expr\BinaryOp\Identical && !$node instanceof Node\Expr\BinaryOp\NotIdentical) { + return []; + } - $leftType = $scope->getType($node->left); - $rightType = $scope->getType($node->right); + $nodeType = $scope->getType($node); + if (!$nodeType instanceof ConstantBooleanType) { + return []; + } - if (!$nodeType->getValue()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Strict comparison using %s between %s and %s will always evaluate to false.', - $node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==', - $leftType->describe(VerbosityLevel::value()), - $rightType->describe(VerbosityLevel::value()) - ))->build(), - ]; - } elseif ($this->checkAlwaysTrueStrictComparison) { - return [ - RuleErrorBuilder::message(sprintf( - 'Strict comparison using %s between %s and %s will always evaluate to true.', - $node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==', - $leftType->describe(VerbosityLevel::value()), - $rightType->describe(VerbosityLevel::value()) - ))->build(), - ]; - } + $leftType = $scope->getType($node->left); + $rightType = $scope->getType($node->right); - return []; - } + if (!$nodeType->getValue()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Strict comparison using %s between %s and %s will always evaluate to false.', + $node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==', + $leftType->describe(VerbosityLevel::value()), + $rightType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } elseif ($this->checkAlwaysTrueStrictComparison) { + return [ + RuleErrorBuilder::message(sprintf( + 'Strict comparison using %s between %s and %s will always evaluate to true.', + $node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==', + $leftType->describe(VerbosityLevel::value()), + $rightType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php index 5e1e96b3a9..c91e05ba55 100644 --- a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php +++ b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } + private bool $treatPhpDocTypesAsCertain; - public function getNodeType(): string - { - return \PhpParser\Node\Expr\Ternary::class; - } + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } - public function processNode( - \PhpParser\Node $node, - \PHPStan\Analyser\Scope $scope - ): array - { - $exprType = $this->helper->getBooleanType($scope, $node->cond); - if ($exprType instanceof ConstantBooleanType) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } + public function getNodeType(): string + { + return \PhpParser\Node\Expr\Ternary::class; + } - $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array { + $exprType = $this->helper->getBooleanType($scope, $node->cond); + if ($exprType instanceof ConstantBooleanType) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - return [ - $addTip(RuleErrorBuilder::message(sprintf( - 'Ternary operator condition is always %s.', - $exprType->getValue() ? 'true' : 'false' - ))) - ->identifier('deadCode.ternaryConstantCondition') - ->metadata([ - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - 'depth' => $node->getAttribute('expressionDepth'), - 'order' => $node->getAttribute('expressionOrder'), - 'value' => $exprType->getValue(), - ]) - ->build(), - ]; - } + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } - return []; - } + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Ternary operator condition is always %s.', + $exprType->getValue() ? 'true' : 'false' + ))) + ->identifier('deadCode.ternaryConstantCondition') + ->metadata([ + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + 'depth' => $node->getAttribute('expressionDepth'), + 'order' => $node->getAttribute('expressionOrder'), + 'value' => $exprType->getValue(), + ]) + ->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Comparison/UnreachableIfBranchesRule.php b/src/Rules/Comparison/UnreachableIfBranchesRule.php index e26b63d748..0fbc779faf 100644 --- a/src/Rules/Comparison/UnreachableIfBranchesRule.php +++ b/src/Rules/Comparison/UnreachableIfBranchesRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } + private bool $treatPhpDocTypesAsCertain; - public function getNodeType(): string - { - return Node\Stmt\If_::class; - } + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } - public function processNode(Node $node, Scope $scope): array - { - $errors = []; - $condition = $node->cond; - $conditionType = $scope->getType($condition)->toBoolean(); - $nextBranchIsDead = $conditionType instanceof ConstantBooleanType && $conditionType->getValue() && $this->helper->shouldSkip($scope, $node->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond); - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, &$condition): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } + public function getNodeType(): string + { + return Node\Stmt\If_::class; + } - $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($condition)->toBoolean(); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + $condition = $node->cond; + $conditionType = $scope->getType($condition)->toBoolean(); + $nextBranchIsDead = $conditionType instanceof ConstantBooleanType && $conditionType->getValue() && $this->helper->shouldSkip($scope, $node->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond); + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, &$condition): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; + $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($condition)->toBoolean(); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } - foreach ($node->elseifs as $elseif) { - if ($nextBranchIsDead) { - $errors[] = $addTip(RuleErrorBuilder::message('Elseif branch is unreachable because previous condition is always true.')->line($elseif->getLine())) - ->identifier('deadCode.unreachableElseif') - ->metadata([ - 'ifDepth' => $node->getAttribute('statementDepth'), - 'ifOrder' => $node->getAttribute('statementOrder'), - 'depth' => $elseif->getAttribute('statementDepth'), - 'order' => $elseif->getAttribute('statementOrder'), - ]) - ->build(); - continue; - } + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; - $condition = $elseif->cond; - $conditionType = $scope->getType($condition)->toBoolean(); - $nextBranchIsDead = $conditionType instanceof ConstantBooleanType && $conditionType->getValue() && $this->helper->shouldSkip($scope, $elseif->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($elseif->cond); - } + foreach ($node->elseifs as $elseif) { + if ($nextBranchIsDead) { + $errors[] = $addTip(RuleErrorBuilder::message('Elseif branch is unreachable because previous condition is always true.')->line($elseif->getLine())) + ->identifier('deadCode.unreachableElseif') + ->metadata([ + 'ifDepth' => $node->getAttribute('statementDepth'), + 'ifOrder' => $node->getAttribute('statementOrder'), + 'depth' => $elseif->getAttribute('statementDepth'), + 'order' => $elseif->getAttribute('statementOrder'), + ]) + ->build(); + continue; + } - if ($node->else !== null && $nextBranchIsDead) { - $errors[] = $addTip(RuleErrorBuilder::message('Else branch is unreachable because previous condition is always true.'))->line($node->else->getLine()) - ->identifier('deadCode.unreachableElse') - ->metadata([ - 'ifDepth' => $node->getAttribute('statementDepth'), - 'ifOrder' => $node->getAttribute('statementOrder'), - ]) - ->build(); - } + $condition = $elseif->cond; + $conditionType = $scope->getType($condition)->toBoolean(); + $nextBranchIsDead = $conditionType instanceof ConstantBooleanType && $conditionType->getValue() && $this->helper->shouldSkip($scope, $elseif->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($elseif->cond); + } - return $errors; - } + if ($node->else !== null && $nextBranchIsDead) { + $errors[] = $addTip(RuleErrorBuilder::message('Else branch is unreachable because previous condition is always true.'))->line($node->else->getLine()) + ->identifier('deadCode.unreachableElse') + ->metadata([ + 'ifDepth' => $node->getAttribute('statementDepth'), + 'ifOrder' => $node->getAttribute('statementOrder'), + ]) + ->build(); + } + return $errors; + } } diff --git a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php index 2c9a217bf0..28172b1760 100644 --- a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php +++ b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php @@ -1,4 +1,6 @@ -helper = $helper; - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } + private bool $treatPhpDocTypesAsCertain; - public function getNodeType(): string - { - return Node\Expr\Ternary::class; - } + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } - public function processNode(Node $node, Scope $scope): array - { - $conditionType = $scope->getType($node->cond)->toBoolean(); - if ( - $conditionType instanceof ConstantBooleanType - && $conditionType->getValue() - && $this->helper->shouldSkip($scope, $node->cond) - && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond) - ) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } + public function getNodeType(): string + { + return Node\Expr\Ternary::class; + } - $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($node->cond); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } + public function processNode(Node $node, Scope $scope): array + { + $conditionType = $scope->getType($node->cond)->toBoolean(); + if ( + $conditionType instanceof ConstantBooleanType + && $conditionType->getValue() + && $this->helper->shouldSkip($scope, $node->cond) + && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond) + ) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } - return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); - }; - return [ - $addTip(RuleErrorBuilder::message('Else branch is unreachable because ternary operator condition is always true.')) - ->line($node->else->getLine()) - ->identifier('deadCode.unreachableTernaryElse') - ->metadata([ - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - 'depth' => $node->getAttribute('expressionDepth'), - 'order' => $node->getAttribute('expressionOrder'), - ]) - ->build(), - ]; - } + $booleanNativeType = $scope->doNotTreatPhpDocTypesAsCertain()->getType($node->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } - return []; - } + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + return [ + $addTip(RuleErrorBuilder::message('Else branch is unreachable because ternary operator condition is always true.')) + ->line($node->else->getLine()) + ->identifier('deadCode.unreachableTernaryElse') + ->metadata([ + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + 'depth' => $node->getAttribute('expressionDepth'), + 'order' => $node->getAttribute('expressionOrder'), + ]) + ->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php b/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php index 8494d0c2b2..d344ac24a9 100644 --- a/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php +++ b/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php @@ -1,4 +1,6 @@ -getType($node); - if ( - $matchResultType instanceof VoidType - && !$scope->isInFirstLevelStatement() - ) { - return [RuleErrorBuilder::message('Result of match expression (void) is used.')->build()]; - } - - return []; - } + public function processNode(Node $node, Scope $scope): array + { + $matchResultType = $scope->getType($node); + if ( + $matchResultType instanceof VoidType + && !$scope->isInFirstLevelStatement() + ) { + return [RuleErrorBuilder::message('Result of match expression (void) is used.')->build()]; + } + return []; + } } diff --git a/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php b/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php index 8051785e27..230609c5fb 100644 --- a/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php +++ b/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php @@ -1,4 +1,6 @@ -hasConstant($node->name)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Constant %s not found.', - (string) $node->name - ))->discoveringSymbolsTip()->build(), - ]; - } - - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->hasConstant($node->name)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Constant %s not found.', + (string) $node->name + ))->discoveringSymbolsTip()->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php index 80a5424321..ba566c5a1c 100644 --- a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php +++ b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php @@ -1,4 +1,6 @@ -container = $container; - } + /** @var AlwaysUsedClassConstantsExtension[]|null */ + private ?array $extensions = null; - public function getExtensions(): array - { - if ($this->extensions === null) { - $this->extensions = $this->container->getServicesByTag(AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG); - } + public function __construct(Container $container) + { + $this->container = $container; + } - return $this->extensions; - } + public function getExtensions(): array + { + if ($this->extensions === null) { + $this->extensions = $this->container->getServicesByTag(AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG); + } + return $this->extensions; + } } diff --git a/src/Rules/DateTimeInstantiationRule.php b/src/Rules/DateTimeInstantiationRule.php index 0e8ead4972..053d5d3ef9 100644 --- a/src/Rules/DateTimeInstantiationRule.php +++ b/src/Rules/DateTimeInstantiationRule.php @@ -1,4 +1,6 @@ -class instanceof \PhpParser\Node\Name) - || \count($node->args) === 0 - || !\in_array(strtolower((string) $node->class), ['datetime', 'datetimeimmutable'], true) - ) { - return []; - } - - $arg = $scope->getType($node->args[0]->value); - if (!($arg instanceof ConstantStringType)) { - return []; - } - - $errors = []; - $dateString = $arg->getValue(); - try { - new DateTime($dateString); - } catch (\Throwable $e) { - // an exception is thrown for errors only but we want to catch warnings too - } - $lastErrors = DateTime::getLastErrors(); - if ($lastErrors !== false) { - foreach ($lastErrors['errors'] as $error) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Instantiating %s with %s produces an error: %s', - (string) $node->class, - $dateString, - $error - ))->build(); - } - } - - return $errors; - } - + public function getNodeType(): string + { + return New_::class; + } + + /** + * @param New_ $node + */ + public function processNode(Node $node, Scope $scope): array + { + if ( + !($node->class instanceof \PhpParser\Node\Name) + || \count($node->args) === 0 + || !\in_array(strtolower((string) $node->class), ['datetime', 'datetimeimmutable'], true) + ) { + return []; + } + + $arg = $scope->getType($node->args[0]->value); + if (!($arg instanceof ConstantStringType)) { + return []; + } + + $errors = []; + $dateString = $arg->getValue(); + try { + new DateTime($dateString); + } catch (\Throwable $e) { + // an exception is thrown for errors only but we want to catch warnings too + } + $lastErrors = DateTime::getLastErrors(); + if ($lastErrors !== false) { + foreach ($lastErrors['errors'] as $error) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Instantiating %s with %s produces an error: %s', + (string) $node->class, + $dateString, + $error + ))->build(); + } + } + + return $errors; + } } diff --git a/src/Rules/DeadCode/NoopRule.php b/src/Rules/DeadCode/NoopRule.php index fa7a42c9dd..16ab2aa97f 100644 --- a/src/Rules/DeadCode/NoopRule.php +++ b/src/Rules/DeadCode/NoopRule.php @@ -1,4 +1,6 @@ -printer = $printer; - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } + public function __construct(Standard $printer) + { + $this->printer = $printer; + } - public function processNode(Node $node, Scope $scope): array - { - $originalExpr = $node->expr; - $expr = $originalExpr; - if ( - $expr instanceof Node\Expr\Cast - || $expr instanceof Node\Expr\UnaryMinus - || $expr instanceof Node\Expr\UnaryPlus - || $expr instanceof Node\Expr\ErrorSuppress - ) { - $expr = $expr->expr; - } - if ( - !$expr instanceof Node\Expr\Variable - && !$expr instanceof Node\Expr\PropertyFetch - && !$expr instanceof Node\Expr\StaticPropertyFetch - && !$expr instanceof Node\Expr\NullsafePropertyFetch - && !$expr instanceof Node\Expr\ArrayDimFetch - && !$expr instanceof Node\Scalar - && !$expr instanceof Node\Expr\Isset_ - && !$expr instanceof Node\Expr\Empty_ - && !$expr instanceof Node\Expr\ConstFetch - && !$expr instanceof Node\Expr\ClassConstFetch - ) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Expression "%s" on a separate line does not do anything.', - $this->printer->prettyPrintExpr($originalExpr) - ))->line($expr->getLine()) - ->identifier('deadCode.noopExpression') - ->metadata([ - 'depth' => $node->getAttribute('statementDepth'), - 'order' => $node->getAttribute('statementOrder'), - ]) - ->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + $originalExpr = $node->expr; + $expr = $originalExpr; + if ( + $expr instanceof Node\Expr\Cast + || $expr instanceof Node\Expr\UnaryMinus + || $expr instanceof Node\Expr\UnaryPlus + || $expr instanceof Node\Expr\ErrorSuppress + ) { + $expr = $expr->expr; + } + if ( + !$expr instanceof Node\Expr\Variable + && !$expr instanceof Node\Expr\PropertyFetch + && !$expr instanceof Node\Expr\StaticPropertyFetch + && !$expr instanceof Node\Expr\NullsafePropertyFetch + && !$expr instanceof Node\Expr\ArrayDimFetch + && !$expr instanceof Node\Scalar + && !$expr instanceof Node\Expr\Isset_ + && !$expr instanceof Node\Expr\Empty_ + && !$expr instanceof Node\Expr\ConstFetch + && !$expr instanceof Node\Expr\ClassConstFetch + ) { + return []; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Expression "%s" on a separate line does not do anything.', + $this->printer->prettyPrintExpr($originalExpr) + ))->line($expr->getLine()) + ->identifier('deadCode.noopExpression') + ->metadata([ + 'depth' => $node->getAttribute('statementDepth'), + 'order' => $node->getAttribute('statementOrder'), + ]) + ->build(), + ]; + } } diff --git a/src/Rules/DeadCode/UnreachableStatementRule.php b/src/Rules/DeadCode/UnreachableStatementRule.php index 67a7105658..ade4e553a1 100644 --- a/src/Rules/DeadCode/UnreachableStatementRule.php +++ b/src/Rules/DeadCode/UnreachableStatementRule.php @@ -1,4 +1,6 @@ -getOriginalStatement() instanceof Node\Stmt\Nop) { - return []; - } - - return [ - RuleErrorBuilder::message('Unreachable statement - code above always terminates.') - ->identifier('deadCode.unreachableStatement') - ->metadata([ - 'depth' => $node->getAttribute('statementDepth'), - 'order' => $node->getAttribute('statementOrder'), - ]) - ->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + if ($node->getOriginalStatement() instanceof Node\Stmt\Nop) { + return []; + } + return [ + RuleErrorBuilder::message('Unreachable statement - code above always terminates.') + ->identifier('deadCode.unreachableStatement') + ->metadata([ + 'depth' => $node->getAttribute('statementDepth'), + 'order' => $node->getAttribute('statementOrder'), + ]) + ->build(), + ]; + } } diff --git a/src/Rules/DeadCode/UnusedPrivateConstantRule.php b/src/Rules/DeadCode/UnusedPrivateConstantRule.php index 6b3006f87f..fe80f2bfc3 100644 --- a/src/Rules/DeadCode/UnusedPrivateConstantRule.php +++ b/src/Rules/DeadCode/UnusedPrivateConstantRule.php @@ -1,4 +1,6 @@ -extensionProvider = $extensionProvider; - } - - public function getNodeType(): string - { - return ClassConstantsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->getClass() instanceof Node\Stmt\Class_) { - return []; - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $classReflection = $scope->getClassReflection(); - - $constants = []; - foreach ($node->getConstants() as $constant) { - if (!$constant->isPrivate()) { - continue; - } - - foreach ($constant->consts as $const) { - $constantName = $const->name->toString(); - - $constantReflection = $classReflection->getConstant($constantName); - foreach ($this->extensionProvider->getExtensions() as $extension) { - if ($extension->isAlwaysUsed($constantReflection)) { - continue 2; - } - } - - $constants[$constantName] = $const; - } - } - - foreach ($node->getFetches() as $fetch) { - $fetchNode = $fetch->getNode(); - if (!$fetchNode->class instanceof Node\Name) { - continue; - } - if (!$fetchNode->name instanceof Node\Identifier) { - continue; - } - $fetchScope = $fetch->getScope(); - $fetchedOnClass = $fetchScope->resolveName($fetchNode->class); - if ($fetchedOnClass !== $classReflection->getName()) { - continue; - } - unset($constants[$fetchNode->name->toString()]); - } - - $errors = []; - foreach ($constants as $constantName => $constantNode) { - $errors[] = RuleErrorBuilder::message(sprintf('Constant %s::%s is unused.', $classReflection->getDisplayName(), $constantName)) - ->line($constantNode->getLine()) - ->identifier('deadCode.unusedClassConstant') - ->metadata([ - 'classOrder' => $node->getClass()->getAttribute('statementOrder'), - 'classDepth' => $node->getClass()->getAttribute('statementDepth'), - 'classStartLine' => $node->getClass()->getStartLine(), - 'constantName' => $constantName, - ]) - ->build(); - } - - return $errors; - } - + private AlwaysUsedClassConstantsExtensionProvider $extensionProvider; + + public function __construct(AlwaysUsedClassConstantsExtensionProvider $extensionProvider) + { + $this->extensionProvider = $extensionProvider; + } + + public function getNodeType(): string + { + return ClassConstantsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getClass() instanceof Node\Stmt\Class_) { + return []; + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $classReflection = $scope->getClassReflection(); + + $constants = []; + foreach ($node->getConstants() as $constant) { + if (!$constant->isPrivate()) { + continue; + } + + foreach ($constant->consts as $const) { + $constantName = $const->name->toString(); + + $constantReflection = $classReflection->getConstant($constantName); + foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($extension->isAlwaysUsed($constantReflection)) { + continue 2; + } + } + + $constants[$constantName] = $const; + } + } + + foreach ($node->getFetches() as $fetch) { + $fetchNode = $fetch->getNode(); + if (!$fetchNode->class instanceof Node\Name) { + continue; + } + if (!$fetchNode->name instanceof Node\Identifier) { + continue; + } + $fetchScope = $fetch->getScope(); + $fetchedOnClass = $fetchScope->resolveName($fetchNode->class); + if ($fetchedOnClass !== $classReflection->getName()) { + continue; + } + unset($constants[$fetchNode->name->toString()]); + } + + $errors = []; + foreach ($constants as $constantName => $constantNode) { + $errors[] = RuleErrorBuilder::message(sprintf('Constant %s::%s is unused.', $classReflection->getDisplayName(), $constantName)) + ->line($constantNode->getLine()) + ->identifier('deadCode.unusedClassConstant') + ->metadata([ + 'classOrder' => $node->getClass()->getAttribute('statementOrder'), + 'classDepth' => $node->getClass()->getAttribute('statementDepth'), + 'classStartLine' => $node->getClass()->getStartLine(), + 'constantName' => $constantName, + ]) + ->build(); + } + + return $errors; + } } diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index 4d3c3cb2fb..ee215484c9 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -1,4 +1,6 @@ -getClass() instanceof Node\Stmt\Class_) { - return []; - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $classReflection = $scope->getClassReflection(); - $constructor = null; - if ($classReflection->hasConstructor()) { - $constructor = $classReflection->getConstructor(); - } - $classType = new ObjectType($classReflection->getName()); - - $methods = []; - foreach ($node->getMethods() as $method) { - if (!$method->isPrivate()) { - continue; - } - $methodName = $method->name->toString(); - if ($constructor !== null && $constructor->getName() === $methodName) { - continue; - } - if (strtolower($methodName) === '__clone') { - continue; - } - $methods[$method->name->toString()] = $method; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getClass() instanceof Node\Stmt\Class_) { + return []; + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + $constructor = null; + if ($classReflection->hasConstructor()) { + $constructor = $classReflection->getConstructor(); + } + $classType = new ObjectType($classReflection->getName()); - $arrayCalls = []; - foreach ($node->getMethodCalls() as $methodCall) { - $methodCallNode = $methodCall->getNode(); - if ($methodCallNode instanceof Node\Expr\Array_) { - $arrayCalls[] = $methodCall; - continue; - } - $callScope = $methodCall->getScope(); - if ($methodCallNode->name instanceof Identifier) { - $methodNames = [$methodCallNode->name->toString()]; - } else { - $methodNameType = $callScope->getType($methodCallNode->name); - $strings = TypeUtils::getConstantStrings($methodNameType); - if (count($strings) === 0) { - return []; - } + $methods = []; + foreach ($node->getMethods() as $method) { + if (!$method->isPrivate()) { + continue; + } + $methodName = $method->name->toString(); + if ($constructor !== null && $constructor->getName() === $methodName) { + continue; + } + if (strtolower($methodName) === '__clone') { + continue; + } + $methods[$method->name->toString()] = $method; + } - $methodNames = array_map(static function (ConstantStringType $type): string { - return $type->getValue(); - }, $strings); - } + $arrayCalls = []; + foreach ($node->getMethodCalls() as $methodCall) { + $methodCallNode = $methodCall->getNode(); + if ($methodCallNode instanceof Node\Expr\Array_) { + $arrayCalls[] = $methodCall; + continue; + } + $callScope = $methodCall->getScope(); + if ($methodCallNode->name instanceof Identifier) { + $methodNames = [$methodCallNode->name->toString()]; + } else { + $methodNameType = $callScope->getType($methodCallNode->name); + $strings = TypeUtils::getConstantStrings($methodNameType); + if (count($strings) === 0) { + return []; + } - if ($methodCallNode instanceof Node\Expr\MethodCall) { - $calledOnType = $callScope->getType($methodCallNode->var); - } else { - if (!$methodCallNode->class instanceof Node\Name) { - continue; - } - $calledOnType = $scope->resolveTypeByName($methodCallNode->class); - } - if ($classType->isSuperTypeOf($calledOnType)->no()) { - continue; - } - if ($calledOnType instanceof MixedType) { - continue; - } - $inMethod = $callScope->getFunction(); - if (!$inMethod instanceof MethodReflection) { - continue; - } + $methodNames = array_map(static function (ConstantStringType $type): string { + return $type->getValue(); + }, $strings); + } - foreach ($methodNames as $methodName) { - if ($inMethod->getName() === $methodName) { - continue; - } - unset($methods[$methodName]); - } - } + if ($methodCallNode instanceof Node\Expr\MethodCall) { + $calledOnType = $callScope->getType($methodCallNode->var); + } else { + if (!$methodCallNode->class instanceof Node\Name) { + continue; + } + $calledOnType = $scope->resolveTypeByName($methodCallNode->class); + } + if ($classType->isSuperTypeOf($calledOnType)->no()) { + continue; + } + if ($calledOnType instanceof MixedType) { + continue; + } + $inMethod = $callScope->getFunction(); + if (!$inMethod instanceof MethodReflection) { + continue; + } - if (count($methods) > 0) { - foreach ($arrayCalls as $arrayCall) { - /** @var Node\Expr\Array_ $array */ - $array = $arrayCall->getNode(); - $arrayScope = $arrayCall->getScope(); - $arrayType = $scope->getType($array); - if (!$arrayType instanceof ConstantArrayType) { - continue; - } - $typeAndMethod = $arrayType->findTypeAndMethodName(); - if ($typeAndMethod === null) { - continue; - } - if ($typeAndMethod->isUnknown()) { - return []; - } - if (!$typeAndMethod->getCertainty()->yes()) { - return []; - } - $calledOnType = $typeAndMethod->getType(); - if ($classType->isSuperTypeOf($calledOnType)->no()) { - continue; - } - if ($calledOnType instanceof MixedType) { - continue; - } - $inMethod = $arrayScope->getFunction(); - if (!$inMethod instanceof MethodReflection) { - continue; - } - if ($inMethod->getName() === $typeAndMethod->getMethod()) { - continue; - } - unset($methods[$typeAndMethod->getMethod()]); - } - } + foreach ($methodNames as $methodName) { + if ($inMethod->getName() === $methodName) { + continue; + } + unset($methods[$methodName]); + } + } - $errors = []; - foreach ($methods as $methodName => $methodNode) { - $methodType = 'Method'; - if ($methodNode->isStatic()) { - $methodType = 'Static method'; - } - $errors[] = RuleErrorBuilder::message(sprintf('%s %s::%s() is unused.', $methodType, $classReflection->getDisplayName(), $methodName)) - ->line($methodNode->getLine()) - ->identifier('deadCode.unusedMethod') - ->metadata([ - 'classOrder' => $node->getClass()->getAttribute('statementOrder'), - 'classDepth' => $node->getClass()->getAttribute('statementDepth'), - 'classStartLine' => $node->getClass()->getStartLine(), - 'methodName' => $methodName, - ]) - ->build(); - } + if (count($methods) > 0) { + foreach ($arrayCalls as $arrayCall) { + /** @var Node\Expr\Array_ $array */ + $array = $arrayCall->getNode(); + $arrayScope = $arrayCall->getScope(); + $arrayType = $scope->getType($array); + if (!$arrayType instanceof ConstantArrayType) { + continue; + } + $typeAndMethod = $arrayType->findTypeAndMethodName(); + if ($typeAndMethod === null) { + continue; + } + if ($typeAndMethod->isUnknown()) { + return []; + } + if (!$typeAndMethod->getCertainty()->yes()) { + return []; + } + $calledOnType = $typeAndMethod->getType(); + if ($classType->isSuperTypeOf($calledOnType)->no()) { + continue; + } + if ($calledOnType instanceof MixedType) { + continue; + } + $inMethod = $arrayScope->getFunction(); + if (!$inMethod instanceof MethodReflection) { + continue; + } + if ($inMethod->getName() === $typeAndMethod->getMethod()) { + continue; + } + unset($methods[$typeAndMethod->getMethod()]); + } + } - return $errors; - } + $errors = []; + foreach ($methods as $methodName => $methodNode) { + $methodType = 'Method'; + if ($methodNode->isStatic()) { + $methodType = 'Static method'; + } + $errors[] = RuleErrorBuilder::message(sprintf('%s %s::%s() is unused.', $methodType, $classReflection->getDisplayName(), $methodName)) + ->line($methodNode->getLine()) + ->identifier('deadCode.unusedMethod') + ->metadata([ + 'classOrder' => $node->getClass()->getAttribute('statementOrder'), + 'classDepth' => $node->getClass()->getAttribute('statementDepth'), + 'classStartLine' => $node->getClass()->getStartLine(), + 'methodName' => $methodName, + ]) + ->build(); + } + return $errors; + } } diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 6ba7338b28..77e7f2207a 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -1,4 +1,6 @@ -extensionProvider = $extensionProvider; - $this->alwaysWrittenTags = $alwaysWrittenTags; - $this->alwaysReadTags = $alwaysReadTags; - $this->checkUninitializedProperties = $checkUninitializedProperties; - } - - public function getNodeType(): string - { - return ClassPropertiesNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->getClass() instanceof Node\Stmt\Class_) { - return []; - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $classReflection = $scope->getClassReflection(); - $classType = new ObjectType($classReflection->getName()); - - $properties = []; - foreach ($node->getProperties() as $property) { - if (!$property->isPrivate()) { - continue; - } - - $alwaysRead = false; - $alwaysWritten = false; - if ($property->getPhpDoc() !== null) { - $text = $property->getPhpDoc(); - foreach ($this->alwaysReadTags as $tag) { - if (strpos($text, $tag) === false) { - continue; - } - - $alwaysRead = true; - break; - } - - foreach ($this->alwaysWrittenTags as $tag) { - if (strpos($text, $tag) === false) { - continue; - } - - $alwaysWritten = true; - break; - } - } - - $propertyName = $property->getName(); - if (!$alwaysRead || !$alwaysWritten) { - if (!$classReflection->hasNativeProperty($propertyName)) { - continue; - } - - $propertyReflection = $classReflection->getNativeProperty($propertyName); - - foreach ($this->extensionProvider->getExtensions() as $extension) { - if ($alwaysRead && $alwaysWritten) { - break; - } - if (!$alwaysRead && $extension->isAlwaysRead($propertyReflection, $propertyName)) { - $alwaysRead = true; - } - if ($alwaysWritten || !$extension->isAlwaysWritten($propertyReflection, $propertyName)) { - continue; - } - - $alwaysWritten = true; - } - } - - $read = $alwaysRead; - $written = $alwaysWritten || $property->getDefault() !== null; - $properties[$propertyName] = [ - 'read' => $read, - 'written' => $written, - 'node' => $property, - ]; - } - - foreach ($node->getPropertyUsages() as $usage) { - $fetch = $usage->getFetch(); - if ($fetch->name instanceof Node\Identifier) { - $propertyNames = [$fetch->name->toString()]; - } else { - $propertyNameType = $usage->getScope()->getType($fetch->name); - $strings = TypeUtils::getConstantStrings($propertyNameType); - if (count($strings) === 0) { - return []; - } - - $propertyNames = array_map(static function (ConstantStringType $type): string { - return $type->getValue(); - }, $strings); - } - if ($fetch instanceof Node\Expr\PropertyFetch) { - $fetchedOnType = $usage->getScope()->getType($fetch->var); - } else { - if (!$fetch->class instanceof Node\Name) { - continue; - } - - $fetchedOnType = $usage->getScope()->resolveTypeByName($fetch->class); - } - - if ($classType->isSuperTypeOf($fetchedOnType)->no()) { - continue; - } - if ($fetchedOnType instanceof MixedType) { - continue; - } - - foreach ($propertyNames as $propertyName) { - if (!array_key_exists($propertyName, $properties)) { - continue; - } - if ($usage instanceof PropertyRead) { - $properties[$propertyName]['read'] = true; - } else { - $properties[$propertyName]['written'] = true; - } - } - } - - $constructors = []; - $classReflection = $scope->getClassReflection(); - if ($classReflection->hasConstructor()) { - $constructors[] = $classReflection->getConstructor()->getName(); - } - - [$uninitializedProperties] = $node->getUninitializedProperties($scope, $constructors, $this->extensionProvider->getExtensions()); - - $errors = []; - foreach ($properties as $name => $data) { - $propertyNode = $data['node']; - if ($propertyNode->isStatic()) { - $propertyName = sprintf('Static property %s::$%s', $scope->getClassReflection()->getDisplayName(), $name); - } else { - $propertyName = sprintf('Property %s::$%s', $scope->getClassReflection()->getDisplayName(), $name); - } - if (!$data['read']) { - if (!$data['written']) { - $errors[] = RuleErrorBuilder::message(sprintf('%s is unused.', $propertyName)) - ->line($propertyNode->getStartLine()) - ->identifier('deadCode.unusedProperty') - ->metadata([ - 'classOrder' => $node->getClass()->getAttribute('statementOrder'), - 'classDepth' => $node->getClass()->getAttribute('statementDepth'), - 'classStartLine' => $node->getClass()->getStartLine(), - 'propertyName' => $name, - ]) - ->build(); - } else { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName))->line($propertyNode->getStartLine())->build(); - } - } elseif (!$data['written'] && (!array_key_exists($name, $uninitializedProperties) || !$this->checkUninitializedProperties)) { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName))->line($propertyNode->getStartLine())->build(); - } - } - - return $errors; - } - + private ReadWritePropertiesExtensionProvider $extensionProvider; + + /** @var string[] */ + private array $alwaysWrittenTags; + + /** @var string[] */ + private array $alwaysReadTags; + + private bool $checkUninitializedProperties; + + /** + * @param ReadWritePropertiesExtensionProvider $extensionProvider + * @param string[] $alwaysWrittenTags + * @param string[] $alwaysReadTags + */ + public function __construct( + ReadWritePropertiesExtensionProvider $extensionProvider, + array $alwaysWrittenTags, + array $alwaysReadTags, + bool $checkUninitializedProperties + ) { + $this->extensionProvider = $extensionProvider; + $this->alwaysWrittenTags = $alwaysWrittenTags; + $this->alwaysReadTags = $alwaysReadTags; + $this->checkUninitializedProperties = $checkUninitializedProperties; + } + + public function getNodeType(): string + { + return ClassPropertiesNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getClass() instanceof Node\Stmt\Class_) { + return []; + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + $classType = new ObjectType($classReflection->getName()); + + $properties = []; + foreach ($node->getProperties() as $property) { + if (!$property->isPrivate()) { + continue; + } + + $alwaysRead = false; + $alwaysWritten = false; + if ($property->getPhpDoc() !== null) { + $text = $property->getPhpDoc(); + foreach ($this->alwaysReadTags as $tag) { + if (strpos($text, $tag) === false) { + continue; + } + + $alwaysRead = true; + break; + } + + foreach ($this->alwaysWrittenTags as $tag) { + if (strpos($text, $tag) === false) { + continue; + } + + $alwaysWritten = true; + break; + } + } + + $propertyName = $property->getName(); + if (!$alwaysRead || !$alwaysWritten) { + if (!$classReflection->hasNativeProperty($propertyName)) { + continue; + } + + $propertyReflection = $classReflection->getNativeProperty($propertyName); + + foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($alwaysRead && $alwaysWritten) { + break; + } + if (!$alwaysRead && $extension->isAlwaysRead($propertyReflection, $propertyName)) { + $alwaysRead = true; + } + if ($alwaysWritten || !$extension->isAlwaysWritten($propertyReflection, $propertyName)) { + continue; + } + + $alwaysWritten = true; + } + } + + $read = $alwaysRead; + $written = $alwaysWritten || $property->getDefault() !== null; + $properties[$propertyName] = [ + 'read' => $read, + 'written' => $written, + 'node' => $property, + ]; + } + + foreach ($node->getPropertyUsages() as $usage) { + $fetch = $usage->getFetch(); + if ($fetch->name instanceof Node\Identifier) { + $propertyNames = [$fetch->name->toString()]; + } else { + $propertyNameType = $usage->getScope()->getType($fetch->name); + $strings = TypeUtils::getConstantStrings($propertyNameType); + if (count($strings) === 0) { + return []; + } + + $propertyNames = array_map(static function (ConstantStringType $type): string { + return $type->getValue(); + }, $strings); + } + if ($fetch instanceof Node\Expr\PropertyFetch) { + $fetchedOnType = $usage->getScope()->getType($fetch->var); + } else { + if (!$fetch->class instanceof Node\Name) { + continue; + } + + $fetchedOnType = $usage->getScope()->resolveTypeByName($fetch->class); + } + + if ($classType->isSuperTypeOf($fetchedOnType)->no()) { + continue; + } + if ($fetchedOnType instanceof MixedType) { + continue; + } + + foreach ($propertyNames as $propertyName) { + if (!array_key_exists($propertyName, $properties)) { + continue; + } + if ($usage instanceof PropertyRead) { + $properties[$propertyName]['read'] = true; + } else { + $properties[$propertyName]['written'] = true; + } + } + } + + $constructors = []; + $classReflection = $scope->getClassReflection(); + if ($classReflection->hasConstructor()) { + $constructors[] = $classReflection->getConstructor()->getName(); + } + + [$uninitializedProperties] = $node->getUninitializedProperties($scope, $constructors, $this->extensionProvider->getExtensions()); + + $errors = []; + foreach ($properties as $name => $data) { + $propertyNode = $data['node']; + if ($propertyNode->isStatic()) { + $propertyName = sprintf('Static property %s::$%s', $scope->getClassReflection()->getDisplayName(), $name); + } else { + $propertyName = sprintf('Property %s::$%s', $scope->getClassReflection()->getDisplayName(), $name); + } + if (!$data['read']) { + if (!$data['written']) { + $errors[] = RuleErrorBuilder::message(sprintf('%s is unused.', $propertyName)) + ->line($propertyNode->getStartLine()) + ->identifier('deadCode.unusedProperty') + ->metadata([ + 'classOrder' => $node->getClass()->getAttribute('statementOrder'), + 'classDepth' => $node->getClass()->getAttribute('statementDepth'), + 'classStartLine' => $node->getClass()->getStartLine(), + 'propertyName' => $name, + ]) + ->build(); + } else { + $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName))->line($propertyNode->getStartLine())->build(); + } + } elseif (!$data['written'] && (!array_key_exists($name, $uninitializedProperties) || !$this->checkUninitializedProperties)) { + $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName))->line($propertyNode->getStartLine())->build(); + } + } + + return $errors; + } } diff --git a/src/Rules/Debug/DumpTypeRule.php b/src/Rules/Debug/DumpTypeRule.php index 6c3c4be802..d98cb11ec1 100644 --- a/src/Rules/Debug/DumpTypeRule.php +++ b/src/Rules/Debug/DumpTypeRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Node\Expr\FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Name) { - return []; - } - - $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); - if ($functionName === null) { - return []; - } - - if (strtolower($functionName) !== 'phpstan\dumptype') { - return []; - } - - if (count($node->args) === 0) { - return [ - RuleErrorBuilder::message(sprintf('Missing argument for %s() function call.', $functionName)) - ->nonIgnorable() - ->build(), - ]; - } - - return [ - RuleErrorBuilder::message( - sprintf( - 'Dumped type: %s', - $scope->getType($node->args[0]->value)->describe(VerbosityLevel::precise()) - ) - )->nonIgnorable()->build(), - ]; - } - + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ($functionName === null) { + return []; + } + + if (strtolower($functionName) !== 'phpstan\dumptype') { + return []; + } + + if (count($node->args) === 0) { + return [ + RuleErrorBuilder::message(sprintf('Missing argument for %s() function call.', $functionName)) + ->nonIgnorable() + ->build(), + ]; + } + + return [ + RuleErrorBuilder::message( + sprintf( + 'Dumped type: %s', + $scope->getType($node->args[0]->value)->describe(VerbosityLevel::precise()) + ) + )->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Debug/FileAssertRule.php b/src/Rules/Debug/FileAssertRule.php index dc0831ae39..b33926cc21 100644 --- a/src/Rules/Debug/FileAssertRule.php +++ b/src/Rules/Debug/FileAssertRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Node\Expr\FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Name) { - return []; - } - - if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { - return []; - } - - $function = $this->reflectionProvider->getFunction($node->name, $scope); - if ($function->getName() === 'PHPStan\\Testing\\assertType') { - return $this->processAssertType($node->args, $scope); - } - - if ($function->getName() === 'PHPStan\\Testing\\assertNativeType') { - return $this->processAssertNativeType($node->args, $scope); - } - - if ($function->getName() === 'PHPStan\\Testing\\assertVariableCertainty') { - return $this->processAssertVariableCertainty($node->args, $scope); - } - - return []; - } - - /** - * @param Node\Arg[] $args - * @param Scope $scope - * @return RuleError[] - */ - private function processAssertType(array $args, Scope $scope): array - { - if (count($args) !== 2) { - return []; - } - - $expectedTypeString = $scope->getType($args[0]->value); - if (!$expectedTypeString instanceof ConstantStringType) { - return [ - RuleErrorBuilder::message('Expected type must be a literal string.')->nonIgnorable()->build(), - ]; - } - - $expressionType = $scope->getType($args[1]->value)->describe(VerbosityLevel::precise()); - if ($expectedTypeString->getValue() === $expressionType) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Expected type %s, actual: %s', $expectedTypeString->getValue(), $expressionType))->nonIgnorable()->build(), - ]; - } - - /** - * @param Node\Arg[] $args - * @param Scope $scope - * @return RuleError[] - */ - private function processAssertNativeType(array $args, Scope $scope): array - { - if (count($args) !== 2) { - return []; - } - - $scope = $scope->doNotTreatPhpDocTypesAsCertain(); - $expectedTypeString = $scope->getNativeType($args[0]->value); - if (!$expectedTypeString instanceof ConstantStringType) { - return [ - RuleErrorBuilder::message('Expected native type must be a literal string.')->nonIgnorable()->build(), - ]; - } - - $expressionType = $scope->getNativeType($args[1]->value)->describe(VerbosityLevel::precise()); - if ($expectedTypeString->getValue() === $expressionType) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Expected native type %s, actual: %s', $expectedTypeString->getValue(), $expressionType))->nonIgnorable()->build(), - ]; - } - - /** - * @param Node\Arg[] $args - * @param Scope $scope - * @return RuleError[] - */ - private function processAssertVariableCertainty(array $args, Scope $scope): array - { - if (count($args) !== 2) { - return []; - } - - $certainty = $args[0]->value; - if (!$certainty instanceof StaticCall) { - return [ - RuleErrorBuilder::message('First argument of %s() must be TrinaryLogic call') - ->nonIgnorable() - ->build(), - ]; - } - if (!$certainty->class instanceof Node\Name) { - return [ - RuleErrorBuilder::message('Invalid TrinaryLogic call.') - ->nonIgnorable() - ->build(), - ]; - } - - if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { - return [ - RuleErrorBuilder::message('Invalid TrinaryLogic call.') - ->nonIgnorable() - ->build(), - ]; - } - - if (!$certainty->name instanceof Node\Identifier) { - return [ - RuleErrorBuilder::message('Invalid TrinaryLogic call.') - ->nonIgnorable() - ->build(), - ]; - } - - // @phpstan-ignore-next-line - $expectedCertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); - $variable = $args[1]->value; - if (!$variable instanceof Node\Expr\Variable) { - return [ - RuleErrorBuilder::message('Invalid assertVariableCertainty call.') - ->nonIgnorable() - ->build(), - ]; - } - if (!is_string($variable->name)) { - return [ - RuleErrorBuilder::message('Invalid assertVariableCertainty call.') - ->nonIgnorable() - ->build(), - ]; - } - - $actualCertaintyValue = $scope->hasVariableType($variable->name); - if ($expectedCertaintyValue->equals($actualCertaintyValue)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Expected variable certainty %s, actual: %s', $expectedCertaintyValue->describe(), $actualCertaintyValue->describe()))->nonIgnorable()->build(), - ]; - } - + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $function = $this->reflectionProvider->getFunction($node->name, $scope); + if ($function->getName() === 'PHPStan\\Testing\\assertType') { + return $this->processAssertType($node->args, $scope); + } + + if ($function->getName() === 'PHPStan\\Testing\\assertNativeType') { + return $this->processAssertNativeType($node->args, $scope); + } + + if ($function->getName() === 'PHPStan\\Testing\\assertVariableCertainty') { + return $this->processAssertVariableCertainty($node->args, $scope); + } + + return []; + } + + /** + * @param Node\Arg[] $args + * @param Scope $scope + * @return RuleError[] + */ + private function processAssertType(array $args, Scope $scope): array + { + if (count($args) !== 2) { + return []; + } + + $expectedTypeString = $scope->getType($args[0]->value); + if (!$expectedTypeString instanceof ConstantStringType) { + return [ + RuleErrorBuilder::message('Expected type must be a literal string.')->nonIgnorable()->build(), + ]; + } + + $expressionType = $scope->getType($args[1]->value)->describe(VerbosityLevel::precise()); + if ($expectedTypeString->getValue() === $expressionType) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Expected type %s, actual: %s', $expectedTypeString->getValue(), $expressionType))->nonIgnorable()->build(), + ]; + } + + /** + * @param Node\Arg[] $args + * @param Scope $scope + * @return RuleError[] + */ + private function processAssertNativeType(array $args, Scope $scope): array + { + if (count($args) !== 2) { + return []; + } + + $scope = $scope->doNotTreatPhpDocTypesAsCertain(); + $expectedTypeString = $scope->getNativeType($args[0]->value); + if (!$expectedTypeString instanceof ConstantStringType) { + return [ + RuleErrorBuilder::message('Expected native type must be a literal string.')->nonIgnorable()->build(), + ]; + } + + $expressionType = $scope->getNativeType($args[1]->value)->describe(VerbosityLevel::precise()); + if ($expectedTypeString->getValue() === $expressionType) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Expected native type %s, actual: %s', $expectedTypeString->getValue(), $expressionType))->nonIgnorable()->build(), + ]; + } + + /** + * @param Node\Arg[] $args + * @param Scope $scope + * @return RuleError[] + */ + private function processAssertVariableCertainty(array $args, Scope $scope): array + { + if (count($args) !== 2) { + return []; + } + + $certainty = $args[0]->value; + if (!$certainty instanceof StaticCall) { + return [ + RuleErrorBuilder::message('First argument of %s() must be TrinaryLogic call') + ->nonIgnorable() + ->build(), + ]; + } + if (!$certainty->class instanceof Node\Name) { + return [ + RuleErrorBuilder::message('Invalid TrinaryLogic call.') + ->nonIgnorable() + ->build(), + ]; + } + + if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { + return [ + RuleErrorBuilder::message('Invalid TrinaryLogic call.') + ->nonIgnorable() + ->build(), + ]; + } + + if (!$certainty->name instanceof Node\Identifier) { + return [ + RuleErrorBuilder::message('Invalid TrinaryLogic call.') + ->nonIgnorable() + ->build(), + ]; + } + + // @phpstan-ignore-next-line + $expectedCertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); + $variable = $args[1]->value; + if (!$variable instanceof Node\Expr\Variable) { + return [ + RuleErrorBuilder::message('Invalid assertVariableCertainty call.') + ->nonIgnorable() + ->build(), + ]; + } + if (!is_string($variable->name)) { + return [ + RuleErrorBuilder::message('Invalid assertVariableCertainty call.') + ->nonIgnorable() + ->build(), + ]; + } + + $actualCertaintyValue = $scope->hasVariableType($variable->name); + if ($expectedCertaintyValue->equals($actualCertaintyValue)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Expected variable certainty %s, actual: %s', $expectedCertaintyValue->describe(), $actualCertaintyValue->describe()))->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php index 025e87690d..0d8de9c7de 100644 --- a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php +++ b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php @@ -1,4 +1,6 @@ -getCaughtType()->describe(VerbosityLevel::typeOnly())) - )->line($node->getLine())->build(), - ]; - } - + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message( + sprintf('Dead catch - %s is never thrown in the try block.', $node->getCaughtType()->describe(VerbosityLevel::typeOnly())) + )->line($node->getLine())->build(), + ]; + } } diff --git a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php index 13074ff545..63fe8bfbf5 100644 --- a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php +++ b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - } + private bool $checkClassCaseSensitivity; - public function getNodeType(): string - { - return Catch_::class; - } + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkClassCaseSensitivity + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + } - public function processNode(Node $node, Scope $scope): array - { - $errors = []; - foreach ($node->types as $class) { - $className = (string) $class; - if (!$this->reflectionProvider->hasClass($className)) { - if ($scope->isInClassExists($className)) { - continue; - } - $errors[] = RuleErrorBuilder::message(sprintf('Caught class %s not found.', $className))->line($class->getLine())->discoveringSymbolsTip()->build(); - continue; - } + public function getNodeType(): string + { + return Catch_::class; + } - $classReflection = $this->reflectionProvider->getClass($className); - if (!$classReflection->isInterface() && !$classReflection->implementsInterface(\Throwable::class)) { - $errors[] = RuleErrorBuilder::message(sprintf('Caught class %s is not an exception.', $classReflection->getDisplayName()))->line($class->getLine())->build(); - } + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->types as $class) { + $className = (string) $class; + if (!$this->reflectionProvider->hasClass($className)) { + if ($scope->isInClassExists($className)) { + continue; + } + $errors[] = RuleErrorBuilder::message(sprintf('Caught class %s not found.', $className))->line($class->getLine())->discoveringSymbolsTip()->build(); + continue; + } - if (!$this->checkClassCaseSensitivity) { - continue; - } + $classReflection = $this->reflectionProvider->getClass($className); + if (!$classReflection->isInterface() && !$classReflection->implementsInterface(\Throwable::class)) { + $errors[] = RuleErrorBuilder::message(sprintf('Caught class %s is not an exception.', $classReflection->getDisplayName()))->line($class->getLine())->build(); + } - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]) - ); - } + if (!$this->checkClassCaseSensitivity) { + continue; + } - return $errors; - } + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]) + ); + } + return $errors; + } } diff --git a/src/Rules/Exceptions/DeadCatchRule.php b/src/Rules/Exceptions/DeadCatchRule.php index d05a634792..8c18deba9c 100644 --- a/src/Rules/Exceptions/DeadCatchRule.php +++ b/src/Rules/Exceptions/DeadCatchRule.php @@ -1,4 +1,6 @@ -toString()); - }, $catch->types)); - }, $node->catches); - $catchesCount = count($catchTypes); - $errors = []; - for ($i = 0; $i < $catchesCount - 1; $i++) { - $firstType = $catchTypes[$i]; - for ($j = $i + 1; $j < $catchesCount; $j++) { - $secondType = $catchTypes[$j]; - if (!$firstType->isSuperTypeOf($secondType)->yes()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Dead catch - %s is already caught by %s above.', - $secondType->describe(VerbosityLevel::typeOnly()), - $firstType->describe(VerbosityLevel::typeOnly()) - ))->line($node->catches[$j]->getLine()) - ->identifier('deadCode.unreachableCatch') - ->metadata([ - 'tryLine' => $node->getLine(), - 'firstCatchOrder' => $i, - 'deadCatchOrder' => $j, - ]) - ->build(); - } - } - - return $errors; - } - + public function getNodeType(): string + { + return Node\Stmt\TryCatch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $catchTypes = array_map(static function (Node\Stmt\Catch_ $catch): Type { + return TypeCombinator::union(...array_map(static function (Node\Name $className): ObjectType { + return new ObjectType($className->toString()); + }, $catch->types)); + }, $node->catches); + $catchesCount = count($catchTypes); + $errors = []; + for ($i = 0; $i < $catchesCount - 1; $i++) { + $firstType = $catchTypes[$i]; + for ($j = $i + 1; $j < $catchesCount; $j++) { + $secondType = $catchTypes[$j]; + if (!$firstType->isSuperTypeOf($secondType)->yes()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Dead catch - %s is already caught by %s above.', + $secondType->describe(VerbosityLevel::typeOnly()), + $firstType->describe(VerbosityLevel::typeOnly()) + ))->line($node->catches[$j]->getLine()) + ->identifier('deadCode.unreachableCatch') + ->metadata([ + 'tryLine' => $node->getLine(), + 'firstCatchOrder' => $i, + 'deadCatchOrder' => $j, + ]) + ->build(); + } + } + + return $errors; + } } diff --git a/src/Rules/Exceptions/ExceptionTypeResolver.php b/src/Rules/Exceptions/ExceptionTypeResolver.php index ec6731adcb..fa11ddc838 100644 --- a/src/Rules/Exceptions/ExceptionTypeResolver.php +++ b/src/Rules/Exceptions/ExceptionTypeResolver.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->uncheckedExceptionRegexes = $uncheckedExceptionRegexes; - $this->uncheckedExceptionClasses = $uncheckedExceptionClasses; - } + /** @var string[] */ + private array $uncheckedExceptionClasses; - public function isCheckedException(string $className): bool - { - foreach ($this->uncheckedExceptionRegexes as $regex) { - if (Strings::match($className, $regex) !== null) { - return false; - } - } + /** + * @param ReflectionProvider $reflectionProvider + * @param string[] $uncheckedExceptionRegexes + * @param string[] $uncheckedExceptionClasses + */ + public function __construct( + ReflectionProvider $reflectionProvider, + array $uncheckedExceptionRegexes, + array $uncheckedExceptionClasses + ) { + $this->reflectionProvider = $reflectionProvider; + $this->uncheckedExceptionRegexes = $uncheckedExceptionRegexes; + $this->uncheckedExceptionClasses = $uncheckedExceptionClasses; + } - foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { - if ($className === $uncheckedExceptionClass) { - return false; - } - } + public function isCheckedException(string $className): bool + { + foreach ($this->uncheckedExceptionRegexes as $regex) { + if (Strings::match($className, $regex) !== null) { + return false; + } + } - if (!$this->reflectionProvider->hasClass($className)) { - return true; - } + foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { + if ($className === $uncheckedExceptionClass) { + return false; + } + } - $classReflection = $this->reflectionProvider->getClass($className); - foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { - if ($classReflection->getName() === $uncheckedExceptionClass) { - return false; - } + if (!$this->reflectionProvider->hasClass($className)) { + return true; + } - if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) { - continue; - } + $classReflection = $this->reflectionProvider->getClass($className); + foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { + if ($classReflection->getName() === $uncheckedExceptionClass) { + return false; + } - return false; - } + if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) { + continue; + } - return true; - } + return false; + } + return true; + } } diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php b/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php index 7e4b175e46..49c33b04bc 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return FunctionReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $statementResult = $node->getStatementResult(); - $functionReflection = $scope->getFunction(); - if (!$functionReflection instanceof FunctionReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $errors = []; - foreach ($this->check->check($functionReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Function %s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.', - $functionReflection->getName(), - $className - )) - ->line($throwPointNode->getLine()) - ->identifier('exceptions.missingThrowsTag') - ->metadata([ - 'exceptionName' => $className, - 'newCatchPosition' => $newCatchPosition, - 'statementDepth' => $throwPointNode->getAttribute('statementDepth'), - 'statementOrder' => $throwPointNode->getAttribute('statementOrder'), - ]) - ->build(); - } - - return $errors; - } - + private MissingCheckedExceptionInThrowsCheck $check; + + public function __construct(MissingCheckedExceptionInThrowsCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return FunctionReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $functionReflection = $scope->getFunction(); + if (!$functionReflection instanceof FunctionReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errors = []; + foreach ($this->check->check($functionReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Function %s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.', + $functionReflection->getName(), + $className + )) + ->line($throwPointNode->getLine()) + ->identifier('exceptions.missingThrowsTag') + ->metadata([ + 'exceptionName' => $className, + 'newCatchPosition' => $newCatchPosition, + 'statementDepth' => $throwPointNode->getAttribute('statementDepth'), + 'statementOrder' => $throwPointNode->getAttribute('statementOrder'), + ]) + ->build(); + } + + return $errors; + } } diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php b/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php index e18c0c1d97..0967e6a6ca 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return MethodReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $statementResult = $node->getStatementResult(); - $methodReflection = $scope->getFunction(); - if (!$methodReflection instanceof MethodReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $errors = []; - foreach ($this->check->check($methodReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $className - )) - ->line($throwPointNode->getLine()) - ->identifier('exceptions.missingThrowsTag') - ->metadata([ - 'exceptionName' => $className, - 'newCatchPosition' => $newCatchPosition, - 'statementDepth' => $throwPointNode->getAttribute('statementDepth'), - 'statementOrder' => $throwPointNode->getAttribute('statementOrder'), - ]) - ->build(); - } - - return $errors; - } - + private MissingCheckedExceptionInThrowsCheck $check; + + public function __construct(MissingCheckedExceptionInThrowsCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return MethodReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof MethodReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errors = []; + foreach ($this->check->check($methodReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $className + )) + ->line($throwPointNode->getLine()) + ->identifier('exceptions.missingThrowsTag') + ->metadata([ + 'exceptionName' => $className, + 'newCatchPosition' => $newCatchPosition, + 'statementDepth' => $throwPointNode->getAttribute('statementDepth'), + 'statementOrder' => $throwPointNode->getAttribute('statementOrder'), + ]) + ->build(); + } + + return $errors; + } } diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php b/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php index 650c517671..d18c554f6c 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php @@ -1,4 +1,6 @@ -exceptionTypeResolver = $exceptionTypeResolver; - } - - /** - * @param Type|null $throwType - * @param ThrowPoint[] $throwPoints - * @return array - */ - public function check(?Type $throwType, array $throwPoints): array - { - if ($throwType === null) { - $throwType = new NeverType(); - } - - $classes = []; - foreach ($throwPoints as $throwPoint) { - if (!$throwPoint->isExplicit()) { - continue; - } - - foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { - if ($throwPointType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { - continue; - } - if ($throwType->isSuperTypeOf($throwPointType)->yes()) { - continue; - } - - if ( - $throwPointType instanceof TypeWithClassName - && !$this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName()) - ) { - continue; - } - - $classes[] = [$throwPointType->describe(VerbosityLevel::typeOnly()), $throwPoint->getNode(), $this->getNewCatchPosition($throwPointType, $throwPoint->getNode())]; - } - } - - return $classes; - } - - private function getNewCatchPosition(Type $throwPointType, Node $throwPointNode): ?int - { - if ($throwPointType instanceof TypeWithClassName) { - // to get rid of type subtraction - $throwPointType = new ObjectType($throwPointType->getClassName()); - } - $tryCatch = $this->findTryCatch($throwPointNode); - if ($tryCatch === null) { - return null; - } - - $position = 0; - foreach ($tryCatch->catches as $catch) { - $type = TypeCombinator::union(...array_map(static function (Node\Name $class): ObjectType { - return new ObjectType($class->toString()); - }, $catch->types)); - if (!$throwPointType->isSuperTypeOf($type)->yes()) { - continue; - } - - $position++; - } - - return $position; - } - - private function findTryCatch(Node $node): ?Node\Stmt\TryCatch - { - if ($node instanceof Node\FunctionLike) { - return null; - } - - if ($node instanceof Node\Stmt\TryCatch) { - return $node; - } - - $parent = $node->getAttribute('parent'); - if ($parent === null) { - return null; - } - - return $this->findTryCatch($parent); - } - + private ExceptionTypeResolver $exceptionTypeResolver; + + public function __construct(ExceptionTypeResolver $exceptionTypeResolver) + { + $this->exceptionTypeResolver = $exceptionTypeResolver; + } + + /** + * @param Type|null $throwType + * @param ThrowPoint[] $throwPoints + * @return array + */ + public function check(?Type $throwType, array $throwPoints): array + { + if ($throwType === null) { + $throwType = new NeverType(); + } + + $classes = []; + foreach ($throwPoints as $throwPoint) { + if (!$throwPoint->isExplicit()) { + continue; + } + + foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { + if ($throwPointType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { + continue; + } + if ($throwType->isSuperTypeOf($throwPointType)->yes()) { + continue; + } + + if ( + $throwPointType instanceof TypeWithClassName + && !$this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName()) + ) { + continue; + } + + $classes[] = [$throwPointType->describe(VerbosityLevel::typeOnly()), $throwPoint->getNode(), $this->getNewCatchPosition($throwPointType, $throwPoint->getNode())]; + } + } + + return $classes; + } + + private function getNewCatchPosition(Type $throwPointType, Node $throwPointNode): ?int + { + if ($throwPointType instanceof TypeWithClassName) { + // to get rid of type subtraction + $throwPointType = new ObjectType($throwPointType->getClassName()); + } + $tryCatch = $this->findTryCatch($throwPointNode); + if ($tryCatch === null) { + return null; + } + + $position = 0; + foreach ($tryCatch->catches as $catch) { + $type = TypeCombinator::union(...array_map(static function (Node\Name $class): ObjectType { + return new ObjectType($class->toString()); + }, $catch->types)); + if (!$throwPointType->isSuperTypeOf($type)->yes()) { + continue; + } + + $position++; + } + + return $position; + } + + private function findTryCatch(Node $node): ?Node\Stmt\TryCatch + { + if ($node instanceof Node\FunctionLike) { + return null; + } + + if ($node instanceof Node\Stmt\TryCatch) { + return $node; + } + + $parent = $node->getAttribute('parent'); + if ($parent === null) { + return null; + } + + return $this->findTryCatch($parent); + } } diff --git a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php index eeb396aa13..93a2506fc5 100644 --- a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php +++ b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php @@ -1,4 +1,6 @@ -getTryCatchExitPoints()) === 0) { - return []; - } - - $errors = []; - foreach ($node->getTryCatchExitPoints() as $exitPoint) { - $errors[] = RuleErrorBuilder::message(sprintf('This %s is overwritten by a different one in the finally block below.', $this->describeExitPoint($exitPoint->getStatement())))->line($exitPoint->getStatement()->getLine())->build(); - } - - foreach ($node->getFinallyExitPoints() as $exitPoint) { - $errors[] = RuleErrorBuilder::message(sprintf('The overwriting %s is on this line.', $this->describeExitPoint($exitPoint->getStatement())))->line($exitPoint->getStatement()->getLine())->build(); - } - - return $errors; - } - - private function describeExitPoint(Node\Stmt $stmt): string - { - if ($stmt instanceof Node\Stmt\Return_) { - return 'return'; - } - - if ($stmt instanceof Node\Stmt\Throw_) { - return 'throw'; - } - - if ($stmt instanceof Node\Stmt\Continue_) { - return 'continue'; - } - - if ($stmt instanceof Node\Stmt\Break_) { - return 'break'; - } - - return 'exit point'; - } - + public function getNodeType(): string + { + return FinallyExitPointsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (count($node->getTryCatchExitPoints()) === 0) { + return []; + } + + $errors = []; + foreach ($node->getTryCatchExitPoints() as $exitPoint) { + $errors[] = RuleErrorBuilder::message(sprintf('This %s is overwritten by a different one in the finally block below.', $this->describeExitPoint($exitPoint->getStatement())))->line($exitPoint->getStatement()->getLine())->build(); + } + + foreach ($node->getFinallyExitPoints() as $exitPoint) { + $errors[] = RuleErrorBuilder::message(sprintf('The overwriting %s is on this line.', $this->describeExitPoint($exitPoint->getStatement())))->line($exitPoint->getStatement()->getLine())->build(); + } + + return $errors; + } + + private function describeExitPoint(Node\Stmt $stmt): string + { + if ($stmt instanceof Node\Stmt\Return_) { + return 'return'; + } + + if ($stmt instanceof Node\Stmt\Throw_) { + return 'throw'; + } + + if ($stmt instanceof Node\Stmt\Continue_) { + return 'continue'; + } + + if ($stmt instanceof Node\Stmt\Break_) { + return 'break'; + } + + return 'exit point'; + } } diff --git a/src/Rules/Exceptions/ThrowExpressionRule.php b/src/Rules/Exceptions/ThrowExpressionRule.php index fd8999066c..31ab3c5bbd 100644 --- a/src/Rules/Exceptions/ThrowExpressionRule.php +++ b/src/Rules/Exceptions/ThrowExpressionRule.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function getNodeType(): string - { - return Node\Expr\Throw_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->phpVersion->supportsThrowExpression()) { - return []; - } - - return [ - RuleErrorBuilder::message('Throw expression is supported only on PHP 8.0 and later.')->nonIgnorable()->build(), - ]; - } - + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return Node\Expr\Throw_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->phpVersion->supportsThrowExpression()) { + return []; + } + + return [ + RuleErrorBuilder::message('Throw expression is supported only on PHP 8.0 and later.')->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php b/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php index a9f6bab81e..9b893d9b9a 100644 --- a/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php +++ b/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return FunctionReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $statementResult = $node->getStatementResult(); - $functionReflection = $scope->getFunction(); - if (!$functionReflection instanceof FunctionReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $throwType = $functionReflection->getThrowType(); - if ($throwType === null) { - return []; - } - - $errors = []; - foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Function %s() has %s in PHPDoc @throws tag but it\'s not thrown.', - $functionReflection->getName(), - $throwClass - )) - ->identifier('exceptions.tooWideThrowType') - ->metadata([ - 'exceptionName' => $throwClass, - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - ]) - ->build(); - } - - return $errors; - } - + private TooWideThrowTypeCheck $check; + + public function __construct(TooWideThrowTypeCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return FunctionReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $functionReflection = $scope->getFunction(); + if (!$functionReflection instanceof FunctionReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $throwType = $functionReflection->getThrowType(); + if ($throwType === null) { + return []; + } + + $errors = []; + foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Function %s() has %s in PHPDoc @throws tag but it\'s not thrown.', + $functionReflection->getName(), + $throwClass + )) + ->identifier('exceptions.tooWideThrowType') + ->metadata([ + 'exceptionName' => $throwClass, + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + ]) + ->build(); + } + + return $errors; + } } diff --git a/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php b/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php index c3f388028c..f28fc045b5 100644 --- a/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php +++ b/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->check = $check; - } + private TooWideThrowTypeCheck $check; - public function getNodeType(): string - { - return MethodReturnStatementsNode::class; - } + public function __construct(FileTypeMapper $fileTypeMapper, TooWideThrowTypeCheck $check) + { + $this->fileTypeMapper = $fileTypeMapper; + $this->check = $check; + } - public function processNode(Node $node, Scope $scope): array - { - $statementResult = $node->getStatementResult(); - $methodReflection = $scope->getFunction(); - if (!$methodReflection instanceof MethodReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return MethodReturnStatementsNode::class; + } - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof MethodReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $classReflection = $scope->getClassReflection(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $classReflection->getName(), - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $methodReflection->getName(), - $docComment->getText() - ); + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - if ($resolvedPhpDoc->getThrowsTag() === null) { - return []; - } + $classReflection = $scope->getClassReflection(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $methodReflection->getName(), + $docComment->getText() + ); - $throwType = $resolvedPhpDoc->getThrowsTag()->getType(); + if ($resolvedPhpDoc->getThrowsTag() === null) { + return []; + } - $errors = []; - foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has %s in PHPDoc @throws tag but it\'s not thrown.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $throwClass - )) - ->identifier('exceptions.tooWideThrowType') - ->metadata([ - 'exceptionName' => $throwClass, - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - ]) - ->build(); - } + $throwType = $resolvedPhpDoc->getThrowsTag()->getType(); - return $errors; - } + $errors = []; + foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s in PHPDoc @throws tag but it\'s not thrown.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $throwClass + )) + ->identifier('exceptions.tooWideThrowType') + ->metadata([ + 'exceptionName' => $throwClass, + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + ]) + ->build(); + } + return $errors; + } } diff --git a/src/Rules/Exceptions/TooWideThrowTypeCheck.php b/src/Rules/Exceptions/TooWideThrowTypeCheck.php index 826b8daad7..9a1ada5cca 100644 --- a/src/Rules/Exceptions/TooWideThrowTypeCheck.php +++ b/src/Rules/Exceptions/TooWideThrowTypeCheck.php @@ -1,4 +1,6 @@ -isExplicit()) { - return new NeverType(); - } - - return $throwPoint->getType(); - }, $throwPoints)); - - $throwClasses = []; - foreach (TypeUtils::flattenTypes($throwType) as $type) { - if (!$throwPointType instanceof NeverType && !$type->isSuperTypeOf($throwPointType)->no()) { - continue; - } - - $throwClasses[] = $type->describe(VerbosityLevel::typeOnly()); - } - - return $throwClasses; - } - + /** + * @param Type $throwType + * @param ThrowPoint[] $throwPoints + * @return string[] + */ + public function check(Type $throwType, array $throwPoints): array + { + if ($throwType instanceof VoidType) { + return []; + } + + $throwPointType = TypeCombinator::union(...array_map(static function (ThrowPoint $throwPoint): Type { + if (!$throwPoint->isExplicit()) { + return new NeverType(); + } + + return $throwPoint->getType(); + }, $throwPoints)); + + $throwClasses = []; + foreach (TypeUtils::flattenTypes($throwType) as $type) { + if (!$throwPointType instanceof NeverType && !$type->isSuperTypeOf($throwPointType)->no()) { + continue; + } + + $throwClasses[] = $type->describe(VerbosityLevel::typeOnly()); + } + + return $throwClasses; + } } diff --git a/src/Rules/FileRuleError.php b/src/Rules/FileRuleError.php index 7f5cd7fc26..f62c587ab6 100644 --- a/src/Rules/FileRuleError.php +++ b/src/Rules/FileRuleError.php @@ -1,10 +1,10 @@ -type = $type; - $this->referencedClasses = $referencedClasses; - $this->unknownClassErrors = $unknownClassErrors; - } - - public function getType(): Type - { - return $this->type; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return $this->referencedClasses; - } - - /** - * @return RuleError[] - */ - public function getUnknownClassErrors(): array - { - return $this->unknownClassErrors; - } - + private \PHPStan\Type\Type $type; + + /** @var string[] */ + private array $referencedClasses; + + /** @var RuleError[] */ + private array $unknownClassErrors; + + /** + * @param \PHPStan\Type\Type $type + * @param string[] $referencedClasses + * @param RuleError[] $unknownClassErrors + */ + public function __construct( + Type $type, + array $referencedClasses, + array $unknownClassErrors + ) { + $this->type = $type; + $this->referencedClasses = $referencedClasses; + $this->unknownClassErrors = $unknownClassErrors; + } + + public function getType(): Type + { + return $this->type; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return $this->referencedClasses; + } + + /** + * @return RuleError[] + */ + public function getUnknownClassErrors(): array + { + return $this->unknownClassErrors; + } } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 83dae6eab8..9b41525f15 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->nullsafeCheck = $nullsafeCheck; - $this->phpVersion = $phpVersion; - $this->checkArgumentTypes = $checkArgumentTypes; - $this->checkArgumentsPassedByReference = $checkArgumentsPassedByReference; - $this->checkExtraArguments = $checkExtraArguments; - $this->checkMissingTypehints = $checkMissingTypehints; - } - - /** - * @param \PHPStan\Reflection\ParametersAcceptor $parametersAcceptor - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\New_ $funcCall - * @param array{string, string, string, string, string, string, string, string, string, string, string, string} $messages - * @return RuleError[] - */ - public function check( - ParametersAcceptor $parametersAcceptor, - Scope $scope, - bool $isBuiltin, - $funcCall, - array $messages - ): array - { - $functionParametersMinCount = 0; - $functionParametersMaxCount = 0; - foreach ($parametersAcceptor->getParameters() as $parameter) { - if (!$parameter->isOptional()) { - $functionParametersMinCount++; - } - - $functionParametersMaxCount++; - } - - if ($parametersAcceptor->isVariadic()) { - $functionParametersMaxCount = -1; - } - - /** @var array $arguments */ - $arguments = []; - /** @var array $args */ - $args = $funcCall->args; - $hasNamedArguments = false; - $hasUnpackedArgument = false; - $errors = []; - foreach ($args as $i => $arg) { - $type = $scope->getType($arg->value); - if ($hasNamedArguments && $arg->unpack) { - $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by an unpacked (...) argument.')->line($arg->getLine())->nonIgnorable()->build(); - } - if ($hasUnpackedArgument && !$arg->unpack) { - $errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.')->line($arg->getLine())->nonIgnorable()->build(); - } - if ($arg->unpack) { - $hasUnpackedArgument = true; - } - $argumentName = null; - if ($arg->name !== null) { - $hasNamedArguments = true; - $argumentName = $arg->name->toString(); - } - if ($arg->unpack) { - $arrays = TypeUtils::getConstantArrays($type); - if (count($arrays) > 0) { - $minKeys = null; - foreach ($arrays as $array) { - $keysCount = count($array->getKeyTypes()); - if ($minKeys !== null && $keysCount >= $minKeys) { - continue; - } - - $minKeys = $keysCount; - } - - for ($j = 0; $j < $minKeys; $j++) { - $types = []; - $commonKey = null; - foreach ($arrays as $constantArray) { - $types[] = $constantArray->getValueTypes()[$j]; - $keyType = $constantArray->getKeyTypes()[$j]; - if ($commonKey === null) { - $commonKey = $keyType->getValue(); - } elseif ($commonKey !== $keyType->getValue()) { - $commonKey = false; - } - } - $keyArgumentName = null; - if (is_string($commonKey)) { - $keyArgumentName = $commonKey; - $hasNamedArguments = true; - } - $arguments[] = [ - $arg->value, - TypeCombinator::union(...$types), - false, - $keyArgumentName, - $arg->getLine(), - ]; - } - } else { - $arguments[] = [ - $arg->value, - $type->getIterableValueType(), - true, - null, - $arg->getLine(), - ]; - } - continue; - } - - $arguments[] = [ - $arg->value, - $type, - false, - $argumentName, - $arg->getLine(), - ]; - } - - if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments()) { - $errors[] = RuleErrorBuilder::message('Named arguments are supported only on PHP 8.0 and later.')->line($funcCall->getLine())->nonIgnorable()->build(); - } - - if (!$hasNamedArguments) { - $invokedParametersCount = count($arguments); - foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName]) { - if ($unpack) { - $invokedParametersCount = max($functionParametersMinCount, $functionParametersMaxCount); - break; - } - } - - if ( - $invokedParametersCount < $functionParametersMinCount - || ($this->checkExtraArguments && $invokedParametersCount > $functionParametersMaxCount) - ) { - if ($functionParametersMinCount === $functionParametersMaxCount) { - $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[0] : $messages[1], - $invokedParametersCount, - $functionParametersMinCount - ))->line($funcCall->getLine())->build(); - } elseif ($functionParametersMaxCount === -1 && $invokedParametersCount < $functionParametersMinCount) { - $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[2] : $messages[3], - $invokedParametersCount, - $functionParametersMinCount - ))->line($funcCall->getLine())->build(); - } elseif ($functionParametersMaxCount !== -1) { - $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[4] : $messages[5], - $invokedParametersCount, - $functionParametersMinCount, - $functionParametersMaxCount - ))->line($funcCall->getLine())->build(); - } - } - } - - if ( - $scope->getType($funcCall) instanceof VoidType - && !$scope->isInFirstLevelStatement() - && !$funcCall instanceof \PhpParser\Node\Expr\New_ - ) { - $errors[] = RuleErrorBuilder::message($messages[7])->line($funcCall->getLine())->build(); - } - - [$addedErrors, $argumentsWithParameters] = $this->processArguments($parametersAcceptor, $funcCall->getLine(), $isBuiltin, $arguments, $hasNamedArguments, $messages[10], $messages[11]); - foreach ($addedErrors as $error) { - $errors[] = $error; - } - - if (!$this->checkArgumentTypes && !$this->checkArgumentsPassedByReference) { - return $errors; - } - - foreach ($argumentsWithParameters as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]) { - if ($this->checkArgumentTypes && $unpack) { - $iterableTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $argumentValue, - '', - static function (Type $type): bool { - return $type->isIterable()->yes(); - } - ); - $iterableTypeResultType = $iterableTypeResult->getType(); - if ( - !$iterableTypeResultType instanceof ErrorType - && !$iterableTypeResultType->isIterable()->yes() - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Only iterables can be unpacked, %s given in argument #%d.', - $iterableTypeResultType->describe(VerbosityLevel::typeOnly()), - $i + 1 - ))->line($argumentLine)->build(); - } - } - - if ($parameter === null) { - continue; - } - - $parameterType = $parameter->getType(); - if ( - $this->checkArgumentTypes - && !$parameter->passedByReference()->createsNewVariable() - && !$this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()) - ) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType); - $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); - $errors[] = RuleErrorBuilder::message(sprintf( - $messages[6], - $argumentName === null ? sprintf( - '#%d %s', - $i + 1, - $parameterDescription - ) : $parameterDescription, - $parameterType->describe($verbosityLevel), - $argumentValueType->describe($verbosityLevel) - ))->line($argumentLine)->build(); - } - - if ( - !$this->checkArgumentsPassedByReference - || !$parameter->passedByReference()->yes() - ) { - continue; - } - - if ($this->nullsafeCheck->containsNullSafe($argumentValue)) { - $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); - $errors[] = RuleErrorBuilder::message(sprintf( - $messages[8], - $argumentName === null ? sprintf('#%d %s', $i + 1, $parameterDescription) : $parameterDescription - ))->line($argumentLine)->build(); - continue; - } - - if ($argumentValue instanceof \PhpParser\Node\Expr\Variable - || $argumentValue instanceof \PhpParser\Node\Expr\ArrayDimFetch - || $argumentValue instanceof \PhpParser\Node\Expr\PropertyFetch - || $argumentValue instanceof \PhpParser\Node\Expr\StaticPropertyFetch) { - continue; - } - - $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); - $errors[] = RuleErrorBuilder::message(sprintf( - $messages[8], - $argumentName === null ? sprintf('#%d %s', $i + 1, $parameterDescription) : $parameterDescription - ))->line($argumentLine)->build(); - } - - if ($this->checkMissingTypehints && $parametersAcceptor instanceof ResolvedFunctionVariant) { - $originalParametersAcceptor = $parametersAcceptor->getOriginalParametersAcceptor(); - $resolvedTypes = $parametersAcceptor->getResolvedTemplateTypeMap()->getTypes(); - if (count($resolvedTypes) > 0) { - $returnTemplateTypes = []; - TypeTraverser::map($originalParametersAcceptor->getReturnType(), static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Type { - if ($type instanceof TemplateType) { - $returnTemplateTypes[$type->getName()] = true; - return $type; - } - - return $traverse($type); - }); - - $parameterTemplateTypes = []; - foreach ($originalParametersAcceptor->getParameters() as $parameter) { - TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$parameterTemplateTypes): Type { - if ($type instanceof TemplateType) { - $parameterTemplateTypes[$type->getName()] = true; - return $type; - } - - return $traverse($type); - }); - } - - foreach ($resolvedTypes as $name => $type) { - if ( - !($type instanceof ErrorType) - && ( - !$type instanceof NeverType - || $type->isExplicit() - ) - ) { - continue; - } - - if (!array_key_exists($name, $returnTemplateTypes)) { - continue; - } - - if (!array_key_exists($name, $parameterTemplateTypes)) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf($messages[9], $name))->line($funcCall->getLine())->tip('See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type')->build(); - } - } - } - - return $errors; - } - - /** - * @param ParametersAcceptor $parametersAcceptor - * @param array $arguments - * @param bool $hasNamedArguments - * @param string $missingParameterMessage - * @param string $unknownParameterMessage - * @return array{RuleError[], array} - */ - private function processArguments( - ParametersAcceptor $parametersAcceptor, - int $line, - bool $isBuiltin, - array $arguments, - bool $hasNamedArguments, - string $missingParameterMessage, - string $unknownParameterMessage - ): array - { - $parameters = $parametersAcceptor->getParameters(); - $parametersByName = []; - $unusedParametersByName = []; - $errors = []; - foreach ($parametersAcceptor->getParameters() as $parameter) { - $parametersByName[$parameter->getName()] = $parameter; - - if ($parameter->isVariadic()) { - continue; - } - - $unusedParametersByName[$parameter->getName()] = $parameter; - } - - $newArguments = []; - - $namedArgumentAlreadyOccurred = false; - foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine]) { - if ($argumentName === null) { - if (!isset($parameters[$i])) { - if (!$parametersAcceptor->isVariadic() || count($parameters) === 0) { - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; - break; - } - - $parameter = $parameters[count($parameters) - 1]; - if (!$parameter->isVariadic()) { - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; - break; // func_get_args - } - } else { - $parameter = $parameters[$i]; - } - } elseif (array_key_exists($argumentName, $parametersByName)) { - $namedArgumentAlreadyOccurred = true; - $parameter = $parametersByName[$argumentName]; - } else { - $namedArgumentAlreadyOccurred = true; - - $parametersCount = count($parameters); - if ( - !$parametersAcceptor->isVariadic() - || $parametersCount <= 0 - || $isBuiltin - ) { - $errors[] = RuleErrorBuilder::message(sprintf($unknownParameterMessage, $argumentName))->line($argumentLine)->build(); - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; - continue; - } - - $parameter = $parameters[$parametersCount - 1]; - } - - if ($namedArgumentAlreadyOccurred && $argumentName === null && !$unpack) { - $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by a positional argument.')->line($argumentLine)->nonIgnorable()->build(); - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; - continue; - } - - $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]; - - if ( - $hasNamedArguments - && !$parameter->isVariadic() - && !array_key_exists($parameter->getName(), $unusedParametersByName) - ) { - $errors[] = RuleErrorBuilder::message(sprintf('Argument for parameter $%s has already been passed.', $parameter->getName()))->line($argumentLine)->build(); - continue; - } - - unset($unusedParametersByName[$parameter->getName()]); - } - - if ($hasNamedArguments) { - foreach ($unusedParametersByName as $parameter) { - if ($parameter->isOptional()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf($missingParameterMessage, sprintf('%s (%s)', $parameter->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly()))))->line($line)->build(); - } - } - - return [$errors, $newArguments]; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private NullsafeCheck $nullsafeCheck; + + private PhpVersion $phpVersion; + + private bool $checkArgumentTypes; + + private bool $checkArgumentsPassedByReference; + + private bool $checkExtraArguments; + + private bool $checkMissingTypehints; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + NullsafeCheck $nullsafeCheck, + PhpVersion $phpVersion, + bool $checkArgumentTypes, + bool $checkArgumentsPassedByReference, + bool $checkExtraArguments, + bool $checkMissingTypehints + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->nullsafeCheck = $nullsafeCheck; + $this->phpVersion = $phpVersion; + $this->checkArgumentTypes = $checkArgumentTypes; + $this->checkArgumentsPassedByReference = $checkArgumentsPassedByReference; + $this->checkExtraArguments = $checkExtraArguments; + $this->checkMissingTypehints = $checkMissingTypehints; + } + + /** + * @param \PHPStan\Reflection\ParametersAcceptor $parametersAcceptor + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\New_ $funcCall + * @param array{string, string, string, string, string, string, string, string, string, string, string, string} $messages + * @return RuleError[] + */ + public function check( + ParametersAcceptor $parametersAcceptor, + Scope $scope, + bool $isBuiltin, + $funcCall, + array $messages + ): array { + $functionParametersMinCount = 0; + $functionParametersMaxCount = 0; + foreach ($parametersAcceptor->getParameters() as $parameter) { + if (!$parameter->isOptional()) { + $functionParametersMinCount++; + } + + $functionParametersMaxCount++; + } + + if ($parametersAcceptor->isVariadic()) { + $functionParametersMaxCount = -1; + } + + /** @var array $arguments */ + $arguments = []; + /** @var array $args */ + $args = $funcCall->args; + $hasNamedArguments = false; + $hasUnpackedArgument = false; + $errors = []; + foreach ($args as $i => $arg) { + $type = $scope->getType($arg->value); + if ($hasNamedArguments && $arg->unpack) { + $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by an unpacked (...) argument.')->line($arg->getLine())->nonIgnorable()->build(); + } + if ($hasUnpackedArgument && !$arg->unpack) { + $errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.')->line($arg->getLine())->nonIgnorable()->build(); + } + if ($arg->unpack) { + $hasUnpackedArgument = true; + } + $argumentName = null; + if ($arg->name !== null) { + $hasNamedArguments = true; + $argumentName = $arg->name->toString(); + } + if ($arg->unpack) { + $arrays = TypeUtils::getConstantArrays($type); + if (count($arrays) > 0) { + $minKeys = null; + foreach ($arrays as $array) { + $keysCount = count($array->getKeyTypes()); + if ($minKeys !== null && $keysCount >= $minKeys) { + continue; + } + + $minKeys = $keysCount; + } + + for ($j = 0; $j < $minKeys; $j++) { + $types = []; + $commonKey = null; + foreach ($arrays as $constantArray) { + $types[] = $constantArray->getValueTypes()[$j]; + $keyType = $constantArray->getKeyTypes()[$j]; + if ($commonKey === null) { + $commonKey = $keyType->getValue(); + } elseif ($commonKey !== $keyType->getValue()) { + $commonKey = false; + } + } + $keyArgumentName = null; + if (is_string($commonKey)) { + $keyArgumentName = $commonKey; + $hasNamedArguments = true; + } + $arguments[] = [ + $arg->value, + TypeCombinator::union(...$types), + false, + $keyArgumentName, + $arg->getLine(), + ]; + } + } else { + $arguments[] = [ + $arg->value, + $type->getIterableValueType(), + true, + null, + $arg->getLine(), + ]; + } + continue; + } + + $arguments[] = [ + $arg->value, + $type, + false, + $argumentName, + $arg->getLine(), + ]; + } + + if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments()) { + $errors[] = RuleErrorBuilder::message('Named arguments are supported only on PHP 8.0 and later.')->line($funcCall->getLine())->nonIgnorable()->build(); + } + + if (!$hasNamedArguments) { + $invokedParametersCount = count($arguments); + foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName]) { + if ($unpack) { + $invokedParametersCount = max($functionParametersMinCount, $functionParametersMaxCount); + break; + } + } + + if ( + $invokedParametersCount < $functionParametersMinCount + || ($this->checkExtraArguments && $invokedParametersCount > $functionParametersMaxCount) + ) { + if ($functionParametersMinCount === $functionParametersMaxCount) { + $errors[] = RuleErrorBuilder::message(sprintf( + $invokedParametersCount === 1 ? $messages[0] : $messages[1], + $invokedParametersCount, + $functionParametersMinCount + ))->line($funcCall->getLine())->build(); + } elseif ($functionParametersMaxCount === -1 && $invokedParametersCount < $functionParametersMinCount) { + $errors[] = RuleErrorBuilder::message(sprintf( + $invokedParametersCount === 1 ? $messages[2] : $messages[3], + $invokedParametersCount, + $functionParametersMinCount + ))->line($funcCall->getLine())->build(); + } elseif ($functionParametersMaxCount !== -1) { + $errors[] = RuleErrorBuilder::message(sprintf( + $invokedParametersCount === 1 ? $messages[4] : $messages[5], + $invokedParametersCount, + $functionParametersMinCount, + $functionParametersMaxCount + ))->line($funcCall->getLine())->build(); + } + } + } + + if ( + $scope->getType($funcCall) instanceof VoidType + && !$scope->isInFirstLevelStatement() + && !$funcCall instanceof \PhpParser\Node\Expr\New_ + ) { + $errors[] = RuleErrorBuilder::message($messages[7])->line($funcCall->getLine())->build(); + } + + [$addedErrors, $argumentsWithParameters] = $this->processArguments($parametersAcceptor, $funcCall->getLine(), $isBuiltin, $arguments, $hasNamedArguments, $messages[10], $messages[11]); + foreach ($addedErrors as $error) { + $errors[] = $error; + } + + if (!$this->checkArgumentTypes && !$this->checkArgumentsPassedByReference) { + return $errors; + } + + foreach ($argumentsWithParameters as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]) { + if ($this->checkArgumentTypes && $unpack) { + $iterableTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $argumentValue, + '', + static function (Type $type): bool { + return $type->isIterable()->yes(); + } + ); + $iterableTypeResultType = $iterableTypeResult->getType(); + if ( + !$iterableTypeResultType instanceof ErrorType + && !$iterableTypeResultType->isIterable()->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Only iterables can be unpacked, %s given in argument #%d.', + $iterableTypeResultType->describe(VerbosityLevel::typeOnly()), + $i + 1 + ))->line($argumentLine)->build(); + } + } + + if ($parameter === null) { + continue; + } + + $parameterType = $parameter->getType(); + if ( + $this->checkArgumentTypes + && !$parameter->passedByReference()->createsNewVariable() + && !$this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()) + ) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType); + $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( + $messages[6], + $argumentName === null ? sprintf( + '#%d %s', + $i + 1, + $parameterDescription + ) : $parameterDescription, + $parameterType->describe($verbosityLevel), + $argumentValueType->describe($verbosityLevel) + ))->line($argumentLine)->build(); + } + + if ( + !$this->checkArgumentsPassedByReference + || !$parameter->passedByReference()->yes() + ) { + continue; + } + + if ($this->nullsafeCheck->containsNullSafe($argumentValue)) { + $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( + $messages[8], + $argumentName === null ? sprintf('#%d %s', $i + 1, $parameterDescription) : $parameterDescription + ))->line($argumentLine)->build(); + continue; + } + + if ($argumentValue instanceof \PhpParser\Node\Expr\Variable + || $argumentValue instanceof \PhpParser\Node\Expr\ArrayDimFetch + || $argumentValue instanceof \PhpParser\Node\Expr\PropertyFetch + || $argumentValue instanceof \PhpParser\Node\Expr\StaticPropertyFetch) { + continue; + } + + $parameterDescription = sprintf('%s$%s', $parameter->isVariadic() ? '...' : '', $parameter->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( + $messages[8], + $argumentName === null ? sprintf('#%d %s', $i + 1, $parameterDescription) : $parameterDescription + ))->line($argumentLine)->build(); + } + + if ($this->checkMissingTypehints && $parametersAcceptor instanceof ResolvedFunctionVariant) { + $originalParametersAcceptor = $parametersAcceptor->getOriginalParametersAcceptor(); + $resolvedTypes = $parametersAcceptor->getResolvedTemplateTypeMap()->getTypes(); + if (count($resolvedTypes) > 0) { + $returnTemplateTypes = []; + TypeTraverser::map($originalParametersAcceptor->getReturnType(), static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Type { + if ($type instanceof TemplateType) { + $returnTemplateTypes[$type->getName()] = true; + return $type; + } + + return $traverse($type); + }); + + $parameterTemplateTypes = []; + foreach ($originalParametersAcceptor->getParameters() as $parameter) { + TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$parameterTemplateTypes): Type { + if ($type instanceof TemplateType) { + $parameterTemplateTypes[$type->getName()] = true; + return $type; + } + + return $traverse($type); + }); + } + + foreach ($resolvedTypes as $name => $type) { + if ( + !($type instanceof ErrorType) + && ( + !$type instanceof NeverType + || $type->isExplicit() + ) + ) { + continue; + } + + if (!array_key_exists($name, $returnTemplateTypes)) { + continue; + } + + if (!array_key_exists($name, $parameterTemplateTypes)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($messages[9], $name))->line($funcCall->getLine())->tip('See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type')->build(); + } + } + } + + return $errors; + } + + /** + * @param ParametersAcceptor $parametersAcceptor + * @param array $arguments + * @param bool $hasNamedArguments + * @param string $missingParameterMessage + * @param string $unknownParameterMessage + * @return array{RuleError[], array} + */ + private function processArguments( + ParametersAcceptor $parametersAcceptor, + int $line, + bool $isBuiltin, + array $arguments, + bool $hasNamedArguments, + string $missingParameterMessage, + string $unknownParameterMessage + ): array { + $parameters = $parametersAcceptor->getParameters(); + $parametersByName = []; + $unusedParametersByName = []; + $errors = []; + foreach ($parametersAcceptor->getParameters() as $parameter) { + $parametersByName[$parameter->getName()] = $parameter; + + if ($parameter->isVariadic()) { + continue; + } + + $unusedParametersByName[$parameter->getName()] = $parameter; + } + + $newArguments = []; + + $namedArgumentAlreadyOccurred = false; + foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine]) { + if ($argumentName === null) { + if (!isset($parameters[$i])) { + if (!$parametersAcceptor->isVariadic() || count($parameters) === 0) { + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + break; + } + + $parameter = $parameters[count($parameters) - 1]; + if (!$parameter->isVariadic()) { + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + break; // func_get_args + } + } else { + $parameter = $parameters[$i]; + } + } elseif (array_key_exists($argumentName, $parametersByName)) { + $namedArgumentAlreadyOccurred = true; + $parameter = $parametersByName[$argumentName]; + } else { + $namedArgumentAlreadyOccurred = true; + + $parametersCount = count($parameters); + if ( + !$parametersAcceptor->isVariadic() + || $parametersCount <= 0 + || $isBuiltin + ) { + $errors[] = RuleErrorBuilder::message(sprintf($unknownParameterMessage, $argumentName))->line($argumentLine)->build(); + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + continue; + } + + $parameter = $parameters[$parametersCount - 1]; + } + + if ($namedArgumentAlreadyOccurred && $argumentName === null && !$unpack) { + $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by a positional argument.')->line($argumentLine)->nonIgnorable()->build(); + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null]; + continue; + } + + $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter]; + + if ( + $hasNamedArguments + && !$parameter->isVariadic() + && !array_key_exists($parameter->getName(), $unusedParametersByName) + ) { + $errors[] = RuleErrorBuilder::message(sprintf('Argument for parameter $%s has already been passed.', $parameter->getName()))->line($argumentLine)->build(); + continue; + } + + unset($unusedParametersByName[$parameter->getName()]); + } + + if ($hasNamedArguments) { + foreach ($unusedParametersByName as $parameter) { + if ($parameter->isOptional()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($missingParameterMessage, sprintf('%s (%s)', $parameter->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly()))))->line($line)->build(); + } + } + + return [$errors, $newArguments]; + } } diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index cd920e636b..b34c8f1a39 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->phpVersion = $phpVersion; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - $this->checkThisOnly = $checkThisOnly; - $this->checkMissingTemplateTypeInParameter = $checkMissingTemplateTypeInParameter; - } - - /** - * @param \PhpParser\Node\Stmt\Function_ $function - * @param string $parameterMessage - * @param string $returnMessage - * @param string $unionTypesMessage - * @param string $templateTypeMissingInParameterMessage - * @return RuleError[] - */ - public function checkFunction( - Function_ $function, - FunctionReflection $functionReflection, - string $parameterMessage, - string $returnMessage, - string $unionTypesMessage, - string $templateTypeMissingInParameterMessage - ): array - { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); - - return $this->checkParametersAcceptor( - $parametersAcceptor, - $function, - $parameterMessage, - $returnMessage, - $unionTypesMessage, - $templateTypeMissingInParameterMessage - ); - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node\Param[] $parameters - * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $returnTypeNode - * @param string $parameterMessage - * @param string $returnMessage - * @param string $unionTypesMessage - * @return \PHPStan\Rules\RuleError[] - */ - public function checkAnonymousFunction( - Scope $scope, - array $parameters, - $returnTypeNode, - string $parameterMessage, - string $returnMessage, - string $unionTypesMessage - ): array - { - $errors = []; - $unionTypeReported = false; - foreach ($parameters as $param) { - if ($param->type === null) { - continue; - } - if ( - !$unionTypeReported - && $param->type instanceof UnionType - && !$this->phpVersion->supportsNativeUnionTypes() - ) { - $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($param->getLine())->nonIgnorable()->build(); - $unionTypeReported = true; - } - - if (!$param->var instanceof Variable || !is_string($param->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $type = $scope->getFunctionType($param->type, false, false); - if ($type instanceof VoidType) { - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, 'void'))->line($param->type->getLine())->nonIgnorable()->build(); - } - foreach ($type->getReferencedClasses() as $class) { - if (!$this->reflectionProvider->hasClass($class) || $this->reflectionProvider->getClass($class)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, $class))->line($param->type->getLine())->build(); - } elseif ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames([ - new ClassNameNodePair($class, $param->type), - ]) - ); - } - } - } - - if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) { - $errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameters)); - } - - if ($returnTypeNode === null) { - return $errors; - } - - if ( - !$unionTypeReported - && $returnTypeNode instanceof UnionType - && !$this->phpVersion->supportsNativeUnionTypes() - ) { - $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($returnTypeNode->getLine())->nonIgnorable()->build(); - } - - $returnType = $scope->getFunctionType($returnTypeNode, false, false); - foreach ($returnType->getReferencedClasses() as $returnTypeClass) { - if (!$this->reflectionProvider->hasClass($returnTypeClass) || $this->reflectionProvider->getClass($returnTypeClass)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $returnTypeClass))->line($returnTypeNode->getLine())->build(); - } elseif ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames([ - new ClassNameNodePair($returnTypeClass, $returnTypeNode), - ]) - ); - } - } - - return $errors; - } - - /** - * @param PhpMethodFromParserNodeReflection $methodReflection - * @param ClassMethod $methodNode - * @param string $parameterMessage - * @param string $returnMessage - * @param string $unionTypesMessage - * @param string $templateTypeMissingInParameterMessage - * @return RuleError[] - */ - public function checkClassMethod( - PhpMethodFromParserNodeReflection $methodReflection, - ClassMethod $methodNode, - string $parameterMessage, - string $returnMessage, - string $unionTypesMessage, - string $templateTypeMissingInParameterMessage - ): array - { - /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - - return $this->checkParametersAcceptor( - $parametersAcceptor, - $methodNode, - $parameterMessage, - $returnMessage, - $unionTypesMessage, - $templateTypeMissingInParameterMessage - ); - } - - /** - * @param ParametersAcceptor $parametersAcceptor - * @param FunctionLike $functionNode - * @param string $parameterMessage - * @param string $returnMessage - * @param string $unionTypesMessage - * @param string $templateTypeMissingInParameterMessage - * @return RuleError[] - */ - private function checkParametersAcceptor( - ParametersAcceptor $parametersAcceptor, - FunctionLike $functionNode, - string $parameterMessage, - string $returnMessage, - string $unionTypesMessage, - string $templateTypeMissingInParameterMessage - ): array - { - $errors = []; - $parameterNodes = $functionNode->getParams(); - if (!$this->phpVersion->supportsNativeUnionTypes()) { - $unionTypeReported = false; - foreach ($parameterNodes as $parameterNode) { - if (!$parameterNode->type instanceof UnionType) { - continue; - } - - $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($parameterNode->getLine())->nonIgnorable()->build(); - $unionTypeReported = true; - break; - } - - if (!$unionTypeReported && $functionNode->getReturnType() instanceof UnionType) { - $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($functionNode->getReturnType()->getLine())->nonIgnorable()->build(); - } - } - - if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) { - $errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameterNodes)); - } - - $returnTypeNode = $functionNode->getReturnType() ?? $functionNode; - foreach ($parametersAcceptor->getParameters() as $parameter) { - $referencedClasses = $this->getParameterReferencedClasses($parameter); - $parameterNode = null; - $parameterNodeCallback = function () use ($parameter, $parameterNodes, &$parameterNode): Param { - if ($parameterNode === null) { - $parameterNode = $this->getParameterNode($parameter->getName(), $parameterNodes); - } - - return $parameterNode; - }; - if ( - $parameter instanceof ParameterReflectionWithPhpDocs - && $parameter->getNativeType() instanceof VoidType - ) { - $parameterVar = $parameterNodeCallback()->var; - if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void'))->line($parameterNodeCallback()->getLine())->nonIgnorable()->build(); - } - foreach ($referencedClasses as $class) { - if ($this->reflectionProvider->hasClass($class) && !$this->reflectionProvider->getClass($class)->isTrait()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - $parameterMessage, - $parameter->getName(), - $class - ))->line($parameterNodeCallback()->getLine())->build(); - } - - if ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($parameterNodeCallback): ClassNameNodePair { - return new ClassNameNodePair($class, $parameterNodeCallback()); - }, $referencedClasses)) - ); - } - if (!($parameter->getType() instanceof NonexistentParentClassType)) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameter->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly())))->line($parameterNodeCallback()->getLine())->build(); - } - - $returnTypeReferencedClasses = $this->getReturnTypeReferencedClasses($parametersAcceptor); - - foreach ($returnTypeReferencedClasses as $class) { - if ($this->reflectionProvider->hasClass($class) && !$this->reflectionProvider->getClass($class)->isTrait()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class))->line($returnTypeNode->getLine())->build(); - } - - if ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($returnTypeNode): ClassNameNodePair { - return new ClassNameNodePair($class, $returnTypeNode); - }, $returnTypeReferencedClasses)) - ); - } - if ($parametersAcceptor->getReturnType() instanceof NonexistentParentClassType) { - $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::typeOnly())))->line($returnTypeNode->getLine())->build(); - } - - if ($this->checkMissingTemplateTypeInParameter) { - $templateTypeMap = $parametersAcceptor->getTemplateTypeMap(); - $templateTypes = $templateTypeMap->getTypes(); - if (count($templateTypes) > 0) { - foreach ($parametersAcceptor->getParameters() as $parameter) { - TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type { - if ($type instanceof TemplateType) { - unset($templateTypes[$type->getName()]); - return $traverse($type); - } - - return $traverse($type); - }); - } - - foreach (array_keys($templateTypes) as $templateTypeName) { - $errors[] = RuleErrorBuilder::message(sprintf($templateTypeMissingInParameterMessage, $templateTypeName))->build(); - } - } - } - - return $errors; - } - - /** - * @param Param[] $parameterNodes - * @return RuleError[] - */ - private function checkRequiredParameterAfterOptional(array $parameterNodes): array - { - /** @var string|null $optionalParameter */ - $optionalParameter = null; - $errors = []; - foreach ($parameterNodes as $parameterNode) { - if (!$parameterNode->var instanceof Variable) { - throw new \PHPStan\ShouldNotHappenException(); - } - if (!is_string($parameterNode->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $parameterName = $parameterNode->var->name; - if ($optionalParameter !== null && $parameterNode->default === null && !$parameterNode->variadic) { - $errors[] = RuleErrorBuilder::message(sprintf('Deprecated in PHP 8.0: Required parameter $%s follows optional parameter $%s.', $parameterName, $optionalParameter))->line($parameterNode->getStartLine())->nonIgnorable()->build(); - continue; - } - if ($parameterNode->default === null) { - continue; - } - if ($parameterNode->type === null) { - $optionalParameter = $parameterName; - continue; - } - - $defaultValue = $parameterNode->default; - if (!$defaultValue instanceof ConstFetch) { - $optionalParameter = $parameterName; - continue; - } - - $constantName = $defaultValue->name->toLowerString(); - if ($constantName === 'null') { - continue; - } - - $optionalParameter = $parameterName; - } - - return $errors; - } - - /** - * @param string $parameterName - * @param Param[] $parameterNodes - * @return Param - */ - private function getParameterNode( - string $parameterName, - array $parameterNodes - ): Param - { - foreach ($parameterNodes as $param) { - if ($param->var instanceof \PhpParser\Node\Expr\Error) { - continue; - } - - if (!is_string($param->var->name)) { - continue; - } - - if ($param->var->name === $parameterName) { - return $param; - } - } - - throw new \PHPStan\ShouldNotHappenException(sprintf('Parameter %s not found.', $parameterName)); - } - - /** - * @param \PHPStan\Reflection\ParameterReflection $parameter - * @return string[] - */ - private function getParameterReferencedClasses(ParameterReflection $parameter): array - { - if (!$parameter instanceof ParameterReflectionWithPhpDocs) { - return $parameter->getType()->getReferencedClasses(); - } - - if ($this->checkThisOnly) { - return $parameter->getNativeType()->getReferencedClasses(); - } - - return array_merge( - $parameter->getNativeType()->getReferencedClasses(), - $parameter->getPhpDocType()->getReferencedClasses() - ); - } - - /** - * @param \PHPStan\Reflection\ParametersAcceptor $parametersAcceptor - * @return string[] - */ - private function getReturnTypeReferencedClasses(ParametersAcceptor $parametersAcceptor): array - { - if (!$parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { - return $parametersAcceptor->getReturnType()->getReferencedClasses(); - } - - if ($this->checkThisOnly) { - return $parametersAcceptor->getNativeReturnType()->getReferencedClasses(); - } - - return array_merge( - $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), - $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() - ); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private PhpVersion $phpVersion; + + private bool $checkClassCaseSensitivity; + + private bool $checkThisOnly; + + private bool $checkMissingTemplateTypeInParameter; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + PhpVersion $phpVersion, + bool $checkClassCaseSensitivity, + bool $checkThisOnly, + bool $checkMissingTemplateTypeInParameter + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->phpVersion = $phpVersion; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + $this->checkThisOnly = $checkThisOnly; + $this->checkMissingTemplateTypeInParameter = $checkMissingTemplateTypeInParameter; + } + + /** + * @param \PhpParser\Node\Stmt\Function_ $function + * @param string $parameterMessage + * @param string $returnMessage + * @param string $unionTypesMessage + * @param string $templateTypeMissingInParameterMessage + * @return RuleError[] + */ + public function checkFunction( + Function_ $function, + FunctionReflection $functionReflection, + string $parameterMessage, + string $returnMessage, + string $unionTypesMessage, + string $templateTypeMissingInParameterMessage + ): array { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + + return $this->checkParametersAcceptor( + $parametersAcceptor, + $function, + $parameterMessage, + $returnMessage, + $unionTypesMessage, + $templateTypeMissingInParameterMessage + ); + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node\Param[] $parameters + * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $returnTypeNode + * @param string $parameterMessage + * @param string $returnMessage + * @param string $unionTypesMessage + * @return \PHPStan\Rules\RuleError[] + */ + public function checkAnonymousFunction( + Scope $scope, + array $parameters, + $returnTypeNode, + string $parameterMessage, + string $returnMessage, + string $unionTypesMessage + ): array { + $errors = []; + $unionTypeReported = false; + foreach ($parameters as $param) { + if ($param->type === null) { + continue; + } + if ( + !$unionTypeReported + && $param->type instanceof UnionType + && !$this->phpVersion->supportsNativeUnionTypes() + ) { + $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($param->getLine())->nonIgnorable()->build(); + $unionTypeReported = true; + } + + if (!$param->var instanceof Variable || !is_string($param->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $type = $scope->getFunctionType($param->type, false, false); + if ($type instanceof VoidType) { + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, 'void'))->line($param->type->getLine())->nonIgnorable()->build(); + } + foreach ($type->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class) || $this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, $class))->line($param->type->getLine())->build(); + } elseif ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames([ + new ClassNameNodePair($class, $param->type), + ]) + ); + } + } + } + + if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) { + $errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameters)); + } + + if ($returnTypeNode === null) { + return $errors; + } + + if ( + !$unionTypeReported + && $returnTypeNode instanceof UnionType + && !$this->phpVersion->supportsNativeUnionTypes() + ) { + $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($returnTypeNode->getLine())->nonIgnorable()->build(); + } + + $returnType = $scope->getFunctionType($returnTypeNode, false, false); + foreach ($returnType->getReferencedClasses() as $returnTypeClass) { + if (!$this->reflectionProvider->hasClass($returnTypeClass) || $this->reflectionProvider->getClass($returnTypeClass)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $returnTypeClass))->line($returnTypeNode->getLine())->build(); + } elseif ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames([ + new ClassNameNodePair($returnTypeClass, $returnTypeNode), + ]) + ); + } + } + + return $errors; + } + + /** + * @param PhpMethodFromParserNodeReflection $methodReflection + * @param ClassMethod $methodNode + * @param string $parameterMessage + * @param string $returnMessage + * @param string $unionTypesMessage + * @param string $templateTypeMissingInParameterMessage + * @return RuleError[] + */ + public function checkClassMethod( + PhpMethodFromParserNodeReflection $methodReflection, + ClassMethod $methodNode, + string $parameterMessage, + string $returnMessage, + string $unionTypesMessage, + string $templateTypeMissingInParameterMessage + ): array { + /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); + + return $this->checkParametersAcceptor( + $parametersAcceptor, + $methodNode, + $parameterMessage, + $returnMessage, + $unionTypesMessage, + $templateTypeMissingInParameterMessage + ); + } + + /** + * @param ParametersAcceptor $parametersAcceptor + * @param FunctionLike $functionNode + * @param string $parameterMessage + * @param string $returnMessage + * @param string $unionTypesMessage + * @param string $templateTypeMissingInParameterMessage + * @return RuleError[] + */ + private function checkParametersAcceptor( + ParametersAcceptor $parametersAcceptor, + FunctionLike $functionNode, + string $parameterMessage, + string $returnMessage, + string $unionTypesMessage, + string $templateTypeMissingInParameterMessage + ): array { + $errors = []; + $parameterNodes = $functionNode->getParams(); + if (!$this->phpVersion->supportsNativeUnionTypes()) { + $unionTypeReported = false; + foreach ($parameterNodes as $parameterNode) { + if (!$parameterNode->type instanceof UnionType) { + continue; + } + + $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($parameterNode->getLine())->nonIgnorable()->build(); + $unionTypeReported = true; + break; + } + + if (!$unionTypeReported && $functionNode->getReturnType() instanceof UnionType) { + $errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($functionNode->getReturnType()->getLine())->nonIgnorable()->build(); + } + } + + if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) { + $errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameterNodes)); + } + + $returnTypeNode = $functionNode->getReturnType() ?? $functionNode; + foreach ($parametersAcceptor->getParameters() as $parameter) { + $referencedClasses = $this->getParameterReferencedClasses($parameter); + $parameterNode = null; + $parameterNodeCallback = function () use ($parameter, $parameterNodes, &$parameterNode): Param { + if ($parameterNode === null) { + $parameterNode = $this->getParameterNode($parameter->getName(), $parameterNodes); + } + + return $parameterNode; + }; + if ( + $parameter instanceof ParameterReflectionWithPhpDocs + && $parameter->getNativeType() instanceof VoidType + ) { + $parameterVar = $parameterNodeCallback()->var; + if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void'))->line($parameterNodeCallback()->getLine())->nonIgnorable()->build(); + } + foreach ($referencedClasses as $class) { + if ($this->reflectionProvider->hasClass($class) && !$this->reflectionProvider->getClass($class)->isTrait()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + $parameterMessage, + $parameter->getName(), + $class + ))->line($parameterNodeCallback()->getLine())->build(); + } + + if ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($parameterNodeCallback): ClassNameNodePair { + return new ClassNameNodePair($class, $parameterNodeCallback()); + }, $referencedClasses)) + ); + } + if (!($parameter->getType() instanceof NonexistentParentClassType)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameter->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly())))->line($parameterNodeCallback()->getLine())->build(); + } + + $returnTypeReferencedClasses = $this->getReturnTypeReferencedClasses($parametersAcceptor); + + foreach ($returnTypeReferencedClasses as $class) { + if ($this->reflectionProvider->hasClass($class) && !$this->reflectionProvider->getClass($class)->isTrait()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class))->line($returnTypeNode->getLine())->build(); + } + + if ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($returnTypeNode): ClassNameNodePair { + return new ClassNameNodePair($class, $returnTypeNode); + }, $returnTypeReferencedClasses)) + ); + } + if ($parametersAcceptor->getReturnType() instanceof NonexistentParentClassType) { + $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::typeOnly())))->line($returnTypeNode->getLine())->build(); + } + + if ($this->checkMissingTemplateTypeInParameter) { + $templateTypeMap = $parametersAcceptor->getTemplateTypeMap(); + $templateTypes = $templateTypeMap->getTypes(); + if (count($templateTypes) > 0) { + foreach ($parametersAcceptor->getParameters() as $parameter) { + TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type { + if ($type instanceof TemplateType) { + unset($templateTypes[$type->getName()]); + return $traverse($type); + } + + return $traverse($type); + }); + } + + foreach (array_keys($templateTypes) as $templateTypeName) { + $errors[] = RuleErrorBuilder::message(sprintf($templateTypeMissingInParameterMessage, $templateTypeName))->build(); + } + } + } + + return $errors; + } + + /** + * @param Param[] $parameterNodes + * @return RuleError[] + */ + private function checkRequiredParameterAfterOptional(array $parameterNodes): array + { + /** @var string|null $optionalParameter */ + $optionalParameter = null; + $errors = []; + foreach ($parameterNodes as $parameterNode) { + if (!$parameterNode->var instanceof Variable) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!is_string($parameterNode->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $parameterName = $parameterNode->var->name; + if ($optionalParameter !== null && $parameterNode->default === null && !$parameterNode->variadic) { + $errors[] = RuleErrorBuilder::message(sprintf('Deprecated in PHP 8.0: Required parameter $%s follows optional parameter $%s.', $parameterName, $optionalParameter))->line($parameterNode->getStartLine())->nonIgnorable()->build(); + continue; + } + if ($parameterNode->default === null) { + continue; + } + if ($parameterNode->type === null) { + $optionalParameter = $parameterName; + continue; + } + + $defaultValue = $parameterNode->default; + if (!$defaultValue instanceof ConstFetch) { + $optionalParameter = $parameterName; + continue; + } + + $constantName = $defaultValue->name->toLowerString(); + if ($constantName === 'null') { + continue; + } + + $optionalParameter = $parameterName; + } + + return $errors; + } + + /** + * @param string $parameterName + * @param Param[] $parameterNodes + * @return Param + */ + private function getParameterNode( + string $parameterName, + array $parameterNodes + ): Param { + foreach ($parameterNodes as $param) { + if ($param->var instanceof \PhpParser\Node\Expr\Error) { + continue; + } + + if (!is_string($param->var->name)) { + continue; + } + + if ($param->var->name === $parameterName) { + return $param; + } + } + + throw new \PHPStan\ShouldNotHappenException(sprintf('Parameter %s not found.', $parameterName)); + } + + /** + * @param \PHPStan\Reflection\ParameterReflection $parameter + * @return string[] + */ + private function getParameterReferencedClasses(ParameterReflection $parameter): array + { + if (!$parameter instanceof ParameterReflectionWithPhpDocs) { + return $parameter->getType()->getReferencedClasses(); + } + + if ($this->checkThisOnly) { + return $parameter->getNativeType()->getReferencedClasses(); + } + + return array_merge( + $parameter->getNativeType()->getReferencedClasses(), + $parameter->getPhpDocType()->getReferencedClasses() + ); + } + + /** + * @param \PHPStan\Reflection\ParametersAcceptor $parametersAcceptor + * @return string[] + */ + private function getReturnTypeReferencedClasses(ParametersAcceptor $parametersAcceptor): array + { + if (!$parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { + return $parametersAcceptor->getReturnType()->getReferencedClasses(); + } + + if ($this->checkThisOnly) { + return $parametersAcceptor->getNativeReturnType()->getReferencedClasses(); + } + + return array_merge( + $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), + $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() + ); + } } diff --git a/src/Rules/FunctionReturnTypeCheck.php b/src/Rules/FunctionReturnTypeCheck.php index f3ee8d568b..5172e64517 100644 --- a/src/Rules/FunctionReturnTypeCheck.php +++ b/src/Rules/FunctionReturnTypeCheck.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PHPStan\Type\Type $returnType - * @param \PhpParser\Node\Expr|null $returnValue - * @param string $emptyReturnStatementMessage - * @param string $voidMessage - * @param string $typeMismatchMessage - * @param bool $isGenerator - * @return RuleError[] - */ - public function checkReturnType( - Scope $scope, - Type $returnType, - ?Expr $returnValue, - Node $returnNode, - string $emptyReturnStatementMessage, - string $voidMessage, - string $typeMismatchMessage, - string $neverMessage, - bool $isGenerator - ): array - { - if ($returnType instanceof NeverType && $returnType->isExplicit()) { - return [ - RuleErrorBuilder::message($neverMessage) - ->line($returnNode->getLine()) - ->build(), - ]; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - if ($isGenerator) { - if (!$returnType instanceof TypeWithClassName) { - return []; - } + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PHPStan\Type\Type $returnType + * @param \PhpParser\Node\Expr|null $returnValue + * @param string $emptyReturnStatementMessage + * @param string $voidMessage + * @param string $typeMismatchMessage + * @param bool $isGenerator + * @return RuleError[] + */ + public function checkReturnType( + Scope $scope, + Type $returnType, + ?Expr $returnValue, + Node $returnNode, + string $emptyReturnStatementMessage, + string $voidMessage, + string $typeMismatchMessage, + string $neverMessage, + bool $isGenerator + ): array { + if ($returnType instanceof NeverType && $returnType->isExplicit()) { + return [ + RuleErrorBuilder::message($neverMessage) + ->line($returnNode->getLine()) + ->build(), + ]; + } - $returnType = GenericTypeVariableResolver::getType( - $returnType, - \Generator::class, - 'TReturn' - ); - if ($returnType === null) { - return []; - } - } + if ($isGenerator) { + if (!$returnType instanceof TypeWithClassName) { + return []; + } - $isVoidSuperType = (new VoidType())->isSuperTypeOf($returnType); - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType, null); - if ($returnValue === null) { - if (!$isVoidSuperType->no()) { - return []; - } + $returnType = GenericTypeVariableResolver::getType( + $returnType, + \Generator::class, + 'TReturn' + ); + if ($returnType === null) { + return []; + } + } - return [ - RuleErrorBuilder::message(sprintf( - $emptyReturnStatementMessage, - $returnType->describe($verbosityLevel) - ))->line($returnNode->getLine())->build(), - ]; - } + $isVoidSuperType = (new VoidType())->isSuperTypeOf($returnType); + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType, null); + if ($returnValue === null) { + if (!$isVoidSuperType->no()) { + return []; + } - $returnValueType = $scope->getType($returnValue); - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType, $returnValueType); + return [ + RuleErrorBuilder::message(sprintf( + $emptyReturnStatementMessage, + $returnType->describe($verbosityLevel) + ))->line($returnNode->getLine())->build(), + ]; + } - if ($isVoidSuperType->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - $voidMessage, - $returnValueType->describe($verbosityLevel) - ))->line($returnNode->getLine())->build(), - ]; - } + $returnValueType = $scope->getType($returnValue); + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType, $returnValueType); - if (!$this->ruleLevelHelper->accepts($returnType, $returnValueType, $scope->isDeclareStrictTypes())) { - return [ - RuleErrorBuilder::message(sprintf( - $typeMismatchMessage, - $returnType->describe($verbosityLevel), - $returnValueType->describe($verbosityLevel) - ))->line($returnNode->getLine())->build(), - ]; - } + if ($isVoidSuperType->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + $voidMessage, + $returnValueType->describe($verbosityLevel) + ))->line($returnNode->getLine())->build(), + ]; + } - return []; - } + if (!$this->ruleLevelHelper->accepts($returnType, $returnValueType, $scope->isDeclareStrictTypes())) { + return [ + RuleErrorBuilder::message(sprintf( + $typeMismatchMessage, + $returnType->describe($verbosityLevel), + $returnValueType->describe($verbosityLevel) + ))->line($returnNode->getLine())->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Functions/ArrowFunctionAttributesRule.php b/src/Rules/Functions/ArrowFunctionAttributesRule.php index b45c47f3c3..e9f3da2bfa 100644 --- a/src/Rules/Functions/ArrowFunctionAttributesRule.php +++ b/src/Rules/Functions/ArrowFunctionAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Expr\ArrowFunction::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_FUNCTION, - 'function' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Expr\ArrowFunction::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_FUNCTION, + 'function' + ); + } } diff --git a/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php b/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php index 0dac5917a3..37a17bf544 100644 --- a/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php +++ b/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php @@ -1,4 +1,6 @@ -nullsafeCheck = $nullsafeCheck; - } - - public function getNodeType(): string - { - return Node\Expr\ArrowFunction::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->byRef) { - return []; - } - - if (!$this->nullsafeCheck->containsNullSafe($node->expr)) { - return []; - } - - return [ - RuleErrorBuilder::message('Nullsafe cannot be returned by reference.')->nonIgnorable()->build(), - ]; - } - + private NullsafeCheck $nullsafeCheck; + + public function __construct(NullsafeCheck $nullsafeCheck) + { + $this->nullsafeCheck = $nullsafeCheck; + } + + public function getNodeType(): string + { + return Node\Expr\ArrowFunction::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->byRef) { + return []; + } + + if (!$this->nullsafeCheck->containsNullSafe($node->expr)) { + return []; + } + + return [ + RuleErrorBuilder::message('Nullsafe cannot be returned by reference.')->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php index 0a4cd265ad..28b53238fd 100644 --- a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php +++ b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php @@ -1,4 +1,6 @@ -returnTypeCheck = $returnTypeCheck; - } - - public function getNodeType(): string - { - return InArrowFunctionNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInAnonymousFunction()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - /** @var \PHPStan\Type\Type $returnType */ - $returnType = $scope->getAnonymousFunctionReturnType(); - $generatorType = new ObjectType(\Generator::class); - - $originalNode = $node->getOriginalNode(); - $isVoidSuperType = (new VoidType())->isSuperTypeOf($returnType); - if ($originalNode->returnType === null && $isVoidSuperType->yes()) { - return []; - } - - return $this->returnTypeCheck->checkReturnType( - $scope, - $returnType, - $originalNode->expr, - $originalNode->expr, - 'Anonymous function should return %s but empty return statement found.', - 'Anonymous function with return type void returns %s but should not return anything.', - 'Anonymous function should return %s but returns %s.', - 'Anonymous function should never return but return statement found.', - $generatorType->isSuperTypeOf($returnType)->yes() - ); - } - + private \PHPStan\Rules\FunctionReturnTypeCheck $returnTypeCheck; + + public function __construct(FunctionReturnTypeCheck $returnTypeCheck) + { + $this->returnTypeCheck = $returnTypeCheck; + } + + public function getNodeType(): string + { + return InArrowFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInAnonymousFunction()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + /** @var \PHPStan\Type\Type $returnType */ + $returnType = $scope->getAnonymousFunctionReturnType(); + $generatorType = new ObjectType(\Generator::class); + + $originalNode = $node->getOriginalNode(); + $isVoidSuperType = (new VoidType())->isSuperTypeOf($returnType); + if ($originalNode->returnType === null && $isVoidSuperType->yes()) { + return []; + } + + return $this->returnTypeCheck->checkReturnType( + $scope, + $returnType, + $originalNode->expr, + $originalNode->expr, + 'Anonymous function should return %s but empty return statement found.', + 'Anonymous function with return type void returns %s but should not return anything.', + 'Anonymous function should return %s but returns %s.', + 'Anonymous function should never return but return statement found.', + $generatorType->isSuperTypeOf($returnType)->yes() + ); + } } diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index c16cdafd39..ad02d8f41f 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -1,4 +1,6 @@ -check = $check; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\FuncCall::class; - } - - public function processNode( - \PhpParser\Node $node, - Scope $scope - ): array - { - if (!$node->name instanceof \PhpParser\Node\Expr) { - return []; - } - - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->name, - 'Invoking callable on an unknown class %s.', - static function (Type $type): bool { - return $type->isCallable()->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - - $isCallable = $type->isCallable(); - if ($isCallable->no()) { - return [ - RuleErrorBuilder::message( - sprintf('Trying to invoke %s but it\'s not a callable.', $type->describe(VerbosityLevel::value())) - )->build(), - ]; - } - if ($this->reportMaybes && $isCallable->maybe()) { - return [ - RuleErrorBuilder::message( - sprintf('Trying to invoke %s but it might not be a callable.', $type->describe(VerbosityLevel::value())) - )->build(), - ]; - } - - $parametersAcceptors = $type->getCallableParametersAcceptors($scope); - $messages = []; - - if ( - count($parametersAcceptors) === 1 - && $parametersAcceptors[0] instanceof InaccessibleMethod - ) { - $method = $parametersAcceptors[0]->getMethod(); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Call to %s method %s() of class %s.', - $method->isPrivate() ? 'private' : 'protected', - $method->getName(), - $method->getDeclaringClass()->getDisplayName() - ))->build(); - } - - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $scope, - $node->args, - $parametersAcceptors - ); - - if ($type instanceof ClosureType) { - $callableDescription = 'closure'; - } else { - $callableDescription = sprintf('callable %s', $type->describe(VerbosityLevel::value())); - } - - return array_merge( - $messages, - $this->check->check( - $parametersAcceptor, - $scope, - false, - $node, - [ - ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', - ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', - ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', - 'Result of ' . $callableDescription . ' (void) is used.', - 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to ' . $callableDescription, - 'Missing parameter $%s in call to ' . $callableDescription . '.', - 'Unknown parameter $%s in call to ' . $callableDescription . '.', - ] - ) - ); - } - + private \PHPStan\Rules\FunctionCallParametersCheck $check; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private bool $reportMaybes; + + public function __construct( + FunctionCallParametersCheck $check, + RuleLevelHelper $ruleLevelHelper, + bool $reportMaybes + ) { + $this->check = $check; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr\FuncCall::class; + } + + public function processNode( + \PhpParser\Node $node, + Scope $scope + ): array { + if (!$node->name instanceof \PhpParser\Node\Expr) { + return []; + } + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->name, + 'Invoking callable on an unknown class %s.', + static function (Type $type): bool { + return $type->isCallable()->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + + $isCallable = $type->isCallable(); + if ($isCallable->no()) { + return [ + RuleErrorBuilder::message( + sprintf('Trying to invoke %s but it\'s not a callable.', $type->describe(VerbosityLevel::value())) + )->build(), + ]; + } + if ($this->reportMaybes && $isCallable->maybe()) { + return [ + RuleErrorBuilder::message( + sprintf('Trying to invoke %s but it might not be a callable.', $type->describe(VerbosityLevel::value())) + )->build(), + ]; + } + + $parametersAcceptors = $type->getCallableParametersAcceptors($scope); + $messages = []; + + if ( + count($parametersAcceptors) === 1 + && $parametersAcceptors[0] instanceof InaccessibleMethod + ) { + $method = $parametersAcceptors[0]->getMethod(); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Call to %s method %s() of class %s.', + $method->isPrivate() ? 'private' : 'protected', + $method->getName(), + $method->getDeclaringClass()->getDisplayName() + ))->build(); + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->args, + $parametersAcceptors + ); + + if ($type instanceof ClosureType) { + $callableDescription = 'closure'; + } else { + $callableDescription = sprintf('callable %s', $type->describe(VerbosityLevel::value())); + } + + return array_merge( + $messages, + $this->check->check( + $parametersAcceptor, + $scope, + false, + $node, + [ + ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', + ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', + ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', + 'Result of ' . $callableDescription . ' (void) is used.', + 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to ' . $callableDescription, + 'Missing parameter $%s in call to ' . $callableDescription . '.', + 'Unknown parameter $%s in call to ' . $callableDescription . '.', + ] + ) + ); + } } diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index e44206ad1e..f5f7a79ab3 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->check = $check; - } + private \PHPStan\Rules\FunctionCallParametersCheck $check; - public function getNodeType(): string - { - return FuncCall::class; - } + public function __construct(ReflectionProvider $reflectionProvider, FunctionCallParametersCheck $check) + { + $this->reflectionProvider = $reflectionProvider; + $this->check = $check; + } - public function processNode(Node $node, Scope $scope): array - { - if (!($node->name instanceof \PhpParser\Node\Name)) { - return []; - } + public function getNodeType(): string + { + return FuncCall::class; + } - if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } - $function = $this->reflectionProvider->getFunction($node->name, $scope); + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } - return $this->check->check( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $node->args, - $function->getVariants() - ), - $scope, - $function->isBuiltin(), - $node, - [ - 'Function ' . $function->getName() . ' invoked with %d parameter, %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameter, at least %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, at least %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameter, %d-%d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of function ' . $function->getName() . ' expects %s, %s given.', - 'Result of function ' . $function->getName() . ' (void) is used.', - 'Parameter %s of function ' . $function->getName() . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to function ' . $function->getName(), - 'Missing parameter $%s in call to function ' . $function->getName() . '.', - 'Unknown parameter $%s in call to function ' . $function->getName() . '.', - ] - ); - } + $function = $this->reflectionProvider->getFunction($node->name, $scope); + return $this->check->check( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->args, + $function->getVariants() + ), + $scope, + $function->isBuiltin(), + $node, + [ + 'Function ' . $function->getName() . ' invoked with %d parameter, %d required.', + 'Function ' . $function->getName() . ' invoked with %d parameters, %d required.', + 'Function ' . $function->getName() . ' invoked with %d parameter, at least %d required.', + 'Function ' . $function->getName() . ' invoked with %d parameters, at least %d required.', + 'Function ' . $function->getName() . ' invoked with %d parameter, %d-%d required.', + 'Function ' . $function->getName() . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of function ' . $function->getName() . ' expects %s, %s given.', + 'Result of function ' . $function->getName() . ' (void) is used.', + 'Parameter %s of function ' . $function->getName() . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to function ' . $function->getName(), + 'Missing parameter $%s in call to function ' . $function->getName() . '.', + 'Unknown parameter $%s in call to function ' . $function->getName() . '.', + ] + ); + } } diff --git a/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php index e4c412676a..b824cdf9a1 100644 --- a/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$node->expr instanceof Node\Expr\FuncCall) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } - $funcCall = $node->expr; - if (!($funcCall->name instanceof \PhpParser\Node\Name)) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node->expr instanceof Node\Expr\FuncCall) { + return []; + } - if (!$this->reflectionProvider->hasFunction($funcCall->name, $scope)) { - return []; - } + $funcCall = $node->expr; + if (!($funcCall->name instanceof \PhpParser\Node\Name)) { + return []; + } - $function = $this->reflectionProvider->getFunction($funcCall->name, $scope); - if ($function->hasSideEffects()->no()) { - $throwsType = $function->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; - } + if (!$this->reflectionProvider->hasFunction($funcCall->name, $scope)) { + return []; + } - $functionResult = $scope->getType($funcCall); - if ($functionResult instanceof NeverType && $functionResult->isExplicit()) { - return []; - } + $function = $this->reflectionProvider->getFunction($funcCall->name, $scope); + if ($function->hasSideEffects()->no()) { + $throwsType = $function->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Call to function %s() on a separate line has no effect.', - $function->getName() - ))->build(), - ]; - } + $functionResult = $scope->getType($funcCall); + if ($functionResult instanceof NeverType && $functionResult->isExplicit()) { + return []; + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() on a separate line has no effect.', + $function->getName() + ))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Functions/CallToNonExistentFunctionRule.php b/src/Rules/Functions/CallToNonExistentFunctionRule.php index b4a10083f2..d8a2897536 100644 --- a/src/Rules/Functions/CallToNonExistentFunctionRule.php +++ b/src/Rules/Functions/CallToNonExistentFunctionRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->checkFunctionNameCase = $checkFunctionNameCase; - } + private bool $checkFunctionNameCase; - public function getNodeType(): string - { - return FuncCall::class; - } + public function __construct( + ReflectionProvider $reflectionProvider, + bool $checkFunctionNameCase + ) { + $this->reflectionProvider = $reflectionProvider; + $this->checkFunctionNameCase = $checkFunctionNameCase; + } - public function processNode(Node $node, Scope $scope): array - { - if (!($node->name instanceof \PhpParser\Node\Name)) { - return []; - } + public function getNodeType(): string + { + return FuncCall::class; + } - if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { - return [ - RuleErrorBuilder::message(sprintf('Function %s not found.', (string) $node->name))->discoveringSymbolsTip()->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } - $function = $this->reflectionProvider->getFunction($node->name, $scope); - $name = (string) $node->name; + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return [ + RuleErrorBuilder::message(sprintf('Function %s not found.', (string) $node->name))->discoveringSymbolsTip()->build(), + ]; + } - if ($this->checkFunctionNameCase) { - /** @var string $calledFunctionName */ - $calledFunctionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); - if ( - strtolower($function->getName()) === strtolower($calledFunctionName) - && $function->getName() !== $calledFunctionName - ) { - return [ - RuleErrorBuilder::message(sprintf( - 'Call to function %s() with incorrect case: %s', - $function->getName(), - $name - ))->build(), - ]; - } - } + $function = $this->reflectionProvider->getFunction($node->name, $scope); + $name = (string) $node->name; - return []; - } + if ($this->checkFunctionNameCase) { + /** @var string $calledFunctionName */ + $calledFunctionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ( + strtolower($function->getName()) === strtolower($calledFunctionName) + && $function->getName() !== $calledFunctionName + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() with incorrect case: %s', + $function->getName(), + $name + ))->build(), + ]; + } + } + return []; + } } diff --git a/src/Rules/Functions/ClosureAttributesRule.php b/src/Rules/Functions/ClosureAttributesRule.php index 2048c09a8b..4835d6e00a 100644 --- a/src/Rules/Functions/ClosureAttributesRule.php +++ b/src/Rules/Functions/ClosureAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Expr\Closure::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_FUNCTION, - 'function' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_FUNCTION, + 'function' + ); + } } diff --git a/src/Rules/Functions/ClosureReturnTypeRule.php b/src/Rules/Functions/ClosureReturnTypeRule.php index d1dac5ad77..e6f67617d9 100644 --- a/src/Rules/Functions/ClosureReturnTypeRule.php +++ b/src/Rules/Functions/ClosureReturnTypeRule.php @@ -1,4 +1,6 @@ -returnTypeCheck = $returnTypeCheck; - } - - public function getNodeType(): string - { - return ClosureReturnStatementsNode::class; - } + public function __construct(FunctionReturnTypeCheck $returnTypeCheck) + { + $this->returnTypeCheck = $returnTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInAnonymousFunction()) { - return []; - } + public function getNodeType(): string + { + return ClosureReturnStatementsNode::class; + } - /** @var \PHPStan\Type\Type $returnType */ - $returnType = $scope->getAnonymousFunctionReturnType(); - $containsNull = TypeCombinator::containsNull($returnType); - $hasNativeTypehint = $node->getClosureExpr()->returnType !== null; + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInAnonymousFunction()) { + return []; + } - $messages = []; - foreach ($node->getReturnStatements() as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - $returnExpr = $returnNode->expr; - if ($returnExpr === null && $containsNull && !$hasNativeTypehint) { - $returnExpr = new Node\Expr\ConstFetch(new Node\Name\FullyQualified('null')); - } - $returnMessages = $this->returnTypeCheck->checkReturnType( - $returnStatement->getScope(), - $returnType, - $returnExpr, - $returnNode, - 'Anonymous function should return %s but empty return statement found.', - 'Anonymous function with return type void returns %s but should not return anything.', - 'Anonymous function should return %s but returns %s.', - 'Anonymous function should never return but return statement found.', - count($node->getYieldStatements()) > 0 - ); + /** @var \PHPStan\Type\Type $returnType */ + $returnType = $scope->getAnonymousFunctionReturnType(); + $containsNull = TypeCombinator::containsNull($returnType); + $hasNativeTypehint = $node->getClosureExpr()->returnType !== null; - foreach ($returnMessages as $returnMessage) { - $messages[] = $returnMessage; - } - } + $messages = []; + foreach ($node->getReturnStatements() as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + $returnExpr = $returnNode->expr; + if ($returnExpr === null && $containsNull && !$hasNativeTypehint) { + $returnExpr = new Node\Expr\ConstFetch(new Node\Name\FullyQualified('null')); + } + $returnMessages = $this->returnTypeCheck->checkReturnType( + $returnStatement->getScope(), + $returnType, + $returnExpr, + $returnNode, + 'Anonymous function should return %s but empty return statement found.', + 'Anonymous function with return type void returns %s but should not return anything.', + 'Anonymous function should return %s but returns %s.', + 'Anonymous function should never return but return statement found.', + count($node->getYieldStatements()) > 0 + ); - return $messages; - } + foreach ($returnMessages as $returnMessage) { + $messages[] = $returnMessage; + } + } + return $messages; + } } diff --git a/src/Rules/Functions/ClosureUsesThisRule.php b/src/Rules/Functions/ClosureUsesThisRule.php index 60018f454b..21d66397ff 100644 --- a/src/Rules/Functions/ClosureUsesThisRule.php +++ b/src/Rules/Functions/ClosureUsesThisRule.php @@ -1,4 +1,6 @@ -static) { - return []; - } - - $messages = []; - foreach ($node->uses as $closureUse) { - $varType = $scope->getType($closureUse->var); - if (!is_string($closureUse->var->name)) { - continue; - } - if (!$varType instanceof ThisType) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) - ->line($closureUse->getLine()) - ->build(); - } - return $messages; - } - + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->static) { + return []; + } + + $messages = []; + foreach ($node->uses as $closureUse) { + $varType = $scope->getType($closureUse->var); + if (!is_string($closureUse->var->name)) { + continue; + } + if (!$varType instanceof ThisType) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) + ->line($closureUse->getLine()) + ->build(); + } + return $messages; + } } diff --git a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php index 8b5e4a304f..66c623f524 100644 --- a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return Node\Expr\ArrowFunction::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->check->checkAnonymousFunction( - $scope, - $node->getParams(), - $node->getReturnType(), - 'Parameter $%s of anonymous function has invalid typehint type %s.', - 'Return typehint of anonymous function has invalid type %s.', - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' - ); - } - + private \PHPStan\Rules\FunctionDefinitionCheck $check; + + public function __construct(FunctionDefinitionCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return Node\Expr\ArrowFunction::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkAnonymousFunction( + $scope, + $node->getParams(), + $node->getReturnType(), + 'Parameter $%s of anonymous function has invalid typehint type %s.', + 'Return typehint of anonymous function has invalid type %s.', + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' + ); + } } diff --git a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php index 3b2d45f799..6cb806299e 100644 --- a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return Closure::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->check->checkAnonymousFunction( - $scope, - $node->getParams(), - $node->getReturnType(), - 'Parameter $%s of anonymous function has invalid typehint type %s.', - 'Return typehint of anonymous function has invalid type %s.', - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' - ); - } - + private \PHPStan\Rules\FunctionDefinitionCheck $check; + + public function __construct(FunctionDefinitionCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkAnonymousFunction( + $scope, + $node->getParams(), + $node->getReturnType(), + 'Parameter $%s of anonymous function has invalid typehint type %s.', + 'Return typehint of anonymous function has invalid type %s.', + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' + ); + } } diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index c03593ec9c..af7c62fe8e 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return InFunctionNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->getFunction() instanceof PhpFunctionFromParserNodeReflection) { - return []; - } - - $functionName = $scope->getFunction()->getName(); - - return $this->check->checkFunction( - $node->getOriginalNode(), - $scope->getFunction(), - sprintf( - 'Parameter $%%s of function %s() has invalid typehint type %%s.', - $functionName - ), - sprintf( - 'Return typehint of function %s() has invalid type %%s.', - $functionName - ), - sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName), - sprintf('Template type %%s of function %s() is not referenced in a parameter.', $functionName) - ); - } - + private \PHPStan\Rules\FunctionDefinitionCheck $check; + + public function __construct(FunctionDefinitionCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return InFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->getFunction() instanceof PhpFunctionFromParserNodeReflection) { + return []; + } + + $functionName = $scope->getFunction()->getName(); + + return $this->check->checkFunction( + $node->getOriginalNode(), + $scope->getFunction(), + sprintf( + 'Parameter $%%s of function %s() has invalid typehint type %%s.', + $functionName + ), + sprintf( + 'Return typehint of function %s() has invalid type %%s.', + $functionName + ), + sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName), + sprintf('Template type %%s of function %s() is not referenced in a parameter.', $functionName) + ); + } } diff --git a/src/Rules/Functions/FunctionAttributesRule.php b/src/Rules/Functions/FunctionAttributesRule.php index 758d779b47..3982dc39bd 100644 --- a/src/Rules/Functions/FunctionAttributesRule.php +++ b/src/Rules/Functions/FunctionAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Stmt\Function_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_FUNCTION, - 'function' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_FUNCTION, + 'function' + ); + } } diff --git a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php index 031a4b9c31..26652607cd 100644 --- a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php @@ -1,4 +1,6 @@ -getFunction(); - if (!$function instanceof PhpFunctionFromParserNodeReflection) { - return []; - } - $parameters = ParametersAcceptorSelector::selectSingle($function->getVariants()); - - $errors = []; - foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { - if ($param->default === null) { - continue; - } - if ( - $param->var instanceof Node\Expr\Error - || !is_string($param->var->name) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $function = $scope->getFunction(); + if (!$function instanceof PhpFunctionFromParserNodeReflection) { + return []; + } + $parameters = ParametersAcceptorSelector::selectSingle($function->getVariants()); - $defaultValueType = $scope->getType($param->default); - $parameterType = $parameters->getParameters()[$paramI]->getType(); - $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); + $errors = []; + foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { + if ($param->default === null) { + continue; + } + if ( + $param->var instanceof Node\Expr\Error + || !is_string($param->var->name) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } - if ($parameterType->accepts($defaultValueType, true)->yes()) { - continue; - } + $defaultValueType = $scope->getType($param->default); + $parameterType = $parameters->getParameters()[$paramI]->getType(); + $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $defaultValueType); + if ($parameterType->accepts($defaultValueType, true)->yes()) { + continue; + } - $errors[] = RuleErrorBuilder::message(sprintf( - 'Default value of the parameter #%d $%s (%s) of function %s() is incompatible with type %s.', - $paramI + 1, - $param->var->name, - $defaultValueType->describe($verbosityLevel), - $function->getName(), - $parameterType->describe($verbosityLevel) - ))->line($param->getLine())->build(); - } + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $defaultValueType); - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Default value of the parameter #%d $%s (%s) of function %s() is incompatible with type %s.', + $paramI + 1, + $param->var->name, + $defaultValueType->describe($verbosityLevel), + $function->getName(), + $parameterType->describe($verbosityLevel) + ))->line($param->getLine())->build(); + } + return $errors; + } } diff --git a/src/Rules/Functions/InnerFunctionRule.php b/src/Rules/Functions/InnerFunctionRule.php index 5d0c0cfe93..17e319013d 100644 --- a/src/Rules/Functions/InnerFunctionRule.php +++ b/src/Rules/Functions/InnerFunctionRule.php @@ -1,4 +1,6 @@ -getFunction() === null) { - return []; - } - - return [ - RuleErrorBuilder::message( - 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.' - )->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + if ($scope->getFunction() === null) { + return []; + } + return [ + RuleErrorBuilder::message( + 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.' + )->build(), + ]; + } } diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php index 63c6a43e79..de9885ee47 100644 --- a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -1,4 +1,6 @@ -missingTypehintCheck = $missingTypehintCheck; - } - - public function getNodeType(): string - { - return InFunctionNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $functionReflection = $scope->getFunction(); - if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) { - return []; - } - - $messages = []; - - foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameterReflection) { - foreach ($this->checkFunctionParameter($functionReflection, $parameterReflection) as $parameterMessage) { - $messages[] = $parameterMessage; - } - } - - return $messages; - } - - /** - * @param \PHPStan\Reflection\FunctionReflection $functionReflection - * @param \PHPStan\Reflection\ParameterReflection $parameterReflection - * @return \PHPStan\Rules\RuleError[] - */ - private function checkFunctionParameter(FunctionReflection $functionReflection, ParameterReflection $parameterReflection): array - { - $parameterType = $parameterReflection->getType(); - - if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Function %s() has parameter $%s with no typehint specified.', - $functionReflection->getName(), - $parameterReflection->getName() - ))->build(), - ]; - } - - $messages = []; - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() has parameter $%s with no value type specified in iterable type %s.', - $functionReflection->getName(), - $parameterReflection->getName(), - $iterableTypeDescription - ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() has parameter $%s with generic %s but does not specify its types: %s', - $functionReflection->getName(), - $parameterReflection->getName(), - $name, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } - - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() has parameter $%s with no signature specified for %s.', - $functionReflection->getName(), - $parameterReflection->getName(), - $callableType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - return $messages; - } - + private \PHPStan\Rules\MissingTypehintCheck $missingTypehintCheck; + + public function __construct( + MissingTypehintCheck $missingTypehintCheck + ) { + $this->missingTypehintCheck = $missingTypehintCheck; + } + + public function getNodeType(): string + { + return InFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $functionReflection = $scope->getFunction(); + if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) { + return []; + } + + $messages = []; + + foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameterReflection) { + foreach ($this->checkFunctionParameter($functionReflection, $parameterReflection) as $parameterMessage) { + $messages[] = $parameterMessage; + } + } + + return $messages; + } + + /** + * @param \PHPStan\Reflection\FunctionReflection $functionReflection + * @param \PHPStan\Reflection\ParameterReflection $parameterReflection + * @return \PHPStan\Rules\RuleError[] + */ + private function checkFunctionParameter(FunctionReflection $functionReflection, ParameterReflection $parameterReflection): array + { + $parameterType = $parameterReflection->getType(); + + if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Function %s() has parameter $%s with no typehint specified.', + $functionReflection->getName(), + $parameterReflection->getName() + ))->build(), + ]; + } + + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() has parameter $%s with no value type specified in iterable type %s.', + $functionReflection->getName(), + $parameterReflection->getName(), + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() has parameter $%s with generic %s but does not specify its types: %s', + $functionReflection->getName(), + $parameterReflection->getName(), + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() has parameter $%s with no signature specified for %s.', + $functionReflection->getName(), + $parameterReflection->getName(), + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $messages; + } } diff --git a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php index 8d75bf1578..8758bd504d 100644 --- a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php @@ -1,4 +1,6 @@ -missingTypehintCheck = $missingTypehintCheck; - } - - public function getNodeType(): string - { - return InFunctionNode::class; - } + public function __construct( + MissingTypehintCheck $missingTypehintCheck + ) { + $this->missingTypehintCheck = $missingTypehintCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $functionReflection = $scope->getFunction(); - if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) { - return []; - } + public function getNodeType(): string + { + return InFunctionNode::class; + } - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + public function processNode(Node $node, Scope $scope): array + { + $functionReflection = $scope->getFunction(); + if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) { + return []; + } - if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Function %s() has no return typehint specified.', - $functionReflection->getName() - ))->build(), - ]; - } + $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - $messages = []; - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($returnType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $messages[] = RuleErrorBuilder::message(sprintf('Function %s() return type has no value type specified in iterable type %s.', $functionReflection->getName(), $iterableTypeDescription))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } + if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Function %s() has no return typehint specified.', + $functionReflection->getName() + ))->build(), + ]; + } - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($returnType) as [$name, $genericTypeNames]) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() return type with generic %s does not specify its types: %s', - $functionReflection->getName(), - $name, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($returnType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf('Function %s() return type has no value type specified in iterable type %s.', $functionReflection->getName(), $iterableTypeDescription))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($returnType) as $callableType) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() return type has no signature specified for %s.', - $functionReflection->getName(), - $callableType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($returnType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() return type with generic %s does not specify its types: %s', + $functionReflection->getName(), + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } - return $messages; - } + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($returnType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() return type has no signature specified for %s.', + $functionReflection->getName(), + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + return $messages; + } } diff --git a/src/Rules/Functions/ParamAttributesRule.php b/src/Rules/Functions/ParamAttributesRule.php index 707b1491f2..3b5e29f09c 100644 --- a/src/Rules/Functions/ParamAttributesRule.php +++ b/src/Rules/Functions/ParamAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Param::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_PARAMETER, - 'parameter' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Param::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_PARAMETER, + 'parameter' + ); + } } diff --git a/src/Rules/Functions/PrintfParametersRule.php b/src/Rules/Functions/PrintfParametersRule.php index 83f3878b7a..0d0bf0e689 100644 --- a/src/Rules/Functions/PrintfParametersRule.php +++ b/src/Rules/Functions/PrintfParametersRule.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function getNodeType(): string - { - return FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!($node->name instanceof \PhpParser\Node\Name)) { - return []; - } - - $functionsArgumentPositions = [ - 'printf' => 0, - 'sprintf' => 0, - 'sscanf' => 1, - 'fscanf' => 1, - ]; - $minimumNumberOfArguments = [ - 'printf' => 1, - 'sprintf' => 1, - 'sscanf' => 3, - 'fscanf' => 3, - ]; - - $name = strtolower((string) $node->name); - if (!isset($functionsArgumentPositions[$name])) { - return []; - } - - $formatArgumentPosition = $functionsArgumentPositions[$name]; - - $args = $node->args; - foreach ($args as $arg) { - if ($arg->unpack) { - return []; - } - } - $argsCount = count($args); - if ($argsCount < $minimumNumberOfArguments[$name]) { - return []; // caught by CallToFunctionParametersRule - } - - $formatArgType = $scope->getType($args[$formatArgumentPosition]->value); - $placeHoldersCount = null; - foreach (TypeUtils::getConstantStrings($formatArgType) as $formatString) { - $format = $formatString->getValue(); - $tempPlaceHoldersCount = $this->getPlaceholdersCount($name, $format); - if ($placeHoldersCount === null) { - $placeHoldersCount = $tempPlaceHoldersCount; - } elseif ($tempPlaceHoldersCount > $placeHoldersCount) { - $placeHoldersCount = $tempPlaceHoldersCount; - } - } - - if ($placeHoldersCount === null) { - return []; - } - - $argsCount -= $formatArgumentPosition; - - if ($argsCount !== $placeHoldersCount + 1) { - return [ - RuleErrorBuilder::message(sprintf( - sprintf( - '%s, %s.', - $placeHoldersCount === 1 ? 'Call to %s contains %d placeholder' : 'Call to %s contains %d placeholders', - $argsCount - 1 === 1 ? '%d value given' : '%d values given' - ), - $name, - $placeHoldersCount, - $argsCount - 1 - ))->build(), - ]; - } - - return []; - } - - private function getPlaceholdersCount(string $functionName, string $format): int - { - $specifiers = in_array($functionName, ['sprintf', 'printf'], true) ? '[bcdeEfFgGosuxX%s]' : '(?:[cdDeEfinosuxX%s]|\[[^\]]+\])'; - $addSpecifier = ''; - if ($this->phpVersion->supportsHhPrintfSpecifier()) { - $addSpecifier .= 'hH'; - } - - $specifiers = sprintf($specifiers, $addSpecifier); - - $pattern = '~(?%*)%(?:(?\d+)\$)?[-+]?(?:[ 0]|(?:\'[^%]))?-?\d*(?:\.\d*)?' . $specifiers . '~'; - - $matches = \Nette\Utils\Strings::matchAll($format, $pattern, PREG_SET_ORDER); - - if (count($matches) === 0) { - return 0; - } - - $placeholders = array_filter($matches, static function (array $match): bool { - return strlen($match['before']) % 2 === 0; - }); - - if (count($placeholders) === 0) { - return 0; - } - - $maxPositionedNumber = 0; - $maxOrdinaryNumber = 0; - foreach ($placeholders as $placeholder) { - if (isset($placeholder['position']) && $placeholder['position'] !== '') { - $maxPositionedNumber = max((int) $placeholder['position'], $maxPositionedNumber); - } else { - $maxOrdinaryNumber++; - } - } - - return max($maxPositionedNumber, $maxOrdinaryNumber); - } - + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } + + $functionsArgumentPositions = [ + 'printf' => 0, + 'sprintf' => 0, + 'sscanf' => 1, + 'fscanf' => 1, + ]; + $minimumNumberOfArguments = [ + 'printf' => 1, + 'sprintf' => 1, + 'sscanf' => 3, + 'fscanf' => 3, + ]; + + $name = strtolower((string) $node->name); + if (!isset($functionsArgumentPositions[$name])) { + return []; + } + + $formatArgumentPosition = $functionsArgumentPositions[$name]; + + $args = $node->args; + foreach ($args as $arg) { + if ($arg->unpack) { + return []; + } + } + $argsCount = count($args); + if ($argsCount < $minimumNumberOfArguments[$name]) { + return []; // caught by CallToFunctionParametersRule + } + + $formatArgType = $scope->getType($args[$formatArgumentPosition]->value); + $placeHoldersCount = null; + foreach (TypeUtils::getConstantStrings($formatArgType) as $formatString) { + $format = $formatString->getValue(); + $tempPlaceHoldersCount = $this->getPlaceholdersCount($name, $format); + if ($placeHoldersCount === null) { + $placeHoldersCount = $tempPlaceHoldersCount; + } elseif ($tempPlaceHoldersCount > $placeHoldersCount) { + $placeHoldersCount = $tempPlaceHoldersCount; + } + } + + if ($placeHoldersCount === null) { + return []; + } + + $argsCount -= $formatArgumentPosition; + + if ($argsCount !== $placeHoldersCount + 1) { + return [ + RuleErrorBuilder::message(sprintf( + sprintf( + '%s, %s.', + $placeHoldersCount === 1 ? 'Call to %s contains %d placeholder' : 'Call to %s contains %d placeholders', + $argsCount - 1 === 1 ? '%d value given' : '%d values given' + ), + $name, + $placeHoldersCount, + $argsCount - 1 + ))->build(), + ]; + } + + return []; + } + + private function getPlaceholdersCount(string $functionName, string $format): int + { + $specifiers = in_array($functionName, ['sprintf', 'printf'], true) ? '[bcdeEfFgGosuxX%s]' : '(?:[cdDeEfinosuxX%s]|\[[^\]]+\])'; + $addSpecifier = ''; + if ($this->phpVersion->supportsHhPrintfSpecifier()) { + $addSpecifier .= 'hH'; + } + + $specifiers = sprintf($specifiers, $addSpecifier); + + $pattern = '~(?%*)%(?:(?\d+)\$)?[-+]?(?:[ 0]|(?:\'[^%]))?-?\d*(?:\.\d*)?' . $specifiers . '~'; + + $matches = \Nette\Utils\Strings::matchAll($format, $pattern, PREG_SET_ORDER); + + if (count($matches) === 0) { + return 0; + } + + $placeholders = array_filter($matches, static function (array $match): bool { + return strlen($match['before']) % 2 === 0; + }); + + if (count($placeholders) === 0) { + return 0; + } + + $maxPositionedNumber = 0; + $maxOrdinaryNumber = 0; + foreach ($placeholders as $placeholder) { + if (isset($placeholder['position']) && $placeholder['position'] !== '') { + $maxPositionedNumber = max((int) $placeholder['position'], $maxPositionedNumber); + } else { + $maxOrdinaryNumber++; + } + } + + return max($maxPositionedNumber, $maxOrdinaryNumber); + } } diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php index 9d7a09576c..f837c2f112 100644 --- a/src/Rules/Functions/RandomIntParametersRule.php +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->reportMaybes = $reportMaybes; - } + private bool $reportMaybes; - public function getNodeType(): string - { - return FuncCall::class; - } + public function __construct(ReflectionProvider $reflectionProvider, bool $reportMaybes) + { + $this->reflectionProvider = $reflectionProvider; + $this->reportMaybes = $reportMaybes; + } - public function processNode(Node $node, Scope $scope): array - { - if (!($node->name instanceof \PhpParser\Node\Name)) { - return []; - } + public function getNodeType(): string + { + return FuncCall::class; + } - if ($this->reflectionProvider->resolveFunctionName($node->name, $scope) !== 'random_int') { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } - $minType = $scope->getType($node->args[0]->value)->toInteger(); - $maxType = $scope->getType($node->args[1]->value)->toInteger(); + if ($this->reflectionProvider->resolveFunctionName($node->name, $scope) !== 'random_int') { + return []; + } - if ( - !$minType instanceof ConstantIntegerType && !$minType instanceof IntegerRangeType - || !$maxType instanceof ConstantIntegerType && !$maxType instanceof IntegerRangeType - ) { - return []; - } + $minType = $scope->getType($node->args[0]->value)->toInteger(); + $maxType = $scope->getType($node->args[1]->value)->toInteger(); - $isSmaller = $maxType->isSmallerThan($minType); + if ( + !$minType instanceof ConstantIntegerType && !$minType instanceof IntegerRangeType + || !$maxType instanceof ConstantIntegerType && !$maxType instanceof IntegerRangeType + ) { + return []; + } - if ($isSmaller->yes() || $isSmaller->maybe() && $this->reportMaybes) { - $message = 'Parameter #1 $min (%s) of function random_int expects lower number than parameter #2 $max (%s).'; - return [ - RuleErrorBuilder::message(sprintf( - $message, - $minType->describe(VerbosityLevel::value()), - $maxType->describe(VerbosityLevel::value()) - ))->build(), - ]; - } + $isSmaller = $maxType->isSmallerThan($minType); - return []; - } + if ($isSmaller->yes() || $isSmaller->maybe() && $this->reportMaybes) { + $message = 'Parameter #1 $min (%s) of function random_int expects lower number than parameter #2 $max (%s).'; + return [ + RuleErrorBuilder::message(sprintf( + $message, + $minType->describe(VerbosityLevel::value()), + $maxType->describe(VerbosityLevel::value()) + ))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Functions/ReturnNullsafeByRefRule.php b/src/Rules/Functions/ReturnNullsafeByRefRule.php index a232103ca1..5ce5f9228e 100644 --- a/src/Rules/Functions/ReturnNullsafeByRefRule.php +++ b/src/Rules/Functions/ReturnNullsafeByRefRule.php @@ -1,4 +1,6 @@ -nullsafeCheck = $nullsafeCheck; - } - - public function getNodeType(): string - { - return ReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->returnsByRef()) { - return []; - } - - $errors = []; - foreach ($node->getReturnStatements() as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - continue; - } - - if (!$this->nullsafeCheck->containsNullSafe($returnNode->expr)) { - continue; - } - - $errors[] = RuleErrorBuilder::message('Nullsafe cannot be returned by reference.')->line($returnNode->getLine())->nonIgnorable()->build(); - } - - return $errors; - } - + private NullsafeCheck $nullsafeCheck; + + public function __construct(NullsafeCheck $nullsafeCheck) + { + $this->nullsafeCheck = $nullsafeCheck; + } + + public function getNodeType(): string + { + return ReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->returnsByRef()) { + return []; + } + + $errors = []; + foreach ($node->getReturnStatements() as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + if ($returnNode->expr === null) { + continue; + } + + if (!$this->nullsafeCheck->containsNullSafe($returnNode->expr)) { + continue; + } + + $errors[] = RuleErrorBuilder::message('Nullsafe cannot be returned by reference.')->line($returnNode->getLine())->nonIgnorable()->build(); + } + + return $errors; + } } diff --git a/src/Rules/Functions/ReturnTypeRule.php b/src/Rules/Functions/ReturnTypeRule.php index a554fcba21..a9a4e37de8 100644 --- a/src/Rules/Functions/ReturnTypeRule.php +++ b/src/Rules/Functions/ReturnTypeRule.php @@ -1,4 +1,6 @@ -returnTypeCheck = $returnTypeCheck; - $this->functionReflector = $functionReflector; - } + private FunctionReflector $functionReflector; - public function getNodeType(): string - { - return Return_::class; - } + public function __construct( + FunctionReturnTypeCheck $returnTypeCheck, + FunctionReflector $functionReflector + ) { + $this->returnTypeCheck = $returnTypeCheck; + $this->functionReflector = $functionReflector; + } - public function processNode(Node $node, Scope $scope): array - { - if ($scope->getFunction() === null) { - return []; - } + public function getNodeType(): string + { + return Return_::class; + } - if ($scope->isInAnonymousFunction()) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if ($scope->getFunction() === null) { + return []; + } - $function = $scope->getFunction(); - if ( - !($function instanceof PhpFunctionFromParserNodeReflection) - || $function instanceof PhpMethodFromParserNodeReflection - ) { - return []; - } + if ($scope->isInAnonymousFunction()) { + return []; + } - $reflection = null; - if (function_exists($function->getName())) { - $reflection = new \ReflectionFunction($function->getName()); - } else { - try { - $reflection = $this->functionReflector->reflect($function->getName()); - } catch (IdentifierNotFound $e) { - // pass - } - } + $function = $scope->getFunction(); + if ( + !($function instanceof PhpFunctionFromParserNodeReflection) + || $function instanceof PhpMethodFromParserNodeReflection + ) { + return []; + } - return $this->returnTypeCheck->checkReturnType( - $scope, - ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(), - $node->expr, - $node, - sprintf( - 'Function %s() should return %%s but empty return statement found.', - $function->getName() - ), - sprintf( - 'Function %s() with return type void returns %%s but should not return anything.', - $function->getName() - ), - sprintf( - 'Function %s() should return %%s but returns %%s.', - $function->getName() - ), - sprintf( - 'Function %s() should never return but return statement found.', - $function->getName() - ), - $reflection !== null && $reflection->isGenerator() - ); - } + $reflection = null; + if (function_exists($function->getName())) { + $reflection = new \ReflectionFunction($function->getName()); + } else { + try { + $reflection = $this->functionReflector->reflect($function->getName()); + } catch (IdentifierNotFound $e) { + // pass + } + } + return $this->returnTypeCheck->checkReturnType( + $scope, + ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(), + $node->expr, + $node, + sprintf( + 'Function %s() should return %%s but empty return statement found.', + $function->getName() + ), + sprintf( + 'Function %s() with return type void returns %%s but should not return anything.', + $function->getName() + ), + sprintf( + 'Function %s() should return %%s but returns %%s.', + $function->getName() + ), + sprintf( + 'Function %s() should never return but return statement found.', + $function->getName() + ), + $reflection !== null && $reflection->isGenerator() + ); + } } diff --git a/src/Rules/Functions/UnusedClosureUsesRule.php b/src/Rules/Functions/UnusedClosureUsesRule.php index d26dac08a1..9656d603f5 100644 --- a/src/Rules/Functions/UnusedClosureUsesRule.php +++ b/src/Rules/Functions/UnusedClosureUsesRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return Node\Expr\Closure::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (count($node->uses) === 0) { - return []; - } - - return $this->check->getUnusedParameters( - $scope, - array_map(static function (Node\Expr\ClosureUse $use): string { - if (!is_string($use->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - return $use->var->name; - }, $node->uses), - $node->stmts, - 'Anonymous function has an unused use $%s.', - 'anonymousFunction.unusedUse', - [ - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - 'depth' => $node->getAttribute('expressionDepth'), - 'order' => $node->getAttribute('expressionOrder'), - ] - ); - } - + private \PHPStan\Rules\UnusedFunctionParametersCheck $check; + + public function __construct(UnusedFunctionParametersCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (count($node->uses) === 0) { + return []; + } + + return $this->check->getUnusedParameters( + $scope, + array_map(static function (Node\Expr\ClosureUse $use): string { + if (!is_string($use->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + return $use->var->name; + }, $node->uses), + $node->stmts, + 'Anonymous function has an unused use $%s.', + 'anonymousFunction.unusedUse', + [ + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + 'depth' => $node->getAttribute('expressionDepth'), + 'order' => $node->getAttribute('expressionOrder'), + ] + ); + } } diff --git a/src/Rules/Generators/YieldFromTypeRule.php b/src/Rules/Generators/YieldFromTypeRule.php index 8e6980eae2..e6cdf010ea 100644 --- a/src/Rules/Generators/YieldFromTypeRule.php +++ b/src/Rules/Generators/YieldFromTypeRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return YieldFrom::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $exprType = $scope->getType($node->expr); - $isIterable = $exprType->isIterable(); - $messagePattern = 'Argument of an invalid type %s passed to yield from, only iterables are supported.'; - if ($isIterable->no()) { - return [ - RuleErrorBuilder::message(sprintf( - $messagePattern, - $exprType->describe(VerbosityLevel::typeOnly()) - ))->line($node->expr->getLine())->build(), - ]; - } elseif ( - !$exprType instanceof MixedType - && $this->reportMaybes - && $isIterable->maybe() - ) { - return [ - RuleErrorBuilder::message(sprintf( - $messagePattern, - $exprType->describe(VerbosityLevel::typeOnly()) - ))->line($node->expr->getLine())->build(), - ]; - } - - $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); - $scopeFunction = $scope->getFunction(); - if ($anonymousFunctionReturnType !== null) { - $returnType = $anonymousFunctionReturnType; - } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); - } else { - return []; // already reported by YieldInGeneratorRule - } - - if ($returnType instanceof MixedType) { - return []; - } - - $messages = []; - if (!$this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $exprType->getIterableKeyType(), $scope->isDeclareStrictTypes())) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $exprType->getIterableKeyType()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects key type %s, %s given.', - $returnType->getIterableKeyType()->describe($verbosityLevel), - $exprType->getIterableKeyType()->describe($verbosityLevel) - ))->line($node->expr->getLine())->build(); - } - if (!$this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $exprType->getIterableValueType(), $scope->isDeclareStrictTypes())) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $exprType->getIterableValueType()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects value type %s, %s given.', - $returnType->getIterableValueType()->describe($verbosityLevel), - $exprType->getIterableValueType()->describe($verbosityLevel) - ))->line($node->expr->getLine())->build(); - } - - $scopeFunction = $scope->getFunction(); - if ($scopeFunction === null) { - return $messages; - } - - if (!$exprType instanceof TypeWithClassName) { - return $messages; - } - - $currentReturnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); - if (!$currentReturnType instanceof TypeWithClassName) { - return $messages; - } - - $exprSendType = GenericTypeVariableResolver::getType($exprType, \Generator::class, 'TSend'); - $thisSendType = GenericTypeVariableResolver::getType($currentReturnType, \Generator::class, 'TSend'); - if ($exprSendType === null || $thisSendType === null) { - return $messages; - } - - $isSuperType = $exprSendType->isSuperTypeOf($thisSendType); - if ($isSuperType->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects delegated TSend type %s, %s given.', - $exprSendType->describe(VerbosityLevel::typeOnly()), - $thisSendType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } elseif ($this->reportMaybes && !$isSuperType->yes()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects delegated TSend type %s, %s given.', - $exprSendType->describe(VerbosityLevel::typeOnly()), - $thisSendType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - if ($scope->getType($node) instanceof VoidType && !$scope->isInFirstLevelStatement()) { - $messages[] = RuleErrorBuilder::message('Result of yield from (void) is used.')->build(); - } - - return $messages; - } - + private RuleLevelHelper $ruleLevelHelper; + + private bool $reportMaybes; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + bool $reportMaybes + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return YieldFrom::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $exprType = $scope->getType($node->expr); + $isIterable = $exprType->isIterable(); + $messagePattern = 'Argument of an invalid type %s passed to yield from, only iterables are supported.'; + if ($isIterable->no()) { + return [ + RuleErrorBuilder::message(sprintf( + $messagePattern, + $exprType->describe(VerbosityLevel::typeOnly()) + ))->line($node->expr->getLine())->build(), + ]; + } elseif ( + !$exprType instanceof MixedType + && $this->reportMaybes + && $isIterable->maybe() + ) { + return [ + RuleErrorBuilder::message(sprintf( + $messagePattern, + $exprType->describe(VerbosityLevel::typeOnly()) + ))->line($node->expr->getLine())->build(), + ]; + } + + $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); + $scopeFunction = $scope->getFunction(); + if ($anonymousFunctionReturnType !== null) { + $returnType = $anonymousFunctionReturnType; + } elseif ($scopeFunction !== null) { + $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + } else { + return []; // already reported by YieldInGeneratorRule + } + + if ($returnType instanceof MixedType) { + return []; + } + + $messages = []; + if (!$this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $exprType->getIterableKeyType(), $scope->isDeclareStrictTypes())) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $exprType->getIterableKeyType()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects key type %s, %s given.', + $returnType->getIterableKeyType()->describe($verbosityLevel), + $exprType->getIterableKeyType()->describe($verbosityLevel) + ))->line($node->expr->getLine())->build(); + } + if (!$this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $exprType->getIterableValueType(), $scope->isDeclareStrictTypes())) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $exprType->getIterableValueType()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects value type %s, %s given.', + $returnType->getIterableValueType()->describe($verbosityLevel), + $exprType->getIterableValueType()->describe($verbosityLevel) + ))->line($node->expr->getLine())->build(); + } + + $scopeFunction = $scope->getFunction(); + if ($scopeFunction === null) { + return $messages; + } + + if (!$exprType instanceof TypeWithClassName) { + return $messages; + } + + $currentReturnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + if (!$currentReturnType instanceof TypeWithClassName) { + return $messages; + } + + $exprSendType = GenericTypeVariableResolver::getType($exprType, \Generator::class, 'TSend'); + $thisSendType = GenericTypeVariableResolver::getType($currentReturnType, \Generator::class, 'TSend'); + if ($exprSendType === null || $thisSendType === null) { + return $messages; + } + + $isSuperType = $exprSendType->isSuperTypeOf($thisSendType); + if ($isSuperType->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects delegated TSend type %s, %s given.', + $exprSendType->describe(VerbosityLevel::typeOnly()), + $thisSendType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } elseif ($this->reportMaybes && !$isSuperType->yes()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects delegated TSend type %s, %s given.', + $exprSendType->describe(VerbosityLevel::typeOnly()), + $thisSendType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + if ($scope->getType($node) instanceof VoidType && !$scope->isInFirstLevelStatement()) { + $messages[] = RuleErrorBuilder::message('Result of yield from (void) is used.')->build(); + } + + return $messages; + } } diff --git a/src/Rules/Generators/YieldInGeneratorRule.php b/src/Rules/Generators/YieldInGeneratorRule.php index 559dc7f090..e9eb48541c 100644 --- a/src/Rules/Generators/YieldInGeneratorRule.php +++ b/src/Rules/Generators/YieldInGeneratorRule.php @@ -1,4 +1,6 @@ -reportMaybes = $reportMaybes; - } - - public function getNodeType(): string - { - return Node\Expr::class; - } + public function __construct(bool $reportMaybes) + { + $this->reportMaybes = $reportMaybes; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$node instanceof Node\Expr\Yield_ && !$node instanceof Node\Expr\YieldFrom) { - return []; - } + public function getNodeType(): string + { + return Node\Expr::class; + } - $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); - $scopeFunction = $scope->getFunction(); - if ($anonymousFunctionReturnType !== null) { - $returnType = $anonymousFunctionReturnType; - } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); - } else { - return [RuleErrorBuilder::message('Yield can be used only inside a function.')->build()]; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof Node\Expr\Yield_ && !$node instanceof Node\Expr\YieldFrom) { + return []; + } - if ($returnType instanceof MixedType) { - return []; - } + $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); + $scopeFunction = $scope->getFunction(); + if ($anonymousFunctionReturnType !== null) { + $returnType = $anonymousFunctionReturnType; + } elseif ($scopeFunction !== null) { + $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + } else { + return [RuleErrorBuilder::message('Yield can be used only inside a function.')->build()]; + } - if ($returnType instanceof NeverType && $returnType->isExplicit()) { - $isSuperType = TrinaryLogic::createNo(); - } else { - $isSuperType = $returnType->isIterable()->and(TrinaryLogic::createFromBoolean( - !$returnType->isArray()->yes() - )); - } - if ($isSuperType->yes()) { - return []; - } + if ($returnType instanceof MixedType) { + return []; + } - if ($isSuperType->maybe() && !$this->reportMaybes) { - return []; - } + if ($returnType instanceof NeverType && $returnType->isExplicit()) { + $isSuperType = TrinaryLogic::createNo(); + } else { + $isSuperType = $returnType->isIterable()->and(TrinaryLogic::createFromBoolean( + !$returnType->isArray()->yes() + )); + } + if ($isSuperType->yes()) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Yield can be used only with these return types: %s.', - 'Generator, Iterator, Traversable, iterable' - ))->build(), - ]; - } + if ($isSuperType->maybe() && !$this->reportMaybes) { + return []; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Yield can be used only with these return types: %s.', + 'Generator, Iterator, Traversable, iterable' + ))->build(), + ]; + } } diff --git a/src/Rules/Generators/YieldTypeRule.php b/src/Rules/Generators/YieldTypeRule.php index f0b04ba578..7b1942d982 100644 --- a/src/Rules/Generators/YieldTypeRule.php +++ b/src/Rules/Generators/YieldTypeRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Expr\Yield_::class; - } + public function __construct( + RuleLevelHelper $ruleLevelHelper + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); - $scopeFunction = $scope->getFunction(); - if ($anonymousFunctionReturnType !== null) { - $returnType = $anonymousFunctionReturnType; - } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); - } else { - return []; // already reported by YieldInGeneratorRule - } + public function getNodeType(): string + { + return Node\Expr\Yield_::class; + } - if ($returnType instanceof MixedType) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); + $scopeFunction = $scope->getFunction(); + if ($anonymousFunctionReturnType !== null) { + $returnType = $anonymousFunctionReturnType; + } elseif ($scopeFunction !== null) { + $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + } else { + return []; // already reported by YieldInGeneratorRule + } - if ($node->key === null) { - $keyType = new IntegerType(); - } else { - $keyType = $scope->getType($node->key); - } + if ($returnType instanceof MixedType) { + return []; + } - if ($node->value === null) { - $valueType = new NullType(); - } else { - $valueType = $scope->getType($node->value); - } + if ($node->key === null) { + $keyType = new IntegerType(); + } else { + $keyType = $scope->getType($node->key); + } - $messages = []; - if (!$this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $keyType, $scope->isDeclareStrictTypes())) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $keyType); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects key type %s, %s given.', - $returnType->getIterableKeyType()->describe($verbosityLevel), - $keyType->describe($verbosityLevel) - ))->build(); - } - if (!$this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $valueType, $scope->isDeclareStrictTypes())) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $valueType); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Generator expects value type %s, %s given.', - $returnType->getIterableValueType()->describe($verbosityLevel), - $valueType->describe($verbosityLevel) - ))->build(); - } - if ($scope->getType($node) instanceof VoidType && !$scope->isInFirstLevelStatement()) { - $messages[] = RuleErrorBuilder::message('Result of yield (void) is used.')->build(); - } + if ($node->value === null) { + $valueType = new NullType(); + } else { + $valueType = $scope->getType($node->value); + } - return $messages; - } + $messages = []; + if (!$this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $keyType, $scope->isDeclareStrictTypes())) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $keyType); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects key type %s, %s given.', + $returnType->getIterableKeyType()->describe($verbosityLevel), + $keyType->describe($verbosityLevel) + ))->build(); + } + if (!$this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $valueType, $scope->isDeclareStrictTypes())) { + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $valueType); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Generator expects value type %s, %s given.', + $returnType->getIterableValueType()->describe($verbosityLevel), + $valueType->describe($verbosityLevel) + ))->build(); + } + if ($scope->getType($node) instanceof VoidType && !$scope->isInFirstLevelStatement()) { + $messages[] = RuleErrorBuilder::message('Result of yield (void) is used.')->build(); + } + return $messages; + } } diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index d6087dbfe7..28b08141c0 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->genericAncestorsCheck = $genericAncestorsCheck; - } + private \PHPStan\Rules\Generics\GenericAncestorsCheck $genericAncestorsCheck; - public function getNodeType(): string - { - return Node\Stmt\Class_::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + GenericAncestorsCheck $genericAncestorsCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->genericAncestorsCheck = $genericAncestorsCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if (!isset($node->namespacedName)) { - // anonymous class - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } - $className = (string) $node->namespacedName; + public function processNode(Node $node, Scope $scope): array + { + if (!isset($node->namespacedName)) { + // anonymous class + return []; + } - $extendsTags = []; - $implementsTags = []; - $docComment = $node->getDocComment(); - if ($docComment !== null) { - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $className, - null, - null, - $docComment->getText() - ); - $extendsTags = $resolvedPhpDoc->getExtendsTags(); - $implementsTags = $resolvedPhpDoc->getImplementsTags(); - } + $className = (string) $node->namespacedName; - $extendsErrors = $this->genericAncestorsCheck->check( - $node->extends !== null ? [$node->extends] : [], - array_map(static function (ExtendsTag $tag): Type { - return $tag->getType(); - }, $extendsTags), - sprintf('Class %s @extends tag contains incompatible type %%s.', $className), - sprintf('Class %s has @extends tag, but does not extend any class.', $className), - sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $className), - 'PHPDoc tag @extends contains generic type %s but class %s is not generic.', - 'Generic type %s in PHPDoc tag @extends does not specify all template types of class %s: %s', - 'Generic type %s in PHPDoc tag @extends specifies %d template types, but class %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of class %s.', - 'PHPDoc tag @extends has invalid type %s.', - sprintf('Class %s extends generic class %%s but does not specify its types: %%s', $className), - sprintf('in extended type %%s of class %s', $className) - ); + $extendsTags = []; + $implementsTags = []; + $docComment = $node->getDocComment(); + if ($docComment !== null) { + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $className, + null, + null, + $docComment->getText() + ); + $extendsTags = $resolvedPhpDoc->getExtendsTags(); + $implementsTags = $resolvedPhpDoc->getImplementsTags(); + } - $implementsErrors = $this->genericAncestorsCheck->check( - $node->implements, - array_map(static function (ImplementsTag $tag): Type { - return $tag->getType(); - }, $implementsTags), - sprintf('Class %s @implements tag contains incompatible type %%s.', $className), - sprintf('Class %s has @implements tag, but does not implement any interface.', $className), - sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $className), - 'PHPDoc tag @implements contains generic type %s but interface %s is not generic.', - 'Generic type %s in PHPDoc tag @implements does not specify all template types of interface %s: %s', - 'Generic type %s in PHPDoc tag @implements specifies %d template types, but interface %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @implements is not subtype of template type %s of interface %s.', - 'PHPDoc tag @implements has invalid type %s.', - sprintf('Class %s implements generic interface %%s but does not specify its types: %%s', $className), - sprintf('in implemented type %%s of class %s', $className) - ); + $extendsErrors = $this->genericAncestorsCheck->check( + $node->extends !== null ? [$node->extends] : [], + array_map(static function (ExtendsTag $tag): Type { + return $tag->getType(); + }, $extendsTags), + sprintf('Class %s @extends tag contains incompatible type %%s.', $className), + sprintf('Class %s has @extends tag, but does not extend any class.', $className), + sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $className), + 'PHPDoc tag @extends contains generic type %s but class %s is not generic.', + 'Generic type %s in PHPDoc tag @extends does not specify all template types of class %s: %s', + 'Generic type %s in PHPDoc tag @extends specifies %d template types, but class %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of class %s.', + 'PHPDoc tag @extends has invalid type %s.', + sprintf('Class %s extends generic class %%s but does not specify its types: %%s', $className), + sprintf('in extended type %%s of class %s', $className) + ); - return array_merge($extendsErrors, $implementsErrors); - } + $implementsErrors = $this->genericAncestorsCheck->check( + $node->implements, + array_map(static function (ImplementsTag $tag): Type { + return $tag->getType(); + }, $implementsTags), + sprintf('Class %s @implements tag contains incompatible type %%s.', $className), + sprintf('Class %s has @implements tag, but does not implement any interface.', $className), + sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $className), + 'PHPDoc tag @implements contains generic type %s but interface %s is not generic.', + 'Generic type %s in PHPDoc tag @implements does not specify all template types of interface %s: %s', + 'Generic type %s in PHPDoc tag @implements specifies %d template types, but interface %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @implements is not subtype of template type %s of interface %s.', + 'PHPDoc tag @implements has invalid type %s.', + sprintf('Class %s implements generic interface %%s but does not specify its types: %%s', $className), + sprintf('in implemented type %%s of class %s', $className) + ); + return array_merge($extendsErrors, $implementsErrors); + } } diff --git a/src/Rules/Generics/ClassTemplateTypeRule.php b/src/Rules/Generics/ClassTemplateTypeRule.php index ec726ce894..6937a7453d 100644 --- a/src/Rules/Generics/ClassTemplateTypeRule.php +++ b/src/Rules/Generics/ClassTemplateTypeRule.php @@ -1,4 +1,6 @@ -templateTypeCheck = $templateTypeCheck; - } - - public function getNodeType(): string - { - return InClassNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - return []; - } - $classReflection = $scope->getClassReflection(); - $className = $classReflection->getName(); - if ($classReflection->isAnonymous()) { - $displayName = 'anonymous class'; - } else { - $displayName = 'class ' . $classReflection->getDisplayName(); - } - - return $this->templateTypeCheck->check( - $node, - TemplateTypeScope::createWithClass($className), - $classReflection->getTemplateTags(), - sprintf('PHPDoc tag @template for %s cannot have existing class %%s as its name.', $displayName), - sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName), - sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName), - sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName) - ); - } - + private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; + + public function __construct( + TemplateTypeCheck $templateTypeCheck + ) { + $this->templateTypeCheck = $templateTypeCheck; + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + return []; + } + $classReflection = $scope->getClassReflection(); + $className = $classReflection->getName(); + if ($classReflection->isAnonymous()) { + $displayName = 'anonymous class'; + } else { + $displayName = 'class ' . $classReflection->getDisplayName(); + } + + return $this->templateTypeCheck->check( + $node, + TemplateTypeScope::createWithClass($className), + $classReflection->getTemplateTags(), + sprintf('PHPDoc tag @template for %s cannot have existing class %%s as its name.', $displayName), + sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName), + sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName), + sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName) + ); + } } diff --git a/src/Rules/Generics/FunctionSignatureVarianceRule.php b/src/Rules/Generics/FunctionSignatureVarianceRule.php index a6d4699100..49d6d05086 100644 --- a/src/Rules/Generics/FunctionSignatureVarianceRule.php +++ b/src/Rules/Generics/FunctionSignatureVarianceRule.php @@ -1,4 +1,6 @@ -varianceCheck = $varianceCheck; - } - - public function getNodeType(): string - { - return InFunctionNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $functionReflection = $scope->getFunction(); - if ($functionReflection === null) { - return []; - } - - $functionName = $functionReflection->getName(); - - return $this->varianceCheck->checkParametersAcceptor( - ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()), - sprintf('in parameter %%s of function %s()', $functionName), - sprintf('in return type of function %s()', $functionName), - sprintf('in function %s()', $functionName), - false - ); - } - + private \PHPStan\Rules\Generics\VarianceCheck $varianceCheck; + + public function __construct(VarianceCheck $varianceCheck) + { + $this->varianceCheck = $varianceCheck; + } + + public function getNodeType(): string + { + return InFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $functionReflection = $scope->getFunction(); + if ($functionReflection === null) { + return []; + } + + $functionName = $functionReflection->getName(); + + return $this->varianceCheck->checkParametersAcceptor( + ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()), + sprintf('in parameter %%s of function %s()', $functionName), + sprintf('in return type of function %s()', $functionName), + sprintf('in function %s()', $functionName), + false + ); + } } diff --git a/src/Rules/Generics/FunctionTemplateTypeRule.php b/src/Rules/Generics/FunctionTemplateTypeRule.php index 2a6d2bc921..fb525263dc 100644 --- a/src/Rules/Generics/FunctionTemplateTypeRule.php +++ b/src/Rules/Generics/FunctionTemplateTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->templateTypeCheck = $templateTypeCheck; - } + private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; - public function getNodeType(): string - { - return Node\Stmt\Function_::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + TemplateTypeCheck $templateTypeCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->templateTypeCheck = $templateTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $functionName = (string) $node->namespacedName; - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - null, - null, - $functionName, - $docComment->getText() - ); + if (!isset($node->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } - return $this->templateTypeCheck->check( - $node, - TemplateTypeScope::createWithFunction($functionName), - $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for function %s() cannot have existing class %%s as its name.', $functionName), - sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $functionName), - sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $functionName), - sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $functionName) - ); - } + $functionName = (string) $node->namespacedName; + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + null, + null, + $functionName, + $docComment->getText() + ); + return $this->templateTypeCheck->check( + $node, + TemplateTypeScope::createWithFunction($functionName), + $resolvedPhpDoc->getTemplateTags(), + sprintf('PHPDoc tag @template for function %s() cannot have existing class %%s as its name.', $functionName), + sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $functionName), + sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $functionName), + sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $functionName) + ); + } } diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index c97a8043cc..0f1d51c9c0 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->genericObjectTypeCheck = $genericObjectTypeCheck; - $this->varianceCheck = $varianceCheck; - $this->checkGenericClassInNonGenericObjectType = $checkGenericClassInNonGenericObjectType; - $this->skipCheckGenericClasses = $skipCheckGenericClasses; - } - - /** - * @param array<\PhpParser\Node\Name> $nameNodes - * @param array<\PHPStan\Type\Type> $ancestorTypes - * @return \PHPStan\Rules\RuleError[] - */ - public function check( - array $nameNodes, - array $ancestorTypes, - string $incompatibleTypeMessage, - string $noNamesMessage, - string $noRelatedNameMessage, - string $classNotGenericMessage, - string $notEnoughTypesMessage, - string $extraTypesMessage, - string $typeIsNotSubtypeMessage, - string $invalidTypeMessage, - string $genericClassInNonGenericObjectType, - string $invalidVarianceMessage - ): array - { - $names = array_fill_keys(array_map(static function (Name $nameNode): string { - return $nameNode->toString(); - }, $nameNodes), true); - - $unusedNames = $names; - - $messages = []; - foreach ($ancestorTypes as $ancestorType) { - if (!$ancestorType instanceof GenericObjectType) { - $messages[] = RuleErrorBuilder::message(sprintf($incompatibleTypeMessage, $ancestorType->describe(VerbosityLevel::typeOnly())))->build(); - continue; - } - - $ancestorTypeClassName = $ancestorType->getClassName(); - if (!isset($names[$ancestorTypeClassName])) { - if (count($names) === 0) { - $messages[] = RuleErrorBuilder::message($noNamesMessage)->build(); - } else { - $messages[] = RuleErrorBuilder::message(sprintf($noRelatedNameMessage, $ancestorTypeClassName, implode(', ', array_keys($names))))->build(); - } - - continue; - } - - unset($unusedNames[$ancestorTypeClassName]); - - $genericObjectTypeCheckMessages = $this->genericObjectTypeCheck->check( - $ancestorType, - $classNotGenericMessage, - $notEnoughTypesMessage, - $extraTypesMessage, - $typeIsNotSubtypeMessage - ); - $messages = array_merge($messages, $genericObjectTypeCheckMessages); - - foreach ($ancestorType->getReferencedClasses() as $referencedClass) { - if ($this->reflectionProvider->hasClass($referencedClass)) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf($invalidTypeMessage, $referencedClass))->build(); - } - - $variance = TemplateTypeVariance::createInvariant(); - $messageContext = sprintf( - $invalidVarianceMessage, - $ancestorType->describe(VerbosityLevel::typeOnly()) - ); - foreach ($this->varianceCheck->check($variance, $ancestorType, $messageContext) as $message) { - $messages[] = $message; - } - } - - if ($this->checkGenericClassInNonGenericObjectType) { - foreach (array_keys($unusedNames) as $unusedName) { - if (!$this->reflectionProvider->hasClass($unusedName)) { - continue; - } - - $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); - if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { - continue; - } - if (!$unusedNameClassReflection->isGeneric()) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - $genericClassInNonGenericObjectType, - $unusedName, - implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } - } - - return $messages; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + private \PHPStan\Rules\Generics\VarianceCheck $varianceCheck; + + private bool $checkGenericClassInNonGenericObjectType; + + /** @var string[] */ + private array $skipCheckGenericClasses; + + /** + * @param string[] $skipCheckGenericClasses + */ + public function __construct( + ReflectionProvider $reflectionProvider, + GenericObjectTypeCheck $genericObjectTypeCheck, + VarianceCheck $varianceCheck, + bool $checkGenericClassInNonGenericObjectType, + array $skipCheckGenericClasses = [] + ) { + $this->reflectionProvider = $reflectionProvider; + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + $this->varianceCheck = $varianceCheck; + $this->checkGenericClassInNonGenericObjectType = $checkGenericClassInNonGenericObjectType; + $this->skipCheckGenericClasses = $skipCheckGenericClasses; + } + + /** + * @param array<\PhpParser\Node\Name> $nameNodes + * @param array<\PHPStan\Type\Type> $ancestorTypes + * @return \PHPStan\Rules\RuleError[] + */ + public function check( + array $nameNodes, + array $ancestorTypes, + string $incompatibleTypeMessage, + string $noNamesMessage, + string $noRelatedNameMessage, + string $classNotGenericMessage, + string $notEnoughTypesMessage, + string $extraTypesMessage, + string $typeIsNotSubtypeMessage, + string $invalidTypeMessage, + string $genericClassInNonGenericObjectType, + string $invalidVarianceMessage + ): array { + $names = array_fill_keys(array_map(static function (Name $nameNode): string { + return $nameNode->toString(); + }, $nameNodes), true); + + $unusedNames = $names; + + $messages = []; + foreach ($ancestorTypes as $ancestorType) { + if (!$ancestorType instanceof GenericObjectType) { + $messages[] = RuleErrorBuilder::message(sprintf($incompatibleTypeMessage, $ancestorType->describe(VerbosityLevel::typeOnly())))->build(); + continue; + } + + $ancestorTypeClassName = $ancestorType->getClassName(); + if (!isset($names[$ancestorTypeClassName])) { + if (count($names) === 0) { + $messages[] = RuleErrorBuilder::message($noNamesMessage)->build(); + } else { + $messages[] = RuleErrorBuilder::message(sprintf($noRelatedNameMessage, $ancestorTypeClassName, implode(', ', array_keys($names))))->build(); + } + + continue; + } + + unset($unusedNames[$ancestorTypeClassName]); + + $genericObjectTypeCheckMessages = $this->genericObjectTypeCheck->check( + $ancestorType, + $classNotGenericMessage, + $notEnoughTypesMessage, + $extraTypesMessage, + $typeIsNotSubtypeMessage + ); + $messages = array_merge($messages, $genericObjectTypeCheckMessages); + + foreach ($ancestorType->getReferencedClasses() as $referencedClass) { + if ($this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf($invalidTypeMessage, $referencedClass))->build(); + } + + $variance = TemplateTypeVariance::createInvariant(); + $messageContext = sprintf( + $invalidVarianceMessage, + $ancestorType->describe(VerbosityLevel::typeOnly()) + ); + foreach ($this->varianceCheck->check($variance, $ancestorType, $messageContext) as $message) { + $messages[] = $message; + } + } + + if ($this->checkGenericClassInNonGenericObjectType) { + foreach (array_keys($unusedNames) as $unusedName) { + if (!$this->reflectionProvider->hasClass($unusedName)) { + continue; + } + + $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); + if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { + continue; + } + if (!$unusedNameClassReflection->isGeneric()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + $genericClassInNonGenericObjectType, + $unusedName, + implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + } + + return $messages; + } } diff --git a/src/Rules/Generics/GenericObjectTypeCheck.php b/src/Rules/Generics/GenericObjectTypeCheck.php index 16eea0ed66..f0e04f4059 100644 --- a/src/Rules/Generics/GenericObjectTypeCheck.php +++ b/src/Rules/Generics/GenericObjectTypeCheck.php @@ -1,4 +1,6 @@ -getGenericTypes($phpDocType); + $messages = []; + foreach ($genericTypes as $genericType) { + $classReflection = $genericType->getClassReflection(); + if ($classReflection === null) { + continue; + } + if (!$classReflection->isGeneric()) { + $messages[] = RuleErrorBuilder::message(sprintf($classNotGenericMessage, $genericType->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName()))->build(); + continue; + } - /** - * @param \PHPStan\Type\Type $phpDocType - * @param string $classNotGenericMessage - * @param string $notEnoughTypesMessage - * @param string $extraTypesMessage - * @param string $typeIsNotSubtypeMessage - * @return \PHPStan\Rules\RuleError[] - */ - public function check( - Type $phpDocType, - string $classNotGenericMessage, - string $notEnoughTypesMessage, - string $extraTypesMessage, - string $typeIsNotSubtypeMessage - ): array - { - $genericTypes = $this->getGenericTypes($phpDocType); - $messages = []; - foreach ($genericTypes as $genericType) { - $classReflection = $genericType->getClassReflection(); - if ($classReflection === null) { - continue; - } - if (!$classReflection->isGeneric()) { - $messages[] = RuleErrorBuilder::message(sprintf($classNotGenericMessage, $genericType->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName()))->build(); - continue; - } - - $templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes()); - - $genericTypeTypes = $genericType->getTypes(); - $templateTypesCount = count($templateTypes); - $genericTypeTypesCount = count($genericTypeTypes); - if ($templateTypesCount > $genericTypeTypesCount) { - $messages[] = RuleErrorBuilder::message(sprintf( - $notEnoughTypesMessage, - $genericType->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(false), - implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())) - ))->build(); - } elseif ($templateTypesCount < $genericTypeTypesCount) { - $messages[] = RuleErrorBuilder::message(sprintf( - $extraTypesMessage, - $genericType->describe(VerbosityLevel::typeOnly()), - $genericTypeTypesCount, - $classReflection->getDisplayName(false), - $templateTypesCount, - implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())) - ))->build(); - } + $templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes()); - $templateTypesCount = count($templateTypes); - for ($i = 0; $i < $templateTypesCount; $i++) { - if (!isset($genericTypeTypes[$i])) { - continue; - } + $genericTypeTypes = $genericType->getTypes(); + $templateTypesCount = count($templateTypes); + $genericTypeTypesCount = count($genericTypeTypes); + if ($templateTypesCount > $genericTypeTypesCount) { + $messages[] = RuleErrorBuilder::message(sprintf( + $notEnoughTypesMessage, + $genericType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(false), + implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())) + ))->build(); + } elseif ($templateTypesCount < $genericTypeTypesCount) { + $messages[] = RuleErrorBuilder::message(sprintf( + $extraTypesMessage, + $genericType->describe(VerbosityLevel::typeOnly()), + $genericTypeTypesCount, + $classReflection->getDisplayName(false), + $templateTypesCount, + implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())) + ))->build(); + } - $templateType = $templateTypes[$i]; - $boundType = TemplateTypeHelper::resolveToBounds($templateType); - $genericTypeType = $genericTypeTypes[$i]; - if ($boundType->isSuperTypeOf($genericTypeType)->yes()) { - if (!$templateType instanceof TemplateType) { - continue; - } - $map = $templateType->inferTemplateTypes($genericTypeType); - for ($j = 0; $j < $templateTypesCount; $j++) { - if ($i === $j) { - continue; - } + $templateTypesCount = count($templateTypes); + for ($i = 0; $i < $templateTypesCount; $i++) { + if (!isset($genericTypeTypes[$i])) { + continue; + } - $templateTypes[$j] = TemplateTypeHelper::resolveTemplateTypes($templateTypes[$j], $map); - } - continue; - } + $templateType = $templateTypes[$i]; + $boundType = TemplateTypeHelper::resolveToBounds($templateType); + $genericTypeType = $genericTypeTypes[$i]; + if ($boundType->isSuperTypeOf($genericTypeType)->yes()) { + if (!$templateType instanceof TemplateType) { + continue; + } + $map = $templateType->inferTemplateTypes($genericTypeType); + for ($j = 0; $j < $templateTypesCount; $j++) { + if ($i === $j) { + continue; + } - $messages[] = RuleErrorBuilder::message(sprintf( - $typeIsNotSubtypeMessage, - $genericTypeType->describe(VerbosityLevel::typeOnly()), - $genericType->describe(VerbosityLevel::typeOnly()), - $templateType->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(false) - ))->build(); - } - } + $templateTypes[$j] = TemplateTypeHelper::resolveTemplateTypes($templateTypes[$j], $map); + } + continue; + } - return $messages; - } + $messages[] = RuleErrorBuilder::message(sprintf( + $typeIsNotSubtypeMessage, + $genericTypeType->describe(VerbosityLevel::typeOnly()), + $genericType->describe(VerbosityLevel::typeOnly()), + $templateType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(false) + ))->build(); + } + } - /** - * @param \PHPStan\Type\Type $phpDocType - * @return \PHPStan\Type\Generic\GenericObjectType[] - */ - private function getGenericTypes(Type $phpDocType): array - { - $genericObjectTypes = []; - TypeTraverser::map($phpDocType, static function (Type $type, callable $traverse) use (&$genericObjectTypes): Type { - if ($type instanceof GenericObjectType) { - $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (!$resolvedType instanceof GenericObjectType) { - throw new \PHPStan\ShouldNotHappenException(); - } - $genericObjectTypes[] = $resolvedType; - $traverse($type); - return $type; - } - $traverse($type); - return $type; - }); + return $messages; + } - return $genericObjectTypes; - } + /** + * @param \PHPStan\Type\Type $phpDocType + * @return \PHPStan\Type\Generic\GenericObjectType[] + */ + private function getGenericTypes(Type $phpDocType): array + { + $genericObjectTypes = []; + TypeTraverser::map($phpDocType, static function (Type $type, callable $traverse) use (&$genericObjectTypes): Type { + if ($type instanceof GenericObjectType) { + $resolvedType = TemplateTypeHelper::resolveToBounds($type); + if (!$resolvedType instanceof GenericObjectType) { + throw new \PHPStan\ShouldNotHappenException(); + } + $genericObjectTypes[] = $resolvedType; + $traverse($type); + return $type; + } + $traverse($type); + return $type; + }); + return $genericObjectTypes; + } } diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index 5a7f294113..a5c7b4352a 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->genericAncestorsCheck = $genericAncestorsCheck; - } + private \PHPStan\Rules\Generics\GenericAncestorsCheck $genericAncestorsCheck; - public function getNodeType(): string - { - return Node\Stmt\Interface_::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + GenericAncestorsCheck $genericAncestorsCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->genericAncestorsCheck = $genericAncestorsCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return Node\Stmt\Interface_::class; + } - $interfaceName = (string) $node->namespacedName; - $extendsTags = []; - $implementsTags = []; - $docComment = $node->getDocComment(); - if ($docComment !== null) { - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $interfaceName, - null, - null, - $docComment->getText() - ); - $extendsTags = $resolvedPhpDoc->getExtendsTags(); - $implementsTags = $resolvedPhpDoc->getImplementsTags(); - } + public function processNode(Node $node, Scope $scope): array + { + if (!isset($node->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } - $extendsErrors = $this->genericAncestorsCheck->check( - $node->extends, - array_map(static function (ExtendsTag $tag): Type { - return $tag->getType(); - }, $extendsTags), - sprintf('Interface %s @extends tag contains incompatible type %%s.', $interfaceName), - sprintf('Interface %s has @extends tag, but does not extend any interface.', $interfaceName), - sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $interfaceName), - 'PHPDoc tag @extends contains generic type %s but interface %s is not generic.', - 'Generic type %s in PHPDoc tag @extends does not specify all template types of interface %s: %s', - 'Generic type %s in PHPDoc tag @extends specifies %d template types, but interface %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of interface %s.', - 'PHPDoc tag @extends has invalid type %s.', - sprintf('Interface %s extends generic interface %%s but does not specify its types: %%s', $interfaceName), - sprintf('in extended type %%s of interface %s', $interfaceName) - ); + $interfaceName = (string) $node->namespacedName; + $extendsTags = []; + $implementsTags = []; + $docComment = $node->getDocComment(); + if ($docComment !== null) { + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $interfaceName, + null, + null, + $docComment->getText() + ); + $extendsTags = $resolvedPhpDoc->getExtendsTags(); + $implementsTags = $resolvedPhpDoc->getImplementsTags(); + } - $implementsErrors = $this->genericAncestorsCheck->check( - [], - array_map(static function (ImplementsTag $tag): Type { - return $tag->getType(); - }, $implementsTags), - sprintf('Interface %s @implements tag contains incompatible type %%s.', $interfaceName), - sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $interfaceName), - '', - '', - '', - '', - '', - '', - '', - '' - ); + $extendsErrors = $this->genericAncestorsCheck->check( + $node->extends, + array_map(static function (ExtendsTag $tag): Type { + return $tag->getType(); + }, $extendsTags), + sprintf('Interface %s @extends tag contains incompatible type %%s.', $interfaceName), + sprintf('Interface %s has @extends tag, but does not extend any interface.', $interfaceName), + sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $interfaceName), + 'PHPDoc tag @extends contains generic type %s but interface %s is not generic.', + 'Generic type %s in PHPDoc tag @extends does not specify all template types of interface %s: %s', + 'Generic type %s in PHPDoc tag @extends specifies %d template types, but interface %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of interface %s.', + 'PHPDoc tag @extends has invalid type %s.', + sprintf('Interface %s extends generic interface %%s but does not specify its types: %%s', $interfaceName), + sprintf('in extended type %%s of interface %s', $interfaceName) + ); - return array_merge($extendsErrors, $implementsErrors); - } + $implementsErrors = $this->genericAncestorsCheck->check( + [], + array_map(static function (ImplementsTag $tag): Type { + return $tag->getType(); + }, $implementsTags), + sprintf('Interface %s @implements tag contains incompatible type %%s.', $interfaceName), + sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $interfaceName), + '', + '', + '', + '', + '', + '', + '', + '' + ); + return array_merge($extendsErrors, $implementsErrors); + } } diff --git a/src/Rules/Generics/InterfaceTemplateTypeRule.php b/src/Rules/Generics/InterfaceTemplateTypeRule.php index 87f8b8cded..62d7a47589 100644 --- a/src/Rules/Generics/InterfaceTemplateTypeRule.php +++ b/src/Rules/Generics/InterfaceTemplateTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->templateTypeCheck = $templateTypeCheck; - } + private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; - public function getNodeType(): string - { - return Node\Stmt\Interface_::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + TemplateTypeCheck $templateTypeCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->templateTypeCheck = $templateTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Interface_::class; + } - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $interfaceName = (string) $node->namespacedName; - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $interfaceName, - null, - null, - $docComment->getText() - ); + if (!isset($node->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } - return $this->templateTypeCheck->check( - $node, - TemplateTypeScope::createWithClass($interfaceName), - $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for interface %s cannot have existing class %%s as its name.', $interfaceName), - sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $interfaceName), - sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $interfaceName), - sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $interfaceName) - ); - } + $interfaceName = (string) $node->namespacedName; + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $interfaceName, + null, + null, + $docComment->getText() + ); + return $this->templateTypeCheck->check( + $node, + TemplateTypeScope::createWithClass($interfaceName), + $resolvedPhpDoc->getTemplateTags(), + sprintf('PHPDoc tag @template for interface %s cannot have existing class %%s as its name.', $interfaceName), + sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $interfaceName), + sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $interfaceName), + sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $interfaceName) + ); + } } diff --git a/src/Rules/Generics/MethodSignatureVarianceRule.php b/src/Rules/Generics/MethodSignatureVarianceRule.php index 49e452476a..03b374d917 100644 --- a/src/Rules/Generics/MethodSignatureVarianceRule.php +++ b/src/Rules/Generics/MethodSignatureVarianceRule.php @@ -1,4 +1,6 @@ -varianceCheck = $varianceCheck; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $method = $scope->getFunction(); - if (!$method instanceof MethodReflection) { - return []; - } - - return $this->varianceCheck->checkParametersAcceptor( - ParametersAcceptorSelector::selectSingle($method->getVariants()), - sprintf('in parameter %%s of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), - sprintf('in return type of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), - sprintf('in method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), - $method->getName() === '__construct' || $method->isStatic() - ); - } - + private \PHPStan\Rules\Generics\VarianceCheck $varianceCheck; + + public function __construct(VarianceCheck $varianceCheck) + { + $this->varianceCheck = $varianceCheck; + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $method = $scope->getFunction(); + if (!$method instanceof MethodReflection) { + return []; + } + + return $this->varianceCheck->checkParametersAcceptor( + ParametersAcceptorSelector::selectSingle($method->getVariants()), + sprintf('in parameter %%s of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), + sprintf('in return type of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), + sprintf('in method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), + $method->getName() === '__construct' || $method->isStatic() + ); + } } diff --git a/src/Rules/Generics/MethodTemplateTypeRule.php b/src/Rules/Generics/MethodTemplateTypeRule.php index 57ac147a2c..5ade067817 100644 --- a/src/Rules/Generics/MethodTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTemplateTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->templateTypeCheck = $templateTypeCheck; - } + private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; - public function getNodeType(): string - { - return Node\Stmt\ClassMethod::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + TemplateTypeCheck $templateTypeCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->templateTypeCheck = $templateTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $classReflection = $scope->getClassReflection(); - $className = $classReflection->getDisplayName(); - $methodName = $node->name->toString(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $className, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $methodName, - $docComment->getText() - ); + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $methodTemplateTags = $resolvedPhpDoc->getTemplateTags(); - $messages = $this->templateTypeCheck->check( - $node, - TemplateTypeScope::createWithMethod($className, $methodName), - $methodTemplateTags, - sprintf('PHPDoc tag @template for method %s::%s() cannot have existing class %%s as its name.', $className, $methodName), - sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $className, $methodName), - sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $className, $methodName), - sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $className, $methodName) - ); + $classReflection = $scope->getClassReflection(); + $className = $classReflection->getDisplayName(); + $methodName = $node->name->toString(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $className, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $methodName, + $docComment->getText() + ); - $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); - foreach (array_keys($methodTemplateTags) as $name) { - if (!isset($classTemplateTypes[$name])) { - continue; - } + $methodTemplateTags = $resolvedPhpDoc->getTemplateTags(); + $messages = $this->templateTypeCheck->check( + $node, + TemplateTypeScope::createWithMethod($className, $methodName), + $methodTemplateTags, + sprintf('PHPDoc tag @template for method %s::%s() cannot have existing class %%s as its name.', $className, $methodName), + sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $className, $methodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $className, $methodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $className, $methodName) + ); - $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false)))->build(); - } + $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); + foreach (array_keys($methodTemplateTags) as $name) { + if (!isset($classTemplateTypes[$name])) { + continue; + } - return $messages; - } + $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false)))->build(); + } + return $messages; + } } diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 057623f0da..05fb50e5bf 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->genericObjectTypeCheck = $genericObjectTypeCheck; - $this->typeAliasResolver = $typeAliasResolver; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - } - - /** - * @param \PhpParser\Node $node - * @param TemplateTypeScope $templateTypeScope - * @param array $templateTags - * @return \PHPStan\Rules\RuleError[] - */ - public function check( - Node $node, - TemplateTypeScope $templateTypeScope, - array $templateTags, - string $sameTemplateTypeNameAsClassMessage, - string $sameTemplateTypeNameAsTypeMessage, - string $invalidBoundTypeMessage, - string $notSupportedBoundMessage - ): array - { - $messages = []; - foreach ($templateTags as $templateTag) { - $templateTagName = $templateTag->getName(); - if ($this->reflectionProvider->hasClass($templateTagName)) { - $messages[] = RuleErrorBuilder::message(sprintf( - $sameTemplateTypeNameAsClassMessage, - $templateTagName - ))->build(); - } - if ($this->typeAliasResolver->hasTypeAlias($templateTagName, $templateTypeScope->getClassName())) { - $messages[] = RuleErrorBuilder::message(sprintf( - $sameTemplateTypeNameAsTypeMessage, - $templateTagName - ))->build(); - } - $boundType = $templateTag->getBound(); - foreach ($boundType->getReferencedClasses() as $referencedClass) { - if ( - $this->reflectionProvider->hasClass($referencedClass) - && !$this->reflectionProvider->getClass($referencedClass)->isTrait() - ) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - $invalidBoundTypeMessage, - $templateTagName, - $referencedClass - ))->build(); - } - - if ($this->checkClassCaseSensitivity) { - $classNameNodePairs = array_map(static function (string $referencedClass) use ($node): ClassNameNodePair { - return new ClassNameNodePair($referencedClass, $node); - }, $boundType->getReferencedClasses()); - $messages = array_merge($messages, $this->classCaseSensitivityCheck->checkClassNames($classNameNodePairs)); - } - - TypeTraverser::map($templateTag->getBound(), static function (Type $type, callable $traverse) use (&$messages, $notSupportedBoundMessage, $templateTagName): Type { - $boundClass = get_class($type); - if ( - $boundClass === MixedType::class - || $boundClass === StringType::class - || $boundClass === IntegerType::class - || $boundClass === ObjectWithoutClassType::class - || $boundClass === ObjectType::class - || $boundClass === GenericObjectType::class - || $type instanceof UnionType - || $type instanceof TemplateType - ) { - return $traverse($type); - } - - $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $type->describe(VerbosityLevel::typeOnly())))->build(); - - return $type; - }); - - $genericObjectErrors = $this->genericObjectTypeCheck->check( - $boundType, - sprintf('PHPDoc tag @template %s bound contains generic type %%s but class %%s is not generic.', $templateTagName), - sprintf('PHPDoc tag @template %s bound has type %%s which does not specify all template types of class %%s: %%s', $templateTagName), - sprintf('PHPDoc tag @template %s bound has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $templateTagName), - sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s is not subtype of template type %%s of class %%s.', $templateTagName) - ); - foreach ($genericObjectErrors as $genericObjectError) { - $messages[] = $genericObjectError; - } - } - - return $messages; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private GenericObjectTypeCheck $genericObjectTypeCheck; + + private TypeAliasResolver $typeAliasResolver; + + private bool $checkClassCaseSensitivity; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + GenericObjectTypeCheck $genericObjectTypeCheck, + TypeAliasResolver $typeAliasResolver, + bool $checkClassCaseSensitivity + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + $this->typeAliasResolver = $typeAliasResolver; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + } + + /** + * @param \PhpParser\Node $node + * @param TemplateTypeScope $templateTypeScope + * @param array $templateTags + * @return \PHPStan\Rules\RuleError[] + */ + public function check( + Node $node, + TemplateTypeScope $templateTypeScope, + array $templateTags, + string $sameTemplateTypeNameAsClassMessage, + string $sameTemplateTypeNameAsTypeMessage, + string $invalidBoundTypeMessage, + string $notSupportedBoundMessage + ): array { + $messages = []; + foreach ($templateTags as $templateTag) { + $templateTagName = $templateTag->getName(); + if ($this->reflectionProvider->hasClass($templateTagName)) { + $messages[] = RuleErrorBuilder::message(sprintf( + $sameTemplateTypeNameAsClassMessage, + $templateTagName + ))->build(); + } + if ($this->typeAliasResolver->hasTypeAlias($templateTagName, $templateTypeScope->getClassName())) { + $messages[] = RuleErrorBuilder::message(sprintf( + $sameTemplateTypeNameAsTypeMessage, + $templateTagName + ))->build(); + } + $boundType = $templateTag->getBound(); + foreach ($boundType->getReferencedClasses() as $referencedClass) { + if ( + $this->reflectionProvider->hasClass($referencedClass) + && !$this->reflectionProvider->getClass($referencedClass)->isTrait() + ) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + $invalidBoundTypeMessage, + $templateTagName, + $referencedClass + ))->build(); + } + + if ($this->checkClassCaseSensitivity) { + $classNameNodePairs = array_map(static function (string $referencedClass) use ($node): ClassNameNodePair { + return new ClassNameNodePair($referencedClass, $node); + }, $boundType->getReferencedClasses()); + $messages = array_merge($messages, $this->classCaseSensitivityCheck->checkClassNames($classNameNodePairs)); + } + + TypeTraverser::map($templateTag->getBound(), static function (Type $type, callable $traverse) use (&$messages, $notSupportedBoundMessage, $templateTagName): Type { + $boundClass = get_class($type); + if ( + $boundClass === MixedType::class + || $boundClass === StringType::class + || $boundClass === IntegerType::class + || $boundClass === ObjectWithoutClassType::class + || $boundClass === ObjectType::class + || $boundClass === GenericObjectType::class + || $type instanceof UnionType + || $type instanceof TemplateType + ) { + return $traverse($type); + } + + $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $type->describe(VerbosityLevel::typeOnly())))->build(); + + return $type; + }); + + $genericObjectErrors = $this->genericObjectTypeCheck->check( + $boundType, + sprintf('PHPDoc tag @template %s bound contains generic type %%s but class %%s is not generic.', $templateTagName), + sprintf('PHPDoc tag @template %s bound has type %%s which does not specify all template types of class %%s: %%s', $templateTagName), + sprintf('PHPDoc tag @template %s bound has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $templateTagName), + sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s is not subtype of template type %%s of class %%s.', $templateTagName) + ); + foreach ($genericObjectErrors as $genericObjectError) { + $messages[] = $genericObjectError; + } + } + + return $messages; + } } diff --git a/src/Rules/Generics/TraitTemplateTypeRule.php b/src/Rules/Generics/TraitTemplateTypeRule.php index 21b89339cd..3513c875a1 100644 --- a/src/Rules/Generics/TraitTemplateTypeRule.php +++ b/src/Rules/Generics/TraitTemplateTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->templateTypeCheck = $templateTypeCheck; - } + private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; - public function getNodeType(): string - { - return Node\Stmt\Trait_::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + TemplateTypeCheck $templateTypeCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->templateTypeCheck = $templateTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $traitName = (string) $node->namespacedName; - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $traitName, - null, - null, - $docComment->getText() - ); + if (!isset($node->namespacedName)) { + throw new \PHPStan\ShouldNotHappenException(); + } - return $this->templateTypeCheck->check( - $node, - TemplateTypeScope::createWithClass($traitName), - $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for trait %s cannot have existing class %%s as its name.', $traitName), - sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $traitName), - sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $traitName), - sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $traitName) - ); - } + $traitName = (string) $node->namespacedName; + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $traitName, + null, + null, + $docComment->getText() + ); + return $this->templateTypeCheck->check( + $node, + TemplateTypeScope::createWithClass($traitName), + $resolvedPhpDoc->getTemplateTags(), + sprintf('PHPDoc tag @template for trait %s cannot have existing class %%s as its name.', $traitName), + sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $traitName), + sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $traitName), + sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $traitName) + ); + } } diff --git a/src/Rules/Generics/UsedTraitsRule.php b/src/Rules/Generics/UsedTraitsRule.php index 9e102bf341..874e812349 100644 --- a/src/Rules/Generics/UsedTraitsRule.php +++ b/src/Rules/Generics/UsedTraitsRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->genericAncestorsCheck = $genericAncestorsCheck; - } + private \PHPStan\Rules\Generics\GenericAncestorsCheck $genericAncestorsCheck; - public function getNodeType(): string - { - return Node\Stmt\TraitUse::class; - } + public function __construct( + FileTypeMapper $fileTypeMapper, + GenericAncestorsCheck $genericAncestorsCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->genericAncestorsCheck = $genericAncestorsCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return Node\Stmt\TraitUse::class; + } - $className = $scope->getClassReflection()->getName(); - $traitName = null; - if ($scope->isInTrait()) { - $traitName = $scope->getTraitReflection()->getName(); - } - $useTags = []; - $docComment = $node->getDocComment(); - if ($docComment !== null) { - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $className, - $traitName, - null, - $docComment->getText() - ); - $useTags = $resolvedPhpDoc->getUsesTags(); - } + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $description = sprintf('class %s', $className); - $typeDescription = 'class'; - if ($traitName !== null) { - $description = sprintf('trait %s', $traitName); - $typeDescription = 'trait'; - } + $className = $scope->getClassReflection()->getName(); + $traitName = null; + if ($scope->isInTrait()) { + $traitName = $scope->getTraitReflection()->getName(); + } + $useTags = []; + $docComment = $node->getDocComment(); + if ($docComment !== null) { + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $className, + $traitName, + null, + $docComment->getText() + ); + $useTags = $resolvedPhpDoc->getUsesTags(); + } - return $this->genericAncestorsCheck->check( - $node->traits, - array_map(static function (UsesTag $tag): Type { - return $tag->getType(); - }, $useTags), - sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)), - sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)), - sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription), - 'PHPDoc tag @use contains generic type %s but trait %s is not generic.', - 'Generic type %s in PHPDoc tag @use does not specify all template types of trait %s: %s', - 'Generic type %s in PHPDoc tag @use specifies %d template types, but trait %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @use is not subtype of template type %s of trait %s.', - 'PHPDoc tag @use has invalid type %s.', - sprintf('%s uses generic trait %%s but does not specify its types: %%s', ucfirst($description)), - sprintf('in used type %%s of %s', $description) - ); - } + $description = sprintf('class %s', $className); + $typeDescription = 'class'; + if ($traitName !== null) { + $description = sprintf('trait %s', $traitName); + $typeDescription = 'trait'; + } + return $this->genericAncestorsCheck->check( + $node->traits, + array_map(static function (UsesTag $tag): Type { + return $tag->getType(); + }, $useTags), + sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)), + sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)), + sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription), + 'PHPDoc tag @use contains generic type %s but trait %s is not generic.', + 'Generic type %s in PHPDoc tag @use does not specify all template types of trait %s: %s', + 'Generic type %s in PHPDoc tag @use specifies %d template types, but trait %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @use is not subtype of template type %s of trait %s.', + 'PHPDoc tag @use has invalid type %s.', + sprintf('%s uses generic trait %%s but does not specify its types: %%s', ucfirst($description)), + sprintf('in used type %%s of %s', $description) + ); + } } diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index e8a95fd3c3..015670d5ee 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -1,4 +1,6 @@ -getParameters() as $parameterReflection) { - $variance = $isStatic - ? TemplateTypeVariance::createStatic() - : TemplateTypeVariance::createContravariant(); - $type = $parameterReflection->getType(); - $message = sprintf($parameterTypeMessage, $parameterReflection->getName()); - foreach ($this->check($variance, $type, $message) as $error) { - $errors[] = $error; - } - } - - foreach ($parametersAcceptor->getTemplateTypeMap()->getTypes() as $templateType) { - if (!$templateType instanceof TemplateType - || $templateType->getScope()->getFunctionName() === null - || $templateType->getVariance()->invariant() - ) { - continue; - } + foreach ($parametersAcceptor->getParameters() as $parameterReflection) { + $variance = $isStatic + ? TemplateTypeVariance::createStatic() + : TemplateTypeVariance::createContravariant(); + $type = $parameterReflection->getType(); + $message = sprintf($parameterTypeMessage, $parameterReflection->getName()); + foreach ($this->check($variance, $type, $message) as $error) { + $errors[] = $error; + } + } - $errors[] = RuleErrorBuilder::message(sprintf( - 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type %s in %s.', - $templateType->getName(), - $generalMessage - ))->build(); - } + foreach ($parametersAcceptor->getTemplateTypeMap()->getTypes() as $templateType) { + if (!$templateType instanceof TemplateType + || $templateType->getScope()->getFunctionName() === null + || $templateType->getVariance()->invariant() + ) { + continue; + } - $variance = TemplateTypeVariance::createCovariant(); - $type = $parametersAcceptor->getReturnType(); - foreach ($this->check($variance, $type, $returnTypeMessage) as $error) { - $errors[] = $error; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type %s in %s.', + $templateType->getName(), + $generalMessage + ))->build(); + } - return $errors; - } + $variance = TemplateTypeVariance::createCovariant(); + $type = $parametersAcceptor->getReturnType(); + foreach ($this->check($variance, $type, $returnTypeMessage) as $error) { + $errors[] = $error; + } - /** @return RuleError[] */ - public function check(TemplateTypeVariance $positionVariance, Type $type, string $messageContext): array - { - $errors = []; + return $errors; + } - foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { - $referredType = $reference->getType(); - if (($referredType->getScope()->getFunctionName() !== null && !$referredType->getVariance()->invariant()) - || $this->isTemplateTypeVarianceValid($reference->getPositionVariance(), $referredType)) { - continue; - } + /** @return RuleError[] */ + public function check(TemplateTypeVariance $positionVariance, Type $type, string $messageContext): array + { + $errors = []; - $errors[] = RuleErrorBuilder::message(sprintf( - 'Template type %s is declared as %s, but occurs in %s position %s.', - $referredType->getName(), - $referredType->getVariance()->describe(), - $reference->getPositionVariance()->describe(), - $messageContext - ))->build(); - } + foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { + $referredType = $reference->getType(); + if (($referredType->getScope()->getFunctionName() !== null && !$referredType->getVariance()->invariant()) + || $this->isTemplateTypeVarianceValid($reference->getPositionVariance(), $referredType)) { + continue; + } - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Template type %s is declared as %s, but occurs in %s position %s.', + $referredType->getName(), + $referredType->getVariance()->describe(), + $reference->getPositionVariance()->describe(), + $messageContext + ))->build(); + } - private function isTemplateTypeVarianceValid(TemplateTypeVariance $positionVariance, TemplateType $type): bool - { - return $positionVariance->validPosition($type->getVariance()); - } + return $errors; + } + private function isTemplateTypeVarianceValid(TemplateTypeVariance $positionVariance, TemplateType $type): bool + { + return $positionVariance->validPosition($type->getVariance()); + } } diff --git a/src/Rules/IdentifierRuleError.php b/src/Rules/IdentifierRuleError.php index fb556fc875..bc4071089a 100644 --- a/src/Rules/IdentifierRuleError.php +++ b/src/Rules/IdentifierRuleError.php @@ -1,10 +1,10 @@ -propertyDescriptor = $propertyDescriptor; - $this->propertyReflectionFinder = $propertyReflectionFinder; - } - - public function check(Expr $expr, Scope $scope, string $operatorDescription, ?RuleError $error = null): ?RuleError - { - if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { - $hasVariable = $scope->hasVariableType($expr->name); - if ($hasVariable->maybe()) { - return null; - } - - return $error; - } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - - $type = $scope->getType($expr->var); - $dimType = $scope->getType($expr->dim); - $hasOffsetValue = $type->hasOffsetValueType($dimType); - if (!$type->isOffsetAccessible()->yes()) { - return $error; - } - - if ($hasOffsetValue->no()) { - return $error ?? RuleErrorBuilder::message( - sprintf( - 'Offset %s on %s %s does not exist.', - $dimType->describe(VerbosityLevel::value()), - $type->describe(VerbosityLevel::value()), - $operatorDescription - ) - )->build(); - } - - if ($hasOffsetValue->maybe()) { - return null; - } - - // If offset is cannot be null, store this error message and see if one of the earlier offsets is. - // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null. - if ($hasOffsetValue->yes()) { - - $error = $error ?? $this->generateError($type->getOffsetValueType($dimType), sprintf( - 'Offset %s on %s %s always exists and', - $dimType->describe(VerbosityLevel::value()), - $type->describe(VerbosityLevel::value()), - $operatorDescription - )); - - if ($error !== null) { - return $this->check($expr->var, $scope, $operatorDescription, $error); - } - } - - // Has offset, it is nullable - return null; - - } elseif ($expr instanceof Node\Expr\PropertyFetch || $expr instanceof Node\Expr\StaticPropertyFetch) { - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $scope); - - if ($propertyReflection === null) { - return null; - } - - if (!$propertyReflection->isNative()) { - return null; - } - - $nativeType = $propertyReflection->getNativeType(); - if (!$nativeType instanceof MixedType) { - if (!$scope->isSpecified($expr)) { - return null; - } - } - - $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $expr); - $propertyType = $propertyReflection->getWritableType(); - - $error = $error ?? $this->generateError( - $propertyReflection->getWritableType(), - sprintf('%s (%s) %s', $propertyDescription, $propertyType->describe(VerbosityLevel::typeOnly()), $operatorDescription) - ); - - if ($error !== null) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->check($expr->var, $scope, $operatorDescription, $error); - } - - if ($expr->class instanceof Expr) { - return $this->check($expr->class, $scope, $operatorDescription, $error); - } - } - - return $error; - } - - return $error ?? $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription)); - } - - private function generateError(Type $type, string $message): ?RuleError - { - $nullType = new NullType(); - - if ($type->equals($nullType)) { - return RuleErrorBuilder::message( - sprintf('%s is always null.', $message) - )->build(); - } - - if ($type->isSuperTypeOf($nullType)->no()) { - return RuleErrorBuilder::message( - sprintf('%s is not nullable.', $message) - )->build(); - } - - return null; - } - + private \PHPStan\Rules\Properties\PropertyDescriptor $propertyDescriptor; + + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + public function __construct( + PropertyDescriptor $propertyDescriptor, + PropertyReflectionFinder $propertyReflectionFinder + ) { + $this->propertyDescriptor = $propertyDescriptor; + $this->propertyReflectionFinder = $propertyReflectionFinder; + } + + public function check(Expr $expr, Scope $scope, string $operatorDescription, ?RuleError $error = null): ?RuleError + { + if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { + $hasVariable = $scope->hasVariableType($expr->name); + if ($hasVariable->maybe()) { + return null; + } + + return $error; + } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { + $type = $scope->getType($expr->var); + $dimType = $scope->getType($expr->dim); + $hasOffsetValue = $type->hasOffsetValueType($dimType); + if (!$type->isOffsetAccessible()->yes()) { + return $error; + } + + if ($hasOffsetValue->no()) { + return $error ?? RuleErrorBuilder::message( + sprintf( + 'Offset %s on %s %s does not exist.', + $dimType->describe(VerbosityLevel::value()), + $type->describe(VerbosityLevel::value()), + $operatorDescription + ) + )->build(); + } + + if ($hasOffsetValue->maybe()) { + return null; + } + + // If offset is cannot be null, store this error message and see if one of the earlier offsets is. + // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null. + if ($hasOffsetValue->yes()) { + $error = $error ?? $this->generateError($type->getOffsetValueType($dimType), sprintf( + 'Offset %s on %s %s always exists and', + $dimType->describe(VerbosityLevel::value()), + $type->describe(VerbosityLevel::value()), + $operatorDescription + )); + + if ($error !== null) { + return $this->check($expr->var, $scope, $operatorDescription, $error); + } + } + + // Has offset, it is nullable + return null; + } elseif ($expr instanceof Node\Expr\PropertyFetch || $expr instanceof Node\Expr\StaticPropertyFetch) { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $scope); + + if ($propertyReflection === null) { + return null; + } + + if (!$propertyReflection->isNative()) { + return null; + } + + $nativeType = $propertyReflection->getNativeType(); + if (!$nativeType instanceof MixedType) { + if (!$scope->isSpecified($expr)) { + return null; + } + } + + $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $expr); + $propertyType = $propertyReflection->getWritableType(); + + $error = $error ?? $this->generateError( + $propertyReflection->getWritableType(), + sprintf('%s (%s) %s', $propertyDescription, $propertyType->describe(VerbosityLevel::typeOnly()), $operatorDescription) + ); + + if ($error !== null) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->check($expr->var, $scope, $operatorDescription, $error); + } + + if ($expr->class instanceof Expr) { + return $this->check($expr->class, $scope, $operatorDescription, $error); + } + } + + return $error; + } + + return $error ?? $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription)); + } + + private function generateError(Type $type, string $message): ?RuleError + { + $nullType = new NullType(); + + if ($type->equals($nullType)) { + return RuleErrorBuilder::message( + sprintf('%s is always null.', $message) + )->build(); + } + + if ($type->isSuperTypeOf($nullType)->no()) { + return RuleErrorBuilder::message( + sprintf('%s is not nullable.', $message) + )->build(); + } + + return null; + } } diff --git a/src/Rules/Keywords/ContinueBreakInLoopRule.php b/src/Rules/Keywords/ContinueBreakInLoopRule.php index 987ae48f5e..595b03ad9b 100644 --- a/src/Rules/Keywords/ContinueBreakInLoopRule.php +++ b/src/Rules/Keywords/ContinueBreakInLoopRule.php @@ -1,4 +1,6 @@ -num instanceof Node\Scalar\LNumber) { - $value = 1; - } else { - $value = $node->num->value; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof Stmt\Continue_ && !$node instanceof Stmt\Break_) { + return []; + } - $parent = $node->getAttribute('parent'); - while ($value > 0) { - if ( - $parent === null - || $parent instanceof Stmt\Function_ - || $parent instanceof Stmt\ClassMethod - || $parent instanceof Node\Expr\Closure - ) { - return [ - RuleErrorBuilder::message(sprintf( - 'Keyword %s used outside of a loop or a switch statement.', - $node instanceof Stmt\Continue_ ? 'continue' : 'break' - ))->nonIgnorable()->build(), - ]; - } - if ( - $parent instanceof Stmt\For_ - || $parent instanceof Stmt\Foreach_ - || $parent instanceof Stmt\Do_ - || $parent instanceof Stmt\While_ - ) { - $value--; - } - if ($parent instanceof Stmt\Case_) { - $value--; - $parent = $parent->getAttribute('parent'); - if (!$parent instanceof Stmt\Switch_) { - throw new \PHPStan\ShouldNotHappenException(); - } - } + if (!$node->num instanceof Node\Scalar\LNumber) { + $value = 1; + } else { + $value = $node->num->value; + } - $parent = $parent->getAttribute('parent'); - } + $parent = $node->getAttribute('parent'); + while ($value > 0) { + if ( + $parent === null + || $parent instanceof Stmt\Function_ + || $parent instanceof Stmt\ClassMethod + || $parent instanceof Node\Expr\Closure + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Keyword %s used outside of a loop or a switch statement.', + $node instanceof Stmt\Continue_ ? 'continue' : 'break' + ))->nonIgnorable()->build(), + ]; + } + if ( + $parent instanceof Stmt\For_ + || $parent instanceof Stmt\Foreach_ + || $parent instanceof Stmt\Do_ + || $parent instanceof Stmt\While_ + ) { + $value--; + } + if ($parent instanceof Stmt\Case_) { + $value--; + $parent = $parent->getAttribute('parent'); + if (!$parent instanceof Stmt\Switch_) { + throw new \PHPStan\ShouldNotHappenException(); + } + } - return []; - } + $parent = $parent->getAttribute('parent'); + } + return []; + } } diff --git a/src/Rules/LineRuleError.php b/src/Rules/LineRuleError.php index 0388b7fca7..cd53243b86 100644 --- a/src/Rules/LineRuleError.php +++ b/src/Rules/LineRuleError.php @@ -1,10 +1,10 @@ -isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $class = $scope->getClassReflection(); - if ($class->isAbstract()) { - return []; - } - - if (!$node->isAbstract()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Non-abstract class %s contains abstract method %s().', $class->getDisplayName(), $node->name->toString()))->nonIgnorable()->build(), - ]; - } - + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $class = $scope->getClassReflection(); + if ($class->isAbstract()) { + return []; + } + + if (!$node->isAbstract()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Non-abstract class %s contains abstract method %s().', $class->getDisplayName(), $node->name->toString()))->nonIgnorable()->build(), + ]; + } } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index d56d19120f..b6b7fc0e38 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->check = $check; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->checkFunctionNameCase = $checkFunctionNameCase; - $this->reportMagicMethods = $reportMagicMethods; - } - - public function getNodeType(): string - { - return MethodCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier) { - return []; - } - - $name = $node->name->name; - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->var, - sprintf('Call to method %s() on an unknown class %%s.', $name), - static function (Type $type) use ($name): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - if (!$type->canCallMethods()->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot call method %s() on %s.', - $name, - $type->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - - if (!$type->hasMethod($name)->yes()) { - $directClassNames = $typeResult->getReferencedClasses(); - if (!$this->reportMagicMethods) { - foreach ($directClassNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->hasNativeMethod('__call')) { - return []; - } - } - } - - if (count($directClassNames) === 1) { - $referencedClass = $directClassNames[0]; - $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); - $parentClassReflection = $methodClassReflection->getParentClass(); - while ($parentClassReflection !== false) { - if ($parentClassReflection->hasMethod($name)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Call to private method %s() of parent class %s.', - $parentClassReflection->getMethod($name, $scope)->getName(), - $parentClassReflection->getDisplayName() - ))->build(), - ]; - } - - $parentClassReflection = $parentClassReflection->getParentClass(); - } - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to an undefined method %s::%s().', - $type->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), - ]; - } - - $methodReflection = $type->getMethod($name, $scope); - $declaringClass = $methodReflection->getDeclaringClass(); - $messagesMethodName = $declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'; - $errors = []; - if (!$scope->canCallMethod($methodReflection)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Call to %s method %s() of class %s.', - $methodReflection->isPrivate() ? 'private' : 'protected', - $methodReflection->getName(), - $declaringClass->getDisplayName() - ))->build(); - } - - $errors = array_merge($errors, $this->check->check( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $node->args, - $methodReflection->getVariants() - ), - $scope, - $declaringClass->isBuiltin(), - $node, - [ - 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of method ' . $messagesMethodName . ' expects %s, %s given.', - 'Result of method ' . $messagesMethodName . ' (void) is used.', - 'Parameter %s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to method ' . $messagesMethodName, - 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', - 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', - ] - )); - - if ( - $this->checkFunctionNameCase - && strtolower($methodReflection->getName()) === strtolower($name) - && $methodReflection->getName() !== $name - ) { - $errors[] = RuleErrorBuilder::message( - sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $name) - )->build(); - } - - return $errors; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\FunctionCallParametersCheck $check; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private bool $checkFunctionNameCase; + + private bool $reportMagicMethods; + + public function __construct( + ReflectionProvider $reflectionProvider, + FunctionCallParametersCheck $check, + RuleLevelHelper $ruleLevelHelper, + bool $checkFunctionNameCase, + bool $reportMagicMethods + ) { + $this->reflectionProvider = $reflectionProvider; + $this->check = $check; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->checkFunctionNameCase = $checkFunctionNameCase; + $this->reportMagicMethods = $reportMagicMethods; + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + $name = $node->name->name; + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + sprintf('Call to method %s() on an unknown class %%s.', $name), + static function (Type $type) use ($name): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + if (!$type->canCallMethods()->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot call method %s() on %s.', + $name, + $type->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + + if (!$type->hasMethod($name)->yes()) { + $directClassNames = $typeResult->getReferencedClasses(); + if (!$this->reportMagicMethods) { + foreach ($directClassNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasNativeMethod('__call')) { + return []; + } + } + } + + if (count($directClassNames) === 1) { + $referencedClass = $directClassNames[0]; + $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); + $parentClassReflection = $methodClassReflection->getParentClass(); + while ($parentClassReflection !== false) { + if ($parentClassReflection->hasMethod($name)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to private method %s() of parent class %s.', + $parentClassReflection->getMethod($name, $scope)->getName(), + $parentClassReflection->getDisplayName() + ))->build(), + ]; + } + + $parentClassReflection = $parentClassReflection->getParentClass(); + } + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Call to an undefined method %s::%s().', + $type->describe(VerbosityLevel::typeOnly()), + $name + ))->build(), + ]; + } + + $methodReflection = $type->getMethod($name, $scope); + $declaringClass = $methodReflection->getDeclaringClass(); + $messagesMethodName = $declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'; + $errors = []; + if (!$scope->canCallMethod($methodReflection)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Call to %s method %s() of class %s.', + $methodReflection->isPrivate() ? 'private' : 'protected', + $methodReflection->getName(), + $declaringClass->getDisplayName() + ))->build(); + } + + $errors = array_merge($errors, $this->check->check( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->args, + $methodReflection->getVariants() + ), + $scope, + $declaringClass->isBuiltin(), + $node, + [ + 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of method ' . $messagesMethodName . ' expects %s, %s given.', + 'Result of method ' . $messagesMethodName . ' (void) is used.', + 'Parameter %s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to method ' . $messagesMethodName, + 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', + 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', + ] + )); + + if ( + $this->checkFunctionNameCase + && strtolower($methodReflection->getName()) === strtolower($name) + && $methodReflection->getName() !== $name + ) { + $errors[] = RuleErrorBuilder::message( + sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $name) + )->build(); + } + + return $errors; + } } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 6fb3a61777..76fc41991a 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->check = $check; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkFunctionNameCase = $checkFunctionNameCase; - $this->reportMagicMethods = $reportMagicMethods; - } - - public function getNodeType(): string - { - return StaticCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier) { - return []; - } - $methodName = $node->name->name; - - $class = $node->class; - $errors = []; - $isAbstract = false; - if ($class instanceof Name) { - $className = (string) $class; - $lowercasedClassName = strtolower($className); - if (in_array($lowercasedClassName, ['self', 'static'], true)) { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Calling %s::%s() outside of class scope.', - $className, - $methodName - ))->build(), - ]; - } - $classType = $scope->resolveTypeByName($class); - } elseif ($lowercasedClassName === 'parent') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Calling %s::%s() outside of class scope.', - $className, - $methodName - ))->build(), - ]; - } - $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === false) { - return [ - RuleErrorBuilder::message(sprintf( - '%s::%s() calls parent::%s() but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), - $scope->getFunctionName(), - $methodName, - $scope->getClassReflection()->getDisplayName() - ))->build(), - ]; - } - - if ($scope->getFunctionName() === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $classType = $scope->resolveTypeByName($class); - } else { - if (!$this->reflectionProvider->hasClass($className)) { - if ($scope->isInClassExists($className)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to static method %s() on an unknown class %s.', - $methodName, - $className - ))->discoveringSymbolsTip()->build(), - ]; - } else { - $errors = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); - } - - $classType = $scope->resolveTypeByName($class); - } - - $classReflection = $classType->getClassReflection(); - if ($classReflection !== null && $classReflection->hasNativeMethod($methodName) && $lowercasedClassName !== 'static') { - $nativeMethodReflection = $classReflection->getNativeMethod($methodName); - if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) { - $isAbstract = $nativeMethodReflection->isAbstract(); - } - } - } else { - $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $class, - sprintf('Call to static method %s() on an unknown class %%s.', $methodName), - static function (Type $type) use ($methodName): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); - } - ); - $classType = $classTypeResult->getType(); - if ($classType instanceof ErrorType) { - return $classTypeResult->getUnknownClassErrors(); - } - } - - if ($classType instanceof GenericClassStringType) { - $classType = $classType->getGenericType(); - if (!(new ObjectWithoutClassType())->isSuperTypeOf($classType)->yes()) { - return []; - } - } elseif ((new StringType())->isSuperTypeOf($classType)->yes()) { - return []; - } - - $typeForDescribe = $classType; - if ($classType instanceof ThisType) { - $typeForDescribe = $classType->getStaticObjectType(); - } - $classType = TypeCombinator::remove($classType, new StringType()); - - if (!$classType->canCallMethods()->yes()) { - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Cannot call static method %s() on %s.', - $methodName, - $typeForDescribe->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]); - } - - if (!$classType->hasMethod($methodName)->yes()) { - if (!$this->reportMagicMethods) { - $directClassNames = TypeUtils::getDirectClassNames($classType); - foreach ($directClassNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->hasNativeMethod('__callStatic')) { - return []; - } - } - } - - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Call to an undefined static method %s::%s().', - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - $methodName - ))->build(), - ]); - } - - $method = $classType->getMethod($methodName, $scope); - if (!$method->isStatic()) { - $function = $scope->getFunction(); - if ( - !$function instanceof MethodReflection - || $function->isStatic() - || !$scope->isInClass() - || ( - $classType instanceof TypeWithClassName - && $scope->getClassReflection()->getName() !== $classType->getClassName() - && !$scope->getClassReflection()->isSubclassOf($classType->getClassName()) - ) - ) { - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Static call to instance method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]); - } - } - - if (!$scope->canCallMethod($method)) { - $errors = array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s %s() of class %s.', - $method->isPrivate() ? 'private' : 'protected', - $method->isStatic() ? 'static method' : 'method', - $method->getName(), - $method->getDeclaringClass()->getDisplayName() - ))->build(), - ]); - } - - if ($isAbstract) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot call abstract%s method %s::%s().', - $method->isStatic() ? ' static' : '', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]; - } - - $lowercasedMethodName = sprintf( - '%s %s', - $method->isStatic() ? 'static method' : 'method', - $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' - ); - $displayMethodName = sprintf( - '%s %s', - $method->isStatic() ? 'Static method' : 'Method', - $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' - ); - - $errors = array_merge($errors, $this->check->check( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $node->args, - $method->getVariants() - ), - $scope, - $method->getDeclaringClass()->isBuiltin(), - $node, - [ - $displayMethodName . ' invoked with %d parameter, %d required.', - $displayMethodName . ' invoked with %d parameters, %d required.', - $displayMethodName . ' invoked with %d parameter, at least %d required.', - $displayMethodName . ' invoked with %d parameters, at least %d required.', - $displayMethodName . ' invoked with %d parameter, %d-%d required.', - $displayMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $lowercasedMethodName . ' expects %s, %s given.', - 'Result of ' . $lowercasedMethodName . ' (void) is used.', - 'Parameter %s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName, - 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', - 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', - ] - )); - - if ( - $this->checkFunctionNameCase - && $method->getName() !== $methodName - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Call to %s with incorrect case: %s', - $lowercasedMethodName, - $methodName - ))->build(); - } - - return $errors; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\FunctionCallParametersCheck $check; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private bool $checkFunctionNameCase; + + private bool $reportMagicMethods; + + public function __construct( + ReflectionProvider $reflectionProvider, + FunctionCallParametersCheck $check, + RuleLevelHelper $ruleLevelHelper, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkFunctionNameCase, + bool $reportMagicMethods + ) { + $this->reflectionProvider = $reflectionProvider; + $this->check = $check; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkFunctionNameCase = $checkFunctionNameCase; + $this->reportMagicMethods = $reportMagicMethods; + } + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + $methodName = $node->name->name; + + $class = $node->class; + $errors = []; + $isAbstract = false; + if ($class instanceof Name) { + $className = (string) $class; + $lowercasedClassName = strtolower($className); + if (in_array($lowercasedClassName, ['self', 'static'], true)) { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Calling %s::%s() outside of class scope.', + $className, + $methodName + ))->build(), + ]; + } + $classType = $scope->resolveTypeByName($class); + } elseif ($lowercasedClassName === 'parent') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Calling %s::%s() outside of class scope.', + $className, + $methodName + ))->build(), + ]; + } + $currentClassReflection = $scope->getClassReflection(); + if ($currentClassReflection->getParentClass() === false) { + return [ + RuleErrorBuilder::message(sprintf( + '%s::%s() calls parent::%s() but %s does not extend any class.', + $scope->getClassReflection()->getDisplayName(), + $scope->getFunctionName(), + $methodName, + $scope->getClassReflection()->getDisplayName() + ))->build(), + ]; + } + + if ($scope->getFunctionName() === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $classType = $scope->resolveTypeByName($class); + } else { + if (!$this->reflectionProvider->hasClass($className)) { + if ($scope->isInClassExists($className)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Call to static method %s() on an unknown class %s.', + $methodName, + $className + ))->discoveringSymbolsTip()->build(), + ]; + } else { + $errors = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); + } + + $classType = $scope->resolveTypeByName($class); + } + + $classReflection = $classType->getClassReflection(); + if ($classReflection !== null && $classReflection->hasNativeMethod($methodName) && $lowercasedClassName !== 'static') { + $nativeMethodReflection = $classReflection->getNativeMethod($methodName); + if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) { + $isAbstract = $nativeMethodReflection->isAbstract(); + } + } + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $class, + sprintf('Call to static method %s() on an unknown class %%s.', $methodName), + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + $classType = $classTypeResult->getType(); + if ($classType instanceof ErrorType) { + return $classTypeResult->getUnknownClassErrors(); + } + } + + if ($classType instanceof GenericClassStringType) { + $classType = $classType->getGenericType(); + if (!(new ObjectWithoutClassType())->isSuperTypeOf($classType)->yes()) { + return []; + } + } elseif ((new StringType())->isSuperTypeOf($classType)->yes()) { + return []; + } + + $typeForDescribe = $classType; + if ($classType instanceof ThisType) { + $typeForDescribe = $classType->getStaticObjectType(); + } + $classType = TypeCombinator::remove($classType, new StringType()); + + if (!$classType->canCallMethods()->yes()) { + return array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Cannot call static method %s() on %s.', + $methodName, + $typeForDescribe->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]); + } + + if (!$classType->hasMethod($methodName)->yes()) { + if (!$this->reportMagicMethods) { + $directClassNames = TypeUtils::getDirectClassNames($classType); + foreach ($directClassNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasNativeMethod('__callStatic')) { + return []; + } + } + } + + return array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Call to an undefined static method %s::%s().', + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + $methodName + ))->build(), + ]); + } + + $method = $classType->getMethod($methodName, $scope); + if (!$method->isStatic()) { + $function = $scope->getFunction(); + if ( + !$function instanceof MethodReflection + || $function->isStatic() + || !$scope->isInClass() + || ( + $classType instanceof TypeWithClassName + && $scope->getClassReflection()->getName() !== $classType->getClassName() + && !$scope->getClassReflection()->isSubclassOf($classType->getClassName()) + ) + ) { + return array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Static call to instance method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]); + } + } + + if (!$scope->canCallMethod($method)) { + $errors = array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s %s() of class %s.', + $method->isPrivate() ? 'private' : 'protected', + $method->isStatic() ? 'static method' : 'method', + $method->getName(), + $method->getDeclaringClass()->getDisplayName() + ))->build(), + ]); + } + + if ($isAbstract) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot call abstract%s method %s::%s().', + $method->isStatic() ? ' static' : '', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]; + } + + $lowercasedMethodName = sprintf( + '%s %s', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' + ); + $displayMethodName = sprintf( + '%s %s', + $method->isStatic() ? 'Static method' : 'Method', + $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' + ); + + $errors = array_merge($errors, $this->check->check( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->args, + $method->getVariants() + ), + $scope, + $method->getDeclaringClass()->isBuiltin(), + $node, + [ + $displayMethodName . ' invoked with %d parameter, %d required.', + $displayMethodName . ' invoked with %d parameters, %d required.', + $displayMethodName . ' invoked with %d parameter, at least %d required.', + $displayMethodName . ' invoked with %d parameters, at least %d required.', + $displayMethodName . ' invoked with %d parameter, %d-%d required.', + $displayMethodName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of ' . $lowercasedMethodName . ' expects %s, %s given.', + 'Result of ' . $lowercasedMethodName . ' (void) is used.', + 'Parameter %s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName, + 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', + 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', + ] + )); + + if ( + $this->checkFunctionNameCase + && $method->getName() !== $methodName + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Call to %s with incorrect case: %s', + $lowercasedMethodName, + $methodName + ))->build(); + } + + return $errors; + } } diff --git a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php index a42bc59fa5..027154caca 100644 --- a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$node->expr instanceof Node\Expr\New_) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } - $instantiation = $node->expr; - if (!$instantiation->class instanceof Node\Name) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$node->expr instanceof Node\Expr\New_) { + return []; + } - $className = $scope->resolveName($instantiation->class); - if (!$this->reflectionProvider->hasClass($className)) { - return []; - } + $instantiation = $node->expr; + if (!$instantiation->class instanceof Node\Name) { + return []; + } - $classReflection = $this->reflectionProvider->getClass($className); - if (!$classReflection->hasConstructor()) { - return []; - } + $className = $scope->resolveName($instantiation->class); + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } - $constructor = $classReflection->getConstructor(); - if ($constructor->hasSideEffects()->no()) { - $throwsType = $constructor->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; - } + $classReflection = $this->reflectionProvider->getClass($className); + if (!$classReflection->hasConstructor()) { + return []; + } - $methodResult = $scope->getType($instantiation); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } + $constructor = $classReflection->getConstructor(); + if ($constructor->hasSideEffects()->no()) { + $throwsType = $constructor->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s::%s() on a separate line has no effect.', - $classReflection->getDisplayName(), - $constructor->getName() - ))->build(), - ]; - } + $methodResult = $scope->getType($instantiation); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s::%s() on a separate line has no effect.', + $classReflection->getDisplayName(), + $constructor->getName() + ))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php index 7592c9bd18..9f1dc6f398 100644 --- a/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - if ($node->expr instanceof Node\Expr\NullsafeMethodCall) { - $scope = $scope->filterByTruthyValue(new Node\Expr\BinaryOp\NotIdentical($node->expr->var, new Node\Expr\ConstFetch(new Node\Name('null')))); - } elseif (!$node->expr instanceof Node\Expr\MethodCall) { - return []; - } + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } - $methodCall = $node->expr; - if (!$methodCall->name instanceof Node\Identifier) { - return []; - } - $methodName = $methodCall->name->toString(); + public function processNode(Node $node, Scope $scope): array + { + if ($node->expr instanceof Node\Expr\NullsafeMethodCall) { + $scope = $scope->filterByTruthyValue(new Node\Expr\BinaryOp\NotIdentical($node->expr->var, new Node\Expr\ConstFetch(new Node\Name('null')))); + } elseif (!$node->expr instanceof Node\Expr\MethodCall) { + return []; + } - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $methodCall->var, - '', - static function (Type $type) use ($methodName): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); - } - ); - $calledOnType = $typeResult->getType(); - if ($calledOnType instanceof ErrorType) { - return []; - } - if (!$calledOnType->canCallMethods()->yes()) { - return []; - } + $methodCall = $node->expr; + if (!$methodCall->name instanceof Node\Identifier) { + return []; + } + $methodName = $methodCall->name->toString(); - if (!$calledOnType->hasMethod($methodName)->yes()) { - return []; - } + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $methodCall->var, + '', + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + $calledOnType = $typeResult->getType(); + if ($calledOnType instanceof ErrorType) { + return []; + } + if (!$calledOnType->canCallMethods()->yes()) { + return []; + } - $method = $calledOnType->getMethod($methodName, $scope); - if ($method->hasSideEffects()->no()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; - } + if (!$calledOnType->hasMethod($methodName)->yes()) { + return []; + } - $methodResult = $scope->getType($methodCall); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } + $method = $calledOnType->getMethod($methodName, $scope); + if ($method->hasSideEffects()->no()) { + $throwsType = $method->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s::%s() on a separate line has no effect.', - $method->isStatic() ? 'static method' : 'method', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]; - } + $methodResult = $scope->getType($methodCall); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s::%s() on a separate line has no effect.', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php index 85d8a47a3b..a1ac9157fb 100644 --- a/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->expr instanceof Node\Expr\StaticCall) { - return []; - } - - $staticCall = $node->expr; - if (!$staticCall->name instanceof Node\Identifier) { - return []; - } - - $methodName = $staticCall->name->toString(); - if ($staticCall->class instanceof Node\Name) { - $className = $scope->resolveName($staticCall->class); - if (!$this->reflectionProvider->hasClass($className)) { - return []; - } - - $calledOnType = new ObjectType($className); - } else { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $staticCall->class, - '', - static function (Type $type) use ($methodName): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); - } - ); - $calledOnType = $typeResult->getType(); - if ($calledOnType instanceof ErrorType) { - return []; - } - } - - if (!$calledOnType->canCallMethods()->yes()) { - return []; - } - - if (!$calledOnType->hasMethod($methodName)->yes()) { - return []; - } - - $method = $calledOnType->getMethod($methodName, $scope); - if ( - ( - strtolower($method->getName()) === '__construct' - || strtolower($method->getName()) === strtolower($method->getDeclaringClass()->getName()) - ) - ) { - return []; - } - - if ($method->hasSideEffects()->no()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; - } - - $methodResult = $scope->getType($staticCall); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s::%s() on a separate line has no effect.', - $method->isStatic() ? 'static method' : 'method', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + ReflectionProvider $reflectionProvider + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Stmt\Expression::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->expr instanceof Node\Expr\StaticCall) { + return []; + } + + $staticCall = $node->expr; + if (!$staticCall->name instanceof Node\Identifier) { + return []; + } + + $methodName = $staticCall->name->toString(); + if ($staticCall->class instanceof Node\Name) { + $className = $scope->resolveName($staticCall->class); + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $calledOnType = new ObjectType($className); + } else { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $staticCall->class, + '', + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + $calledOnType = $typeResult->getType(); + if ($calledOnType instanceof ErrorType) { + return []; + } + } + + if (!$calledOnType->canCallMethods()->yes()) { + return []; + } + + if (!$calledOnType->hasMethod($methodName)->yes()) { + return []; + } + + $method = $calledOnType->getMethod($methodName, $scope); + if ( + ( + strtolower($method->getName()) === '__construct' + || strtolower($method->getName()) === strtolower($method->getDeclaringClass()->getName()) + ) + ) { + return []; + } + + if ($method->hasSideEffects()->no()) { + $throwsType = $method->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } + + $methodResult = $scope->getType($staticCall); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s::%s() on a separate line has no effect.', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 18d088b5a6..3cf14b0030 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -1,4 +1,6 @@ -check = $check; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $methodReflection = $scope->getFunction(); - if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->check->checkClassMethod( - $methodReflection, - $node->getOriginalNode(), - sprintf( - 'Parameter $%%s of method %s::%s() has invalid typehint type %%s.', - $scope->getClassReflection()->getDisplayName(), - $methodReflection->getName() - ), - sprintf( - 'Return typehint of method %s::%s() has invalid type %%s.', - $scope->getClassReflection()->getDisplayName(), - $methodReflection->getName() - ), - sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()), - sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()) - ); - } - + private \PHPStan\Rules\FunctionDefinitionCheck $check; + + public function __construct(FunctionDefinitionCheck $check) + { + $this->check = $check; + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->check->checkClassMethod( + $methodReflection, + $node->getOriginalNode(), + sprintf( + 'Parameter $%%s of method %s::%s() has invalid typehint type %%s.', + $scope->getClassReflection()->getDisplayName(), + $methodReflection->getName() + ), + sprintf( + 'Return typehint of method %s::%s() has invalid type %%s.', + $scope->getClassReflection()->getDisplayName(), + $methodReflection->getName() + ), + sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()), + sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()) + ); + } } diff --git a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php index 172b0170ee..27305342a6 100644 --- a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php @@ -1,4 +1,6 @@ -getFunction(); - if (!$method instanceof PhpMethodFromParserNodeReflection) { - return []; - } - - $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); + public function processNode(Node $node, Scope $scope): array + { + $method = $scope->getFunction(); + if (!$method instanceof PhpMethodFromParserNodeReflection) { + return []; + } - $errors = []; - foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { - if ($param->default === null) { - continue; - } - if ( - $param->var instanceof Node\Expr\Error - || !is_string($param->var->name) - ) { - throw new \PHPStan\ShouldNotHappenException(); - } + $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $defaultValueType = $scope->getType($param->default); - $parameterType = $parameters->getParameters()[$paramI]->getType(); - $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); + $errors = []; + foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { + if ($param->default === null) { + continue; + } + if ( + $param->var instanceof Node\Expr\Error + || !is_string($param->var->name) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } - if ($parameterType->accepts($defaultValueType, true)->yes()) { - continue; - } + $defaultValueType = $scope->getType($param->default); + $parameterType = $parameters->getParameters()[$paramI]->getType(); + $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $defaultValueType); + if ($parameterType->accepts($defaultValueType, true)->yes()) { + continue; + } - $errors[] = RuleErrorBuilder::message(sprintf( - 'Default value of the parameter #%d $%s (%s) of method %s::%s() is incompatible with type %s.', - $paramI + 1, - $param->var->name, - $defaultValueType->describe($verbosityLevel), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $parameterType->describe($verbosityLevel) - ))->line($param->getLine())->build(); - } + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $defaultValueType); - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Default value of the parameter #%d $%s (%s) of method %s::%s() is incompatible with type %s.', + $paramI + 1, + $param->var->name, + $defaultValueType->describe($verbosityLevel), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $parameterType->describe($verbosityLevel) + ))->line($param->getLine())->build(); + } + return $errors; + } } diff --git a/src/Rules/Methods/MethodAttributesRule.php b/src/Rules/Methods/MethodAttributesRule.php index 5e501f76d5..9065354cc2 100644 --- a/src/Rules/Methods/MethodAttributesRule.php +++ b/src/Rules/Methods/MethodAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Stmt\ClassMethod::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_METHOD, - 'method' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_METHOD, + 'method' + ); + } } diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 2599c46ea2..b29ee3e6de 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -1,4 +1,6 @@ -reportMaybes = $reportMaybes; - $this->reportStatic = $reportStatic; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $method = $scope->getFunction(); - if (!$method instanceof PhpMethodFromParserNodeReflection) { - return []; - } - - $methodName = $method->getName(); - if ($methodName === '__construct') { - return []; - } - if (!$this->reportStatic && $method->isStatic()) { - return []; - } - if ($method->isPrivate()) { - return []; - } - $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); - - $errors = []; - $declaringClass = $method->getDeclaringClass(); - foreach ($this->collectParentMethods($methodName, $method->getDeclaringClass()) as $parentMethod) { - $parentVariants = $parentMethod->getVariants(); - if (count($parentVariants) !== 1) { - continue; - } - $parentParameters = $parentVariants[0]; - if (!$parentParameters instanceof ParametersAcceptorWithPhpDocs) { - continue; - } - - [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $parameters, $parentParameters); - if ($returnTypeCompatibility->no() || (!$returnTypeCompatibility->yes() && $this->reportMaybes)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Return type (%s) of method %s::%s() should be %s with return type (%s) of method %s::%s()', - $returnType->describe(VerbosityLevel::value()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $returnTypeCompatibility->no() ? 'compatible' : 'covariant', - $parentReturnType->describe(VerbosityLevel::value()), - $parentMethod->getDeclaringClass()->getDisplayName(), - $parentMethod->getName() - ))->build(); - } - - $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $parameters->getParameters(), $parentParameters->getParameters()); - foreach ($parameterResults as $parameterIndex => [$parameterResult, $parameterType, $parentParameterType]) { - if ($parameterResult->yes()) { - continue; - } - if (!$parameterResult->no() && !$this->reportMaybes) { - continue; - } - $parameter = $parameters->getParameters()[$parameterIndex]; - $parentParameter = $parentParameters->getParameters()[$parameterIndex]; - $errors[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s (%s) of method %s::%s() should be %s with parameter $%s (%s) of method %s::%s()', - $parameterIndex + 1, - $parameter->getName(), - $parameterType->describe(VerbosityLevel::value()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $parameterResult->no() ? 'compatible' : 'contravariant', - $parentParameter->getName(), - $parentParameterType->describe(VerbosityLevel::value()), - $parentMethod->getDeclaringClass()->getDisplayName(), - $parentMethod->getName() - ))->build(); - } - } - - return $errors; - } - - /** - * @param string $methodName - * @param \PHPStan\Reflection\ClassReflection $class - * @return \PHPStan\Reflection\MethodReflection[] - */ - private function collectParentMethods(string $methodName, ClassReflection $class): array - { - $parentMethods = []; - - $parentClass = $class->getParentClass(); - if ($parentClass !== false && $parentClass->hasNativeMethod($methodName)) { - $parentMethod = $parentClass->getNativeMethod($methodName); - if (!$parentMethod->isPrivate()) { - $parentMethods[] = $parentMethod; - } - } - - foreach ($class->getInterfaces() as $interface) { - if (!$interface->hasNativeMethod($methodName)) { - continue; - } - - $parentMethods[] = $interface->getNativeMethod($methodName); - } - - return $parentMethods; - } - - /** - * @param ParametersAcceptorWithPhpDocs $currentVariant - * @param ParametersAcceptorWithPhpDocs $parentVariant - * @return array{TrinaryLogic, Type, Type} - */ - private function checkReturnTypeCompatibility( - ClassReflection $declaringClass, - ParametersAcceptorWithPhpDocs $currentVariant, - ParametersAcceptorWithPhpDocs $parentVariant - ): array - { - $returnType = TypehintHelper::decideType( - $currentVariant->getNativeReturnType(), - TemplateTypeHelper::resolveToBounds($currentVariant->getPhpDocReturnType()) - ); - $originalParentReturnType = TypehintHelper::decideType( - $parentVariant->getNativeReturnType(), - TemplateTypeHelper::resolveToBounds($parentVariant->getPhpDocReturnType()) - ); - $parentReturnType = $this->transformStaticType($declaringClass, $originalParentReturnType); - // Allow adding `void` return type hints when the parent defines no return type - if ($returnType instanceof VoidType && $parentReturnType instanceof MixedType) { - return [TrinaryLogic::createYes(), $returnType, $parentReturnType]; - } - - // We can return anything - if ($parentReturnType instanceof VoidType) { - return [TrinaryLogic::createYes(), $returnType, $parentReturnType]; - } - - return [$parentReturnType->isSuperTypeOf($returnType), TypehintHelper::decideType( - $currentVariant->getNativeReturnType(), - $currentVariant->getPhpDocReturnType() - ), $originalParentReturnType]; - } - - /** - * @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parameters - * @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parentParameters - * @return array - */ - private function checkParameterTypeCompatibility( - ClassReflection $declaringClass, - array $parameters, - array $parentParameters - ): array - { - $parameterResults = []; - - $numberOfParameters = min(count($parameters), count($parentParameters)); - for ($i = 0; $i < $numberOfParameters; $i++) { - $parameter = $parameters[$i]; - $parentParameter = $parentParameters[$i]; - - $parameterType = TypehintHelper::decideType( - $parameter->getNativeType(), - TemplateTypeHelper::resolveToBounds($parameter->getPhpDocType()) - ); - $originalParameterType = TypehintHelper::decideType( - $parentParameter->getNativeType(), - TemplateTypeHelper::resolveToBounds($parentParameter->getPhpDocType()) - ); - $parentParameterType = $this->transformStaticType($declaringClass, $originalParameterType); - - $parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), TypehintHelper::decideType( - $parameter->getNativeType(), - $parameter->getPhpDocType() - ), $originalParameterType]; - } - - return $parameterResults; - } - - private function transformStaticType(ClassReflection $declaringClass, Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { - if ($type instanceof StaticType) { - if ($declaringClass->isFinal()) { - $changedType = new ObjectType($declaringClass->getName()); - } else { - $changedType = $type->changeBaseClass($declaringClass); - } - return $traverse($changedType); - } - - return $traverse($type); - }); - } - + private bool $reportMaybes; + + private bool $reportStatic; + + public function __construct( + bool $reportMaybes, + bool $reportStatic + ) { + $this->reportMaybes = $reportMaybes; + $this->reportStatic = $reportStatic; + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $method = $scope->getFunction(); + if (!$method instanceof PhpMethodFromParserNodeReflection) { + return []; + } + + $methodName = $method->getName(); + if ($methodName === '__construct') { + return []; + } + if (!$this->reportStatic && $method->isStatic()) { + return []; + } + if ($method->isPrivate()) { + return []; + } + $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); + + $errors = []; + $declaringClass = $method->getDeclaringClass(); + foreach ($this->collectParentMethods($methodName, $method->getDeclaringClass()) as $parentMethod) { + $parentVariants = $parentMethod->getVariants(); + if (count($parentVariants) !== 1) { + continue; + } + $parentParameters = $parentVariants[0]; + if (!$parentParameters instanceof ParametersAcceptorWithPhpDocs) { + continue; + } + + [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $parameters, $parentParameters); + if ($returnTypeCompatibility->no() || (!$returnTypeCompatibility->yes() && $this->reportMaybes)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Return type (%s) of method %s::%s() should be %s with return type (%s) of method %s::%s()', + $returnType->describe(VerbosityLevel::value()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $returnTypeCompatibility->no() ? 'compatible' : 'covariant', + $parentReturnType->describe(VerbosityLevel::value()), + $parentMethod->getDeclaringClass()->getDisplayName(), + $parentMethod->getName() + ))->build(); + } + + $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $parameters->getParameters(), $parentParameters->getParameters()); + foreach ($parameterResults as $parameterIndex => [$parameterResult, $parameterType, $parentParameterType]) { + if ($parameterResult->yes()) { + continue; + } + if (!$parameterResult->no() && !$this->reportMaybes) { + continue; + } + $parameter = $parameters->getParameters()[$parameterIndex]; + $parentParameter = $parentParameters->getParameters()[$parameterIndex]; + $errors[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s (%s) of method %s::%s() should be %s with parameter $%s (%s) of method %s::%s()', + $parameterIndex + 1, + $parameter->getName(), + $parameterType->describe(VerbosityLevel::value()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $parameterResult->no() ? 'compatible' : 'contravariant', + $parentParameter->getName(), + $parentParameterType->describe(VerbosityLevel::value()), + $parentMethod->getDeclaringClass()->getDisplayName(), + $parentMethod->getName() + ))->build(); + } + } + + return $errors; + } + + /** + * @param string $methodName + * @param \PHPStan\Reflection\ClassReflection $class + * @return \PHPStan\Reflection\MethodReflection[] + */ + private function collectParentMethods(string $methodName, ClassReflection $class): array + { + $parentMethods = []; + + $parentClass = $class->getParentClass(); + if ($parentClass !== false && $parentClass->hasNativeMethod($methodName)) { + $parentMethod = $parentClass->getNativeMethod($methodName); + if (!$parentMethod->isPrivate()) { + $parentMethods[] = $parentMethod; + } + } + + foreach ($class->getInterfaces() as $interface) { + if (!$interface->hasNativeMethod($methodName)) { + continue; + } + + $parentMethods[] = $interface->getNativeMethod($methodName); + } + + return $parentMethods; + } + + /** + * @param ParametersAcceptorWithPhpDocs $currentVariant + * @param ParametersAcceptorWithPhpDocs $parentVariant + * @return array{TrinaryLogic, Type, Type} + */ + private function checkReturnTypeCompatibility( + ClassReflection $declaringClass, + ParametersAcceptorWithPhpDocs $currentVariant, + ParametersAcceptorWithPhpDocs $parentVariant + ): array { + $returnType = TypehintHelper::decideType( + $currentVariant->getNativeReturnType(), + TemplateTypeHelper::resolveToBounds($currentVariant->getPhpDocReturnType()) + ); + $originalParentReturnType = TypehintHelper::decideType( + $parentVariant->getNativeReturnType(), + TemplateTypeHelper::resolveToBounds($parentVariant->getPhpDocReturnType()) + ); + $parentReturnType = $this->transformStaticType($declaringClass, $originalParentReturnType); + // Allow adding `void` return type hints when the parent defines no return type + if ($returnType instanceof VoidType && $parentReturnType instanceof MixedType) { + return [TrinaryLogic::createYes(), $returnType, $parentReturnType]; + } + + // We can return anything + if ($parentReturnType instanceof VoidType) { + return [TrinaryLogic::createYes(), $returnType, $parentReturnType]; + } + + return [$parentReturnType->isSuperTypeOf($returnType), TypehintHelper::decideType( + $currentVariant->getNativeReturnType(), + $currentVariant->getPhpDocReturnType() + ), $originalParentReturnType]; + } + + /** + * @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parameters + * @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parentParameters + * @return array + */ + private function checkParameterTypeCompatibility( + ClassReflection $declaringClass, + array $parameters, + array $parentParameters + ): array { + $parameterResults = []; + + $numberOfParameters = min(count($parameters), count($parentParameters)); + for ($i = 0; $i < $numberOfParameters; $i++) { + $parameter = $parameters[$i]; + $parentParameter = $parentParameters[$i]; + + $parameterType = TypehintHelper::decideType( + $parameter->getNativeType(), + TemplateTypeHelper::resolveToBounds($parameter->getPhpDocType()) + ); + $originalParameterType = TypehintHelper::decideType( + $parentParameter->getNativeType(), + TemplateTypeHelper::resolveToBounds($parentParameter->getPhpDocType()) + ); + $parentParameterType = $this->transformStaticType($declaringClass, $originalParameterType); + + $parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), TypehintHelper::decideType( + $parameter->getNativeType(), + $parameter->getPhpDocType() + ), $originalParameterType]; + } + + return $parameterResults; + } + + private function transformStaticType(ClassReflection $declaringClass, Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { + if ($type instanceof StaticType) { + if ($declaringClass->isFinal()) { + $changedType = new ObjectType($declaringClass->getName()); + } else { + $changedType = $type->changeBaseClass($declaringClass); + } + return $traverse($changedType); + } + + return $traverse($type); + }); + } } diff --git a/src/Rules/Methods/MissingMethodImplementationRule.php b/src/Rules/Methods/MissingMethodImplementationRule.php index 201ce41461..eee890ac5e 100644 --- a/src/Rules/Methods/MissingMethodImplementationRule.php +++ b/src/Rules/Methods/MissingMethodImplementationRule.php @@ -1,4 +1,6 @@ -getClassReflection(); - if ($classReflection->isInterface()) { - return []; - } - if ($classReflection->isAbstract()) { - return []; - } - - $messages = []; - - try { - $nativeMethods = $classReflection->getNativeReflection()->getMethods(); - } catch (IdentifierNotFound $e) { - return []; - } - foreach ($nativeMethods as $method) { - if (!$method->isAbstract()) { - continue; - } - - $declaringClass = $method->getDeclaringClass(); - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Non-abstract class %s contains abstract method %s() from %s %s.', - $classReflection->getDisplayName(), - $method->getName(), - $declaringClass->isInterface() ? 'interface' : 'class', - $declaringClass->getName() - ))->nonIgnorable()->build(); - } - - return $messages; - } - + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + if ($classReflection->isInterface()) { + return []; + } + if ($classReflection->isAbstract()) { + return []; + } + + $messages = []; + + try { + $nativeMethods = $classReflection->getNativeReflection()->getMethods(); + } catch (IdentifierNotFound $e) { + return []; + } + foreach ($nativeMethods as $method) { + if (!$method->isAbstract()) { + continue; + } + + $declaringClass = $method->getDeclaringClass(); + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Non-abstract class %s contains abstract method %s() from %s %s.', + $classReflection->getDisplayName(), + $method->getName(), + $declaringClass->isInterface() ? 'interface' : 'class', + $declaringClass->getName() + ))->nonIgnorable()->build(); + } + + return $messages; + } } diff --git a/src/Rules/Methods/MissingMethodParameterTypehintRule.php b/src/Rules/Methods/MissingMethodParameterTypehintRule.php index 13a7c2983a..0e8484ef68 100644 --- a/src/Rules/Methods/MissingMethodParameterTypehintRule.php +++ b/src/Rules/Methods/MissingMethodParameterTypehintRule.php @@ -1,4 +1,6 @@ -missingTypehintCheck = $missingTypehintCheck; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $methodReflection = $scope->getFunction(); - if (!$methodReflection instanceof MethodReflection) { - return []; - } - - $messages = []; - - foreach (ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getParameters() as $parameterReflection) { - foreach ($this->checkMethodParameter($methodReflection, $parameterReflection) as $parameterMessage) { - $messages[] = $parameterMessage; - } - } - - return $messages; - } - - /** - * @param \PHPStan\Reflection\MethodReflection $methodReflection - * @param \PHPStan\Reflection\ParameterReflection $parameterReflection - * @return \PHPStan\Rules\RuleError[] - */ - private function checkMethodParameter(MethodReflection $methodReflection, ParameterReflection $parameterReflection): array - { - $parameterType = $parameterReflection->getType(); - - if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has parameter $%s with no typehint specified.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $parameterReflection->getName() - ))->build(), - ]; - } - - $messages = []; - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has parameter $%s with no value type specified in iterable type %s.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $parameterReflection->getName(), - $iterableTypeDescription - ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has parameter $%s with generic %s but does not specify its types: %s', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $parameterReflection->getName(), - $name, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } - - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has parameter $%s with no signature specified for %s.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $parameterReflection->getName(), - $callableType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - return $messages; - } - + private \PHPStan\Rules\MissingTypehintCheck $missingTypehintCheck; + + public function __construct(MissingTypehintCheck $missingTypehintCheck) + { + $this->missingTypehintCheck = $missingTypehintCheck; + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof MethodReflection) { + return []; + } + + $messages = []; + + foreach (ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getParameters() as $parameterReflection) { + foreach ($this->checkMethodParameter($methodReflection, $parameterReflection) as $parameterMessage) { + $messages[] = $parameterMessage; + } + } + + return $messages; + } + + /** + * @param \PHPStan\Reflection\MethodReflection $methodReflection + * @param \PHPStan\Reflection\ParameterReflection $parameterReflection + * @return \PHPStan\Rules\RuleError[] + */ + private function checkMethodParameter(MethodReflection $methodReflection, ParameterReflection $parameterReflection): array + { + $parameterType = $parameterReflection->getType(); + + if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has parameter $%s with no typehint specified.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $parameterReflection->getName() + ))->build(), + ]; + } + + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has parameter $%s with no value type specified in iterable type %s.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $parameterReflection->getName(), + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has parameter $%s with generic %s but does not specify its types: %s', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $parameterReflection->getName(), + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has parameter $%s with no signature specified for %s.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $parameterReflection->getName(), + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $messages; + } } diff --git a/src/Rules/Methods/MissingMethodReturnTypehintRule.php b/src/Rules/Methods/MissingMethodReturnTypehintRule.php index c8779f0e2a..88ad0ffbe3 100644 --- a/src/Rules/Methods/MissingMethodReturnTypehintRule.php +++ b/src/Rules/Methods/MissingMethodReturnTypehintRule.php @@ -1,4 +1,6 @@ -missingTypehintCheck = $missingTypehintCheck; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } + public function __construct(MissingTypehintCheck $missingTypehintCheck) + { + $this->missingTypehintCheck = $missingTypehintCheck; + } - public function processNode(Node $node, Scope $scope): array - { - $methodReflection = $scope->getFunction(); - if (!$methodReflection instanceof MethodReflection) { - return []; - } + public function getNodeType(): string + { + return InClassMethodNode::class; + } - $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + public function processNode(Node $node, Scope $scope): array + { + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof MethodReflection) { + return []; + } - if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has no return typehint specified.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName() - ))->build(), - ]; - } + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - $messages = []; - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($returnType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() return type has no value type specified in iterable type %s.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $iterableTypeDescription - ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } + if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has no return typehint specified.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName() + ))->build(), + ]; + } - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($returnType) as [$name, $genericTypeNames]) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() return type with generic %s does not specify its types: %s', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $name, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($returnType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() return type has no value type specified in iterable type %s.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($returnType) as $callableType) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() return type has no signature specified for %s.', - $methodReflection->getDeclaringClass()->getDisplayName(), - $methodReflection->getName(), - $callableType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($returnType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() return type with generic %s does not specify its types: %s', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } - return $messages; - } + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($returnType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() return type has no signature specified for %s.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + return $messages; + } } diff --git a/src/Rules/Methods/NullsafeMethodCallRule.php b/src/Rules/Methods/NullsafeMethodCallRule.php index a70bf220f2..149bee59a0 100644 --- a/src/Rules/Methods/NullsafeMethodCallRule.php +++ b/src/Rules/Methods/NullsafeMethodCallRule.php @@ -1,4 +1,6 @@ -getType($node->var); - if ($calledOnType->equals($nullType)) { - return []; - } - - if (!$calledOnType->isSuperTypeOf($nullType)->no()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Using nullsafe method call on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))->build(), - ]; - } - + public function getNodeType(): string + { + return Node\Expr\NullsafeMethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $nullType = new NullType(); + $calledOnType = $scope->getType($node->var); + if ($calledOnType->equals($nullType)) { + return []; + } + + if (!$calledOnType->isSuperTypeOf($nullType)->no()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Using nullsafe method call on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))->build(), + ]; + } } diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 059c21df82..ce5af6db1d 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - $this->methodSignatureRule = $methodSignatureRule; - $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; - } - - public function getNodeType(): string - { - return InClassMethodNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $method = $scope->getFunction(); - if (!$method instanceof PhpMethodFromParserNodeReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $prototype = $method->getPrototype(); - if ($prototype->getDeclaringClass()->getName() === $method->getDeclaringClass()->getName()) { - if (strtolower($method->getName()) === '__construct') { - $parent = $method->getDeclaringClass()->getParentClass(); - if ($parent !== false && $parent->hasConstructor()) { - $parentConstructor = $parent->getConstructor(); - if ($parentConstructor->isFinal()->yes()) { - return $this->addErrors([ - RuleErrorBuilder::message(sprintf( - 'Method %s::%s() overrides final method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $parent->getDisplayName(), - $parentConstructor->getName() - ))->nonIgnorable()->build(), - ], $node, $scope); - } - } - } - - return []; - } - - if (!$prototype instanceof MethodPrototypeReflection) { - return []; - } - - $messages = []; - if ($prototype->isFinal()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() overrides final method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - - if ($prototype->isStatic()) { - if (!$method->isStatic()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Non-static method %s::%s() overrides static method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - } elseif ($method->isStatic()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Static method %s::%s() overrides non-static method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - - if ($prototype->isPublic()) { - if (!$method->isPublic()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s method %s::%s() overriding public method %s::%s() should also be public.', - $method->isPrivate() ? 'Private' : 'Protected', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - } elseif ($method->isPrivate()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Private method %s::%s() overriding protected method %s::%s() should be protected or public.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - - $prototypeVariants = $prototype->getVariants(); - if (count($prototypeVariants) !== 1) { - return $this->addErrors($messages, $node, $scope); - } - - $prototypeVariant = $prototypeVariants[0]; - - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodParameters = $methodVariant->getParameters(); - - $prototypeAfterVariadic = false; - foreach ($prototypeVariant->getParameters() as $i => $prototypeParameter) { - if (!array_key_exists($i, $methodParameters)) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() overrides method %s::%s() but misses parameter #%d $%s.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName(), - $i + 1, - $prototypeParameter->getName() - ))->nonIgnorable()->build(); - continue; - } - - $methodParameter = $methodParameters[$i]; - if ($prototypeParameter->passedByReference()->no()) { - if (!$methodParameter->passedByReference()->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is passed by reference but parameter #%d $%s of method %s::%s() is not passed by reference.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - } elseif ($methodParameter->passedByReference()->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not passed by reference but parameter #%d $%s of method %s::%s() is passed by reference.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - - if ($prototypeParameter->isVariadic()) { - $prototypeAfterVariadic = true; - if (!$methodParameter->isVariadic()) { - if (!$methodParameter->isOptional()) { - if (count($methodParameters) !== $i + 1) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not optional.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->nonIgnorable()->build(); - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not variadic but parameter #%d $%s of method %s::%s() is variadic.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - continue; - } elseif (count($methodParameters) === $i + 1) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not variadic.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->nonIgnorable()->build(); - } - } - } elseif ($methodParameter->isVariadic()) { - if ($this->phpVersion->supportsLessOverridenParametersWithVariadic()) { - $remainingPrototypeParameters = array_slice($prototypeVariant->getParameters(), $i); - foreach ($remainingPrototypeParameters as $j => $remainingPrototypeParameter) { - if (!$remainingPrototypeParameter instanceof ParameterReflectionWithPhpDocs) { - continue; - } - if ($methodParameter->getNativeType()->isSuperTypeOf($remainingPrototypeParameter->getNativeType())->yes()) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d ...$%s (%s) of method %s::%s() is not contravariant with parameter #%d $%s (%s) of method %s::%s().', - $i + 1, - $methodParameter->getName(), - $methodParameter->getNativeType()->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + $j + 1, - $remainingPrototypeParameter->getName(), - $remainingPrototypeParameter->getNativeType()->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - break; - } - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is variadic but parameter #%d $%s of method %s::%s() is not variadic.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - continue; - } - - if ($prototypeParameter->isOptional() && !$methodParameter->isOptional()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is required but parameter #%d $%s of method %s::%s() is optional.', - $i + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - - $methodParameterType = $methodParameter->getNativeType(); - - if (!$prototypeParameter instanceof ParameterReflectionWithPhpDocs) { - continue; - } - - $prototypeParameterType = $prototypeParameter->getNativeType(); - if (!$this->phpVersion->supportsParameterTypeWidening()) { - if (!$methodParameterType->equals($prototypeParameterType)) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s (%s) of method %s::%s() does not match parameter #%d $%s (%s) of method %s::%s().', - $i + 1, - $methodParameter->getName(), - $methodParameterType->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - continue; - } - - if ($this->isTypeCompatible($methodParameterType, $prototypeParameterType, $this->phpVersion->supportsParameterContravariance())) { - continue; - } - - if ($this->phpVersion->supportsParameterContravariance()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s (%s) of method %s::%s() is not contravariant with parameter #%d $%s (%s) of method %s::%s().', - $i + 1, - $methodParameter->getName(), - $methodParameterType->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } else { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s (%s) of method %s::%s() is not compatible with parameter #%d $%s (%s) of method %s::%s().', - $i + 1, - $methodParameter->getName(), - $methodParameterType->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $i + 1, - $prototypeParameter->getName(), - $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - } - - if (!isset($i)) { - $i = -1; - } - - foreach ($methodParameters as $j => $methodParameter) { - if ($j <= $i) { - continue; - } - - if ( - $j === count($methodParameters) - 1 - && $prototypeAfterVariadic - && !$methodParameter->isVariadic() - ) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not variadic.', - $j + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->nonIgnorable()->build(); - continue; - } - - if (!$methodParameter->isOptional()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Parameter #%d $%s of method %s::%s() is not optional.', - $j + 1, - $methodParameter->getName(), - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->nonIgnorable()->build(); - continue; - } - } - - $methodReturnType = $methodVariant->getNativeReturnType(); - - if (!$prototypeVariant instanceof FunctionVariantWithPhpDocs) { - return $this->addErrors($messages, $node, $scope); - } - - $prototypeReturnType = $prototypeVariant->getNativeReturnType(); - - if (!$this->isTypeCompatible($prototypeReturnType, $methodReturnType, $this->phpVersion->supportsReturnCovariance())) { - if ($this->phpVersion->supportsReturnCovariance()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Return type %s of method %s::%s() is not covariant with return type %s of method %s::%s().', - $methodReturnType->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototypeReturnType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } else { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Return type %s of method %s::%s() is not compatible with return type %s of method %s::%s().', - $methodReturnType->describe(VerbosityLevel::typeOnly()), - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototypeReturnType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $prototype->getName() - ))->nonIgnorable()->build(); - } - } - - return $this->addErrors($messages, $node, $scope); - } - - private function isTypeCompatible(Type $methodParameterType, Type $prototypeParameterType, bool $supportsContravariance): bool - { - if ($methodParameterType instanceof MixedType) { - return true; - } - - if (!$supportsContravariance) { - if (TypeCombinator::containsNull($methodParameterType)) { - $prototypeParameterType = TypeCombinator::removeNull($prototypeParameterType); - } - $methodParameterType = TypeCombinator::removeNull($methodParameterType); - if ($methodParameterType->equals($prototypeParameterType)) { - return true; - } - - if ($methodParameterType instanceof IterableType) { - if ($prototypeParameterType instanceof ArrayType) { - return true; - } - if ($prototypeParameterType instanceof ObjectType && $prototypeParameterType->getClassName() === \Traversable::class) { - return true; - } - } - - return false; - } - - return $methodParameterType->isSuperTypeOf($prototypeParameterType)->yes(); - } - - /** - * @param RuleError[] $errors - * @return (string|RuleError)[] - */ - private function addErrors( - array $errors, - InClassMethodNode $classMethod, - Scope $scope - ): array - { - if (count($errors) > 0) { - return $errors; - } - - if (!$this->checkPhpDocMethodSignatures) { - return $errors; - } - - return $this->methodSignatureRule->processNode($classMethod, $scope); - } - + private PhpVersion $phpVersion; + + private MethodSignatureRule $methodSignatureRule; + + private bool $checkPhpDocMethodSignatures; + + public function __construct( + PhpVersion $phpVersion, + MethodSignatureRule $methodSignatureRule, + bool $checkPhpDocMethodSignatures + ) { + $this->phpVersion = $phpVersion; + $this->methodSignatureRule = $methodSignatureRule; + $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $method = $scope->getFunction(); + if (!$method instanceof PhpMethodFromParserNodeReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $prototype = $method->getPrototype(); + if ($prototype->getDeclaringClass()->getName() === $method->getDeclaringClass()->getName()) { + if (strtolower($method->getName()) === '__construct') { + $parent = $method->getDeclaringClass()->getParentClass(); + if ($parent !== false && $parent->hasConstructor()) { + $parentConstructor = $parent->getConstructor(); + if ($parentConstructor->isFinal()->yes()) { + return $this->addErrors([ + RuleErrorBuilder::message(sprintf( + 'Method %s::%s() overrides final method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $parent->getDisplayName(), + $parentConstructor->getName() + ))->nonIgnorable()->build(), + ], $node, $scope); + } + } + } + + return []; + } + + if (!$prototype instanceof MethodPrototypeReflection) { + return []; + } + + $messages = []; + if ($prototype->isFinal()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() overrides final method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + if ($prototype->isStatic()) { + if (!$method->isStatic()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Non-static method %s::%s() overrides static method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } elseif ($method->isStatic()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Static method %s::%s() overrides non-static method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + if ($prototype->isPublic()) { + if (!$method->isPublic()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s method %s::%s() overriding public method %s::%s() should also be public.', + $method->isPrivate() ? 'Private' : 'Protected', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } elseif ($method->isPrivate()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Private method %s::%s() overriding protected method %s::%s() should be protected or public.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + $prototypeVariants = $prototype->getVariants(); + if (count($prototypeVariants) !== 1) { + return $this->addErrors($messages, $node, $scope); + } + + $prototypeVariant = $prototypeVariants[0]; + + $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodParameters = $methodVariant->getParameters(); + + $prototypeAfterVariadic = false; + foreach ($prototypeVariant->getParameters() as $i => $prototypeParameter) { + if (!array_key_exists($i, $methodParameters)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() overrides method %s::%s() but misses parameter #%d $%s.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName(), + $i + 1, + $prototypeParameter->getName() + ))->nonIgnorable()->build(); + continue; + } + + $methodParameter = $methodParameters[$i]; + if ($prototypeParameter->passedByReference()->no()) { + if (!$methodParameter->passedByReference()->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is passed by reference but parameter #%d $%s of method %s::%s() is not passed by reference.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } elseif ($methodParameter->passedByReference()->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not passed by reference but parameter #%d $%s of method %s::%s() is passed by reference.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + if ($prototypeParameter->isVariadic()) { + $prototypeAfterVariadic = true; + if (!$methodParameter->isVariadic()) { + if (!$methodParameter->isOptional()) { + if (count($methodParameters) !== $i + 1) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not optional.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->nonIgnorable()->build(); + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not variadic but parameter #%d $%s of method %s::%s() is variadic.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + continue; + } elseif (count($methodParameters) === $i + 1) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not variadic.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->nonIgnorable()->build(); + } + } + } elseif ($methodParameter->isVariadic()) { + if ($this->phpVersion->supportsLessOverridenParametersWithVariadic()) { + $remainingPrototypeParameters = array_slice($prototypeVariant->getParameters(), $i); + foreach ($remainingPrototypeParameters as $j => $remainingPrototypeParameter) { + if (!$remainingPrototypeParameter instanceof ParameterReflectionWithPhpDocs) { + continue; + } + if ($methodParameter->getNativeType()->isSuperTypeOf($remainingPrototypeParameter->getNativeType())->yes()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d ...$%s (%s) of method %s::%s() is not contravariant with parameter #%d $%s (%s) of method %s::%s().', + $i + 1, + $methodParameter->getName(), + $methodParameter->getNativeType()->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + $j + 1, + $remainingPrototypeParameter->getName(), + $remainingPrototypeParameter->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + break; + } + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is variadic but parameter #%d $%s of method %s::%s() is not variadic.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + continue; + } + + if ($prototypeParameter->isOptional() && !$methodParameter->isOptional()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is required but parameter #%d $%s of method %s::%s() is optional.', + $i + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + $methodParameterType = $methodParameter->getNativeType(); + + if (!$prototypeParameter instanceof ParameterReflectionWithPhpDocs) { + continue; + } + + $prototypeParameterType = $prototypeParameter->getNativeType(); + if (!$this->phpVersion->supportsParameterTypeWidening()) { + if (!$methodParameterType->equals($prototypeParameterType)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s (%s) of method %s::%s() does not match parameter #%d $%s (%s) of method %s::%s().', + $i + 1, + $methodParameter->getName(), + $methodParameterType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototypeParameterType->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + continue; + } + + if ($this->isTypeCompatible($methodParameterType, $prototypeParameterType, $this->phpVersion->supportsParameterContravariance())) { + continue; + } + + if ($this->phpVersion->supportsParameterContravariance()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s (%s) of method %s::%s() is not contravariant with parameter #%d $%s (%s) of method %s::%s().', + $i + 1, + $methodParameter->getName(), + $methodParameterType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototypeParameterType->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } else { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s (%s) of method %s::%s() is not compatible with parameter #%d $%s (%s) of method %s::%s().', + $i + 1, + $methodParameter->getName(), + $methodParameterType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $i + 1, + $prototypeParameter->getName(), + $prototypeParameterType->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } + + if (!isset($i)) { + $i = -1; + } + + foreach ($methodParameters as $j => $methodParameter) { + if ($j <= $i) { + continue; + } + + if ( + $j === count($methodParameters) - 1 + && $prototypeAfterVariadic + && !$methodParameter->isVariadic() + ) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not variadic.', + $j + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->nonIgnorable()->build(); + continue; + } + + if (!$methodParameter->isOptional()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Parameter #%d $%s of method %s::%s() is not optional.', + $j + 1, + $methodParameter->getName(), + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->nonIgnorable()->build(); + continue; + } + } + + $methodReturnType = $methodVariant->getNativeReturnType(); + + if (!$prototypeVariant instanceof FunctionVariantWithPhpDocs) { + return $this->addErrors($messages, $node, $scope); + } + + $prototypeReturnType = $prototypeVariant->getNativeReturnType(); + + if (!$this->isTypeCompatible($prototypeReturnType, $methodReturnType, $this->phpVersion->supportsReturnCovariance())) { + if ($this->phpVersion->supportsReturnCovariance()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Return type %s of method %s::%s() is not covariant with return type %s of method %s::%s().', + $methodReturnType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototypeReturnType->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } else { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Return type %s of method %s::%s() is not compatible with return type %s of method %s::%s().', + $methodReturnType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototypeReturnType->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } + + return $this->addErrors($messages, $node, $scope); + } + + private function isTypeCompatible(Type $methodParameterType, Type $prototypeParameterType, bool $supportsContravariance): bool + { + if ($methodParameterType instanceof MixedType) { + return true; + } + + if (!$supportsContravariance) { + if (TypeCombinator::containsNull($methodParameterType)) { + $prototypeParameterType = TypeCombinator::removeNull($prototypeParameterType); + } + $methodParameterType = TypeCombinator::removeNull($methodParameterType); + if ($methodParameterType->equals($prototypeParameterType)) { + return true; + } + + if ($methodParameterType instanceof IterableType) { + if ($prototypeParameterType instanceof ArrayType) { + return true; + } + if ($prototypeParameterType instanceof ObjectType && $prototypeParameterType->getClassName() === \Traversable::class) { + return true; + } + } + + return false; + } + + return $methodParameterType->isSuperTypeOf($prototypeParameterType)->yes(); + } + + /** + * @param RuleError[] $errors + * @return (string|RuleError)[] + */ + private function addErrors( + array $errors, + InClassMethodNode $classMethod, + Scope $scope + ): array { + if (count($errors) > 0) { + return $errors; + } + + if (!$this->checkPhpDocMethodSignatures) { + return $errors; + } + + return $this->methodSignatureRule->processNode($classMethod, $scope); + } } diff --git a/src/Rules/Methods/ReturnTypeRule.php b/src/Rules/Methods/ReturnTypeRule.php index 451eb96a26..e9b332d0fb 100644 --- a/src/Rules/Methods/ReturnTypeRule.php +++ b/src/Rules/Methods/ReturnTypeRule.php @@ -1,4 +1,6 @@ -returnTypeCheck = $returnTypeCheck; - } - - public function getNodeType(): string - { - return Return_::class; - } + public function __construct(FunctionReturnTypeCheck $returnTypeCheck) + { + $this->returnTypeCheck = $returnTypeCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if ($scope->getFunction() === null) { - return []; - } + public function getNodeType(): string + { + return Return_::class; + } - if ($scope->isInAnonymousFunction()) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if ($scope->getFunction() === null) { + return []; + } - $method = $scope->getFunction(); - if (!($method instanceof MethodReflection)) { - return []; - } + if ($scope->isInAnonymousFunction()) { + return []; + } - $reflection = null; - if ($method->getDeclaringClass()->getNativeReflection()->hasMethod($method->getName())) { - $reflection = $method->getDeclaringClass()->getNativeReflection()->getMethod($method->getName()); - } + $method = $scope->getFunction(); + if (!($method instanceof MethodReflection)) { + return []; + } - return $this->returnTypeCheck->checkReturnType( - $scope, - ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(), - $node->expr, - $node, - sprintf( - 'Method %s::%s() should return %%s but empty return statement found.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ), - sprintf( - 'Method %s::%s() with return type void returns %%s but should not return anything.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ), - sprintf( - 'Method %s::%s() should return %%s but returns %%s.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ), - sprintf( - 'Method %s::%s() should never return but return statement found.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ), - $reflection !== null && $reflection->isGenerator() - ); - } + $reflection = null; + if ($method->getDeclaringClass()->getNativeReflection()->hasMethod($method->getName())) { + $reflection = $method->getDeclaringClass()->getNativeReflection()->getMethod($method->getName()); + } + return $this->returnTypeCheck->checkReturnType( + $scope, + ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(), + $node->expr, + $node, + sprintf( + 'Method %s::%s() should return %%s but empty return statement found.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ), + sprintf( + 'Method %s::%s() with return type void returns %%s but should not return anything.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ), + sprintf( + 'Method %s::%s() should return %%s but returns %%s.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ), + sprintf( + 'Method %s::%s() should never return but return statement found.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ), + $reflection !== null && $reflection->isGenerator() + ); + } } diff --git a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php b/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php index b9154d40c2..8e81f262ad 100644 --- a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php +++ b/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php @@ -1,4 +1,6 @@ -checkObjectTypehint = $checkObjectTypehint; - } - - public function getNodeType(): string - { - return ClosureReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $closure = $node->getClosureExpr(); - if ($closure->returnType !== null) { - return []; - } - - $messagePattern = 'Anonymous function should have native return typehint "%s".'; - $statementResult = $node->getStatementResult(); - if ($statementResult->hasYield()) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'Generator'))->build(), - ]; - } - - $returnStatements = $node->getReturnStatements(); - if (count($returnStatements) === 0) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), - ]; - } - - $returnTypes = []; - $voidReturnNodes = []; - $hasNull = false; - foreach ($returnStatements as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - $voidReturnNodes[] = $returnNode; - $hasNull = true; - continue; - } - - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); - } - - if (count($returnTypes) === 0) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), - ]; - } - - $messages = []; - foreach ($voidReturnNodes as $voidReturnStatement) { - $messages[] = RuleErrorBuilder::message('Mixing returning values with empty return statements - return null should be used here.') - ->line($voidReturnStatement->getLine()) - ->build(); - } - - $returnType = TypeCombinator::union(...$returnTypes); - if ( - $returnType instanceof MixedType - || $returnType instanceof NeverType - || $returnType instanceof IntersectionType - || $returnType instanceof NullType - ) { - return $messages; - } - - if (TypeCombinator::containsNull($returnType)) { - $hasNull = true; - $returnType = TypeCombinator::removeNull($returnType); - } - - if ( - $returnType instanceof UnionType - || $returnType instanceof ResourceType - ) { - return $messages; - } - - if (!$statementResult->isAlwaysTerminating()) { - $messages[] = RuleErrorBuilder::message('Anonymous function sometimes return something but return statement at the end is missing.')->build(); - return $messages; - } - - $returnType = TypeUtils::generalizeType($returnType); - $description = $returnType->describe(VerbosityLevel::typeOnly()); - if ($returnType->isArray()->yes()) { - $description = 'array'; - } - if ($hasNull) { - $description = '?' . $description; - } - - if ( - !$this->checkObjectTypehint - && $returnType instanceof ObjectWithoutClassType - ) { - return $messages; - } - - $messages[] = RuleErrorBuilder::message(sprintf($messagePattern, $description))->build(); - - return $messages; - } - + private bool $checkObjectTypehint; + + public function __construct(bool $checkObjectTypehint) + { + $this->checkObjectTypehint = $checkObjectTypehint; + } + + public function getNodeType(): string + { + return ClosureReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $closure = $node->getClosureExpr(); + if ($closure->returnType !== null) { + return []; + } + + $messagePattern = 'Anonymous function should have native return typehint "%s".'; + $statementResult = $node->getStatementResult(); + if ($statementResult->hasYield()) { + return [ + RuleErrorBuilder::message(sprintf($messagePattern, 'Generator'))->build(), + ]; + } + + $returnStatements = $node->getReturnStatements(); + if (count($returnStatements) === 0) { + return [ + RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), + ]; + } + + $returnTypes = []; + $voidReturnNodes = []; + $hasNull = false; + foreach ($returnStatements as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + if ($returnNode->expr === null) { + $voidReturnNodes[] = $returnNode; + $hasNull = true; + continue; + } + + $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + } + + if (count($returnTypes) === 0) { + return [ + RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), + ]; + } + + $messages = []; + foreach ($voidReturnNodes as $voidReturnStatement) { + $messages[] = RuleErrorBuilder::message('Mixing returning values with empty return statements - return null should be used here.') + ->line($voidReturnStatement->getLine()) + ->build(); + } + + $returnType = TypeCombinator::union(...$returnTypes); + if ( + $returnType instanceof MixedType + || $returnType instanceof NeverType + || $returnType instanceof IntersectionType + || $returnType instanceof NullType + ) { + return $messages; + } + + if (TypeCombinator::containsNull($returnType)) { + $hasNull = true; + $returnType = TypeCombinator::removeNull($returnType); + } + + if ( + $returnType instanceof UnionType + || $returnType instanceof ResourceType + ) { + return $messages; + } + + if (!$statementResult->isAlwaysTerminating()) { + $messages[] = RuleErrorBuilder::message('Anonymous function sometimes return something but return statement at the end is missing.')->build(); + return $messages; + } + + $returnType = TypeUtils::generalizeType($returnType); + $description = $returnType->describe(VerbosityLevel::typeOnly()); + if ($returnType->isArray()->yes()) { + $description = 'array'; + } + if ($hasNull) { + $description = '?' . $description; + } + + if ( + !$this->checkObjectTypehint + && $returnType instanceof ObjectWithoutClassType + ) { + return $messages; + } + + $messages[] = RuleErrorBuilder::message(sprintf($messagePattern, $description))->build(); + + return $messages; + } } diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 7074f4fb88..961484867d 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -1,4 +1,6 @@ -checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; - $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; - } - - public function getNodeType(): string - { - return ExecutionEndNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $statementResult = $node->getStatementResult(); - if ($statementResult->isAlwaysTerminating()) { - return []; - } - - $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); - $scopeFunction = $scope->getFunction(); - if ($anonymousFunctionReturnType !== null) { - $returnType = $anonymousFunctionReturnType; - $description = 'Anonymous function'; - if (!$node->hasNativeReturnTypehint()) { - return []; - } - } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); - if ($scopeFunction instanceof MethodReflection) { - $description = sprintf('Method %s::%s()', $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getName()); - } else { - $description = sprintf('Function %s()', $scopeFunction->getName()); - } - } else { - throw new \PHPStan\ShouldNotHappenException(); - } - - $isVoidSuperType = $returnType->isSuperTypeOf(new VoidType()); - if ($isVoidSuperType->yes() && !$returnType instanceof MixedType) { - return []; - } - - if ($statementResult->hasYield()) { - if ($returnType instanceof TypeWithClassName && $this->checkPhpDocMissingReturn) { - $generatorReturnType = GenericTypeVariableResolver::getType( - $returnType, - \Generator::class, - 'TReturn' - ); - if ($generatorReturnType !== null) { - $returnType = $generatorReturnType; - if ($returnType instanceof VoidType) { - return []; - } - if (!$returnType instanceof MixedType) { - return [ - RuleErrorBuilder::message( - sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) - )->line($node->getNode()->getStartLine())->build(), - ]; - } - } - } - return []; - } - - if (!$node->hasNativeReturnTypehint() && !$this->checkPhpDocMissingReturn) { - return []; - } - - if ($returnType instanceof NeverType && $returnType->isExplicit()) { - return [ - RuleErrorBuilder::message(sprintf('%s should always throw an exception or terminate script execution but doesn\'t do that.', $description))->line($node->getNode()->getStartLine())->build(), - ]; - } - - if ( - $returnType instanceof MixedType - && !$returnType instanceof TemplateMixedType - && ( - !$returnType->isExplicitMixed() - || !$this->checkExplicitMixedMissingReturn - ) - ) { - return []; - } - - return [ - RuleErrorBuilder::message( - sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) - )->line($node->getNode()->getStartLine())->build(), - ]; - } - + private bool $checkExplicitMixedMissingReturn; + + private bool $checkPhpDocMissingReturn; + + public function __construct( + bool $checkExplicitMixedMissingReturn, + bool $checkPhpDocMissingReturn + ) { + $this->checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; + $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; + } + + public function getNodeType(): string + { + return ExecutionEndNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + if ($statementResult->isAlwaysTerminating()) { + return []; + } + + $anonymousFunctionReturnType = $scope->getAnonymousFunctionReturnType(); + $scopeFunction = $scope->getFunction(); + if ($anonymousFunctionReturnType !== null) { + $returnType = $anonymousFunctionReturnType; + $description = 'Anonymous function'; + if (!$node->hasNativeReturnTypehint()) { + return []; + } + } elseif ($scopeFunction !== null) { + $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + if ($scopeFunction instanceof MethodReflection) { + $description = sprintf('Method %s::%s()', $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getName()); + } else { + $description = sprintf('Function %s()', $scopeFunction->getName()); + } + } else { + throw new \PHPStan\ShouldNotHappenException(); + } + + $isVoidSuperType = $returnType->isSuperTypeOf(new VoidType()); + if ($isVoidSuperType->yes() && !$returnType instanceof MixedType) { + return []; + } + + if ($statementResult->hasYield()) { + if ($returnType instanceof TypeWithClassName && $this->checkPhpDocMissingReturn) { + $generatorReturnType = GenericTypeVariableResolver::getType( + $returnType, + \Generator::class, + 'TReturn' + ); + if ($generatorReturnType !== null) { + $returnType = $generatorReturnType; + if ($returnType instanceof VoidType) { + return []; + } + if (!$returnType instanceof MixedType) { + return [ + RuleErrorBuilder::message( + sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) + )->line($node->getNode()->getStartLine())->build(), + ]; + } + } + } + return []; + } + + if (!$node->hasNativeReturnTypehint() && !$this->checkPhpDocMissingReturn) { + return []; + } + + if ($returnType instanceof NeverType && $returnType->isExplicit()) { + return [ + RuleErrorBuilder::message(sprintf('%s should always throw an exception or terminate script execution but doesn\'t do that.', $description))->line($node->getNode()->getStartLine())->build(), + ]; + } + + if ( + $returnType instanceof MixedType + && !$returnType instanceof TemplateMixedType + && ( + !$returnType->isExplicitMixed() + || !$this->checkExplicitMixedMissingReturn + ) + ) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) + )->line($node->getNode()->getStartLine())->build(), + ]; + } } diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 25ecdde5e3..bd552c26a8 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -1,4 +1,6 @@ -checkGenericClassInNonGenericObjectType: false in your %configurationFile%.'; - - private const ITERABLE_GENERIC_CLASS_NAMES = [ - \Traversable::class, - \Iterator::class, - \IteratorAggregate::class, - \Generator::class, - ]; - - private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; - - private bool $checkMissingIterableValueType; - - private bool $checkGenericClassInNonGenericObjectType; - - private bool $checkMissingCallableSignature; - - /** @var string[] */ - private array $skipCheckGenericClasses; - - /** - * @param string[] $skipCheckGenericClasses - */ - public function __construct( - ReflectionProvider $reflectionProvider, - bool $checkMissingIterableValueType, - bool $checkGenericClassInNonGenericObjectType, - bool $checkMissingCallableSignature, - array $skipCheckGenericClasses = [] - ) - { - $this->reflectionProvider = $reflectionProvider; - $this->checkMissingIterableValueType = $checkMissingIterableValueType; - $this->checkGenericClassInNonGenericObjectType = $checkGenericClassInNonGenericObjectType; - $this->checkMissingCallableSignature = $checkMissingCallableSignature; - $this->skipCheckGenericClasses = $skipCheckGenericClasses; - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\Type[] - */ - public function getIterableTypesWithMissingValueTypehint(Type $type): array - { - if (!$this->checkMissingIterableValueType) { - return []; - } - - $iterablesWithMissingValueTypehint = []; - TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$iterablesWithMissingValueTypehint): Type { - if ($type instanceof TemplateType) { - return $type; - } - if ($type->isIterable()->yes()) { - $iterableValue = $type->getIterableValueType(); - if ($iterableValue instanceof MixedType && !$iterableValue->isExplicitMixed()) { - if ( - $type instanceof TypeWithClassName - && !in_array($type->getClassName(), self::ITERABLE_GENERIC_CLASS_NAMES, true) - && $this->reflectionProvider->hasClass($type->getClassName()) - ) { - $classReflection = $this->reflectionProvider->getClass($type->getClassName()); - if ($classReflection->isGeneric()) { - return $type; - } - } - $iterablesWithMissingValueTypehint[] = $type; - } - return $type; - } - return $traverse($type); - }); - - return $iterablesWithMissingValueTypehint; - } - - /** - * @param \PHPStan\Type\Type $type - * @return array - */ - public function getNonGenericObjectTypesWithGenericClass(Type $type): array - { - if (!$this->checkGenericClassInNonGenericObjectType) { - return []; - } - - $objectTypes = []; - TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$objectTypes): Type { - if ($type instanceof GenericObjectType) { - $traverse($type); - return $type; - } - if ($type instanceof TemplateType) { - return $type; - } - if ($type instanceof ObjectType) { - $classReflection = $type->getClassReflection(); - if ($classReflection === null) { - return $type; - } - if (in_array($classReflection->getName(), self::ITERABLE_GENERIC_CLASS_NAMES, true)) { - // checked by getIterableTypesWithMissingValueTypehint() already - return $type; - } - if (in_array($classReflection->getName(), $this->skipCheckGenericClasses, true)) { - return $type; - } - if ($classReflection->isTrait()) { - return $type; - } - if (!$classReflection->isGeneric()) { - return $type; - } - - $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (!$resolvedType instanceof ObjectType) { - throw new \PHPStan\ShouldNotHappenException(); - } - $objectTypes[] = [ - sprintf('%s %s', $classReflection->isInterface() ? 'interface' : 'class', $classReflection->getDisplayName(false)), - array_keys($classReflection->getTemplateTypeMap()->getTypes()), - ]; - return $type; - } - $traverse($type); - return $type; - }); - - return $objectTypes; - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\Type[] - */ - public function getCallablesWithMissingSignature(Type $type): array - { - if (!$this->checkMissingCallableSignature) { - return []; - } - - $result = []; - TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$result): Type { - if ( - ($type instanceof CallableType && $type->isCommonCallable()) || - ($type instanceof ObjectType && $type->getClassName() === \Closure::class)) { - $result[] = $type; - } - return $traverse($type); - }); - - return $result; - } - + public const TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP = 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type'; + + public const TURN_OFF_NON_GENERIC_CHECK_TIP = 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.'; + + private const ITERABLE_GENERIC_CLASS_NAMES = [ + \Traversable::class, + \Iterator::class, + \IteratorAggregate::class, + \Generator::class, + ]; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private bool $checkMissingIterableValueType; + + private bool $checkGenericClassInNonGenericObjectType; + + private bool $checkMissingCallableSignature; + + /** @var string[] */ + private array $skipCheckGenericClasses; + + /** + * @param string[] $skipCheckGenericClasses + */ + public function __construct( + ReflectionProvider $reflectionProvider, + bool $checkMissingIterableValueType, + bool $checkGenericClassInNonGenericObjectType, + bool $checkMissingCallableSignature, + array $skipCheckGenericClasses = [] + ) { + $this->reflectionProvider = $reflectionProvider; + $this->checkMissingIterableValueType = $checkMissingIterableValueType; + $this->checkGenericClassInNonGenericObjectType = $checkGenericClassInNonGenericObjectType; + $this->checkMissingCallableSignature = $checkMissingCallableSignature; + $this->skipCheckGenericClasses = $skipCheckGenericClasses; + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\Type[] + */ + public function getIterableTypesWithMissingValueTypehint(Type $type): array + { + if (!$this->checkMissingIterableValueType) { + return []; + } + + $iterablesWithMissingValueTypehint = []; + TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$iterablesWithMissingValueTypehint): Type { + if ($type instanceof TemplateType) { + return $type; + } + if ($type->isIterable()->yes()) { + $iterableValue = $type->getIterableValueType(); + if ($iterableValue instanceof MixedType && !$iterableValue->isExplicitMixed()) { + if ( + $type instanceof TypeWithClassName + && !in_array($type->getClassName(), self::ITERABLE_GENERIC_CLASS_NAMES, true) + && $this->reflectionProvider->hasClass($type->getClassName()) + ) { + $classReflection = $this->reflectionProvider->getClass($type->getClassName()); + if ($classReflection->isGeneric()) { + return $type; + } + } + $iterablesWithMissingValueTypehint[] = $type; + } + return $type; + } + return $traverse($type); + }); + + return $iterablesWithMissingValueTypehint; + } + + /** + * @param \PHPStan\Type\Type $type + * @return array + */ + public function getNonGenericObjectTypesWithGenericClass(Type $type): array + { + if (!$this->checkGenericClassInNonGenericObjectType) { + return []; + } + + $objectTypes = []; + TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$objectTypes): Type { + if ($type instanceof GenericObjectType) { + $traverse($type); + return $type; + } + if ($type instanceof TemplateType) { + return $type; + } + if ($type instanceof ObjectType) { + $classReflection = $type->getClassReflection(); + if ($classReflection === null) { + return $type; + } + if (in_array($classReflection->getName(), self::ITERABLE_GENERIC_CLASS_NAMES, true)) { + // checked by getIterableTypesWithMissingValueTypehint() already + return $type; + } + if (in_array($classReflection->getName(), $this->skipCheckGenericClasses, true)) { + return $type; + } + if ($classReflection->isTrait()) { + return $type; + } + if (!$classReflection->isGeneric()) { + return $type; + } + + $resolvedType = TemplateTypeHelper::resolveToBounds($type); + if (!$resolvedType instanceof ObjectType) { + throw new \PHPStan\ShouldNotHappenException(); + } + $objectTypes[] = [ + sprintf('%s %s', $classReflection->isInterface() ? 'interface' : 'class', $classReflection->getDisplayName(false)), + array_keys($classReflection->getTemplateTypeMap()->getTypes()), + ]; + return $type; + } + $traverse($type); + return $type; + }); + + return $objectTypes; + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\Type[] + */ + public function getCallablesWithMissingSignature(Type $type): array + { + if (!$this->checkMissingCallableSignature) { + return []; + } + + $result = []; + TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$result): Type { + if ( + ($type instanceof CallableType && $type->isCommonCallable()) || + ($type instanceof ObjectType && $type->getClassName() === \Closure::class)) { + $result[] = $type; + } + return $traverse($type); + }); + + return $result; + } } diff --git a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php index b6854978f2..4e5a8c33d8 100644 --- a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkFunctionNameCase = $checkFunctionNameCase; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\GroupUse::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $errors = []; - foreach ($node->uses as $use) { - $error = null; - - /** @var Node\Name $name */ - $name = Node\Name::concat($node->prefix, $use->name, ['startLine' => $use->getLine()]); - if ( - $node->type === Use_::TYPE_CONSTANT - || $use->type === Use_::TYPE_CONSTANT - ) { - $error = $this->checkConstant($name); - } elseif ( - $node->type === Use_::TYPE_FUNCTION - || $use->type === Use_::TYPE_FUNCTION - ) { - $error = $this->checkFunction($name); - } elseif ($use->type === Use_::TYPE_NORMAL) { - $error = $this->checkClass($name); - } else { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($error === null) { - continue; - } - - $errors[] = $error; - } - - return $errors; - } - - private function checkConstant(Node\Name $name): ?RuleError - { - if (!$this->reflectionProvider->hasConstant($name, null)) { - return RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $name))->discoveringSymbolsTip()->line($name->getLine())->build(); - } - - return null; - } - - private function checkFunction(Node\Name $name): ?RuleError - { - if (!$this->reflectionProvider->hasFunction($name, null)) { - return RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $name))->discoveringSymbolsTip()->line($name->getLine())->build(); - } - - if ($this->checkFunctionNameCase) { - $functionReflection = $this->reflectionProvider->getFunction($name, null); - $realName = $functionReflection->getName(); - $usedName = (string) $name; - if ( - strtolower($realName) === strtolower($usedName) - && $realName !== $usedName - ) { - return RuleErrorBuilder::message(sprintf( - 'Function %s used with incorrect case: %s.', - $realName, - $usedName - ))->line($name->getLine())->build(); - } - } - - return null; - } - - private function checkClass(Node\Name $name): ?RuleError - { - $errors = $this->classCaseSensitivityCheck->checkClassNames([ - new ClassNameNodePair((string) $name, $name), - ]); - if (count($errors) === 0) { - return null; - } elseif (count($errors) === 1) { - return $errors[0]; - } - - throw new \PHPStan\ShouldNotHappenException(); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private bool $checkFunctionNameCase; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkFunctionNameCase + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkFunctionNameCase = $checkFunctionNameCase; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\GroupUse::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->uses as $use) { + $error = null; + + /** @var Node\Name $name */ + $name = Node\Name::concat($node->prefix, $use->name, ['startLine' => $use->getLine()]); + if ( + $node->type === Use_::TYPE_CONSTANT + || $use->type === Use_::TYPE_CONSTANT + ) { + $error = $this->checkConstant($name); + } elseif ( + $node->type === Use_::TYPE_FUNCTION + || $use->type === Use_::TYPE_FUNCTION + ) { + $error = $this->checkFunction($name); + } elseif ($use->type === Use_::TYPE_NORMAL) { + $error = $this->checkClass($name); + } else { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($error === null) { + continue; + } + + $errors[] = $error; + } + + return $errors; + } + + private function checkConstant(Node\Name $name): ?RuleError + { + if (!$this->reflectionProvider->hasConstant($name, null)) { + return RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $name))->discoveringSymbolsTip()->line($name->getLine())->build(); + } + + return null; + } + + private function checkFunction(Node\Name $name): ?RuleError + { + if (!$this->reflectionProvider->hasFunction($name, null)) { + return RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $name))->discoveringSymbolsTip()->line($name->getLine())->build(); + } + + if ($this->checkFunctionNameCase) { + $functionReflection = $this->reflectionProvider->getFunction($name, null); + $realName = $functionReflection->getName(); + $usedName = (string) $name; + if ( + strtolower($realName) === strtolower($usedName) + && $realName !== $usedName + ) { + return RuleErrorBuilder::message(sprintf( + 'Function %s used with incorrect case: %s.', + $realName, + $usedName + ))->line($name->getLine())->build(); + } + } + + return null; + } + + private function checkClass(Node\Name $name): ?RuleError + { + $errors = $this->classCaseSensitivityCheck->checkClassNames([ + new ClassNameNodePair((string) $name, $name), + ]); + if (count($errors) === 0) { + return null; + } elseif (count($errors) === 1) { + return $errors[0]; + } + + throw new \PHPStan\ShouldNotHappenException(); + } } diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index 40a575934b..713aea2893 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkFunctionNameCase = $checkFunctionNameCase; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\Use_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node->type === Node\Stmt\Use_::TYPE_UNKNOWN) { - throw new \PHPStan\ShouldNotHappenException(); - } - - foreach ($node->uses as $use) { - if ($use->type !== Node\Stmt\Use_::TYPE_UNKNOWN) { - throw new \PHPStan\ShouldNotHappenException(); - } - } - - if ($node->type === Node\Stmt\Use_::TYPE_CONSTANT) { - return $this->checkConstants($node->uses); - } - - if ($node->type === Node\Stmt\Use_::TYPE_FUNCTION) { - return $this->checkFunctions($node->uses); - } - - return $this->checkClasses($node->uses); - } - - /** - * @param \PhpParser\Node\Stmt\UseUse[] $uses - * @return RuleError[] - */ - private function checkConstants(array $uses): array - { - $errors = []; - foreach ($uses as $use) { - if ($this->reflectionProvider->hasConstant($use->name, null)) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $use->name))->line($use->name->getLine())->discoveringSymbolsTip()->build(); - } - - return $errors; - } - - /** - * @param \PhpParser\Node\Stmt\UseUse[] $uses - * @return RuleError[] - */ - private function checkFunctions(array $uses): array - { - $errors = []; - foreach ($uses as $use) { - if (!$this->reflectionProvider->hasFunction($use->name, null)) { - $errors[] = RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $use->name))->line($use->name->getLine())->discoveringSymbolsTip()->build(); - } elseif ($this->checkFunctionNameCase) { - $functionReflection = $this->reflectionProvider->getFunction($use->name, null); - $realName = $functionReflection->getName(); - $usedName = (string) $use->name; - if ( - strtolower($realName) === strtolower($usedName) - && $realName !== $usedName - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Function %s used with incorrect case: %s.', - $realName, - $usedName - ))->line($use->name->getLine())->build(); - } - } - } - - return $errors; - } - - /** - * @param \PhpParser\Node\Stmt\UseUse[] $uses - * @return RuleError[] - */ - private function checkClasses(array $uses): array - { - return $this->classCaseSensitivityCheck->checkClassNames( - array_map(static function (\PhpParser\Node\Stmt\UseUse $use): ClassNameNodePair { - return new ClassNameNodePair((string) $use->name, $use->name); - }, $uses) - ); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private bool $checkFunctionNameCase; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkFunctionNameCase + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkFunctionNameCase = $checkFunctionNameCase; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\Use_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->type === Node\Stmt\Use_::TYPE_UNKNOWN) { + throw new \PHPStan\ShouldNotHappenException(); + } + + foreach ($node->uses as $use) { + if ($use->type !== Node\Stmt\Use_::TYPE_UNKNOWN) { + throw new \PHPStan\ShouldNotHappenException(); + } + } + + if ($node->type === Node\Stmt\Use_::TYPE_CONSTANT) { + return $this->checkConstants($node->uses); + } + + if ($node->type === Node\Stmt\Use_::TYPE_FUNCTION) { + return $this->checkFunctions($node->uses); + } + + return $this->checkClasses($node->uses); + } + + /** + * @param \PhpParser\Node\Stmt\UseUse[] $uses + * @return RuleError[] + */ + private function checkConstants(array $uses): array + { + $errors = []; + foreach ($uses as $use) { + if ($this->reflectionProvider->hasConstant($use->name, null)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $use->name))->line($use->name->getLine())->discoveringSymbolsTip()->build(); + } + + return $errors; + } + + /** + * @param \PhpParser\Node\Stmt\UseUse[] $uses + * @return RuleError[] + */ + private function checkFunctions(array $uses): array + { + $errors = []; + foreach ($uses as $use) { + if (!$this->reflectionProvider->hasFunction($use->name, null)) { + $errors[] = RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $use->name))->line($use->name->getLine())->discoveringSymbolsTip()->build(); + } elseif ($this->checkFunctionNameCase) { + $functionReflection = $this->reflectionProvider->getFunction($use->name, null); + $realName = $functionReflection->getName(); + $usedName = (string) $use->name; + if ( + strtolower($realName) === strtolower($usedName) + && $realName !== $usedName + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Function %s used with incorrect case: %s.', + $realName, + $usedName + ))->line($use->name->getLine())->build(); + } + } + } + + return $errors; + } + + /** + * @param \PhpParser\Node\Stmt\UseUse[] $uses + * @return RuleError[] + */ + private function checkClasses(array $uses): array + { + return $this->classCaseSensitivityCheck->checkClassNames( + array_map(static function (\PhpParser\Node\Stmt\UseUse $use): ClassNameNodePair { + return new ClassNameNodePair((string) $use->name, $use->name); + }, $uses) + ); + } } diff --git a/src/Rules/NonIgnorableRuleError.php b/src/Rules/NonIgnorableRuleError.php index 4b79dac56d..3334bcb3c3 100644 --- a/src/Rules/NonIgnorableRuleError.php +++ b/src/Rules/NonIgnorableRuleError.php @@ -1,8 +1,9 @@ -containsNullSafe($expr->var); - } - - if ($expr instanceof Expr\PropertyFetch) { - return $this->containsNullSafe($expr->var); - } - - if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->containsNullSafe($expr->class); - } - - if ($expr instanceof Expr\MethodCall) { - return $this->containsNullSafe($expr->var); - } - - if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { - return $this->containsNullSafe($expr->class); - } - - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { - foreach ($expr->items as $item) { - if ($item === null) { - continue; - } - - if ($item->key !== null && $this->containsNullSafe($item->key)) { - return true; - } - - if ($this->containsNullSafe($item->value)) { - return true; - } - } - } - - return false; - } - + public function containsNullSafe(Expr $expr): bool + { + if ( + $expr instanceof Expr\NullsafePropertyFetch + || $expr instanceof Expr\NullsafeMethodCall + ) { + return true; + } + + if ($expr instanceof Expr\ArrayDimFetch) { + return $this->containsNullSafe($expr->var); + } + + if ($expr instanceof Expr\PropertyFetch) { + return $this->containsNullSafe($expr->var); + } + + if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { + return $this->containsNullSafe($expr->class); + } + + if ($expr instanceof Expr\MethodCall) { + return $this->containsNullSafe($expr->var); + } + + if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { + return $this->containsNullSafe($expr->class); + } + + if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + if ($item->key !== null && $this->containsNullSafe($item->key)) { + return true; + } + + if ($this->containsNullSafe($item->value)) { + return true; + } + } + } + + return false; + } } diff --git a/src/Rules/Operators/InvalidAssignVarRule.php b/src/Rules/Operators/InvalidAssignVarRule.php index 2c700f4e81..ab60c15fa6 100644 --- a/src/Rules/Operators/InvalidAssignVarRule.php +++ b/src/Rules/Operators/InvalidAssignVarRule.php @@ -1,4 +1,6 @@ -nullsafeCheck = $nullsafeCheck; - } - - public function getNodeType(): string - { - return Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Assign - && !$node instanceof AssignOp - && !$node instanceof AssignRef - ) { - return []; - } - - if ($this->nullsafeCheck->containsNullSafe($node->var)) { - return [ - RuleErrorBuilder::message('Nullsafe operator cannot be on left side of assignment.')->nonIgnorable()->build(), - ]; - } - - if ($node instanceof AssignRef && $this->nullsafeCheck->containsNullSafe($node->expr)) { - return [ - RuleErrorBuilder::message('Nullsafe operator cannot be on right side of assignment by reference.')->nonIgnorable()->build(), - ]; - } - - if ($this->containsNonAssignableExpression($node->var)) { - return [ - RuleErrorBuilder::message('Expression on left side of assignment is not assignable.')->nonIgnorable()->build(), - ]; - } - - return []; - } - - - private function containsNonAssignableExpression(Expr $expr): bool - { - if ($expr instanceof Expr\Variable) { - return false; - } - - if ($expr instanceof Expr\PropertyFetch) { - return false; - } - - if ($expr instanceof Expr\ArrayDimFetch) { - return false; - } - - if ($expr instanceof Expr\StaticPropertyFetch) { - return false; - } - - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { - foreach ($expr->items as $item) { - if ($item === null) { - continue; - } - if (!$this->containsNonAssignableExpression($item->value)) { - continue; - } - - return true; - } - - return false; - } - - return true; - } - + private NullsafeCheck $nullsafeCheck; + + public function __construct(NullsafeCheck $nullsafeCheck) + { + $this->nullsafeCheck = $nullsafeCheck; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Assign + && !$node instanceof AssignOp + && !$node instanceof AssignRef + ) { + return []; + } + + if ($this->nullsafeCheck->containsNullSafe($node->var)) { + return [ + RuleErrorBuilder::message('Nullsafe operator cannot be on left side of assignment.')->nonIgnorable()->build(), + ]; + } + + if ($node instanceof AssignRef && $this->nullsafeCheck->containsNullSafe($node->expr)) { + return [ + RuleErrorBuilder::message('Nullsafe operator cannot be on right side of assignment by reference.')->nonIgnorable()->build(), + ]; + } + + if ($this->containsNonAssignableExpression($node->var)) { + return [ + RuleErrorBuilder::message('Expression on left side of assignment is not assignable.')->nonIgnorable()->build(), + ]; + } + + return []; + } + + + private function containsNonAssignableExpression(Expr $expr): bool + { + if ($expr instanceof Expr\Variable) { + return false; + } + + if ($expr instanceof Expr\PropertyFetch) { + return false; + } + + if ($expr instanceof Expr\ArrayDimFetch) { + return false; + } + + if ($expr instanceof Expr\StaticPropertyFetch) { + return false; + } + + if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + if (!$this->containsNonAssignableExpression($item->value)) { + continue; + } + + return true; + } + + return false; + } + + return true; + } } diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 9cabaec6be..e14f5f364a 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -1,4 +1,6 @@ -printer = $printer; - $this->ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Expr::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Expr\BinaryOp - && !$node instanceof Node\Expr\AssignOp - ) { - return []; - } - - if ($scope->getType($node) instanceof ErrorType) { - $leftName = '__PHPSTAN__LEFT__'; - $rightName = '__PHPSTAN__RIGHT__'; - $leftVariable = new Node\Expr\Variable($leftName); - $rightVariable = new Node\Expr\Variable($rightName); - if ($node instanceof Node\Expr\AssignOp) { - $newNode = clone $node; - $left = $node->var; - $right = $node->expr; - $newNode->var = $leftVariable; - $newNode->expr = $rightVariable; - } else { - $newNode = clone $node; - $left = $node->left; - $right = $node->right; - $newNode->left = $leftVariable; - $newNode->right = $rightVariable; - } - - if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) { - $callback = static function (Type $type): bool { - return !$type->toString() instanceof ErrorType; - }; - } else { - $callback = static function (Type $type): bool { - return !$type->toNumber() instanceof ErrorType; - }; - } - - $leftType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $left, - '', - $callback - )->getType(); - if ($leftType instanceof ErrorType) { - return []; - } - - $rightType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $right, - '', - $callback - )->getType(); - if ($rightType instanceof ErrorType) { - return []; - } - - if (!$scope instanceof MutatingScope) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $scope = $scope - ->assignVariable($leftName, $leftType) - ->assignVariable($rightName, $rightType); - - if (!$scope->getType($newNode) instanceof ErrorType) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Binary operation "%s" between %s and %s results in an error.', - substr(substr($this->printer->prettyPrintExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)), - $scope->getType($left)->describe(VerbosityLevel::value()), - $scope->getType($right)->describe(VerbosityLevel::value()) - ))->line($left->getLine())->build(), - ]; - } - - return []; - } - + private \PhpParser\PrettyPrinter\Standard $printer; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + public function __construct( + \PhpParser\PrettyPrinter\Standard $printer, + RuleLevelHelper $ruleLevelHelper + ) { + $this->printer = $printer; + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Expr\BinaryOp + && !$node instanceof Node\Expr\AssignOp + ) { + return []; + } + + if ($scope->getType($node) instanceof ErrorType) { + $leftName = '__PHPSTAN__LEFT__'; + $rightName = '__PHPSTAN__RIGHT__'; + $leftVariable = new Node\Expr\Variable($leftName); + $rightVariable = new Node\Expr\Variable($rightName); + if ($node instanceof Node\Expr\AssignOp) { + $newNode = clone $node; + $left = $node->var; + $right = $node->expr; + $newNode->var = $leftVariable; + $newNode->expr = $rightVariable; + } else { + $newNode = clone $node; + $left = $node->left; + $right = $node->right; + $newNode->left = $leftVariable; + $newNode->right = $rightVariable; + } + + if ($node instanceof Node\Expr\AssignOp\Concat || $node instanceof Node\Expr\BinaryOp\Concat) { + $callback = static function (Type $type): bool { + return !$type->toString() instanceof ErrorType; + }; + } else { + $callback = static function (Type $type): bool { + return !$type->toNumber() instanceof ErrorType; + }; + } + + $leftType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $left, + '', + $callback + )->getType(); + if ($leftType instanceof ErrorType) { + return []; + } + + $rightType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $right, + '', + $callback + )->getType(); + if ($rightType instanceof ErrorType) { + return []; + } + + if (!$scope instanceof MutatingScope) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $scope = $scope + ->assignVariable($leftName, $leftType) + ->assignVariable($rightName, $rightType); + + if (!$scope->getType($newNode) instanceof ErrorType) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Binary operation "%s" between %s and %s results in an error.', + substr(substr($this->printer->prettyPrintExpr($newNode), strlen($leftName) + 2), 0, -(strlen($rightName) + 2)), + $scope->getType($left)->describe(VerbosityLevel::value()), + $scope->getType($right)->describe(VerbosityLevel::value()) + ))->line($left->getLine())->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Operators/InvalidComparisonOperationRule.php b/src/Rules/Operators/InvalidComparisonOperationRule.php index 83c4ac30ad..783b2a1c8c 100644 --- a/src/Rules/Operators/InvalidComparisonOperationRule.php +++ b/src/Rules/Operators/InvalidComparisonOperationRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Node\Expr\BinaryOp::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Expr\BinaryOp\Equal - && !$node instanceof Node\Expr\BinaryOp\NotEqual - && !$node instanceof Node\Expr\BinaryOp\Smaller - && !$node instanceof Node\Expr\BinaryOp\SmallerOrEqual - && !$node instanceof Node\Expr\BinaryOp\Greater - && !$node instanceof Node\Expr\BinaryOp\GreaterOrEqual - && !$node instanceof Node\Expr\BinaryOp\Spaceship - ) { - return []; - } - - if ( - ($this->isNumberType($scope, $node->left) && ( - $this->isObjectType($scope, $node->right) || $this->isArrayType($scope, $node->right) - )) - || ($this->isNumberType($scope, $node->right) && ( - $this->isObjectType($scope, $node->left) || $this->isArrayType($scope, $node->left) - )) - ) { - return [ - RuleErrorBuilder::message(sprintf( - 'Comparison operation "%s" between %s and %s results in an error.', - $node->getOperatorSigil(), - $scope->getType($node->left)->describe(VerbosityLevel::value()), - $scope->getType($node->right)->describe(VerbosityLevel::value()) - ))->line($node->left->getLine())->build(), - ]; - } - - return []; - } - - private function isNumberType(Scope $scope, Node\Expr $expr): bool - { - $acceptedType = new UnionType([new IntegerType(), new FloatType()]); - $onlyNumber = static function (Type $type) use ($acceptedType): bool { - return $acceptedType->accepts($type, true)->yes(); - }; - - $type = $this->ruleLevelHelper->findTypeToCheck($scope, $expr, '', $onlyNumber)->getType(); - - if ( - $type instanceof ErrorType - || !$type->equals($scope->getType($expr)) - ) { - return false; - } - - return !$acceptedType->isSuperTypeOf($type)->no(); - } - - private function isObjectType(Scope $scope, Node\Expr $expr): bool - { - $acceptedType = new ObjectWithoutClassType(); - - $type = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $expr, - '', - static function (Type $type) use ($acceptedType): bool { - return $acceptedType->isSuperTypeOf($type)->yes(); - } - )->getType(); - - if ($type instanceof ErrorType) { - return false; - } - - $isSuperType = $acceptedType->isSuperTypeOf($type); - if ($type instanceof \PHPStan\Type\BenevolentUnionType) { - return !$isSuperType->no(); - } - - return $isSuperType->yes(); - } - - private function isArrayType(Scope $scope, Node\Expr $expr): bool - { - $type = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $expr, - '', - static function (Type $type): bool { - return $type->isArray()->yes(); - } - )->getType(); - - return !($type instanceof ErrorType) && $type->isArray()->yes(); - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return Node\Expr\BinaryOp::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Expr\BinaryOp\Equal + && !$node instanceof Node\Expr\BinaryOp\NotEqual + && !$node instanceof Node\Expr\BinaryOp\Smaller + && !$node instanceof Node\Expr\BinaryOp\SmallerOrEqual + && !$node instanceof Node\Expr\BinaryOp\Greater + && !$node instanceof Node\Expr\BinaryOp\GreaterOrEqual + && !$node instanceof Node\Expr\BinaryOp\Spaceship + ) { + return []; + } + + if ( + ($this->isNumberType($scope, $node->left) && ( + $this->isObjectType($scope, $node->right) || $this->isArrayType($scope, $node->right) + )) + || ($this->isNumberType($scope, $node->right) && ( + $this->isObjectType($scope, $node->left) || $this->isArrayType($scope, $node->left) + )) + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Comparison operation "%s" between %s and %s results in an error.', + $node->getOperatorSigil(), + $scope->getType($node->left)->describe(VerbosityLevel::value()), + $scope->getType($node->right)->describe(VerbosityLevel::value()) + ))->line($node->left->getLine())->build(), + ]; + } + + return []; + } + + private function isNumberType(Scope $scope, Node\Expr $expr): bool + { + $acceptedType = new UnionType([new IntegerType(), new FloatType()]); + $onlyNumber = static function (Type $type) use ($acceptedType): bool { + return $acceptedType->accepts($type, true)->yes(); + }; + + $type = $this->ruleLevelHelper->findTypeToCheck($scope, $expr, '', $onlyNumber)->getType(); + + if ( + $type instanceof ErrorType + || !$type->equals($scope->getType($expr)) + ) { + return false; + } + + return !$acceptedType->isSuperTypeOf($type)->no(); + } + + private function isObjectType(Scope $scope, Node\Expr $expr): bool + { + $acceptedType = new ObjectWithoutClassType(); + + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static function (Type $type) use ($acceptedType): bool { + return $acceptedType->isSuperTypeOf($type)->yes(); + } + )->getType(); + + if ($type instanceof ErrorType) { + return false; + } + + $isSuperType = $acceptedType->isSuperTypeOf($type); + if ($type instanceof \PHPStan\Type\BenevolentUnionType) { + return !$isSuperType->no(); + } + + return $isSuperType->yes(); + } + + private function isArrayType(Scope $scope, Node\Expr $expr): bool + { + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static function (Type $type): bool { + return $type->isArray()->yes(); + } + )->getType(); + + return !($type instanceof ErrorType) && $type->isArray()->yes(); + } } diff --git a/src/Rules/Operators/InvalidIncDecOperationRule.php b/src/Rules/Operators/InvalidIncDecOperationRule.php index 1bbc260fa5..85e90c142d 100644 --- a/src/Rules/Operators/InvalidIncDecOperationRule.php +++ b/src/Rules/Operators/InvalidIncDecOperationRule.php @@ -1,4 +1,6 @@ -checkThisOnly = $checkThisOnly; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } + public function __construct(bool $checkThisOnly) + { + $this->checkThisOnly = $checkThisOnly; + } - public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array - { - if ( - !$node instanceof \PhpParser\Node\Expr\PreInc - && !$node instanceof \PhpParser\Node\Expr\PostInc - && !$node instanceof \PhpParser\Node\Expr\PreDec - && !$node instanceof \PhpParser\Node\Expr\PostDec - ) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } - $operatorString = $node instanceof \PhpParser\Node\Expr\PreInc || $node instanceof \PhpParser\Node\Expr\PostInc ? '++' : '--'; + public function processNode(\PhpParser\Node $node, \PHPStan\Analyser\Scope $scope): array + { + if ( + !$node instanceof \PhpParser\Node\Expr\PreInc + && !$node instanceof \PhpParser\Node\Expr\PostInc + && !$node instanceof \PhpParser\Node\Expr\PreDec + && !$node instanceof \PhpParser\Node\Expr\PostDec + ) { + return []; + } - if ( - !$node->var instanceof \PhpParser\Node\Expr\Variable - && !$node->var instanceof \PhpParser\Node\Expr\ArrayDimFetch - && !$node->var instanceof \PhpParser\Node\Expr\PropertyFetch - && !$node->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch - ) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot use %s on a non-variable.', - $operatorString - ))->line($node->var->getLine())->build(), - ]; - } + $operatorString = $node instanceof \PhpParser\Node\Expr\PreInc || $node instanceof \PhpParser\Node\Expr\PostInc ? '++' : '--'; - if (!$this->checkThisOnly) { - $varType = $scope->getType($node->var); - if (!$varType->toString() instanceof ErrorType) { - return []; - } - if (!$varType->toNumber() instanceof ErrorType) { - return []; - } + if ( + !$node->var instanceof \PhpParser\Node\Expr\Variable + && !$node->var instanceof \PhpParser\Node\Expr\ArrayDimFetch + && !$node->var instanceof \PhpParser\Node\Expr\PropertyFetch + && !$node->var instanceof \PhpParser\Node\Expr\StaticPropertyFetch + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot use %s on a non-variable.', + $operatorString + ))->line($node->var->getLine())->build(), + ]; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot use %s on %s.', - $operatorString, - $varType->describe(VerbosityLevel::value()) - ))->line($node->var->getLine())->build(), - ]; - } + if (!$this->checkThisOnly) { + $varType = $scope->getType($node->var); + if (!$varType->toString() instanceof ErrorType) { + return []; + } + if (!$varType->toNumber() instanceof ErrorType) { + return []; + } - return []; - } + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot use %s on %s.', + $operatorString, + $varType->describe(VerbosityLevel::value()) + ))->line($node->var->getLine())->build(), + ]; + } + return []; + } } diff --git a/src/Rules/Operators/InvalidUnaryOperationRule.php b/src/Rules/Operators/InvalidUnaryOperationRule.php index feddcd33be..0636254774 100644 --- a/src/Rules/Operators/InvalidUnaryOperationRule.php +++ b/src/Rules/Operators/InvalidUnaryOperationRule.php @@ -1,4 +1,6 @@ -getType($node) instanceof ErrorType) { - - if ($node instanceof \PhpParser\Node\Expr\UnaryPlus) { - $operator = '+'; - } elseif ($node instanceof \PhpParser\Node\Expr\UnaryMinus) { - $operator = '-'; - } else { - $operator = '~'; - } - return [ - RuleErrorBuilder::message(sprintf( - 'Unary operation "%s" on %s results in an error.', - $operator, - $scope->getType($node->expr)->describe(VerbosityLevel::value()) - ))->line($node->expr->getLine())->build(), - ]; - } - - return []; - } - + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ( + !$node instanceof \PhpParser\Node\Expr\UnaryPlus + && !$node instanceof \PhpParser\Node\Expr\UnaryMinus + && !$node instanceof \PhpParser\Node\Expr\BitwiseNot + ) { + return []; + } + + if ($scope->getType($node) instanceof ErrorType) { + if ($node instanceof \PhpParser\Node\Expr\UnaryPlus) { + $operator = '+'; + } elseif ($node instanceof \PhpParser\Node\Expr\UnaryMinus) { + $operator = '-'; + } else { + $operator = '~'; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Unary operation "%s" on %s results in an error.', + $operator, + $scope->getType($node->expr)->describe(VerbosityLevel::value()) + ))->line($node->expr->getLine())->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index 48015e4187..fd2827f318 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->genericObjectTypeCheck = $genericObjectTypeCheck; - } - - public function getNodeType(): string - { - return \PhpParser\Node\FunctionLike::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } - - $functionName = null; - if ($node instanceof Node\Stmt\ClassMethod) { - $functionName = $node->name->name; - } elseif ($node instanceof Node\Stmt\Function_) { - $functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); - } - - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $functionName, - $docComment->getText() - ); - $nativeParameterTypes = $this->getNativeParameterTypes($node, $scope); - $nativeReturnType = $this->getNativeReturnType($node, $scope); - - $errors = []; - - foreach ($resolvedPhpDoc->getParamTags() as $parameterName => $phpDocParamTag) { - $phpDocParamType = $phpDocParamTag->getType(); - if (!isset($nativeParameterTypes[$parameterName])) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @param references unknown parameter: $%s', - $parameterName - ))->identifier('phpDoc.unknownParameter')->metadata(['parameterName' => $parameterName])->build(); - - } elseif ( - $phpDocParamType instanceof ErrorType - || ($phpDocParamType instanceof NeverType && !$phpDocParamType->isExplicit()) - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @param for parameter $%s contains unresolvable type.', - $parameterName - ))->build(); - - } else { - $nativeParamType = $nativeParameterTypes[$parameterName]; - if ( - $phpDocParamTag->isVariadic() - && $phpDocParamType instanceof ArrayType - && !$nativeParamType instanceof ArrayType - ) { - $phpDocParamType = $phpDocParamType->getItemType(); - } - $isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType)); - - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $phpDocParamType, - sprintf( - 'PHPDoc tag @param for parameter $%s contains generic type %%s but class %%s is not generic.', - $parameterName - ), - sprintf( - 'Generic type %%s in PHPDoc tag @param for parameter $%s does not specify all template types of class %%s: %%s', - $parameterName - ), - sprintf( - 'Generic type %%s in PHPDoc tag @param for parameter $%s specifies %%d template types, but class %%s supports only %%d: %%s', - $parameterName - ), - sprintf( - 'Type %%s in generic type %%s in PHPDoc tag @param for parameter $%s is not subtype of template type %%s of class %%s.', - $parameterName - ) - )); - - if ($isParamSuperType->no()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @param for parameter $%s with type %s is incompatible with native type %s.', - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()) - ))->build(); - - } elseif ($isParamSuperType->maybe()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @param for parameter $%s with type %s is not subtype of native type %s.', - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - } - } - - if ($resolvedPhpDoc->getReturnTag() !== null) { - $phpDocReturnType = TemplateTypeHelper::resolveToBounds($resolvedPhpDoc->getReturnTag()->getType()); - - if ( - $phpDocReturnType instanceof ErrorType - || ($phpDocReturnType instanceof NeverType && !$phpDocReturnType->isExplicit()) - ) { - $errors[] = RuleErrorBuilder::message('PHPDoc tag @return contains unresolvable type.')->build(); - - } else { - $isReturnSuperType = $nativeReturnType->isSuperTypeOf($phpDocReturnType); - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $phpDocReturnType, - 'PHPDoc tag @return contains generic type %s but class %s is not generic.', - 'Generic type %s in PHPDoc tag @return does not specify all template types of class %s: %s', - 'Generic type %s in PHPDoc tag @return specifies %d template types, but class %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @return is not subtype of template type %s of class %s.' - )); - if ($isReturnSuperType->no()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @return with type %s is incompatible with native type %s.', - $phpDocReturnType->describe(VerbosityLevel::typeOnly()), - $nativeReturnType->describe(VerbosityLevel::typeOnly()) - ))->build(); - - } elseif ($isReturnSuperType->maybe()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @return with type %s is not subtype of native type %s.', - $phpDocReturnType->describe(VerbosityLevel::typeOnly()), - $nativeReturnType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - } - } - - return $errors; - } - - /** - * @param Node\FunctionLike $node - * @param Scope $scope - * @return Type[] - */ - private function getNativeParameterTypes(\PhpParser\Node\FunctionLike $node, Scope $scope): array - { - $nativeParameterTypes = []; - foreach ($node->getParams() as $parameter) { - $isNullable = $scope->isParameterValueNullable($parameter); - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $nativeParameterTypes[$parameter->var->name] = $scope->getFunctionType( - $parameter->type, - $isNullable, - false - ); - } - - return $nativeParameterTypes; - } - - private function getNativeReturnType(\PhpParser\Node\FunctionLike $node, Scope $scope): Type - { - return $scope->getFunctionType($node->getReturnType(), false, false); - } - + private FileTypeMapper $fileTypeMapper; + + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + public function __construct( + FileTypeMapper $fileTypeMapper, + GenericObjectTypeCheck $genericObjectTypeCheck + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + } + + public function getNodeType(): string + { + return \PhpParser\Node\FunctionLike::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $functionName = null; + if ($node instanceof Node\Stmt\ClassMethod) { + $functionName = $node->name->name; + } elseif ($node instanceof Node\Stmt\Function_) { + $functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $functionName, + $docComment->getText() + ); + $nativeParameterTypes = $this->getNativeParameterTypes($node, $scope); + $nativeReturnType = $this->getNativeReturnType($node, $scope); + + $errors = []; + + foreach ($resolvedPhpDoc->getParamTags() as $parameterName => $phpDocParamTag) { + $phpDocParamType = $phpDocParamTag->getType(); + if (!isset($nativeParameterTypes[$parameterName])) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @param references unknown parameter: $%s', + $parameterName + ))->identifier('phpDoc.unknownParameter')->metadata(['parameterName' => $parameterName])->build(); + } elseif ( + $phpDocParamType instanceof ErrorType + || ($phpDocParamType instanceof NeverType && !$phpDocParamType->isExplicit()) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @param for parameter $%s contains unresolvable type.', + $parameterName + ))->build(); + } else { + $nativeParamType = $nativeParameterTypes[$parameterName]; + if ( + $phpDocParamTag->isVariadic() + && $phpDocParamType instanceof ArrayType + && !$nativeParamType instanceof ArrayType + ) { + $phpDocParamType = $phpDocParamType->getItemType(); + } + $isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType)); + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $phpDocParamType, + sprintf( + 'PHPDoc tag @param for parameter $%s contains generic type %%s but class %%s is not generic.', + $parameterName + ), + sprintf( + 'Generic type %%s in PHPDoc tag @param for parameter $%s does not specify all template types of class %%s: %%s', + $parameterName + ), + sprintf( + 'Generic type %%s in PHPDoc tag @param for parameter $%s specifies %%d template types, but class %%s supports only %%d: %%s', + $parameterName + ), + sprintf( + 'Type %%s in generic type %%s in PHPDoc tag @param for parameter $%s is not subtype of template type %%s of class %%s.', + $parameterName + ) + )); + + if ($isParamSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @param for parameter $%s with type %s is incompatible with native type %s.', + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } elseif ($isParamSuperType->maybe()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @param for parameter $%s with type %s is not subtype of native type %s.', + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + } + } + + if ($resolvedPhpDoc->getReturnTag() !== null) { + $phpDocReturnType = TemplateTypeHelper::resolveToBounds($resolvedPhpDoc->getReturnTag()->getType()); + + if ( + $phpDocReturnType instanceof ErrorType + || ($phpDocReturnType instanceof NeverType && !$phpDocReturnType->isExplicit()) + ) { + $errors[] = RuleErrorBuilder::message('PHPDoc tag @return contains unresolvable type.')->build(); + } else { + $isReturnSuperType = $nativeReturnType->isSuperTypeOf($phpDocReturnType); + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $phpDocReturnType, + 'PHPDoc tag @return contains generic type %s but class %s is not generic.', + 'Generic type %s in PHPDoc tag @return does not specify all template types of class %s: %s', + 'Generic type %s in PHPDoc tag @return specifies %d template types, but class %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @return is not subtype of template type %s of class %s.' + )); + if ($isReturnSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @return with type %s is incompatible with native type %s.', + $phpDocReturnType->describe(VerbosityLevel::typeOnly()), + $nativeReturnType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } elseif ($isReturnSuperType->maybe()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @return with type %s is not subtype of native type %s.', + $phpDocReturnType->describe(VerbosityLevel::typeOnly()), + $nativeReturnType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + } + } + + return $errors; + } + + /** + * @param Node\FunctionLike $node + * @param Scope $scope + * @return Type[] + */ + private function getNativeParameterTypes(\PhpParser\Node\FunctionLike $node, Scope $scope): array + { + $nativeParameterTypes = []; + foreach ($node->getParams() as $parameter) { + $isNullable = $scope->isParameterValueNullable($parameter); + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $nativeParameterTypes[$parameter->var->name] = $scope->getFunctionType( + $parameter->type, + $isNullable, + false + ); + } + + return $nativeParameterTypes; + } + + private function getNativeReturnType(\PhpParser\Node\FunctionLike $node, Scope $scope): Type + { + return $scope->getFunctionType($node->getReturnType(), false, false); + } } diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index cd03ea7397..a50e33fd0e 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -1,4 +1,6 @@ -genericObjectTypeCheck = $genericObjectTypeCheck; - } - - public function getNodeType(): string - { - return ClassPropertyNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $propertyName = $node->getName(); - $propertyReflection = $scope->getClassReflection()->getNativeProperty($propertyName); - - if (!$propertyReflection->hasPhpDoc()) { - return []; - } - - $phpDocType = $propertyReflection->getPhpDocType(); - $description = 'PHPDoc tag @var'; - if ($propertyReflection->isPromoted()) { - $description = 'PHPDoc type'; - } - - $messages = []; - if ( - $phpDocType instanceof ErrorType - || ($phpDocType instanceof NeverType && !$phpDocType->isExplicit()) - ) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s for property %s::$%s contains unresolvable type.', - $description, - $propertyReflection->getDeclaringClass()->getName(), - $propertyName - ))->build(); - } - - $nativeType = $propertyReflection->getNativeType(); - $isSuperType = $nativeType->isSuperTypeOf($phpDocType); - if ($isSuperType->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s for property %s::$%s with type %s is incompatible with native type %s.', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName, - $phpDocType->describe(VerbosityLevel::typeOnly()), - $nativeType->describe(VerbosityLevel::typeOnly()) - ))->build(); - - } elseif ($isSuperType->maybe()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s for property %s::$%s with type %s is not subtype of native type %s.', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName, - $phpDocType->describe(VerbosityLevel::typeOnly()), - $nativeType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - $messages = array_merge($messages, $this->genericObjectTypeCheck->check( - $phpDocType, - sprintf( - '%s for property %s::$%s contains generic type %%s but class %%s is not generic.', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName - ), - sprintf( - 'Generic type %%s in %s for property %s::$%s does not specify all template types of class %%s: %%s', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName - ), - sprintf( - 'Generic type %%s in %s for property %s::$%s specifies %%d template types, but class %%s supports only %%d: %%s', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName - ), - sprintf( - 'Type %%s in generic type %%s in %s for property %s::$%s is not subtype of template type %%s of class %%s.', - $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName - ) - )); - - return $messages; - } - + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + public function __construct(GenericObjectTypeCheck $genericObjectTypeCheck) + { + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + } + + public function getNodeType(): string + { + return ClassPropertyNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $propertyName = $node->getName(); + $propertyReflection = $scope->getClassReflection()->getNativeProperty($propertyName); + + if (!$propertyReflection->hasPhpDoc()) { + return []; + } + + $phpDocType = $propertyReflection->getPhpDocType(); + $description = 'PHPDoc tag @var'; + if ($propertyReflection->isPromoted()) { + $description = 'PHPDoc type'; + } + + $messages = []; + if ( + $phpDocType instanceof ErrorType + || ($phpDocType instanceof NeverType && !$phpDocType->isExplicit()) + ) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s for property %s::$%s contains unresolvable type.', + $description, + $propertyReflection->getDeclaringClass()->getName(), + $propertyName + ))->build(); + } + + $nativeType = $propertyReflection->getNativeType(); + $isSuperType = $nativeType->isSuperTypeOf($phpDocType); + if ($isSuperType->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s for property %s::$%s with type %s is incompatible with native type %s.', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName, + $phpDocType->describe(VerbosityLevel::typeOnly()), + $nativeType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } elseif ($isSuperType->maybe()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s for property %s::$%s with type %s is not subtype of native type %s.', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName, + $phpDocType->describe(VerbosityLevel::typeOnly()), + $nativeType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + $messages = array_merge($messages, $this->genericObjectTypeCheck->check( + $phpDocType, + sprintf( + '%s for property %s::$%s contains generic type %%s but class %%s is not generic.', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName + ), + sprintf( + 'Generic type %%s in %s for property %s::$%s does not specify all template types of class %%s: %%s', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName + ), + sprintf( + 'Generic type %%s in %s for property %s::$%s specifies %%d template types, but class %%s supports only %%d: %%s', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName + ), + sprintf( + 'Type %%s in generic type %%s in %s for property %s::$%s is not subtype of template type %%s of class %%s.', + $description, + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyName + ) + )); + + return $messages; + } } diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index f17d2df84e..bfb085d3a8 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -1,4 +1,6 @@ -phpDocLexer = $phpDocLexer; - $this->phpDocParser = $phpDocParser; - } + private PhpDocParser $phpDocParser; - public function getNodeType(): string - { - return \PhpParser\Node::class; - } + public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser) + { + $this->phpDocLexer = $phpDocLexer; + $this->phpDocParser = $phpDocParser; + } - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Stmt\ClassLike - && !$node instanceof Node\FunctionLike - && !$node instanceof Node\Stmt\Foreach_ - && !$node instanceof Node\Stmt\Property - && !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignRef - ) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node::class; + } - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } - $phpDocString = $docComment->getText(); - $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); - $phpDocNode = $this->phpDocParser->parse($tokens); + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Stmt\ClassLike + && !$node instanceof Node\FunctionLike + && !$node instanceof Node\Stmt\Foreach_ + && !$node instanceof Node\Stmt\Property + && !$node instanceof Node\Expr\Assign + && !$node instanceof Node\Expr\AssignRef + ) { + return []; + } - $errors = []; - foreach ($phpDocNode->getTags() as $phpDocTag) { - if (strpos($phpDocTag->name, '@phpstan-') !== 0 - || in_array($phpDocTag->name, self::POSSIBLE_PHPSTAN_TAGS, true) - ) { - continue; - } + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + $phpDocString = $docComment->getText(); + $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); + $phpDocNode = $this->phpDocParser->parse($tokens); - $errors[] = RuleErrorBuilder::message(sprintf( - 'Unknown PHPDoc tag: %s', - $phpDocTag->name - ))->build(); - } + $errors = []; + foreach ($phpDocNode->getTags() as $phpDocTag) { + if (strpos($phpDocTag->name, '@phpstan-') !== 0 + || in_array($phpDocTag->name, self::POSSIBLE_PHPSTAN_TAGS, true) + ) { + continue; + } - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'Unknown PHPDoc tag: %s', + $phpDocTag->name + ))->build(); + } + return $errors; + } } diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index 3b0260a001..9179730fcc 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -1,4 +1,6 @@ -phpDocLexer = $phpDocLexer; - $this->phpDocParser = $phpDocParser; - } + private PhpDocParser $phpDocParser; - public function getNodeType(): string - { - return \PhpParser\Node::class; - } + public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser) + { + $this->phpDocLexer = $phpDocLexer; + $this->phpDocParser = $phpDocParser; + } - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Stmt\ClassLike - && !$node instanceof Node\FunctionLike - && !$node instanceof Node\Stmt\Foreach_ - && !$node instanceof Node\Stmt\Property - && !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignRef - ) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node::class; + } - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Stmt\ClassLike + && !$node instanceof Node\FunctionLike + && !$node instanceof Node\Stmt\Foreach_ + && !$node instanceof Node\Stmt\Property + && !$node instanceof Node\Expr\Assign + && !$node instanceof Node\Expr\AssignRef + ) { + return []; + } - $phpDocString = $docComment->getText(); - $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); - $phpDocNode = $this->phpDocParser->parse($tokens); + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $errors = []; - foreach ($phpDocNode->getTags() as $phpDocTag) { - if (!($phpDocTag->value instanceof InvalidTagValueNode)) { - continue; - } + $phpDocString = $docComment->getText(); + $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString)); + $phpDocNode = $this->phpDocParser->parse($tokens); - if (strpos($phpDocTag->name, '@psalm-') === 0) { - continue; - } + $errors = []; + foreach ($phpDocNode->getTags() as $phpDocTag) { + if (!($phpDocTag->value instanceof InvalidTagValueNode)) { + continue; + } - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s has invalid value (%s): %s', - $phpDocTag->name, - $phpDocTag->value->value, - $phpDocTag->value->exception->getMessage() - ))->build(); - } + if (strpos($phpDocTag->name, '@psalm-') === 0) { + continue; + } - return $errors; - } + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s has invalid value (%s): %s', + $phpDocTag->name, + $phpDocTag->value->value, + $phpDocTag->value->exception->getMessage() + ))->build(); + } + return $errors; + } } diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 437dbd9e36..837f1156fb 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->genericObjectTypeCheck = $genericObjectTypeCheck; - $this->missingTypehintCheck = $missingTypehintCheck; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - $this->checkMissingVarTagTypehint = $checkMissingVarTagTypehint; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - $node instanceof Node\Stmt\Property - || $node instanceof Node\Stmt\PropertyProperty - ) { - return []; - } - - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } - - $function = $scope->getFunction(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $docComment->getText() - ); - - $errors = []; - foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { - $varTagType = $varTag->getType(); - $identifier = 'PHPDoc tag @var'; - if (is_string($name)) { - $identifier .= sprintf(' for variable $%s', $name); - } - if ( - $varTagType instanceof ErrorType - || ($varTagType instanceof NeverType && !$varTagType->isExplicit()) - ) { - $errors[] = RuleErrorBuilder::message(sprintf('%s contains unresolvable type.', $identifier))->line($docComment->getStartLine())->build(); - continue; - } - - if ($this->checkMissingVarTagTypehint) { - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($varTagType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $errors[] = RuleErrorBuilder::message(sprintf( - '%s has no value type specified in iterable type %s.', - $identifier, - $iterableTypeDescription - ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } - } - - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $varTagType, - sprintf('%s contains generic type %%s but class %%s is not generic.', $identifier), - sprintf('Generic type %%s in %s does not specify all template types of class %%s: %%s', $identifier), - sprintf('Generic type %%s in %s specifies %%d template types, but class %%s supports only %%d: %%s', $identifier), - sprintf('Type %%s in generic type %%s in %s is not subtype of template type %%s of class %%s.', $identifier) - )); - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($varTagType) as [$innerName, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s contains generic %s but does not specify its types: %s', - $identifier, - $innerName, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } - - $referencedClasses = $varTagType->getReferencedClasses(); - foreach ($referencedClasses as $referencedClass) { - if ($this->reflectionProvider->hasClass($referencedClass)) { - if ($this->reflectionProvider->getClass($referencedClass)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf( - sprintf('%s has invalid type %%s.', $identifier), - $referencedClass - ))->build(); - } - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - sprintf('%s contains unknown class %%s.', $identifier), - $referencedClass - ))->discoveringSymbolsTip()->build(); - } - - if (!$this->checkClassCaseSensitivity) { - continue; - } - - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($node): ClassNameNodePair { - return new ClassNameNodePair($class, $node); - }, $referencedClasses)) - ); - } - - return $errors; - } - + private FileTypeMapper $fileTypeMapper; + + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + private MissingTypehintCheck $missingTypehintCheck; + + private bool $checkClassCaseSensitivity; + + private bool $checkMissingVarTagTypehint; + + public function __construct( + FileTypeMapper $fileTypeMapper, + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + GenericObjectTypeCheck $genericObjectTypeCheck, + MissingTypehintCheck $missingTypehintCheck, + bool $checkClassCaseSensitivity, + bool $checkMissingVarTagTypehint + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + $this->missingTypehintCheck = $missingTypehintCheck; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + $this->checkMissingVarTagTypehint = $checkMissingVarTagTypehint; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + $node instanceof Node\Stmt\Property + || $node instanceof Node\Stmt\PropertyProperty + ) { + return []; + } + + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $function = $scope->getFunction(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $docComment->getText() + ); + + $errors = []; + foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { + $varTagType = $varTag->getType(); + $identifier = 'PHPDoc tag @var'; + if (is_string($name)) { + $identifier .= sprintf(' for variable $%s', $name); + } + if ( + $varTagType instanceof ErrorType + || ($varTagType instanceof NeverType && !$varTagType->isExplicit()) + ) { + $errors[] = RuleErrorBuilder::message(sprintf('%s contains unresolvable type.', $identifier))->line($docComment->getStartLine())->build(); + continue; + } + + if ($this->checkMissingVarTagTypehint) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($varTagType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s has no value type specified in iterable type %s.', + $identifier, + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } + } + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $varTagType, + sprintf('%s contains generic type %%s but class %%s is not generic.', $identifier), + sprintf('Generic type %%s in %s does not specify all template types of class %%s: %%s', $identifier), + sprintf('Generic type %%s in %s specifies %%d template types, but class %%s supports only %%d: %%s', $identifier), + sprintf('Type %%s in generic type %%s in %s is not subtype of template type %%s of class %%s.', $identifier) + )); + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($varTagType) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s contains generic %s but does not specify its types: %s', + $identifier, + $innerName, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + + $referencedClasses = $varTagType->getReferencedClasses(); + foreach ($referencedClasses as $referencedClass) { + if ($this->reflectionProvider->hasClass($referencedClass)) { + if ($this->reflectionProvider->getClass($referencedClass)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf( + sprintf('%s has invalid type %%s.', $identifier), + $referencedClass + ))->build(); + } + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + sprintf('%s contains unknown class %%s.', $identifier), + $referencedClass + ))->discoveringSymbolsTip()->build(); + } + + if (!$this->checkClassCaseSensitivity) { + continue; + } + + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($node): ClassNameNodePair { + return new ClassNameNodePair($class, $node); + }, $referencedClasses)) + ); + } + + return $errors; + } } diff --git a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php index 8027a757f0..6069b64b09 100644 --- a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php +++ b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt::class; - } + public function __construct(FileTypeMapper $fileTypeMapper) + { + $this->fileTypeMapper = $fileTypeMapper; + } - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment === null) { - return []; - } + public function getNodeType(): string + { + return \PhpParser\Node\Stmt::class; + } - if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) { - return []; // is handled by virtual nodes - } + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } - $functionName = null; - if ($scope->getFunction() !== null) { - $functionName = $scope->getFunction()->getName(); - } + if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) { + return []; // is handled by virtual nodes + } - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $functionName, - $docComment->getText() - ); + $functionName = null; + if ($scope->getFunction() !== null) { + $functionName = $scope->getFunction()->getName(); + } - if ($resolvedPhpDoc->getThrowsTag() === null) { - return []; - } + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $functionName, + $docComment->getText() + ); - $phpDocThrowsType = $resolvedPhpDoc->getThrowsTag()->getType(); - if ((new VoidType())->isSuperTypeOf($phpDocThrowsType)->yes()) { - return []; - } + if ($resolvedPhpDoc->getThrowsTag() === null) { + return []; + } - $isThrowsSuperType = (new ObjectType(\Throwable::class))->isSuperTypeOf($phpDocThrowsType); - if ($isThrowsSuperType->yes()) { - return []; - } + $phpDocThrowsType = $resolvedPhpDoc->getThrowsTag()->getType(); + if ((new VoidType())->isSuperTypeOf($phpDocThrowsType)->yes()) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @throws with type %s is not subtype of Throwable', - $phpDocThrowsType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + $isThrowsSuperType = (new ObjectType(\Throwable::class))->isSuperTypeOf($phpDocThrowsType); + if ($isThrowsSuperType->yes()) { + return []; + } + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @throws with type %s is not subtype of Throwable', + $phpDocThrowsType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 18c95f0bc4..7fa23e9550 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -1,4 +1,6 @@ -fileTypeMapper = $fileTypeMapper; - $this->checkWrongVarUsage = $checkWrongVarUsage; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - $node instanceof Node\Stmt\Property - || $node instanceof Node\Stmt\PropertyProperty - || $node instanceof Node\Stmt\ClassConst - || $node instanceof Node\Stmt\Const_ - || ($node instanceof VirtualNode && !$node instanceof InFunctionNode && !$node instanceof InClassMethodNode && !$node instanceof InClassNode) - ) { - return []; - } - - $varTags = []; - $function = $scope->getFunction(); - foreach ($node->getComments() as $comment) { - if (!$comment instanceof Doc) { - continue; - } - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $comment->getText() - ); - foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { - $varTags[$key] = $varTag; - } - } - - if (count($varTags) === 0) { - return []; - } - - if ($node instanceof Node\Stmt\Foreach_) { - return $this->processForeach($node->expr, $node->keyVar, $node->valueVar, $varTags); - } - - if ($node instanceof Node\Stmt\Static_) { - return $this->processStatic($node->vars, $varTags); - } - - if ($node instanceof Node\Stmt\Expression) { - return $this->processExpression($scope, $node->expr, $varTags); - } - - if ($node instanceof Node\Stmt\Throw_ || $node instanceof Node\Stmt\Return_) { - return $this->processStmt($scope, $varTags, $node->expr); - } - - if ($node instanceof Node\Stmt\Global_) { - return $this->processGlobal($scope, $node, $varTags); - } - - if ($node instanceof InClassNode || $node instanceof InClassMethodNode || $node instanceof InFunctionNode) { - if ($this->checkWrongVarUsage) { - $description = 'a function'; - $originalNode = $node->getOriginalNode(); - if ($originalNode instanceof Node\Stmt\Interface_) { - $description = 'an interface'; - } elseif ($originalNode instanceof Node\Stmt\Class_) { - $description = 'a class'; - } elseif ($originalNode instanceof Node\Stmt\Trait_) { - throw new \PHPStan\ShouldNotHappenException(); - } elseif ($originalNode instanceof Node\Stmt\ClassMethod) { - $description = 'a method'; - } - return [ - RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @var above %s has no effect.', - $description - ))->build(), - ]; - } - - return []; - } - - return $this->processStmt($scope, $varTags, null); - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node\Expr $var - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @return \PHPStan\Rules\RuleError[] - */ - private function processAssign(Scope $scope, Node\Expr $var, array $varTags): array - { - $errors = []; - $hasMultipleMessage = false; - $assignedVariables = $this->getAssignedVariables($var); - foreach (array_keys($varTags) as $key) { - if (is_int($key)) { - if (count($varTags) !== 1) { - if (!$hasMultipleMessage) { - $errors[] = RuleErrorBuilder::message('Multiple PHPDoc @var tags above single variable assignment are not supported.')->build(); - $hasMultipleMessage = true; - } - } elseif (count($assignedVariables) !== 1) { - $errors[] = RuleErrorBuilder::message( - 'PHPDoc tag @var above assignment does not specify variable name.' - )->build(); - } - continue; - } - - if (!$scope->hasVariableType($key)->no()) { - continue; - } - - if (in_array($key, $assignedVariables, true)) { - continue; - } - - if (count($assignedVariables) === 1 && count($varTags) === 1) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in PHPDoc tag @var does not match assigned variable $%s.', - $key, - $assignedVariables[0] - ))->build(); - } else { - $errors[] = RuleErrorBuilder::message(sprintf('Variable $%s in PHPDoc tag @var does not exist.', $key))->build(); - } - } - - return $errors; - } - - /** - * @param Expr $expr - * @return string[] - */ - private function getAssignedVariables(Expr $expr): array - { - if ($expr instanceof Expr\Variable) { - if (is_string($expr->name)) { - return [$expr->name]; - } - - return []; - } - - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { - $names = []; - foreach ($expr->items as $item) { - if ($item === null) { - continue; - } - - $names = array_merge($names, $this->getAssignedVariables($item->value)); - } - - return $names; - } - - return []; - } - - /** - * @param \PhpParser\Node\Expr|null $keyVar - * @param \PhpParser\Node\Expr $valueVar - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @return \PHPStan\Rules\RuleError[] - */ - private function processForeach(Node\Expr $iterateeExpr, ?Node\Expr $keyVar, Node\Expr $valueVar, array $varTags): array - { - $variableNames = []; - if ($iterateeExpr instanceof Node\Expr\Variable && is_string($iterateeExpr->name)) { - $variableNames[] = $iterateeExpr->name; - } - if ($keyVar instanceof Node\Expr\Variable && is_string($keyVar->name)) { - $variableNames[] = $keyVar->name; - } - $variableNames = array_merge($variableNames, $this->getAssignedVariables($valueVar)); - - $errors = []; - foreach (array_keys($varTags) as $name) { - if (is_int($name)) { - if (count($variableNames) === 1) { - continue; - } - $errors[] = RuleErrorBuilder::message( - 'PHPDoc tag @var above foreach loop does not specify variable name.' - )->build(); - continue; - } - - if (in_array($name, $variableNames, true)) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in PHPDoc tag @var does not match any variable in the foreach loop: %s', - $name, - implode(', ', array_map(static function (string $name): string { - return sprintf('$%s', $name); - }, $variableNames)) - ))->build(); - } - - return $errors; - } - - /** - * @param \PhpParser\Node\Stmt\StaticVar[] $vars - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @return \PHPStan\Rules\RuleError[] - */ - private function processStatic(array $vars, array $varTags): array - { - $variableNames = []; - foreach ($vars as $var) { - if (!is_string($var->var->name)) { - continue; - } - - $variableNames[$var->var->name] = true; - } - - $errors = []; - foreach (array_keys($varTags) as $name) { - if (is_int($name)) { - if (count($vars) === 1) { - continue; - } - - $errors[] = RuleErrorBuilder::message( - 'PHPDoc tag @var above multiple static variables does not specify variable name.' - )->build(); - continue; - } - - if (isset($variableNames[$name])) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in PHPDoc tag @var does not match any static variable: %s', - $name, - implode(', ', array_map(static function (string $name): string { - return sprintf('$%s', $name); - }, array_keys($variableNames))) - ))->build(); - } - - return $errors; - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node\Expr $expr - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @return \PHPStan\Rules\RuleError[] - */ - private function processExpression(Scope $scope, Expr $expr, array $varTags): array - { - if ($expr instanceof Node\Expr\Assign || $expr instanceof Node\Expr\AssignRef) { - return $this->processAssign($scope, $expr->var, $varTags); - } - - return $this->processStmt($scope, $varTags, null); - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @param Expr|null $defaultExpr - * @return \PHPStan\Rules\RuleError[] - */ - private function processStmt(Scope $scope, array $varTags, ?Expr $defaultExpr): array - { - $errors = []; - - $variableLessVarTags = []; - foreach ($varTags as $name => $varTag) { - if (is_int($name)) { - $variableLessVarTags[] = $varTag; - continue; - } - - if (!$scope->hasVariableType($name)->no()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf('Variable $%s in PHPDoc tag @var does not exist.', $name))->build(); - } - - if (count($variableLessVarTags) !== 1 || $defaultExpr === null) { - if (count($variableLessVarTags) > 0) { - $errors[] = RuleErrorBuilder::message('PHPDoc tag @var does not specify variable name.')->build(); - } - } - - return $errors; - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags - * @return \PHPStan\Rules\RuleError[] - */ - private function processGlobal(Scope $scope, Node\Stmt\Global_ $node, array $varTags): array - { - $variableNames = []; - foreach ($node->vars as $var) { - if (!$var instanceof Expr\Variable) { - continue; - } - if (!is_string($var->name)) { - continue; - } - - $variableNames[$var->name] = true; - } - - $errors = []; - foreach (array_keys($varTags) as $name) { - if (is_int($name)) { - if (count($variableNames) === 1) { - continue; - } - - $errors[] = RuleErrorBuilder::message( - 'PHPDoc tag @var above multiple global variables does not specify variable name.' - )->build(); - continue; - } - - if (isset($variableNames[$name])) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in PHPDoc tag @var does not match any global variable: %s', - $name, - implode(', ', array_map(static function (string $name): string { - return sprintf('$%s', $name); - }, array_keys($variableNames))) - ))->build(); - } - - return $errors; - } - + private FileTypeMapper $fileTypeMapper; + + private bool $checkWrongVarUsage; + + public function __construct( + FileTypeMapper $fileTypeMapper, + bool $checkWrongVarUsage = false + ) { + $this->fileTypeMapper = $fileTypeMapper; + $this->checkWrongVarUsage = $checkWrongVarUsage; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + $node instanceof Node\Stmt\Property + || $node instanceof Node\Stmt\PropertyProperty + || $node instanceof Node\Stmt\ClassConst + || $node instanceof Node\Stmt\Const_ + || ($node instanceof VirtualNode && !$node instanceof InFunctionNode && !$node instanceof InClassMethodNode && !$node instanceof InClassNode) + ) { + return []; + } + + $varTags = []; + $function = $scope->getFunction(); + foreach ($node->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $comment->getText() + ); + foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { + $varTags[$key] = $varTag; + } + } + + if (count($varTags) === 0) { + return []; + } + + if ($node instanceof Node\Stmt\Foreach_) { + return $this->processForeach($node->expr, $node->keyVar, $node->valueVar, $varTags); + } + + if ($node instanceof Node\Stmt\Static_) { + return $this->processStatic($node->vars, $varTags); + } + + if ($node instanceof Node\Stmt\Expression) { + return $this->processExpression($scope, $node->expr, $varTags); + } + + if ($node instanceof Node\Stmt\Throw_ || $node instanceof Node\Stmt\Return_) { + return $this->processStmt($scope, $varTags, $node->expr); + } + + if ($node instanceof Node\Stmt\Global_) { + return $this->processGlobal($scope, $node, $varTags); + } + + if ($node instanceof InClassNode || $node instanceof InClassMethodNode || $node instanceof InFunctionNode) { + if ($this->checkWrongVarUsage) { + $description = 'a function'; + $originalNode = $node->getOriginalNode(); + if ($originalNode instanceof Node\Stmt\Interface_) { + $description = 'an interface'; + } elseif ($originalNode instanceof Node\Stmt\Class_) { + $description = 'a class'; + } elseif ($originalNode instanceof Node\Stmt\Trait_) { + throw new \PHPStan\ShouldNotHappenException(); + } elseif ($originalNode instanceof Node\Stmt\ClassMethod) { + $description = 'a method'; + } + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var above %s has no effect.', + $description + ))->build(), + ]; + } + + return []; + } + + return $this->processStmt($scope, $varTags, null); + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node\Expr $var + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @return \PHPStan\Rules\RuleError[] + */ + private function processAssign(Scope $scope, Node\Expr $var, array $varTags): array + { + $errors = []; + $hasMultipleMessage = false; + $assignedVariables = $this->getAssignedVariables($var); + foreach (array_keys($varTags) as $key) { + if (is_int($key)) { + if (count($varTags) !== 1) { + if (!$hasMultipleMessage) { + $errors[] = RuleErrorBuilder::message('Multiple PHPDoc @var tags above single variable assignment are not supported.')->build(); + $hasMultipleMessage = true; + } + } elseif (count($assignedVariables) !== 1) { + $errors[] = RuleErrorBuilder::message( + 'PHPDoc tag @var above assignment does not specify variable name.' + )->build(); + } + continue; + } + + if (!$scope->hasVariableType($key)->no()) { + continue; + } + + if (in_array($key, $assignedVariables, true)) { + continue; + } + + if (count($assignedVariables) === 1 && count($varTags) === 1) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in PHPDoc tag @var does not match assigned variable $%s.', + $key, + $assignedVariables[0] + ))->build(); + } else { + $errors[] = RuleErrorBuilder::message(sprintf('Variable $%s in PHPDoc tag @var does not exist.', $key))->build(); + } + } + + return $errors; + } + + /** + * @param Expr $expr + * @return string[] + */ + private function getAssignedVariables(Expr $expr): array + { + if ($expr instanceof Expr\Variable) { + if (is_string($expr->name)) { + return [$expr->name]; + } + + return []; + } + + if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + $names = []; + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + $names = array_merge($names, $this->getAssignedVariables($item->value)); + } + + return $names; + } + + return []; + } + + /** + * @param \PhpParser\Node\Expr|null $keyVar + * @param \PhpParser\Node\Expr $valueVar + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @return \PHPStan\Rules\RuleError[] + */ + private function processForeach(Node\Expr $iterateeExpr, ?Node\Expr $keyVar, Node\Expr $valueVar, array $varTags): array + { + $variableNames = []; + if ($iterateeExpr instanceof Node\Expr\Variable && is_string($iterateeExpr->name)) { + $variableNames[] = $iterateeExpr->name; + } + if ($keyVar instanceof Node\Expr\Variable && is_string($keyVar->name)) { + $variableNames[] = $keyVar->name; + } + $variableNames = array_merge($variableNames, $this->getAssignedVariables($valueVar)); + + $errors = []; + foreach (array_keys($varTags) as $name) { + if (is_int($name)) { + if (count($variableNames) === 1) { + continue; + } + $errors[] = RuleErrorBuilder::message( + 'PHPDoc tag @var above foreach loop does not specify variable name.' + )->build(); + continue; + } + + if (in_array($name, $variableNames, true)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in PHPDoc tag @var does not match any variable in the foreach loop: %s', + $name, + implode(', ', array_map(static function (string $name): string { + return sprintf('$%s', $name); + }, $variableNames)) + ))->build(); + } + + return $errors; + } + + /** + * @param \PhpParser\Node\Stmt\StaticVar[] $vars + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @return \PHPStan\Rules\RuleError[] + */ + private function processStatic(array $vars, array $varTags): array + { + $variableNames = []; + foreach ($vars as $var) { + if (!is_string($var->var->name)) { + continue; + } + + $variableNames[$var->var->name] = true; + } + + $errors = []; + foreach (array_keys($varTags) as $name) { + if (is_int($name)) { + if (count($vars) === 1) { + continue; + } + + $errors[] = RuleErrorBuilder::message( + 'PHPDoc tag @var above multiple static variables does not specify variable name.' + )->build(); + continue; + } + + if (isset($variableNames[$name])) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in PHPDoc tag @var does not match any static variable: %s', + $name, + implode(', ', array_map(static function (string $name): string { + return sprintf('$%s', $name); + }, array_keys($variableNames))) + ))->build(); + } + + return $errors; + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node\Expr $expr + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @return \PHPStan\Rules\RuleError[] + */ + private function processExpression(Scope $scope, Expr $expr, array $varTags): array + { + if ($expr instanceof Node\Expr\Assign || $expr instanceof Node\Expr\AssignRef) { + return $this->processAssign($scope, $expr->var, $varTags); + } + + return $this->processStmt($scope, $varTags, null); + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @param Expr|null $defaultExpr + * @return \PHPStan\Rules\RuleError[] + */ + private function processStmt(Scope $scope, array $varTags, ?Expr $defaultExpr): array + { + $errors = []; + + $variableLessVarTags = []; + foreach ($varTags as $name => $varTag) { + if (is_int($name)) { + $variableLessVarTags[] = $varTag; + continue; + } + + if (!$scope->hasVariableType($name)->no()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf('Variable $%s in PHPDoc tag @var does not exist.', $name))->build(); + } + + if (count($variableLessVarTags) !== 1 || $defaultExpr === null) { + if (count($variableLessVarTags) > 0) { + $errors[] = RuleErrorBuilder::message('PHPDoc tag @var does not specify variable name.')->build(); + } + } + + return $errors; + } + + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags + * @return \PHPStan\Rules\RuleError[] + */ + private function processGlobal(Scope $scope, Node\Stmt\Global_ $node, array $varTags): array + { + $variableNames = []; + foreach ($node->vars as $var) { + if (!$var instanceof Expr\Variable) { + continue; + } + if (!is_string($var->name)) { + continue; + } + + $variableNames[$var->name] = true; + } + + $errors = []; + foreach (array_keys($varTags) as $name) { + if (is_int($name)) { + if (count($variableNames) === 1) { + continue; + } + + $errors[] = RuleErrorBuilder::message( + 'PHPDoc tag @var above multiple global variables does not specify variable name.' + )->build(); + continue; + } + + if (isset($variableNames[$name])) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in PHPDoc tag @var does not match any global variable: %s', + $name, + implode(', ', array_map(static function (string $name): string { + return sprintf('$%s', $name); + }, array_keys($variableNames))) + ))->build(); + } + + return $errors; + } } diff --git a/src/Rules/Properties/AccessPropertiesInAssignRule.php b/src/Rules/Properties/AccessPropertiesInAssignRule.php index 2ed10a19fe..43eee5c584 100644 --- a/src/Rules/Properties/AccessPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessPropertiesInAssignRule.php @@ -1,4 +1,6 @@ -accessPropertiesRule = $accessPropertiesRule; - } - - public function getNodeType(): string - { - return Node\Expr\Assign::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->var instanceof Node\Expr\PropertyFetch) { - return []; - } - - return $this->accessPropertiesRule->processNode($node->var, $scope); - } - + private \PHPStan\Rules\Properties\AccessPropertiesRule $accessPropertiesRule; + + public function __construct(AccessPropertiesRule $accessPropertiesRule) + { + $this->accessPropertiesRule = $accessPropertiesRule; + } + + public function getNodeType(): string + { + return Node\Expr\Assign::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->var instanceof Node\Expr\PropertyFetch) { + return []; + } + + return $this->accessPropertiesRule->processNode($node->var, $scope); + } } diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 7ab6fe9409..94e3ce8755 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->reportMagicProperties = $reportMagicProperties; - } - - public function getNodeType(): string - { - return PropertyFetch::class; - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - if ($node->name instanceof Identifier) { - $names = [$node->name->name]; - } else { - $names = array_map(static function (ConstantStringType $type): string { - return $type->getValue(); - }, TypeUtils::getConstantStrings($scope->getType($node->name))); - } - - $errors = []; - foreach ($names as $name) { - $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); - } - - return $errors; - } - - /** - * @param Scope $scope - * @param PropertyFetch $node - * @param string $name - * @return RuleError[] - */ - private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->var, - sprintf('Access to property $%s on an unknown class %%s.', $name), - static function (Type $type) use ($name): bool { - return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - - if ($scope->isInExpressionAssign($node)) { - return []; - } - - if (!$type->canAccessProperties()->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot access property $%s on %s.', - $name, - $type->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - - if (!$type->hasProperty($name)->yes()) { - if ($scope->isSpecified($node)) { - return []; - } - - $classNames = $typeResult->getReferencedClasses(); - if (!$this->reportMagicProperties) { - foreach ($classNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ( - $classReflection->hasNativeMethod('__get') - || $classReflection->hasNativeMethod('__set') - ) { - return []; - } - } - } - - if (count($classNames) === 1) { - $referencedClass = $typeResult->getReferencedClasses()[0]; - $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); - $parentClassReflection = $propertyClassReflection->getParentClass(); - while ($parentClassReflection !== false) { - if ($parentClassReflection->hasProperty($name)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Access to private property $%s of parent class %s.', - $name, - $parentClassReflection->getDisplayName() - ))->build(), - ]; - } - - $parentClassReflection = $parentClassReflection->getParentClass(); - } - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Access to an undefined property %s::$%s.', - $type->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), - ]; - } - - $propertyReflection = $type->getProperty($name, $scope); - if (!$scope->canAccessProperty($propertyReflection)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Access to %s property %s::$%s.', - $propertyReflection->isPrivate() ? 'private' : 'protected', - $type->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private bool $reportMagicProperties; + + public function __construct( + ReflectionProvider $reflectionProvider, + RuleLevelHelper $ruleLevelHelper, + bool $reportMagicProperties + ) { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->reportMagicProperties = $reportMagicProperties; + } + + public function getNodeType(): string + { + return PropertyFetch::class; + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + if ($node->name instanceof Identifier) { + $names = [$node->name->name]; + } else { + $names = array_map(static function (ConstantStringType $type): string { + return $type->getValue(); + }, TypeUtils::getConstantStrings($scope->getType($node->name))); + } + + $errors = []; + foreach ($names as $name) { + $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); + } + + return $errors; + } + + /** + * @param Scope $scope + * @param PropertyFetch $node + * @param string $name + * @return RuleError[] + */ + private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + sprintf('Access to property $%s on an unknown class %%s.', $name), + static function (Type $type) use ($name): bool { + return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + + if ($scope->isInExpressionAssign($node)) { + return []; + } + + if (!$type->canAccessProperties()->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot access property $%s on %s.', + $name, + $type->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + + if (!$type->hasProperty($name)->yes()) { + if ($scope->isSpecified($node)) { + return []; + } + + $classNames = $typeResult->getReferencedClasses(); + if (!$this->reportMagicProperties) { + foreach ($classNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ( + $classReflection->hasNativeMethod('__get') + || $classReflection->hasNativeMethod('__set') + ) { + return []; + } + } + } + + if (count($classNames) === 1) { + $referencedClass = $typeResult->getReferencedClasses()[0]; + $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); + $parentClassReflection = $propertyClassReflection->getParentClass(); + while ($parentClassReflection !== false) { + if ($parentClassReflection->hasProperty($name)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Access to private property $%s of parent class %s.', + $name, + $parentClassReflection->getDisplayName() + ))->build(), + ]; + } + + $parentClassReflection = $parentClassReflection->getParentClass(); + } + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Access to an undefined property %s::$%s.', + $type->describe(VerbosityLevel::typeOnly()), + $name + ))->build(), + ]; + } + + $propertyReflection = $type->getProperty($name, $scope); + if (!$scope->canAccessProperty($propertyReflection)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Access to %s property %s::$%s.', + $propertyReflection->isPrivate() ? 'private' : 'protected', + $type->describe(VerbosityLevel::typeOnly()), + $name + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php b/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php index efd61a5c59..65b988a7c3 100644 --- a/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php @@ -1,4 +1,6 @@ -accessStaticPropertiesRule = $accessStaticPropertiesRule; - } - - public function getNodeType(): string - { - return Node\Expr\Assign::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->var instanceof Node\Expr\StaticPropertyFetch) { - return []; - } - - return $this->accessStaticPropertiesRule->processNode($node->var, $scope); - } - + private \PHPStan\Rules\Properties\AccessStaticPropertiesRule $accessStaticPropertiesRule; + + public function __construct(AccessStaticPropertiesRule $accessStaticPropertiesRule) + { + $this->accessStaticPropertiesRule = $accessStaticPropertiesRule; + } + + public function getNodeType(): string + { + return Node\Expr\Assign::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->var instanceof Node\Expr\StaticPropertyFetch) { + return []; + } + + return $this->accessStaticPropertiesRule->processNode($node->var, $scope); + } } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 4ae0bdf44f..e99eb7f1b6 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - } - - public function getNodeType(): string - { - return StaticPropertyFetch::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node->name instanceof Node\VarLikeIdentifier) { - $names = [$node->name->name]; - } else { - $names = array_map(static function (ConstantStringType $type): string { - return $type->getValue(); - }, TypeUtils::getConstantStrings($scope->getType($node->name))); - } - - $errors = []; - foreach ($names as $name) { - $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); - } - - return $errors; - } - - /** - * @param Scope $scope - * @param StaticPropertyFetch $node - * @param string $name - * @return RuleError[] - */ - private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, string $name): array - { - $messages = []; - if ($node->class instanceof Name) { - $class = (string) $node->class; - $lowercasedClass = strtolower($class); - if (in_array($lowercasedClass, ['self', 'static'], true)) { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Accessing %s::$%s outside of class scope.', - $class, - $name - ))->build(), - ]; - } - $classType = $scope->resolveTypeByName($node->class); - } elseif ($lowercasedClass === 'parent') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Accessing %s::$%s outside of class scope.', - $class, - $name - ))->build(), - ]; - } - if ($scope->getClassReflection()->getParentClass() === false) { - return [ - RuleErrorBuilder::message(sprintf( - '%s::%s() accesses parent::$%s but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), - $scope->getFunctionName(), - $name, - $scope->getClassReflection()->getDisplayName() - ))->build(), - ]; - } - - if ($scope->getFunctionName() === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $currentMethodReflection = $scope->getClassReflection()->getNativeMethod($scope->getFunctionName()); - if (!$currentMethodReflection->isStatic()) { - // calling parent::method() from instance method - return []; - } - - $classType = $scope->resolveTypeByName($node->class); - } else { - if (!$this->reflectionProvider->hasClass($class)) { - if ($scope->isInClassExists($class)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Access to static property $%s on an unknown class %s.', - $name, - $class - ))->discoveringSymbolsTip()->build(), - ]; - } else { - $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($class, $node->class)]); - } - - $classType = $scope->resolveTypeByName($node->class); - } - - $classReflection = $classType->getClassReflection(); - if ($classReflection !== null && $classReflection->isTrait()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Access to static property $%s on trait %s.', - $name, - $classReflection->getName() - ))->build(), - ]; - } - } else { - $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->class, - sprintf('Access to static property $%s on an unknown class %%s.', $name), - static function (Type $type) use ($name): bool { - return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); - } - ); - $classType = $classTypeResult->getType(); - if ($classType instanceof ErrorType) { - return $classTypeResult->getUnknownClassErrors(); - } - } - - if ((new StringType())->isSuperTypeOf($classType)->yes()) { - return []; - } - - $typeForDescribe = $classType; - if ($classType instanceof ThisType) { - $typeForDescribe = $classType->getStaticObjectType(); - } - $classType = TypeCombinator::remove($classType, new StringType()); - - if ($scope->isInExpressionAssign($node)) { - return []; - } - - if (!$classType->canAccessProperties()->yes()) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Cannot access static property $%s on %s.', - $name, - $typeForDescribe->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]); - } - - if (!$classType->hasProperty($name)->yes()) { - if ($scope->isSpecified($node)) { - return $messages; - } - - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Access to an undefined static property %s::$%s.', - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), - ]); - } - - $property = $classType->getProperty($name, $scope); - if (!$property->isStatic()) { - $hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType); - foreach ($hasPropertyTypes as $hasPropertyType) { - if ($hasPropertyType->getPropertyName() === $name) { - return []; - } - } - - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Static access to instance property %s::$%s.', - $property->getDeclaringClass()->getDisplayName(), - $name - ))->build(), - ]); - } - - if (!$scope->canAccessProperty($property)) { - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Access to %s property $%s of class %s.', - $property->isPrivate() ? 'private' : 'protected', - $name, - $property->getDeclaringClass()->getDisplayName() - ))->build(), - ]); - } - - return $messages; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + public function __construct( + ReflectionProvider $reflectionProvider, + RuleLevelHelper $ruleLevelHelper, + ClassCaseSensitivityCheck $classCaseSensitivityCheck + ) { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + } + + public function getNodeType(): string + { + return StaticPropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\VarLikeIdentifier) { + $names = [$node->name->name]; + } else { + $names = array_map(static function (ConstantStringType $type): string { + return $type->getValue(); + }, TypeUtils::getConstantStrings($scope->getType($node->name))); + } + + $errors = []; + foreach ($names as $name) { + $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); + } + + return $errors; + } + + /** + * @param Scope $scope + * @param StaticPropertyFetch $node + * @param string $name + * @return RuleError[] + */ + private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, string $name): array + { + $messages = []; + if ($node->class instanceof Name) { + $class = (string) $node->class; + $lowercasedClass = strtolower($class); + if (in_array($lowercasedClass, ['self', 'static'], true)) { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Accessing %s::$%s outside of class scope.', + $class, + $name + ))->build(), + ]; + } + $classType = $scope->resolveTypeByName($node->class); + } elseif ($lowercasedClass === 'parent') { + if (!$scope->isInClass()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Accessing %s::$%s outside of class scope.', + $class, + $name + ))->build(), + ]; + } + if ($scope->getClassReflection()->getParentClass() === false) { + return [ + RuleErrorBuilder::message(sprintf( + '%s::%s() accesses parent::$%s but %s does not extend any class.', + $scope->getClassReflection()->getDisplayName(), + $scope->getFunctionName(), + $name, + $scope->getClassReflection()->getDisplayName() + ))->build(), + ]; + } + + if ($scope->getFunctionName() === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $currentMethodReflection = $scope->getClassReflection()->getNativeMethod($scope->getFunctionName()); + if (!$currentMethodReflection->isStatic()) { + // calling parent::method() from instance method + return []; + } + + $classType = $scope->resolveTypeByName($node->class); + } else { + if (!$this->reflectionProvider->hasClass($class)) { + if ($scope->isInClassExists($class)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Access to static property $%s on an unknown class %s.', + $name, + $class + ))->discoveringSymbolsTip()->build(), + ]; + } else { + $messages = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($class, $node->class)]); + } + + $classType = $scope->resolveTypeByName($node->class); + } + + $classReflection = $classType->getClassReflection(); + if ($classReflection !== null && $classReflection->isTrait()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Access to static property $%s on trait %s.', + $name, + $classReflection->getName() + ))->build(), + ]; + } + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + sprintf('Access to static property $%s on an unknown class %%s.', $name), + static function (Type $type) use ($name): bool { + return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); + } + ); + $classType = $classTypeResult->getType(); + if ($classType instanceof ErrorType) { + return $classTypeResult->getUnknownClassErrors(); + } + } + + if ((new StringType())->isSuperTypeOf($classType)->yes()) { + return []; + } + + $typeForDescribe = $classType; + if ($classType instanceof ThisType) { + $typeForDescribe = $classType->getStaticObjectType(); + } + $classType = TypeCombinator::remove($classType, new StringType()); + + if ($scope->isInExpressionAssign($node)) { + return []; + } + + if (!$classType->canAccessProperties()->yes()) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Cannot access static property $%s on %s.', + $name, + $typeForDescribe->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]); + } + + if (!$classType->hasProperty($name)->yes()) { + if ($scope->isSpecified($node)) { + return $messages; + } + + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Access to an undefined static property %s::$%s.', + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + $name + ))->build(), + ]); + } + + $property = $classType->getProperty($name, $scope); + if (!$property->isStatic()) { + $hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType); + foreach ($hasPropertyTypes as $hasPropertyType) { + if ($hasPropertyType->getPropertyName() === $name) { + return []; + } + } + + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Static access to instance property %s::$%s.', + $property->getDeclaringClass()->getDisplayName(), + $name + ))->build(), + ]); + } + + if (!$scope->canAccessProperty($property)) { + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Access to %s property $%s of class %s.', + $property->isPrivate() ? 'private' : 'protected', + $name, + $property->getDeclaringClass()->getDisplayName() + ))->build(), + ]); + } + + return $messages; + } } diff --git a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php index 24d929355e..0a6164f859 100644 --- a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return ClassPropertyNode::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return ClassPropertyNode::class; + } - $classReflection = $scope->getClassReflection(); - $default = $node->getDefault(); - if ($default === null) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $propertyReflection = $classReflection->getNativeProperty($node->getName()); - $propertyType = $propertyReflection->getWritableType(); - if ($propertyReflection->getNativeType() instanceof MixedType) { - if ($default instanceof Node\Expr\ConstFetch && (string) $default->name === 'null') { - return []; - } - } - $defaultValueType = $scope->getType($default); - if ($this->ruleLevelHelper->accepts($propertyType, $defaultValueType, true)) { - return []; - } + $classReflection = $scope->getClassReflection(); + $default = $node->getDefault(); + if ($default === null) { + return []; + } - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $defaultValueType); + $propertyReflection = $classReflection->getNativeProperty($node->getName()); + $propertyType = $propertyReflection->getWritableType(); + if ($propertyReflection->getNativeType() instanceof MixedType) { + if ($default instanceof Node\Expr\ConstFetch && (string) $default->name === 'null') { + return []; + } + } + $defaultValueType = $scope->getType($default); + if ($this->ruleLevelHelper->accepts($propertyType, $defaultValueType, true)) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - '%s %s::$%s (%s) does not accept default value of type %s.', - $node->isStatic() ? 'Static property' : 'Property', - $classReflection->getDisplayName(), - $node->getName(), - $propertyType->describe($verbosityLevel), - $defaultValueType->describe($verbosityLevel) - ))->build(), - ]; - } + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $defaultValueType); + return [ + RuleErrorBuilder::message(sprintf( + '%s %s::$%s (%s) does not accept default value of type %s.', + $node->isStatic() ? 'Static property' : 'Property', + $classReflection->getDisplayName(), + $node->getName(), + $propertyType->describe($verbosityLevel), + $defaultValueType->describe($verbosityLevel) + ))->build(), + ]; + } } diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index 6a4c1f1cf1..c4c18d33b8 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; - $this->checkThisOnly = $checkThisOnly; - } - - public function getNodeType(): string - { - return ClassPropertyNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $propertyReflection = $scope->getClassReflection()->getNativeProperty($node->getName()); - if ($this->checkThisOnly) { - $referencedClasses = $propertyReflection->getNativeType()->getReferencedClasses(); - } else { - $referencedClasses = array_merge( - $propertyReflection->getNativeType()->getReferencedClasses(), - $propertyReflection->getPhpDocType()->getReferencedClasses() - ); - } - - $errors = []; - foreach ($referencedClasses as $referencedClass) { - if ($this->reflectionProvider->hasClass($referencedClass)) { - if ($this->reflectionProvider->getClass($referencedClass)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s has invalid type %s.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName(), - $referencedClass - ))->build(); - } - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s has unknown class %s as its type.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName(), - $referencedClass - ))->discoveringSymbolsTip()->build(); - } - - if ($this->checkClassCaseSensitivity) { - $errors = array_merge( - $errors, - $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($node): ClassNameNodePair { - return new ClassNameNodePair($class, $node); - }, $referencedClasses)) - ); - } - - return $errors; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + + private bool $checkClassCaseSensitivity; + + private bool $checkThisOnly; + + public function __construct( + ReflectionProvider $reflectionProvider, + ClassCaseSensitivityCheck $classCaseSensitivityCheck, + bool $checkClassCaseSensitivity, + bool $checkThisOnly + ) { + $this->reflectionProvider = $reflectionProvider; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; + $this->checkThisOnly = $checkThisOnly; + } + + public function getNodeType(): string + { + return ClassPropertyNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $propertyReflection = $scope->getClassReflection()->getNativeProperty($node->getName()); + if ($this->checkThisOnly) { + $referencedClasses = $propertyReflection->getNativeType()->getReferencedClasses(); + } else { + $referencedClasses = array_merge( + $propertyReflection->getNativeType()->getReferencedClasses(), + $propertyReflection->getPhpDocType()->getReferencedClasses() + ); + } + + $errors = []; + foreach ($referencedClasses as $referencedClass) { + if ($this->reflectionProvider->hasClass($referencedClass)) { + if ($this->reflectionProvider->getClass($referencedClass)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s has invalid type %s.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName(), + $referencedClass + ))->build(); + } + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s has unknown class %s as its type.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName(), + $referencedClass + ))->discoveringSymbolsTip()->build(); + } + + if ($this->checkClassCaseSensitivity) { + $errors = array_merge( + $errors, + $this->classCaseSensitivityCheck->checkClassNames(array_map(static function (string $class) use ($node): ClassNameNodePair { + return new ClassNameNodePair($class, $node); + }, $referencedClasses)) + ); + } + + return $errors; + } } diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 97efdcd412..21e643cf40 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -1,4 +1,6 @@ -originalPropertyReflection = $originalPropertyReflection; - $this->scope = $scope; - $this->propertyName = $propertyName; - $this->readableType = $readableType; - $this->writableType = $writableType; - } - - public function getScope(): Scope - { - return $this->scope; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->originalPropertyReflection->getDeclaringClass(); - } - - public function getName(): string - { - return $this->propertyName; - } - - public function isStatic(): bool - { - return $this->originalPropertyReflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->originalPropertyReflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->originalPropertyReflection->isPublic(); - } - - public function getDocComment(): ?string - { - return $this->originalPropertyReflection->getDocComment(); - } - - public function getReadableType(): Type - { - return $this->readableType; - } - - public function getWritableType(): Type - { - return $this->writableType; - } - - public function canChangeTypeAfterAssignment(): bool - { - return $this->originalPropertyReflection->canChangeTypeAfterAssignment(); - } - - public function isReadable(): bool - { - return $this->originalPropertyReflection->isReadable(); - } - - public function isWritable(): bool - { - return $this->originalPropertyReflection->isWritable(); - } - - public function isDeprecated(): TrinaryLogic - { - return $this->originalPropertyReflection->isDeprecated(); - } - - public function getDeprecatedDescription(): ?string - { - return $this->originalPropertyReflection->getDeprecatedDescription(); - } - - public function isInternal(): TrinaryLogic - { - return $this->originalPropertyReflection->isInternal(); - } - - public function isNative(): bool - { - $reflection = $this->originalPropertyReflection; - while ($reflection instanceof WrapperPropertyReflection) { - $reflection = $reflection->getOriginalReflection(); - } - - return $reflection instanceof PhpPropertyReflection; - } - - public function getNativeType(): ?Type - { - $reflection = $this->originalPropertyReflection; - while ($reflection instanceof WrapperPropertyReflection) { - $reflection = $reflection->getOriginalReflection(); - } - - if (!$reflection instanceof PhpPropertyReflection) { - return null; - } - - return $reflection->getNativeType(); - } - + private PropertyReflection $originalPropertyReflection; + + private Scope $scope; + + private string $propertyName; + + private Type $readableType; + + private Type $writableType; + + public function __construct( + PropertyReflection $originalPropertyReflection, + Scope $scope, + string $propertyName, + Type $readableType, + Type $writableType + ) { + $this->originalPropertyReflection = $originalPropertyReflection; + $this->scope = $scope; + $this->propertyName = $propertyName; + $this->readableType = $readableType; + $this->writableType = $writableType; + } + + public function getScope(): Scope + { + return $this->scope; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->originalPropertyReflection->getDeclaringClass(); + } + + public function getName(): string + { + return $this->propertyName; + } + + public function isStatic(): bool + { + return $this->originalPropertyReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->originalPropertyReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->originalPropertyReflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->originalPropertyReflection->getDocComment(); + } + + public function getReadableType(): Type + { + return $this->readableType; + } + + public function getWritableType(): Type + { + return $this->writableType; + } + + public function canChangeTypeAfterAssignment(): bool + { + return $this->originalPropertyReflection->canChangeTypeAfterAssignment(); + } + + public function isReadable(): bool + { + return $this->originalPropertyReflection->isReadable(); + } + + public function isWritable(): bool + { + return $this->originalPropertyReflection->isWritable(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->originalPropertyReflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->originalPropertyReflection->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->originalPropertyReflection->isInternal(); + } + + public function isNative(): bool + { + $reflection = $this->originalPropertyReflection; + while ($reflection instanceof WrapperPropertyReflection) { + $reflection = $reflection->getOriginalReflection(); + } + + return $reflection instanceof PhpPropertyReflection; + } + + public function getNativeType(): ?Type + { + $reflection = $this->originalPropertyReflection; + while ($reflection instanceof WrapperPropertyReflection) { + $reflection = $reflection->getOriginalReflection(); + } + + if (!$reflection instanceof PhpPropertyReflection) { + return null; + } + + return $reflection->getNativeType(); + } } diff --git a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php index eed89ae937..79ba8318a6 100644 --- a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php +++ b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php @@ -1,4 +1,6 @@ -container = $container; - } + /** @var ReadWritePropertiesExtension[]|null */ + private ?array $extensions = null; - public function getExtensions(): array - { - if ($this->extensions === null) { - $this->extensions = $this->container->getServicesByTag(ReadWritePropertiesExtensionProvider::EXTENSION_TAG); - } + public function __construct(Container $container) + { + $this->container = $container; + } - return $this->extensions; - } + public function getExtensions(): array + { + if ($this->extensions === null) { + $this->extensions = $this->container->getServicesByTag(ReadWritePropertiesExtensionProvider::EXTENSION_TAG); + } + return $this->extensions; + } } diff --git a/src/Rules/Properties/MissingPropertyTypehintRule.php b/src/Rules/Properties/MissingPropertyTypehintRule.php index f45f29ce7a..3440847f30 100644 --- a/src/Rules/Properties/MissingPropertyTypehintRule.php +++ b/src/Rules/Properties/MissingPropertyTypehintRule.php @@ -1,4 +1,6 @@ -missingTypehintCheck = $missingTypehintCheck; - } - - public function getNodeType(): string - { - return ClassPropertyNode::class; - } + public function __construct(MissingTypehintCheck $missingTypehintCheck) + { + $this->missingTypehintCheck = $missingTypehintCheck; + } - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function getNodeType(): string + { + return ClassPropertyNode::class; + } - $propertyReflection = $scope->getClassReflection()->getNativeProperty($node->getName()); - $propertyType = $propertyReflection->getReadableType(); - if ($propertyType instanceof MixedType && !$propertyType->isExplicitMixed()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Property %s::$%s has no typehint specified.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName() - ))->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $messages = []; - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($propertyType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s type has no value type specified in iterable type %s.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName(), - $iterableTypeDescription - ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); - } + $propertyReflection = $scope->getClassReflection()->getNativeProperty($node->getName()); + $propertyType = $propertyReflection->getReadableType(); + if ($propertyType instanceof MixedType && !$propertyType->isExplicitMixed()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Property %s::$%s has no typehint specified.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->build(), + ]; + } - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($propertyType) as [$name, $genericTypeNames]) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s with generic %s does not specify its types: %s', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName(), - $name, - implode(', ', $genericTypeNames) - ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); - } + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($propertyType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s type has no value type specified in iterable type %s.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName(), + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($propertyType) as $callableType) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s type has no signature specified for %s.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $node->getName(), - $callableType->describe(VerbosityLevel::typeOnly()) - ))->build(); - } + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($propertyType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s with generic %s does not specify its types: %s', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName(), + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } - return $messages; - } + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($propertyType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s type has no signature specified for %s.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName(), + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + return $messages; + } } diff --git a/src/Rules/Properties/NullsafePropertyFetchRule.php b/src/Rules/Properties/NullsafePropertyFetchRule.php index 803eabf0f2..20123845c5 100644 --- a/src/Rules/Properties/NullsafePropertyFetchRule.php +++ b/src/Rules/Properties/NullsafePropertyFetchRule.php @@ -1,4 +1,6 @@ -getType($node->var); - if ($calledOnType->equals($nullType)) { - return []; - } - - if (!$calledOnType->isSuperTypeOf($nullType)->no()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))->build(), - ]; - } - + public function getNodeType(): string + { + return Node\Expr\NullsafePropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $nullType = new NullType(); + $calledOnType = $scope->getType($node->var); + if ($calledOnType->equals($nullType)) { + return []; + } + + if (!$calledOnType->isSuperTypeOf($nullType)->no()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly())))->build(), + ]; + } } diff --git a/src/Rules/Properties/PropertyAttributesRule.php b/src/Rules/Properties/PropertyAttributesRule.php index d00a080e8f..856a94550c 100644 --- a/src/Rules/Properties/PropertyAttributesRule.php +++ b/src/Rules/Properties/PropertyAttributesRule.php @@ -1,4 +1,6 @@ -attributesCheck = $attributesCheck; - } - - public function getNodeType(): string - { - return Node\Stmt\Property::class; - } - - public function processNode(Node $node, Scope $scope): array - { - return $this->attributesCheck->check( - $scope, - $node->attrGroups, - \Attribute::TARGET_PROPERTY, - 'property' - ); - } - + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\Property::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_PROPERTY, + 'property' + ); + } } diff --git a/src/Rules/Properties/PropertyDescriptor.php b/src/Rules/Properties/PropertyDescriptor.php index e8e1a74a24..6b52081e5a 100644 --- a/src/Rules/Properties/PropertyDescriptor.php +++ b/src/Rules/Properties/PropertyDescriptor.php @@ -1,4 +1,6 @@ -isStatic()) { - return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); - } - - return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); - } - - /** - * @param \PHPStan\Reflection\PropertyReflection $property - * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch - * @return string - */ - public function describeProperty(PropertyReflection $property, $propertyFetch): string - { - /** @var \PhpParser\Node\Identifier $name */ - $name = $propertyFetch->name; - if (!$property->isStatic()) { - return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $name->name); - } - - return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $name->name); - } - + public function describePropertyByName(PropertyReflection $property, string $propertyName): string + { + if (!$property->isStatic()) { + return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); + } + + return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); + } + + /** + * @param \PHPStan\Reflection\PropertyReflection $property + * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch + * @return string + */ + public function describeProperty(PropertyReflection $property, $propertyFetch): string + { + /** @var \PhpParser\Node\Identifier $name */ + $name = $propertyFetch->name; + if (!$property->isStatic()) { + return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $name->name); + } + + return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $name->name); + } } diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index 3e07277ada..f845907ae2 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -1,4 +1,6 @@ -name instanceof \PhpParser\Node\Identifier) { - $names = [$propertyFetch->name->name]; - } else { - $names = array_map(static function (ConstantStringType $name): string { - return $name->getValue(); - }, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name))); - } - - $reflections = []; - $propertyHolderType = $scope->getType($propertyFetch->var); - foreach ($names as $name) { - $reflection = $this->findPropertyReflection( - $propertyHolderType, - $name, - $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( - $propertyFetch->name, - new String_($name) - )) : $scope - ); - if ($reflection === null) { - continue; - } - - $reflections[] = $reflection; - } - - return $reflections; - } - - if ($propertyFetch->class instanceof \PhpParser\Node\Name) { - $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); - } else { - $propertyHolderType = $scope->getType($propertyFetch->class); - } - - if ($propertyFetch->name instanceof VarLikeIdentifier) { - $names = [$propertyFetch->name->name]; - } else { - $names = array_map(static function (ConstantStringType $name): string { - return $name->getValue(); - }, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name))); - } - - $reflections = []; - foreach ($names as $name) { - $reflection = $this->findPropertyReflection( - $propertyHolderType, - $name, - $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( - $propertyFetch->name, - new String_($name) - )) : $scope - ); - if ($reflection === null) { - continue; - } - - $reflections[] = $reflection; - } - - return $reflections; - } - - /** - * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch - * @param \PHPStan\Analyser\Scope $scope - * @return FoundPropertyReflection|null - */ - public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?FoundPropertyReflection - { - if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) { - if (!$propertyFetch->name instanceof \PhpParser\Node\Identifier) { - return null; - } - $propertyHolderType = $scope->getType($propertyFetch->var); - return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); - } - - if (!$propertyFetch->name instanceof \PhpParser\Node\Identifier) { - return null; - } - - if ($propertyFetch->class instanceof \PhpParser\Node\Name) { - $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); - } else { - $propertyHolderType = $scope->getType($propertyFetch->class); - } - - return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); - } - - private function findPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection - { - if (!$propertyHolderType->hasProperty($propertyName)->yes()) { - return null; - } - - $originalProperty = $propertyHolderType->getProperty($propertyName, $scope); - - return new FoundPropertyReflection( - $originalProperty, - $scope, - $propertyName, - $originalProperty->getReadableType(), - $originalProperty->getWritableType() - ); - } - + /** + * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch + * @param \PHPStan\Analyser\Scope $scope + * @return FoundPropertyReflection[] + */ + public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): array + { + if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) { + if ($propertyFetch->name instanceof \PhpParser\Node\Identifier) { + $names = [$propertyFetch->name->name]; + } else { + $names = array_map(static function (ConstantStringType $name): string { + return $name->getValue(); + }, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name))); + } + + $reflections = []; + $propertyHolderType = $scope->getType($propertyFetch->var); + foreach ($names as $name) { + $reflection = $this->findPropertyReflection( + $propertyHolderType, + $name, + $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( + $propertyFetch->name, + new String_($name) + )) : $scope + ); + if ($reflection === null) { + continue; + } + + $reflections[] = $reflection; + } + + return $reflections; + } + + if ($propertyFetch->class instanceof \PhpParser\Node\Name) { + $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); + } else { + $propertyHolderType = $scope->getType($propertyFetch->class); + } + + if ($propertyFetch->name instanceof VarLikeIdentifier) { + $names = [$propertyFetch->name->name]; + } else { + $names = array_map(static function (ConstantStringType $name): string { + return $name->getValue(); + }, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name))); + } + + $reflections = []; + foreach ($names as $name) { + $reflection = $this->findPropertyReflection( + $propertyHolderType, + $name, + $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( + $propertyFetch->name, + new String_($name) + )) : $scope + ); + if ($reflection === null) { + continue; + } + + $reflections[] = $reflection; + } + + return $reflections; + } + + /** + * @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch + * @param \PHPStan\Analyser\Scope $scope + * @return FoundPropertyReflection|null + */ + public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?FoundPropertyReflection + { + if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) { + if (!$propertyFetch->name instanceof \PhpParser\Node\Identifier) { + return null; + } + $propertyHolderType = $scope->getType($propertyFetch->var); + return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + } + + if (!$propertyFetch->name instanceof \PhpParser\Node\Identifier) { + return null; + } + + if ($propertyFetch->class instanceof \PhpParser\Node\Name) { + $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); + } else { + $propertyHolderType = $scope->getType($propertyFetch->class); + } + + return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + } + + private function findPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection + { + if (!$propertyHolderType->hasProperty($propertyName)->yes()) { + return null; + } + + $originalProperty = $propertyHolderType->getProperty($propertyName, $scope); + + return new FoundPropertyReflection( + $originalProperty, + $scope, + $propertyName, + $originalProperty->getReadableType(), + $originalProperty->getWritableType() + ); + } } diff --git a/src/Rules/Properties/ReadWritePropertiesExtension.php b/src/Rules/Properties/ReadWritePropertiesExtension.php index 8eb0a9ab03..407b13004e 100644 --- a/src/Rules/Properties/ReadWritePropertiesExtension.php +++ b/src/Rules/Properties/ReadWritePropertiesExtension.php @@ -1,4 +1,6 @@ -propertyDescriptor = $propertyDescriptor; - $this->propertyReflectionFinder = $propertyReflectionFinder; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->checkThisOnly = $checkThisOnly; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !($node instanceof Node\Expr\PropertyFetch) - && !($node instanceof Node\Expr\StaticPropertyFetch) - ) { - return []; - } - - if ( - $node instanceof Node\Expr\PropertyFetch - && $this->checkThisOnly - && !$this->ruleLevelHelper->isThis($node->var) - ) { - return []; - } - - if ($scope->isInExpressionAssign($node)) { - return []; - } - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $scope); - if ($propertyReflection === null) { - return []; - } - if (!$scope->canAccessProperty($propertyReflection)) { - return []; - } - - if (!$propertyReflection->isReadable()) { - $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $node); - - return [ - RuleErrorBuilder::message(sprintf( - '%s is not readable.', - $propertyDescription - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\Properties\PropertyDescriptor $propertyDescriptor; + + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + private RuleLevelHelper $ruleLevelHelper; + + private bool $checkThisOnly; + + public function __construct( + PropertyDescriptor $propertyDescriptor, + PropertyReflectionFinder $propertyReflectionFinder, + RuleLevelHelper $ruleLevelHelper, + bool $checkThisOnly + ) { + $this->propertyDescriptor = $propertyDescriptor; + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->checkThisOnly = $checkThisOnly; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !($node instanceof Node\Expr\PropertyFetch) + && !($node instanceof Node\Expr\StaticPropertyFetch) + ) { + return []; + } + + if ( + $node instanceof Node\Expr\PropertyFetch + && $this->checkThisOnly + && !$this->ruleLevelHelper->isThis($node->var) + ) { + return []; + } + + if ($scope->isInExpressionAssign($node)) { + return []; + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $scope); + if ($propertyReflection === null) { + return []; + } + if (!$scope->canAccessProperty($propertyReflection)) { + return []; + } + + if (!$propertyReflection->isReadable()) { + $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $node); + + return [ + RuleErrorBuilder::message(sprintf( + '%s is not readable.', + $propertyDescription + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index 1a123ffdea..9f5ba3a384 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->propertyDescriptor = $propertyDescriptor; - $this->propertyReflectionFinder = $propertyReflectionFinder; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignOp - && !$node instanceof Node\Expr\AssignRef - ) { - return []; - } - - if ( - !($node->var instanceof Node\Expr\PropertyFetch) - && !($node->var instanceof Node\Expr\StaticPropertyFetch) - ) { - return []; - } - - /** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */ - $propertyFetch = $node->var; - $propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope); - - $errors = []; - foreach ($propertyReflections as $propertyReflection) { - $errors = array_merge($errors, $this->processSingleProperty( - $propertyReflection, - $node - )); - } - - return $errors; - } - - /** - * @param FoundPropertyReflection $propertyReflection - * @param Node\Expr $node - * @return RuleError[] - */ - private function processSingleProperty( - FoundPropertyReflection $propertyReflection, - Node\Expr $node - ): array - { - $propertyType = $propertyReflection->getWritableType(); - $scope = $propertyReflection->getScope(); - - if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef) { - $assignedValueType = $scope->getType($node->expr); - } else { - $assignedValueType = $scope->getType($node); - } - if (!$this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes())) { - $propertyDescription = $this->propertyDescriptor->describePropertyByName($propertyReflection, $propertyReflection->getName()); - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedValueType); - - return [ - RuleErrorBuilder::message(sprintf( - '%s (%s) does not accept %s.', - $propertyDescription, - $propertyType->describe($verbosityLevel), - $assignedValueType->describe($verbosityLevel) - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Rules\Properties\PropertyDescriptor $propertyDescriptor; + + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + PropertyDescriptor $propertyDescriptor, + PropertyReflectionFinder $propertyReflectionFinder + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->propertyDescriptor = $propertyDescriptor; + $this->propertyReflectionFinder = $propertyReflectionFinder; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Expr\Assign + && !$node instanceof Node\Expr\AssignOp + && !$node instanceof Node\Expr\AssignRef + ) { + return []; + } + + if ( + !($node->var instanceof Node\Expr\PropertyFetch) + && !($node->var instanceof Node\Expr\StaticPropertyFetch) + ) { + return []; + } + + /** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */ + $propertyFetch = $node->var; + $propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope); + + $errors = []; + foreach ($propertyReflections as $propertyReflection) { + $errors = array_merge($errors, $this->processSingleProperty( + $propertyReflection, + $node + )); + } + + return $errors; + } + + /** + * @param FoundPropertyReflection $propertyReflection + * @param Node\Expr $node + * @return RuleError[] + */ + private function processSingleProperty( + FoundPropertyReflection $propertyReflection, + Node\Expr $node + ): array { + $propertyType = $propertyReflection->getWritableType(); + $scope = $propertyReflection->getScope(); + + if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef) { + $assignedValueType = $scope->getType($node->expr); + } else { + $assignedValueType = $scope->getType($node); + } + if (!$this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes())) { + $propertyDescription = $this->propertyDescriptor->describePropertyByName($propertyReflection, $propertyReflection->getName()); + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedValueType); + + return [ + RuleErrorBuilder::message(sprintf( + '%s (%s) does not accept %s.', + $propertyDescription, + $propertyType->describe($verbosityLevel), + $assignedValueType->describe($verbosityLevel) + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Properties/UninitializedPropertyRule.php b/src/Rules/Properties/UninitializedPropertyRule.php index ca4d9a8118..c3420d711a 100644 --- a/src/Rules/Properties/UninitializedPropertyRule.php +++ b/src/Rules/Properties/UninitializedPropertyRule.php @@ -1,4 +1,6 @@ - */ - private array $additionalConstructorsCache = []; - - /** - * @param string[] $additionalConstructors - */ - public function __construct( - ReadWritePropertiesExtensionProvider $extensionProvider, - array $additionalConstructors - ) - { - $this->extensionProvider = $extensionProvider; - $this->additionalConstructors = $additionalConstructors; - } - - public function getNodeType(): string - { - return ClassPropertiesNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $classReflection = $scope->getClassReflection(); - [$properties, $prematureAccess] = $node->getUninitializedProperties($scope, $this->getConstructors($classReflection), $this->extensionProvider->getExtensions()); - - $errors = []; - foreach ($properties as $propertyName => $propertyNode) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Class %s has an uninitialized property $%s. Give it default value or assign it in the constructor.', - $classReflection->getDisplayName(), - $propertyName - ))->line($propertyNode->getLine())->build(); - } - - foreach ($prematureAccess as [$propertyName, $line]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Access to an uninitialized property %s::$%s.', - $classReflection->getDisplayName(), - $propertyName - ))->line($line)->build(); - } - - return $errors; - } - - /** - * @param ClassReflection $classReflection - * @return string[] - */ - private function getConstructors(ClassReflection $classReflection): array - { - if (array_key_exists($classReflection->getName(), $this->additionalConstructorsCache)) { - return $this->additionalConstructorsCache[$classReflection->getName()]; - } - $constructors = []; - if ($classReflection->hasConstructor()) { - $constructors[] = $classReflection->getConstructor()->getName(); - } - - $nativeReflection = $classReflection->getNativeReflection(); - foreach ($this->additionalConstructors as $additionalConstructor) { - [$className, $methodName] = explode('::', $additionalConstructor); - if (!$nativeReflection->hasMethod($methodName)) { - continue; - } - $nativeMethod = $nativeReflection->getMethod($methodName); - if ($nativeMethod->getDeclaringClass()->getName() !== $nativeReflection->getName()) { - continue; - } - - try { - $prototype = $nativeMethod->getPrototype(); - } catch (\ReflectionException $e) { - $prototype = $nativeMethod; - } - - if ($prototype->getDeclaringClass()->getName() !== $className) { - continue; - } - - $constructors[] = $methodName; - } - - $this->additionalConstructorsCache[$classReflection->getName()] = $constructors; - - return $constructors; - } - + private ReadWritePropertiesExtensionProvider $extensionProvider; + + /** @var string[] */ + private array $additionalConstructors; + + /** @var array */ + private array $additionalConstructorsCache = []; + + /** + * @param string[] $additionalConstructors + */ + public function __construct( + ReadWritePropertiesExtensionProvider $extensionProvider, + array $additionalConstructors + ) { + $this->extensionProvider = $extensionProvider; + $this->additionalConstructors = $additionalConstructors; + } + + public function getNodeType(): string + { + return ClassPropertiesNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + [$properties, $prematureAccess] = $node->getUninitializedProperties($scope, $this->getConstructors($classReflection), $this->extensionProvider->getExtensions()); + + $errors = []; + foreach ($properties as $propertyName => $propertyNode) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Class %s has an uninitialized property $%s. Give it default value or assign it in the constructor.', + $classReflection->getDisplayName(), + $propertyName + ))->line($propertyNode->getLine())->build(); + } + + foreach ($prematureAccess as [$propertyName, $line]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Access to an uninitialized property %s::$%s.', + $classReflection->getDisplayName(), + $propertyName + ))->line($line)->build(); + } + + return $errors; + } + + /** + * @param ClassReflection $classReflection + * @return string[] + */ + private function getConstructors(ClassReflection $classReflection): array + { + if (array_key_exists($classReflection->getName(), $this->additionalConstructorsCache)) { + return $this->additionalConstructorsCache[$classReflection->getName()]; + } + $constructors = []; + if ($classReflection->hasConstructor()) { + $constructors[] = $classReflection->getConstructor()->getName(); + } + + $nativeReflection = $classReflection->getNativeReflection(); + foreach ($this->additionalConstructors as $additionalConstructor) { + [$className, $methodName] = explode('::', $additionalConstructor); + if (!$nativeReflection->hasMethod($methodName)) { + continue; + } + $nativeMethod = $nativeReflection->getMethod($methodName); + if ($nativeMethod->getDeclaringClass()->getName() !== $nativeReflection->getName()) { + continue; + } + + try { + $prototype = $nativeMethod->getPrototype(); + } catch (\ReflectionException $e) { + $prototype = $nativeMethod; + } + + if ($prototype->getDeclaringClass()->getName() !== $className) { + continue; + } + + $constructors[] = $methodName; + } + + $this->additionalConstructorsCache[$classReflection->getName()] = $constructors; + + return $constructors; + } } diff --git a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php index ef79346ab0..cc25781e49 100644 --- a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php +++ b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - $this->propertyDescriptor = $propertyDescriptor; - $this->propertyReflectionFinder = $propertyReflectionFinder; - $this->checkThisOnly = $checkThisOnly; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignOp - && !$node instanceof Node\Expr\AssignRef - ) { - return []; - } - - if ( - !($node->var instanceof Node\Expr\PropertyFetch) - && !($node->var instanceof Node\Expr\StaticPropertyFetch) - ) { - return []; - } - - if ( - $node->var instanceof Node\Expr\PropertyFetch - && $this->checkThisOnly - && !$this->ruleLevelHelper->isThis($node->var->var) - ) { - return []; - } - - /** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */ - $propertyFetch = $node->var; - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $scope); - if ($propertyReflection === null) { - return []; - } - - if (!$scope->canAccessProperty($propertyReflection)) { - return []; - } - - if (!$propertyReflection->isWritable()) { - $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $propertyFetch); - - return [ - RuleErrorBuilder::message(sprintf( - '%s is not writable.', - $propertyDescription - ))->build(), - ]; - } - - return []; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + private \PHPStan\Rules\Properties\PropertyDescriptor $propertyDescriptor; + + private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + + private bool $checkThisOnly; + + public function __construct( + RuleLevelHelper $ruleLevelHelper, + PropertyDescriptor $propertyDescriptor, + PropertyReflectionFinder $propertyReflectionFinder, + bool $checkThisOnly + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + $this->propertyDescriptor = $propertyDescriptor; + $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->checkThisOnly = $checkThisOnly; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Expr\Assign + && !$node instanceof Node\Expr\AssignOp + && !$node instanceof Node\Expr\AssignRef + ) { + return []; + } + + if ( + !($node->var instanceof Node\Expr\PropertyFetch) + && !($node->var instanceof Node\Expr\StaticPropertyFetch) + ) { + return []; + } + + if ( + $node->var instanceof Node\Expr\PropertyFetch + && $this->checkThisOnly + && !$this->ruleLevelHelper->isThis($node->var->var) + ) { + return []; + } + + /** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */ + $propertyFetch = $node->var; + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $scope); + if ($propertyReflection === null) { + return []; + } + + if (!$scope->canAccessProperty($propertyReflection)) { + return []; + } + + if (!$propertyReflection->isWritable()) { + $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $propertyFetch); + + return [ + RuleErrorBuilder::message(sprintf( + '%s is not writable.', + $propertyDescription + ))->build(), + ]; + } + + return []; + } } diff --git a/src/Rules/Regexp/RegularExpressionPatternRule.php b/src/Rules/Regexp/RegularExpressionPatternRule.php index b1548f40b7..a3fd394d00 100644 --- a/src/Rules/Regexp/RegularExpressionPatternRule.php +++ b/src/Rules/Regexp/RegularExpressionPatternRule.php @@ -1,4 +1,6 @@ -extractPatterns($node, $scope); - - $errors = []; - foreach ($patterns as $pattern) { - $errorMessage = $this->validatePattern($pattern); - if ($errorMessage === null) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->build(); - } - - return $errors; - } - - /** - * @param FuncCall $functionCall - * @param Scope $scope - * @return string[] - */ - private function extractPatterns(FuncCall $functionCall, Scope $scope): array - { - if (!$functionCall->name instanceof Node\Name) { - return []; - } - $functionName = strtolower((string) $functionCall->name); - if (!\Nette\Utils\Strings::startsWith($functionName, 'preg_')) { - return []; - } - - if (!isset($functionCall->args[0])) { - return []; - } - $patternNode = $functionCall->args[0]->value; - $patternType = $scope->getType($patternNode); - - $patternStrings = []; - - foreach (TypeUtils::getConstantStrings($patternType) as $constantStringType) { - if ( - !in_array($functionName, [ - 'preg_match', - 'preg_match_all', - 'preg_split', - 'preg_grep', - 'preg_replace', - 'preg_replace_callback', - 'preg_filter', - ], true) - ) { - continue; - } - - $patternStrings[] = $constantStringType->getValue(); - } - - foreach (TypeUtils::getConstantArrays($patternType) as $constantArrayType) { - if ( - in_array($functionName, [ - 'preg_replace', - 'preg_replace_callback', - 'preg_filter', - ], true) - ) { - foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { - if (!$arrayKeyType instanceof ConstantStringType) { - continue; - } - - $patternStrings[] = $arrayKeyType->getValue(); - } - } - - if ($functionName !== 'preg_replace_callback_array') { - continue; - } - - foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { - if (!$arrayKeyType instanceof ConstantStringType) { - continue; - } - - $patternStrings[] = $arrayKeyType->getValue(); - } - } - - return $patternStrings; - } - - private function validatePattern(string $pattern): ?string - { - try { - \Nette\Utils\Strings::match('', $pattern); - } catch (\Nette\Utils\RegexpException $e) { - return $e->getMessage(); - } - - return null; - } - + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $patterns = $this->extractPatterns($node, $scope); + + $errors = []; + foreach ($patterns as $pattern) { + $errorMessage = $this->validatePattern($pattern); + if ($errorMessage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->build(); + } + + return $errors; + } + + /** + * @param FuncCall $functionCall + * @param Scope $scope + * @return string[] + */ + private function extractPatterns(FuncCall $functionCall, Scope $scope): array + { + if (!$functionCall->name instanceof Node\Name) { + return []; + } + $functionName = strtolower((string) $functionCall->name); + if (!\Nette\Utils\Strings::startsWith($functionName, 'preg_')) { + return []; + } + + if (!isset($functionCall->args[0])) { + return []; + } + $patternNode = $functionCall->args[0]->value; + $patternType = $scope->getType($patternNode); + + $patternStrings = []; + + foreach (TypeUtils::getConstantStrings($patternType) as $constantStringType) { + if ( + !in_array($functionName, [ + 'preg_match', + 'preg_match_all', + 'preg_split', + 'preg_grep', + 'preg_replace', + 'preg_replace_callback', + 'preg_filter', + ], true) + ) { + continue; + } + + $patternStrings[] = $constantStringType->getValue(); + } + + foreach (TypeUtils::getConstantArrays($patternType) as $constantArrayType) { + if ( + in_array($functionName, [ + 'preg_replace', + 'preg_replace_callback', + 'preg_filter', + ], true) + ) { + foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { + if (!$arrayKeyType instanceof ConstantStringType) { + continue; + } + + $patternStrings[] = $arrayKeyType->getValue(); + } + } + + if ($functionName !== 'preg_replace_callback_array') { + continue; + } + + foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { + if (!$arrayKeyType instanceof ConstantStringType) { + continue; + } + + $patternStrings[] = $arrayKeyType->getValue(); + } + } + + return $patternStrings; + } + + private function validatePattern(string $pattern): ?string + { + try { + \Nette\Utils\Strings::match('', $pattern); + } catch (\Nette\Utils\RegexpException $e) { + return $e->getMessage(); + } + + return null; + } } diff --git a/src/Rules/Registry.php b/src/Rules/Registry.php index fe75ac79f4..cddbf201f4 100644 --- a/src/Rules/Registry.php +++ b/src/Rules/Registry.php @@ -1,55 +1,55 @@ -rules[$rule->getNodeType()][] = $rule; - } - } - - /** - * @template TNodeType of \PhpParser\Node - * @phpstan-param class-string $nodeType - * @param \PhpParser\Node $nodeType - * @phpstan-return array<\PHPStan\Rules\Rule> - * @return \PHPStan\Rules\Rule[] - */ - public function getRules(string $nodeType): array - { - if (!isset($this->cache[$nodeType])) { - $parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType); - - $rules = []; - foreach ($parentNodeTypes as $parentNodeType) { - foreach ($this->rules[$parentNodeType] ?? [] as $rule) { - $rules[] = $rule; - } - } - - $this->cache[$nodeType] = $rules; - } - - /** - * @phpstan-var array<\PHPStan\Rules\Rule> $selectedRules - * @var \PHPStan\Rules\Rule[] $selectedRules - */ - $selectedRules = $this->cache[$nodeType]; - - return $selectedRules; - } - + /** @var \PHPStan\Rules\Rule[][] */ + private array $rules = []; + + /** @var \PHPStan\Rules\Rule[][] */ + private array $cache = []; + + /** + * @param \PHPStan\Rules\Rule[] $rules + */ + public function __construct(array $rules) + { + foreach ($rules as $rule) { + $this->rules[$rule->getNodeType()][] = $rule; + } + } + + /** + * @template TNodeType of \PhpParser\Node + * @phpstan-param class-string $nodeType + * @param \PhpParser\Node $nodeType + * @phpstan-return array<\PHPStan\Rules\Rule> + * @return \PHPStan\Rules\Rule[] + */ + public function getRules(string $nodeType): array + { + if (!isset($this->cache[$nodeType])) { + $parentNodeTypes = [$nodeType] + class_parents($nodeType) + class_implements($nodeType); + + $rules = []; + foreach ($parentNodeTypes as $parentNodeType) { + foreach ($this->rules[$parentNodeType] ?? [] as $rule) { + $rules[] = $rule; + } + } + + $this->cache[$nodeType] = $rules; + } + + /** + * @phpstan-var array<\PHPStan\Rules\Rule> $selectedRules + * @var \PHPStan\Rules\Rule[] $selectedRules + */ + $selectedRules = $this->cache[$nodeType]; + + return $selectedRules; + } } diff --git a/src/Rules/RegistryFactory.php b/src/Rules/RegistryFactory.php index a632f11c6b..91412e1085 100644 --- a/src/Rules/RegistryFactory.php +++ b/src/Rules/RegistryFactory.php @@ -1,4 +1,6 @@ -container = $container; - } + private Container $container; - public function create(): Registry - { - return new Registry( - $this->container->getServicesByTag(self::RULE_TAG) - ); - } + public function __construct(Container $container) + { + $this->container = $container; + } + public function create(): Registry + { + return new Registry( + $this->container->getServicesByTag(self::RULE_TAG) + ); + } } diff --git a/src/Rules/Rule.php b/src/Rules/Rule.php index cf16651c5c..e97efdb63d 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/Rule.php @@ -1,4 +1,6 @@ - + * @return string + */ + public function getNodeType(): string; - /** - * @phpstan-return class-string - * @return string - */ - public function getNodeType(): string; - - /** - * @phpstan-param TNodeType $node - * @param \PhpParser\Node $node - * @param \PHPStan\Analyser\Scope $scope - * @return (string|RuleError)[] errors - */ - public function processNode(Node $node, Scope $scope): array; - + /** + * @phpstan-param TNodeType $node + * @param \PhpParser\Node $node + * @param \PHPStan\Analyser\Scope $scope + * @return (string|RuleError)[] errors + */ + public function processNode(Node $node, Scope $scope): array; } diff --git a/src/Rules/RuleError.php b/src/Rules/RuleError.php index 6fadd1cd7e..996859b008 100644 --- a/src/Rules/RuleError.php +++ b/src/Rules/RuleError.php @@ -1,10 +1,10 @@ -properties['message'] = $message; - $this->type = self::TYPE_MESSAGE; - } - - /** - * @return array - */ - public static function getRuleErrorTypes(): array - { - return [ - self::TYPE_MESSAGE => [ - RuleError::class, - 'message', - 'string', - 'string', - ], - self::TYPE_LINE => [ - LineRuleError::class, - 'line', - 'int', - 'int', - ], - self::TYPE_FILE => [ - FileRuleError::class, - 'file', - 'string', - 'string', - ], - self::TYPE_TIP => [ - TipRuleError::class, - 'tip', - 'string', - 'string', - ], - self::TYPE_IDENTIFIER => [ - IdentifierRuleError::class, - 'identifier', - 'string', - 'string', - ], - self::TYPE_METADATA => [ - MetadataRuleError::class, - 'metadata', - 'array', - 'mixed[]', - ], - self::TYPE_NON_IGNORABLE => [ - NonIgnorableRuleError::class, - null, - null, - null, - ], - ]; - } - - public static function message(string $message): self - { - return new self($message); - } - - public function line(int $line): self - { - $this->properties['line'] = $line; - $this->type |= self::TYPE_LINE; - - return $this; - } - - public function file(string $file): self - { - $this->properties['file'] = $file; - $this->type |= self::TYPE_FILE; - - return $this; - } - - public function tip(string $tip): self - { - $this->properties['tip'] = $tip; - $this->type |= self::TYPE_TIP; - - return $this; - } - - public function discoveringSymbolsTip(): self - { - return $this->tip('Learn more at https://phpstan.org/user-guide/discovering-symbols'); - } - - public function identifier(string $identifier): self - { - $this->properties['identifier'] = $identifier; - $this->type |= self::TYPE_IDENTIFIER; - - return $this; - } - - /** - * @param mixed[] $metadata - */ - public function metadata(array $metadata): self - { - $this->properties['metadata'] = $metadata; - $this->type |= self::TYPE_METADATA; - - return $this; - } - - public function nonIgnorable(): self - { - $this->type |= self::TYPE_NON_IGNORABLE; - - return $this; - } - - public function build(): RuleError - { - /** @var class-string $className */ - $className = sprintf('PHPStan\\Rules\\RuleErrors\\RuleError%d', $this->type); - if (!class_exists($className)) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s does not exist.', $className)); - } - - $ruleError = new $className(); - foreach ($this->properties as $propertyName => $value) { - $ruleError->{$propertyName} = $value; - } - - return $ruleError; - } - + private const TYPE_MESSAGE = 1; + private const TYPE_LINE = 2; + private const TYPE_FILE = 4; + private const TYPE_TIP = 8; + private const TYPE_IDENTIFIER = 16; + private const TYPE_METADATA = 32; + private const TYPE_NON_IGNORABLE = 64; + + private int $type; + + /** @var mixed[] */ + private array $properties; + + private function __construct(string $message) + { + $this->properties['message'] = $message; + $this->type = self::TYPE_MESSAGE; + } + + /** + * @return array + */ + public static function getRuleErrorTypes(): array + { + return [ + self::TYPE_MESSAGE => [ + RuleError::class, + 'message', + 'string', + 'string', + ], + self::TYPE_LINE => [ + LineRuleError::class, + 'line', + 'int', + 'int', + ], + self::TYPE_FILE => [ + FileRuleError::class, + 'file', + 'string', + 'string', + ], + self::TYPE_TIP => [ + TipRuleError::class, + 'tip', + 'string', + 'string', + ], + self::TYPE_IDENTIFIER => [ + IdentifierRuleError::class, + 'identifier', + 'string', + 'string', + ], + self::TYPE_METADATA => [ + MetadataRuleError::class, + 'metadata', + 'array', + 'mixed[]', + ], + self::TYPE_NON_IGNORABLE => [ + NonIgnorableRuleError::class, + null, + null, + null, + ], + ]; + } + + public static function message(string $message): self + { + return new self($message); + } + + public function line(int $line): self + { + $this->properties['line'] = $line; + $this->type |= self::TYPE_LINE; + + return $this; + } + + public function file(string $file): self + { + $this->properties['file'] = $file; + $this->type |= self::TYPE_FILE; + + return $this; + } + + public function tip(string $tip): self + { + $this->properties['tip'] = $tip; + $this->type |= self::TYPE_TIP; + + return $this; + } + + public function discoveringSymbolsTip(): self + { + return $this->tip('Learn more at https://phpstan.org/user-guide/discovering-symbols'); + } + + public function identifier(string $identifier): self + { + $this->properties['identifier'] = $identifier; + $this->type |= self::TYPE_IDENTIFIER; + + return $this; + } + + /** + * @param mixed[] $metadata + */ + public function metadata(array $metadata): self + { + $this->properties['metadata'] = $metadata; + $this->type |= self::TYPE_METADATA; + + return $this; + } + + public function nonIgnorable(): self + { + $this->type |= self::TYPE_NON_IGNORABLE; + + return $this; + } + + public function build(): RuleError + { + /** @var class-string $className */ + $className = sprintf('PHPStan\\Rules\\RuleErrors\\RuleError%d', $this->type); + if (!class_exists($className)) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Class %s does not exist.', $className)); + } + + $ruleError = new $className(); + foreach ($this->properties as $propertyName => $value) { + $ruleError->{$propertyName} = $value; + } + + return $ruleError; + } } diff --git a/src/Rules/RuleErrors/RuleError1.php b/src/Rules/RuleErrors/RuleError1.php index 8b24e289f8..1f8af1e24d 100644 --- a/src/Rules/RuleErrors/RuleError1.php +++ b/src/Rules/RuleErrors/RuleError1.php @@ -1,4 +1,6 @@ -message; - } - + public function getMessage(): string + { + return $this->message; + } } diff --git a/src/Rules/RuleErrors/RuleError101.php b/src/Rules/RuleErrors/RuleError101.php index fe947a8d21..6c75dafaec 100644 --- a/src/Rules/RuleErrors/RuleError101.php +++ b/src/Rules/RuleErrors/RuleError101.php @@ -1,4 +1,6 @@ -message; - } - - public function getFile(): string - { - return $this->file; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $file; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getFile(): string + { + return $this->file; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError103.php b/src/Rules/RuleErrors/RuleError103.php index 6303a368cc..a575eeb015 100644 --- a/src/Rules/RuleErrors/RuleError103.php +++ b/src/Rules/RuleErrors/RuleError103.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getFile(): string + { + return $this->file; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError105.php b/src/Rules/RuleErrors/RuleError105.php index 2f2f3077ea..463f5aee6b 100644 --- a/src/Rules/RuleErrors/RuleError105.php +++ b/src/Rules/RuleErrors/RuleError105.php @@ -1,4 +1,6 @@ -message; - } - - public function getTip(): string - { - return $this->tip; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $tip; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError107.php b/src/Rules/RuleErrors/RuleError107.php index cfe2a83969..e638abff75 100644 --- a/src/Rules/RuleErrors/RuleError107.php +++ b/src/Rules/RuleErrors/RuleError107.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError109.php b/src/Rules/RuleErrors/RuleError109.php index 5f4765bf2b..9732671bb4 100644 --- a/src/Rules/RuleErrors/RuleError109.php +++ b/src/Rules/RuleErrors/RuleError109.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError11.php b/src/Rules/RuleErrors/RuleError11.php index 6603af96dd..7e0987f636 100644 --- a/src/Rules/RuleErrors/RuleError11.php +++ b/src/Rules/RuleErrors/RuleError11.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError111.php b/src/Rules/RuleErrors/RuleError111.php index a3b958496f..484572a893 100644 --- a/src/Rules/RuleErrors/RuleError111.php +++ b/src/Rules/RuleErrors/RuleError111.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError113.php b/src/Rules/RuleErrors/RuleError113.php index 1e770a3bbc..863815133a 100644 --- a/src/Rules/RuleErrors/RuleError113.php +++ b/src/Rules/RuleErrors/RuleError113.php @@ -1,4 +1,6 @@ -message; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $identifier; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError115.php b/src/Rules/RuleErrors/RuleError115.php index 7f4a7c4853..f912bfc01d 100644 --- a/src/Rules/RuleErrors/RuleError115.php +++ b/src/Rules/RuleErrors/RuleError115.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError117.php b/src/Rules/RuleErrors/RuleError117.php index 2cffaad58b..ed5b2044d4 100644 --- a/src/Rules/RuleErrors/RuleError117.php +++ b/src/Rules/RuleErrors/RuleError117.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError119.php b/src/Rules/RuleErrors/RuleError119.php index e9bddceb6e..d1edc59055 100644 --- a/src/Rules/RuleErrors/RuleError119.php +++ b/src/Rules/RuleErrors/RuleError119.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError121.php b/src/Rules/RuleErrors/RuleError121.php index 1e26fb8da8..b6edc1c5a7 100644 --- a/src/Rules/RuleErrors/RuleError121.php +++ b/src/Rules/RuleErrors/RuleError121.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError123.php b/src/Rules/RuleErrors/RuleError123.php index 98d76c5ea4..5e3e33e808 100644 --- a/src/Rules/RuleErrors/RuleError123.php +++ b/src/Rules/RuleErrors/RuleError123.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError125.php b/src/Rules/RuleErrors/RuleError125.php index 1e1a3b3c6b..47821eaebe 100644 --- a/src/Rules/RuleErrors/RuleError125.php +++ b/src/Rules/RuleErrors/RuleError125.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError127.php b/src/Rules/RuleErrors/RuleError127.php index dd268dda70..154a5f6b6c 100644 --- a/src/Rules/RuleErrors/RuleError127.php +++ b/src/Rules/RuleErrors/RuleError127.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError13.php b/src/Rules/RuleErrors/RuleError13.php index 91e53ebf45..cb8a1a3e73 100644 --- a/src/Rules/RuleErrors/RuleError13.php +++ b/src/Rules/RuleErrors/RuleError13.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError15.php b/src/Rules/RuleErrors/RuleError15.php index ae65a55526..af122f0889 100644 --- a/src/Rules/RuleErrors/RuleError15.php +++ b/src/Rules/RuleErrors/RuleError15.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError17.php b/src/Rules/RuleErrors/RuleError17.php index a436eb2397..49bbd33161 100644 --- a/src/Rules/RuleErrors/RuleError17.php +++ b/src/Rules/RuleErrors/RuleError17.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getIdentifier(): string - { - return $this->identifier; - } + public function getMessage(): string + { + return $this->message; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError19.php b/src/Rules/RuleErrors/RuleError19.php index e5248ff1e9..5bcca088cc 100644 --- a/src/Rules/RuleErrors/RuleError19.php +++ b/src/Rules/RuleErrors/RuleError19.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getLine(): int + { + return $this->line; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError21.php b/src/Rules/RuleErrors/RuleError21.php index 17d6cc978f..9c5d7903eb 100644 --- a/src/Rules/RuleErrors/RuleError21.php +++ b/src/Rules/RuleErrors/RuleError21.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError23.php b/src/Rules/RuleErrors/RuleError23.php index da249a89cf..232290b3a1 100644 --- a/src/Rules/RuleErrors/RuleError23.php +++ b/src/Rules/RuleErrors/RuleError23.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError25.php b/src/Rules/RuleErrors/RuleError25.php index 879216abd9..1c046d0fb6 100644 --- a/src/Rules/RuleErrors/RuleError25.php +++ b/src/Rules/RuleErrors/RuleError25.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError27.php b/src/Rules/RuleErrors/RuleError27.php index af88436146..5bfbab0a9e 100644 --- a/src/Rules/RuleErrors/RuleError27.php +++ b/src/Rules/RuleErrors/RuleError27.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError29.php b/src/Rules/RuleErrors/RuleError29.php index 40855a0417..95c085e1b7 100644 --- a/src/Rules/RuleErrors/RuleError29.php +++ b/src/Rules/RuleErrors/RuleError29.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError3.php b/src/Rules/RuleErrors/RuleError3.php index 26169a3398..a24dea1c94 100644 --- a/src/Rules/RuleErrors/RuleError3.php +++ b/src/Rules/RuleErrors/RuleError3.php @@ -1,4 +1,6 @@ -message; - } + public int $line; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } + public function getLine(): int + { + return $this->line; + } } diff --git a/src/Rules/RuleErrors/RuleError31.php b/src/Rules/RuleErrors/RuleError31.php index 0495d84a4e..4ae4c3adbf 100644 --- a/src/Rules/RuleErrors/RuleError31.php +++ b/src/Rules/RuleErrors/RuleError31.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError33.php b/src/Rules/RuleErrors/RuleError33.php index 56198b54d7..5fcd9a97e9 100644 --- a/src/Rules/RuleErrors/RuleError33.php +++ b/src/Rules/RuleErrors/RuleError33.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getMessage(): string + { + return $this->message; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError35.php b/src/Rules/RuleErrors/RuleError35.php index 9911746c12..600ee09983 100644 --- a/src/Rules/RuleErrors/RuleError35.php +++ b/src/Rules/RuleErrors/RuleError35.php @@ -1,4 +1,6 @@ -message; - } - - public function getLine(): int - { - return $this->line; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public int $line; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError37.php b/src/Rules/RuleErrors/RuleError37.php index 119a3d5e87..bfb881e3a8 100644 --- a/src/Rules/RuleErrors/RuleError37.php +++ b/src/Rules/RuleErrors/RuleError37.php @@ -1,4 +1,6 @@ -message; - } - - public function getFile(): string - { - return $this->file; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $file; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getFile(): string + { + return $this->file; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError39.php b/src/Rules/RuleErrors/RuleError39.php index 56f98f8a05..54dedd597d 100644 --- a/src/Rules/RuleErrors/RuleError39.php +++ b/src/Rules/RuleErrors/RuleError39.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getFile(): string + { + return $this->file; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError41.php b/src/Rules/RuleErrors/RuleError41.php index 45e916e381..47d37566b8 100644 --- a/src/Rules/RuleErrors/RuleError41.php +++ b/src/Rules/RuleErrors/RuleError41.php @@ -1,4 +1,6 @@ -message; - } - - public function getTip(): string - { - return $this->tip; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $tip; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError43.php b/src/Rules/RuleErrors/RuleError43.php index 62824431f8..f4cad28465 100644 --- a/src/Rules/RuleErrors/RuleError43.php +++ b/src/Rules/RuleErrors/RuleError43.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError45.php b/src/Rules/RuleErrors/RuleError45.php index 254e17df77..0e6d4e86fc 100644 --- a/src/Rules/RuleErrors/RuleError45.php +++ b/src/Rules/RuleErrors/RuleError45.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError47.php b/src/Rules/RuleErrors/RuleError47.php index 9a17976afd..0c6a3564b1 100644 --- a/src/Rules/RuleErrors/RuleError47.php +++ b/src/Rules/RuleErrors/RuleError47.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getTip(): string + { + return $this->tip; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError49.php b/src/Rules/RuleErrors/RuleError49.php index 6780a90cde..8ac16a4635 100644 --- a/src/Rules/RuleErrors/RuleError49.php +++ b/src/Rules/RuleErrors/RuleError49.php @@ -1,4 +1,6 @@ -message; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public string $identifier; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError5.php b/src/Rules/RuleErrors/RuleError5.php index 99f66f59f4..0cf0c6e293 100644 --- a/src/Rules/RuleErrors/RuleError5.php +++ b/src/Rules/RuleErrors/RuleError5.php @@ -1,4 +1,6 @@ -message; - } + public string $file; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } + public function getFile(): string + { + return $this->file; + } } diff --git a/src/Rules/RuleErrors/RuleError51.php b/src/Rules/RuleErrors/RuleError51.php index 7c70ad7c0b..f8b160d821 100644 --- a/src/Rules/RuleErrors/RuleError51.php +++ b/src/Rules/RuleErrors/RuleError51.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getLine(): int + { + return $this->line; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError53.php b/src/Rules/RuleErrors/RuleError53.php index 23a1bad346..aa63bc1837 100644 --- a/src/Rules/RuleErrors/RuleError53.php +++ b/src/Rules/RuleErrors/RuleError53.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError55.php b/src/Rules/RuleErrors/RuleError55.php index b0fe074d84..823a4da209 100644 --- a/src/Rules/RuleErrors/RuleError55.php +++ b/src/Rules/RuleErrors/RuleError55.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError57.php b/src/Rules/RuleErrors/RuleError57.php index 5395e55b8d..9eca7e9d88 100644 --- a/src/Rules/RuleErrors/RuleError57.php +++ b/src/Rules/RuleErrors/RuleError57.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError59.php b/src/Rules/RuleErrors/RuleError59.php index db750c988e..028bfd5702 100644 --- a/src/Rules/RuleErrors/RuleError59.php +++ b/src/Rules/RuleErrors/RuleError59.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError61.php b/src/Rules/RuleErrors/RuleError61.php index 63286dd5b1..a0ecfed61c 100644 --- a/src/Rules/RuleErrors/RuleError61.php +++ b/src/Rules/RuleErrors/RuleError61.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError63.php b/src/Rules/RuleErrors/RuleError63.php index dcfe4ac0ee..4c7d3c9227 100644 --- a/src/Rules/RuleErrors/RuleError63.php +++ b/src/Rules/RuleErrors/RuleError63.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getIdentifier(): string + { + return $this->identifier; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError65.php b/src/Rules/RuleErrors/RuleError65.php index c37e5c66ca..528801ee08 100644 --- a/src/Rules/RuleErrors/RuleError65.php +++ b/src/Rules/RuleErrors/RuleError65.php @@ -1,4 +1,6 @@ -message; - } - + public function getMessage(): string + { + return $this->message; + } } diff --git a/src/Rules/RuleErrors/RuleError67.php b/src/Rules/RuleErrors/RuleError67.php index 924b6b70f8..e2164b5850 100644 --- a/src/Rules/RuleErrors/RuleError67.php +++ b/src/Rules/RuleErrors/RuleError67.php @@ -1,4 +1,6 @@ -message; - } + public int $line; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } + public function getLine(): int + { + return $this->line; + } } diff --git a/src/Rules/RuleErrors/RuleError69.php b/src/Rules/RuleErrors/RuleError69.php index 3cb5843e4e..2679a21986 100644 --- a/src/Rules/RuleErrors/RuleError69.php +++ b/src/Rules/RuleErrors/RuleError69.php @@ -1,4 +1,6 @@ -message; - } + public string $file; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } + public function getFile(): string + { + return $this->file; + } } diff --git a/src/Rules/RuleErrors/RuleError7.php b/src/Rules/RuleErrors/RuleError7.php index 606e57ecb4..ace60317f7 100644 --- a/src/Rules/RuleErrors/RuleError7.php +++ b/src/Rules/RuleErrors/RuleError7.php @@ -1,4 +1,6 @@ -message; - } + public string $file; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } + public function getFile(): string + { + return $this->file; + } } diff --git a/src/Rules/RuleErrors/RuleError71.php b/src/Rules/RuleErrors/RuleError71.php index 27c04fc061..50ff4016b2 100644 --- a/src/Rules/RuleErrors/RuleError71.php +++ b/src/Rules/RuleErrors/RuleError71.php @@ -1,4 +1,6 @@ -message; - } + public string $file; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } + public function getFile(): string + { + return $this->file; + } } diff --git a/src/Rules/RuleErrors/RuleError73.php b/src/Rules/RuleErrors/RuleError73.php index 55d9a91539..771dd8c0e0 100644 --- a/src/Rules/RuleErrors/RuleError73.php +++ b/src/Rules/RuleErrors/RuleError73.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError75.php b/src/Rules/RuleErrors/RuleError75.php index 600407fbf1..a291a01d94 100644 --- a/src/Rules/RuleErrors/RuleError75.php +++ b/src/Rules/RuleErrors/RuleError75.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError77.php b/src/Rules/RuleErrors/RuleError77.php index 56512c8faf..a0a47effb1 100644 --- a/src/Rules/RuleErrors/RuleError77.php +++ b/src/Rules/RuleErrors/RuleError77.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError79.php b/src/Rules/RuleErrors/RuleError79.php index 95a10f354f..78d90c0936 100644 --- a/src/Rules/RuleErrors/RuleError79.php +++ b/src/Rules/RuleErrors/RuleError79.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError81.php b/src/Rules/RuleErrors/RuleError81.php index 122bd698e1..a645cc1ca2 100644 --- a/src/Rules/RuleErrors/RuleError81.php +++ b/src/Rules/RuleErrors/RuleError81.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getIdentifier(): string - { - return $this->identifier; - } + public function getMessage(): string + { + return $this->message; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError83.php b/src/Rules/RuleErrors/RuleError83.php index 3e7248cad3..d1d81af8dd 100644 --- a/src/Rules/RuleErrors/RuleError83.php +++ b/src/Rules/RuleErrors/RuleError83.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getLine(): int + { + return $this->line; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError85.php b/src/Rules/RuleErrors/RuleError85.php index 68866e7a18..b8dd06f038 100644 --- a/src/Rules/RuleErrors/RuleError85.php +++ b/src/Rules/RuleErrors/RuleError85.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError87.php b/src/Rules/RuleErrors/RuleError87.php index 742bb0c7b2..476a7e4d93 100644 --- a/src/Rules/RuleErrors/RuleError87.php +++ b/src/Rules/RuleErrors/RuleError87.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getFile(): string + { + return $this->file; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError89.php b/src/Rules/RuleErrors/RuleError89.php index 83f5a00039..c3674b7509 100644 --- a/src/Rules/RuleErrors/RuleError89.php +++ b/src/Rules/RuleErrors/RuleError89.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError9.php b/src/Rules/RuleErrors/RuleError9.php index a4cd956791..c9e91d593c 100644 --- a/src/Rules/RuleErrors/RuleError9.php +++ b/src/Rules/RuleErrors/RuleError9.php @@ -1,4 +1,6 @@ -message; - } + public string $tip; - public function getTip(): string - { - return $this->tip; - } + public function getMessage(): string + { + return $this->message; + } + public function getTip(): string + { + return $this->tip; + } } diff --git a/src/Rules/RuleErrors/RuleError91.php b/src/Rules/RuleErrors/RuleError91.php index dfd25a1e71..4bbc59da96 100644 --- a/src/Rules/RuleErrors/RuleError91.php +++ b/src/Rules/RuleErrors/RuleError91.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getLine(): int + { + return $this->line; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError93.php b/src/Rules/RuleErrors/RuleError93.php index 691a43d24a..9611ceea90 100644 --- a/src/Rules/RuleErrors/RuleError93.php +++ b/src/Rules/RuleErrors/RuleError93.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getFile(): string - { - return $this->file; - } + public function getMessage(): string + { + return $this->message; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError95.php b/src/Rules/RuleErrors/RuleError95.php index b3170a3515..fedc5ee48b 100644 --- a/src/Rules/RuleErrors/RuleError95.php +++ b/src/Rules/RuleErrors/RuleError95.php @@ -1,4 +1,6 @@ -message; - } + public string $identifier; - public function getLine(): int - { - return $this->line; - } + public function getMessage(): string + { + return $this->message; + } - public function getFile(): string - { - return $this->file; - } + public function getLine(): int + { + return $this->line; + } - public function getTip(): string - { - return $this->tip; - } + public function getFile(): string + { + return $this->file; + } - public function getIdentifier(): string - { - return $this->identifier; - } + public function getTip(): string + { + return $this->tip; + } + public function getIdentifier(): string + { + return $this->identifier; + } } diff --git a/src/Rules/RuleErrors/RuleError97.php b/src/Rules/RuleErrors/RuleError97.php index 1a7c512453..c5a2c24ac1 100644 --- a/src/Rules/RuleErrors/RuleError97.php +++ b/src/Rules/RuleErrors/RuleError97.php @@ -1,4 +1,6 @@ -message; - } + /** @var mixed[] */ + public array $metadata; - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } + public function getMessage(): string + { + return $this->message; + } + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleErrors/RuleError99.php b/src/Rules/RuleErrors/RuleError99.php index 76064d3486..b6968ff8ad 100644 --- a/src/Rules/RuleErrors/RuleError99.php +++ b/src/Rules/RuleErrors/RuleError99.php @@ -1,4 +1,6 @@ -message; - } - - public function getLine(): int - { - return $this->line; - } - - /** - * @return mixed[] - */ - public function getMetadata(): array - { - return $this->metadata; - } - + public string $message; + + public int $line; + + /** @var mixed[] */ + public array $metadata; + + public function getMessage(): string + { + return $this->message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } } diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 402775e057..7e33c83d13 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - $this->checkNullables = $checkNullables; - $this->checkThisOnly = $checkThisOnly; - $this->checkUnionTypes = $checkUnionTypes; - $this->checkExplicitMixed = $checkExplicitMixed; - } - - public function isThis(Expr $expression): bool - { - return $expression instanceof Expr\Variable && $expression->name === 'this'; - } - - public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTypes): bool - { - if ( - $this->checkExplicitMixed - && $acceptedType instanceof MixedType - && $acceptedType->isExplicitMixed() - ) { - $acceptedType = new StrictMixedType(); - } - - if ( - !$this->checkNullables - && !$acceptingType instanceof NullType - && !$acceptedType instanceof NullType - && !$acceptedType instanceof BenevolentUnionType - ) { - $acceptedType = TypeCombinator::removeNull($acceptedType); - } - - if ($acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) { - foreach ($acceptingType->getTypes() as $innerType) { - if (self::accepts($innerType, $acceptedType, $strictTypes)) { - return true; - } - } - - return false; - } - - if ( - $acceptedType->isArray()->yes() - && $acceptingType->isArray()->yes() - && !$acceptingType->isIterableAtLeastOnce()->yes() - && count(TypeUtils::getConstantArrays($acceptedType)) === 0 - && count(TypeUtils::getConstantArrays($acceptingType)) === 0 - ) { - return self::accepts( - $acceptingType->getIterableKeyType(), - $acceptedType->getIterableKeyType(), - $strictTypes - ) && self::accepts( - $acceptingType->getIterableValueType(), - $acceptedType->getIterableValueType(), - $strictTypes - ); - } - - $accepts = $acceptingType->accepts($acceptedType, $strictTypes); - - return $this->checkUnionTypes ? $accepts->yes() : !$accepts->no(); - } - - /** - * @param Scope $scope - * @param Expr $var - * @param string $unknownClassErrorPattern - * @param callable(Type $type): bool $unionTypeCriteriaCallback - * @return FoundTypeResult - */ - public function findTypeToCheck( - Scope $scope, - Expr $var, - string $unknownClassErrorPattern, - callable $unionTypeCriteriaCallback - ): FoundTypeResult - { - if ($this->checkThisOnly && !$this->isThis($var)) { - return new FoundTypeResult(new ErrorType(), [], []); - } - $type = $scope->getType($var); - if (!$this->checkNullables && !$type instanceof NullType) { - $type = \PHPStan\Type\TypeCombinator::removeNull($type); - } - - if (TypeCombinator::containsNull($type)) { - $type = $scope->getType(NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var)); - } - - if ( - $this->checkExplicitMixed - && $type instanceof MixedType - && !$type instanceof TemplateMixedType - && $type->isExplicitMixed() - ) { - return new FoundTypeResult(new StrictMixedType(), [], []); - } - - if ($type instanceof MixedType || $type instanceof NeverType) { - return new FoundTypeResult(new ErrorType(), [], []); - } - if ($type instanceof StaticType) { - $type = $type->getStaticObjectType(); - } - - $errors = []; - $directClassNames = TypeUtils::getDirectClassNames($type); - $hasClassExistsClass = false; - foreach ($directClassNames as $referencedClass) { - if ($this->reflectionProvider->hasClass($referencedClass)) { - $classReflection = $this->reflectionProvider->getClass($referencedClass); - if (!$classReflection->isTrait()) { - continue; - } - } - - if ($scope->isInClassExists($referencedClass)) { - $hasClassExistsClass = true; - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf($unknownClassErrorPattern, $referencedClass))->line($var->getLine())->discoveringSymbolsTip()->build(); - } - - if (count($errors) > 0 || $hasClassExistsClass) { - return new FoundTypeResult(new ErrorType(), [], $errors); - } - - if (!$this->checkUnionTypes) { - if ($type instanceof ObjectWithoutClassType) { - return new FoundTypeResult(new ErrorType(), [], []); - } - if ($type instanceof UnionType) { - $newTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$unionTypeCriteriaCallback($innerType)) { - continue; - } - - $newTypes[] = $innerType; - } - - if (count($newTypes) > 0) { - return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, []); - } - } - } - - return new FoundTypeResult($type, $directClassNames, []); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + private bool $checkNullables; + + private bool $checkThisOnly; + + private bool $checkUnionTypes; + + private bool $checkExplicitMixed; + + public function __construct( + ReflectionProvider $reflectionProvider, + bool $checkNullables, + bool $checkThisOnly, + bool $checkUnionTypes, + bool $checkExplicitMixed = false + ) { + $this->reflectionProvider = $reflectionProvider; + $this->checkNullables = $checkNullables; + $this->checkThisOnly = $checkThisOnly; + $this->checkUnionTypes = $checkUnionTypes; + $this->checkExplicitMixed = $checkExplicitMixed; + } + + public function isThis(Expr $expression): bool + { + return $expression instanceof Expr\Variable && $expression->name === 'this'; + } + + public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTypes): bool + { + if ( + $this->checkExplicitMixed + && $acceptedType instanceof MixedType + && $acceptedType->isExplicitMixed() + ) { + $acceptedType = new StrictMixedType(); + } + + if ( + !$this->checkNullables + && !$acceptingType instanceof NullType + && !$acceptedType instanceof NullType + && !$acceptedType instanceof BenevolentUnionType + ) { + $acceptedType = TypeCombinator::removeNull($acceptedType); + } + + if ($acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) { + foreach ($acceptingType->getTypes() as $innerType) { + if (self::accepts($innerType, $acceptedType, $strictTypes)) { + return true; + } + } + + return false; + } + + if ( + $acceptedType->isArray()->yes() + && $acceptingType->isArray()->yes() + && !$acceptingType->isIterableAtLeastOnce()->yes() + && count(TypeUtils::getConstantArrays($acceptedType)) === 0 + && count(TypeUtils::getConstantArrays($acceptingType)) === 0 + ) { + return self::accepts( + $acceptingType->getIterableKeyType(), + $acceptedType->getIterableKeyType(), + $strictTypes + ) && self::accepts( + $acceptingType->getIterableValueType(), + $acceptedType->getIterableValueType(), + $strictTypes + ); + } + + $accepts = $acceptingType->accepts($acceptedType, $strictTypes); + + return $this->checkUnionTypes ? $accepts->yes() : !$accepts->no(); + } + + /** + * @param Scope $scope + * @param Expr $var + * @param string $unknownClassErrorPattern + * @param callable(Type $type): bool $unionTypeCriteriaCallback + * @return FoundTypeResult + */ + public function findTypeToCheck( + Scope $scope, + Expr $var, + string $unknownClassErrorPattern, + callable $unionTypeCriteriaCallback + ): FoundTypeResult { + if ($this->checkThisOnly && !$this->isThis($var)) { + return new FoundTypeResult(new ErrorType(), [], []); + } + $type = $scope->getType($var); + if (!$this->checkNullables && !$type instanceof NullType) { + $type = \PHPStan\Type\TypeCombinator::removeNull($type); + } + + if (TypeCombinator::containsNull($type)) { + $type = $scope->getType(NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var)); + } + + if ( + $this->checkExplicitMixed + && $type instanceof MixedType + && !$type instanceof TemplateMixedType + && $type->isExplicitMixed() + ) { + return new FoundTypeResult(new StrictMixedType(), [], []); + } + + if ($type instanceof MixedType || $type instanceof NeverType) { + return new FoundTypeResult(new ErrorType(), [], []); + } + if ($type instanceof StaticType) { + $type = $type->getStaticObjectType(); + } + + $errors = []; + $directClassNames = TypeUtils::getDirectClassNames($type); + $hasClassExistsClass = false; + foreach ($directClassNames as $referencedClass) { + if ($this->reflectionProvider->hasClass($referencedClass)) { + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->isTrait()) { + continue; + } + } + + if ($scope->isInClassExists($referencedClass)) { + $hasClassExistsClass = true; + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($unknownClassErrorPattern, $referencedClass))->line($var->getLine())->discoveringSymbolsTip()->build(); + } + + if (count($errors) > 0 || $hasClassExistsClass) { + return new FoundTypeResult(new ErrorType(), [], $errors); + } + + if (!$this->checkUnionTypes) { + if ($type instanceof ObjectWithoutClassType) { + return new FoundTypeResult(new ErrorType(), [], []); + } + if ($type instanceof UnionType) { + $newTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$unionTypeCriteriaCallback($innerType)) { + continue; + } + + $newTypes[] = $innerType; + } + + if (count($newTypes) > 0) { + return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, []); + } + } + } + + return new FoundTypeResult($type, $directClassNames, []); + } } diff --git a/src/Rules/TipRuleError.php b/src/Rules/TipRuleError.php index fa9f8f9885..40fb3eee8e 100644 --- a/src/Rules/TipRuleError.php +++ b/src/Rules/TipRuleError.php @@ -1,10 +1,10 @@ -getAnonymousFunctionReturnType(); - if ($functionReturnType === null || !$functionReturnType instanceof UnionType) { - return []; - } - - $arrowFunction = $node->getOriginalNode(); - if ($arrowFunction->returnType === null) { - return []; - } - $expr = $arrowFunction->expr; - if ($expr instanceof Node\Expr\YieldFrom || $expr instanceof Node\Expr\Yield_) { - return []; - } - - $returnType = $scope->getType($expr); - if ($returnType instanceof NullType) { - return []; - } - $messages = []; - foreach ($functionReturnType->getTypes() as $type) { - if (!$type->isSuperTypeOf($returnType)->no()) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Anonymous function never returns %s so it can be removed from the return typehint.', - $type->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - return $messages; - } - + public function getNodeType(): string + { + return InArrowFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $functionReturnType = $scope->getAnonymousFunctionReturnType(); + if ($functionReturnType === null || !$functionReturnType instanceof UnionType) { + return []; + } + + $arrowFunction = $node->getOriginalNode(); + if ($arrowFunction->returnType === null) { + return []; + } + $expr = $arrowFunction->expr; + if ($expr instanceof Node\Expr\YieldFrom || $expr instanceof Node\Expr\Yield_) { + return []; + } + + $returnType = $scope->getType($expr); + if ($returnType instanceof NullType) { + return []; + } + $messages = []; + foreach ($functionReturnType->getTypes() as $type) { + if (!$type->isSuperTypeOf($returnType)->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Anonymous function never returns %s so it can be removed from the return typehint.', + $type->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $messages; + } } diff --git a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php index 9277287e8f..de796920d1 100644 --- a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php @@ -1,4 +1,6 @@ -getAnonymousFunctionReturnType(); - if ($closureReturnType === null || !$closureReturnType instanceof UnionType) { - return []; - } - - $closureExpr = $node->getClosureExpr(); - if ($closureExpr->returnType === null) { - return []; - } - - $statementResult = $node->getStatementResult(); - if ($statementResult->hasYield()) { - return []; - } - - $returnStatements = $node->getReturnStatements(); - if (count($returnStatements) === 0) { - return []; - } - - $returnTypes = []; - foreach ($returnStatements as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - continue; - } - - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); - } - - if (count($returnTypes) === 0) { - return []; - } - - $returnType = TypeCombinator::union(...$returnTypes); - if ($returnType instanceof NullType) { - return []; - } - - $messages = []; - foreach ($closureReturnType->getTypes() as $type) { - if (!$type->isSuperTypeOf($returnType)->no()) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Anonymous function never returns %s so it can be removed from the return typehint.', - $type->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - return $messages; - } - + public function getNodeType(): string + { + return ClosureReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $closureReturnType = $scope->getAnonymousFunctionReturnType(); + if ($closureReturnType === null || !$closureReturnType instanceof UnionType) { + return []; + } + + $closureExpr = $node->getClosureExpr(); + if ($closureExpr->returnType === null) { + return []; + } + + $statementResult = $node->getStatementResult(); + if ($statementResult->hasYield()) { + return []; + } + + $returnStatements = $node->getReturnStatements(); + if (count($returnStatements) === 0) { + return []; + } + + $returnTypes = []; + foreach ($returnStatements as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + if ($returnNode->expr === null) { + continue; + } + + $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + } + + if (count($returnTypes) === 0) { + return []; + } + + $returnType = TypeCombinator::union(...$returnTypes); + if ($returnType instanceof NullType) { + return []; + } + + $messages = []; + foreach ($closureReturnType->getTypes() as $type) { + if (!$type->isSuperTypeOf($returnType)->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Anonymous function never returns %s so it can be removed from the return typehint.', + $type->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $messages; + } } diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 747685bc9e..a95f30a433 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -1,4 +1,6 @@ -getFunction(); - if (!$function instanceof FunctionReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $functionReturnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); - if (!$functionReturnType instanceof UnionType) { - return []; - } - $statementResult = $node->getStatementResult(); - if ($statementResult->hasYield()) { - return []; - } + public function processNode(Node $node, Scope $scope): array + { + $function = $scope->getFunction(); + if (!$function instanceof FunctionReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } - $returnStatements = $node->getReturnStatements(); - if (count($returnStatements) === 0) { - return []; - } + $functionReturnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); + if (!$functionReturnType instanceof UnionType) { + return []; + } + $statementResult = $node->getStatementResult(); + if ($statementResult->hasYield()) { + return []; + } - $returnTypes = []; - foreach ($returnStatements as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - continue; - } + $returnStatements = $node->getReturnStatements(); + if (count($returnStatements) === 0) { + return []; + } - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); - } + $returnTypes = []; + foreach ($returnStatements as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + if ($returnNode->expr === null) { + continue; + } - if (count($returnTypes) === 0) { - return []; - } + $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + } - $returnType = TypeCombinator::union(...$returnTypes); + if (count($returnTypes) === 0) { + return []; + } - $messages = []; - foreach ($functionReturnType->getTypes() as $type) { - if (!$type->isSuperTypeOf($returnType)->no()) { - continue; - } + $returnType = TypeCombinator::union(...$returnTypes); - $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() never returns %s so it can be removed from the return typehint.', - $function->getName(), - $type->describe(VerbosityLevel::typeOnly()) - ))->build(); - } + $messages = []; + foreach ($functionReturnType->getTypes() as $type) { + if (!$type->isSuperTypeOf($returnType)->no()) { + continue; + } - return $messages; - } + $messages[] = RuleErrorBuilder::message(sprintf( + 'Function %s() never returns %s so it can be removed from the return typehint.', + $function->getName(), + $type->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + return $messages; + } } diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 107b06af13..1b147d3892 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -1,4 +1,6 @@ -checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods; - } - - public function getNodeType(): string - { - return MethodReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $method = $scope->getFunction(); - if (!$method instanceof MethodReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - $isFirstDeclaration = $method->getPrototype()->getDeclaringClass() === $method->getDeclaringClass(); - if (!$method->isPrivate()) { - if (!$this->checkProtectedAndPublicMethods) { - return []; - } - if ($isFirstDeclaration && !$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) { - return []; - } - } - - $methodReturnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); - if (!$methodReturnType instanceof UnionType) { - return []; - } - $statementResult = $node->getStatementResult(); - if ($statementResult->hasYield()) { - return []; - } - - $returnStatements = $node->getReturnStatements(); - if (count($returnStatements) === 0) { - return []; - } - - $returnTypes = []; - foreach ($returnStatements as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - continue; - } - - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); - } - - if (count($returnTypes) === 0) { - return []; - } - - $returnType = TypeCombinator::union(...$returnTypes); - if ( - !$method->isPrivate() - && ($returnType instanceof NullType || $returnType instanceof ConstantBooleanType) - && !$isFirstDeclaration - ) { - return []; - } - - $messages = []; - foreach ($methodReturnType->getTypes() as $type) { - if (!$type->isSuperTypeOf($returnType)->no()) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() never returns %s so it can be removed from the return typehint.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $type->describe(VerbosityLevel::typeOnly()) - ))->build(); - } - - return $messages; - } - + private bool $checkProtectedAndPublicMethods; + + public function __construct(bool $checkProtectedAndPublicMethods) + { + $this->checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods; + } + + public function getNodeType(): string + { + return MethodReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $method = $scope->getFunction(); + if (!$method instanceof MethodReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + $isFirstDeclaration = $method->getPrototype()->getDeclaringClass() === $method->getDeclaringClass(); + if (!$method->isPrivate()) { + if (!$this->checkProtectedAndPublicMethods) { + return []; + } + if ($isFirstDeclaration && !$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) { + return []; + } + } + + $methodReturnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + if (!$methodReturnType instanceof UnionType) { + return []; + } + $statementResult = $node->getStatementResult(); + if ($statementResult->hasYield()) { + return []; + } + + $returnStatements = $node->getReturnStatements(); + if (count($returnStatements) === 0) { + return []; + } + + $returnTypes = []; + foreach ($returnStatements as $returnStatement) { + $returnNode = $returnStatement->getReturnNode(); + if ($returnNode->expr === null) { + continue; + } + + $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + } + + if (count($returnTypes) === 0) { + return []; + } + + $returnType = TypeCombinator::union(...$returnTypes); + if ( + !$method->isPrivate() + && ($returnType instanceof NullType || $returnType instanceof ConstantBooleanType) + && !$isFirstDeclaration + ) { + return []; + } + + $messages = []; + foreach ($methodReturnType->getTypes() as $type) { + if (!$type->isSuperTypeOf($returnType)->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() never returns %s so it can be removed from the return typehint.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $type->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $messages; + } } diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 40233b2c47..b76bb78c1b 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - /** - * @param \PHPStan\Analyser\Scope $scope - * @param string[] $parameterNames - * @param \PhpParser\Node[] $statements - * @param string $unusedParameterMessage - * @param string $identifier - * @param mixed[] $additionalMetadata - * @return RuleError[] - */ - public function getUnusedParameters( - Scope $scope, - array $parameterNames, - array $statements, - string $unusedParameterMessage, - string $identifier, - array $additionalMetadata - ): array - { - $unusedParameters = array_fill_keys($parameterNames, true); - foreach ($this->getUsedVariables($scope, $statements) as $variableName) { - if (!isset($unusedParameters[$variableName])) { - continue; - } + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } - unset($unusedParameters[$variableName]); - } - $errors = []; - foreach (array_keys($unusedParameters) as $name) { - $errors[] = RuleErrorBuilder::message( - sprintf($unusedParameterMessage, $name) - )->identifier($identifier)->metadata($additionalMetadata + ['variableName' => $name])->build(); - } + /** + * @param \PHPStan\Analyser\Scope $scope + * @param string[] $parameterNames + * @param \PhpParser\Node[] $statements + * @param string $unusedParameterMessage + * @param string $identifier + * @param mixed[] $additionalMetadata + * @return RuleError[] + */ + public function getUnusedParameters( + Scope $scope, + array $parameterNames, + array $statements, + string $unusedParameterMessage, + string $identifier, + array $additionalMetadata + ): array { + $unusedParameters = array_fill_keys($parameterNames, true); + foreach ($this->getUsedVariables($scope, $statements) as $variableName) { + if (!isset($unusedParameters[$variableName])) { + continue; + } - return $errors; - } + unset($unusedParameters[$variableName]); + } + $errors = []; + foreach (array_keys($unusedParameters) as $name) { + $errors[] = RuleErrorBuilder::message( + sprintf($unusedParameterMessage, $name) + )->identifier($identifier)->metadata($additionalMetadata + ['variableName' => $name])->build(); + } - /** - * @param \PHPStan\Analyser\Scope $scope - * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node - * @return string[] - */ - private function getUsedVariables(Scope $scope, $node): array - { - $variableNames = []; - if ($node instanceof Node) { - if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { - $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); - if ($functionName === 'func_get_args') { - return $scope->getDefinedVariables(); - } - } - if ($node instanceof Node\Expr\Variable && is_string($node->name) && $node->name !== 'this') { - return [$node->name]; - } - if ($node instanceof Node\Expr\ClosureUse && is_string($node->var->name)) { - return [$node->var->name]; - } - if ( - $node instanceof Node\Expr\FuncCall - && $node->name instanceof Node\Name - && (string) $node->name === 'compact' - ) { - foreach ($node->args as $arg) { - $argType = $scope->getType($arg->value); - if (!($argType instanceof ConstantStringType)) { - continue; - } + return $errors; + } - $variableNames[] = $argType->getValue(); - } - } - foreach ($node->getSubNodeNames() as $subNodeName) { - if ($node instanceof Node\Expr\Closure && $subNodeName !== 'uses') { - continue; - } - $subNode = $node->{$subNodeName}; - $variableNames = array_merge($variableNames, $this->getUsedVariables($scope, $subNode)); - } - } elseif (is_array($node)) { - foreach ($node as $subNode) { - $variableNames = array_merge($variableNames, $this->getUsedVariables($scope, $subNode)); - } - } + /** + * @param \PHPStan\Analyser\Scope $scope + * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node + * @return string[] + */ + private function getUsedVariables(Scope $scope, $node): array + { + $variableNames = []; + if ($node instanceof Node) { + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ($functionName === 'func_get_args') { + return $scope->getDefinedVariables(); + } + } + if ($node instanceof Node\Expr\Variable && is_string($node->name) && $node->name !== 'this') { + return [$node->name]; + } + if ($node instanceof Node\Expr\ClosureUse && is_string($node->var->name)) { + return [$node->var->name]; + } + if ( + $node instanceof Node\Expr\FuncCall + && $node->name instanceof Node\Name + && (string) $node->name === 'compact' + ) { + foreach ($node->args as $arg) { + $argType = $scope->getType($arg->value); + if (!($argType instanceof ConstantStringType)) { + continue; + } - return $variableNames; - } + $variableNames[] = $argType->getValue(); + } + } + foreach ($node->getSubNodeNames() as $subNodeName) { + if ($node instanceof Node\Expr\Closure && $subNodeName !== 'uses') { + continue; + } + $subNode = $node->{$subNodeName}; + $variableNames = array_merge($variableNames, $this->getUsedVariables($scope, $subNode)); + } + } elseif (is_array($node)) { + foreach ($node as $subNode) { + $variableNames = array_merge($variableNames, $this->getUsedVariables($scope, $subNode)); + } + } + return $variableNames; + } } diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index 6292893e6a..8a5b5b57c2 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -1,4 +1,6 @@ -checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; - } - - public function getNodeType(): string - { - return Node\Expr\FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node->name instanceof Node\Expr) { - return []; - } - - $functionName = strtolower($node->name->toString()); - - if ($functionName !== 'compact') { - return []; - } - - $functionArguments = $node->args; - $messages = []; - - foreach ($functionArguments as $argument) { - $argumentType = $scope->getType($argument->value); - $constantStrings = $this->findConstantStrings($argumentType); - foreach ($constantStrings as $constantString) { - $variableName = $constantString->getValue(); - $scopeHasVariable = $scope->hasVariableType($variableName); - - if ($scopeHasVariable->no()) { - $messages[] = RuleErrorBuilder::message( - sprintf('Call to function compact() contains undefined variable $%s.', $variableName) - )->line($argument->getLine())->build(); - } elseif ($this->checkMaybeUndefinedVariables && $scopeHasVariable->maybe()) { - $messages[] = RuleErrorBuilder::message( - sprintf('Call to function compact() contains possibly undefined variable $%s.', $variableName) - )->line($argument->getLine())->build(); - } - } - } - - return $messages; - } - - /** - * @param Type $type - * @return array - */ - private function findConstantStrings(Type $type): array - { - if ($type instanceof ConstantStringType) { - return [$type]; - } - - if ($type instanceof ConstantArrayType) { - $result = []; - foreach ($type->getValueTypes() as $valueType) { - $constantStrings = $this->findConstantStrings($valueType); - $result = array_merge($result, $constantStrings); - } - - return $result; - } - - return []; - } - + private bool $checkMaybeUndefinedVariables; + + public function __construct(bool $checkMaybeUndefinedVariables) + { + $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Expr) { + return []; + } + + $functionName = strtolower($node->name->toString()); + + if ($functionName !== 'compact') { + return []; + } + + $functionArguments = $node->args; + $messages = []; + + foreach ($functionArguments as $argument) { + $argumentType = $scope->getType($argument->value); + $constantStrings = $this->findConstantStrings($argumentType); + foreach ($constantStrings as $constantString) { + $variableName = $constantString->getValue(); + $scopeHasVariable = $scope->hasVariableType($variableName); + + if ($scopeHasVariable->no()) { + $messages[] = RuleErrorBuilder::message( + sprintf('Call to function compact() contains undefined variable $%s.', $variableName) + )->line($argument->getLine())->build(); + } elseif ($this->checkMaybeUndefinedVariables && $scopeHasVariable->maybe()) { + $messages[] = RuleErrorBuilder::message( + sprintf('Call to function compact() contains possibly undefined variable $%s.', $variableName) + )->line($argument->getLine())->build(); + } + } + } + + return $messages; + } + + /** + * @param Type $type + * @return array + */ + private function findConstantStrings(Type $type): array + { + if ($type instanceof ConstantStringType) { + return [$type]; + } + + if ($type instanceof ConstantArrayType) { + $result = []; + foreach ($type->getValueTypes() as $valueType) { + $constantStrings = $this->findConstantStrings($valueType); + $result = array_merge($result, $constantStrings); + } + + return $result; + } + + return []; + } } diff --git a/src/Rules/Variables/DefinedVariableRule.php b/src/Rules/Variables/DefinedVariableRule.php index f50735792d..e96fa11c6a 100644 --- a/src/Rules/Variables/DefinedVariableRule.php +++ b/src/Rules/Variables/DefinedVariableRule.php @@ -1,4 +1,6 @@ -cliArgumentsVariablesRegistered = $cliArgumentsVariablesRegistered; - $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; - } + private bool $checkMaybeUndefinedVariables; - public function getNodeType(): string - { - return Variable::class; - } + public function __construct( + bool $cliArgumentsVariablesRegistered, + bool $checkMaybeUndefinedVariables + ) { + $this->cliArgumentsVariablesRegistered = $cliArgumentsVariablesRegistered; + $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; + } - public function processNode(Node $node, Scope $scope): array - { - if (!is_string($node->name)) { - return []; - } + public function getNodeType(): string + { + return Variable::class; + } - if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [ - 'argc', - 'argv', - ], true)) { - $isInMain = !$scope->isInClass() && !$scope->isInAnonymousFunction() && $scope->getFunction() === null; - if ($isInMain) { - return []; - } - } + public function processNode(Node $node, Scope $scope): array + { + if (!is_string($node->name)) { + return []; + } - if ($scope->isInExpressionAssign($node)) { - return []; - } + if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [ + 'argc', + 'argv', + ], true)) { + $isInMain = !$scope->isInClass() && !$scope->isInAnonymousFunction() && $scope->getFunction() === null; + if ($isInMain) { + return []; + } + } - if ($scope->hasVariableType($node->name)->no()) { - return [ - RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name)) - ->identifier('variable.undefined') - ->metadata([ - 'variableName' => $node->name, - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - 'depth' => $node->getAttribute('expressionDepth'), - 'order' => $node->getAttribute('expressionOrder'), - 'variables' => $scope->getDefinedVariables(), - 'parentVariables' => $this->getParentVariables($scope), - ]) - ->build(), - ]; - } elseif ( - $this->checkMaybeUndefinedVariables - && !$scope->hasVariableType($node->name)->yes() - ) { - return [ - RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name)) - ->identifier('variable.maybeUndefined') - ->metadata([ - 'variableName' => $node->name, - 'statementDepth' => $node->getAttribute('statementDepth'), - 'statementOrder' => $node->getAttribute('statementOrder'), - 'depth' => $node->getAttribute('expressionDepth'), - 'order' => $node->getAttribute('expressionOrder'), - 'variables' => $scope->getDefinedVariables(), - 'parentVariables' => $this->getParentVariables($scope), - ]) - ->build(), - ]; - } + if ($scope->isInExpressionAssign($node)) { + return []; + } - return []; - } + if ($scope->hasVariableType($node->name)->no()) { + return [ + RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name)) + ->identifier('variable.undefined') + ->metadata([ + 'variableName' => $node->name, + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + 'depth' => $node->getAttribute('expressionDepth'), + 'order' => $node->getAttribute('expressionOrder'), + 'variables' => $scope->getDefinedVariables(), + 'parentVariables' => $this->getParentVariables($scope), + ]) + ->build(), + ]; + } elseif ( + $this->checkMaybeUndefinedVariables + && !$scope->hasVariableType($node->name)->yes() + ) { + return [ + RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name)) + ->identifier('variable.maybeUndefined') + ->metadata([ + 'variableName' => $node->name, + 'statementDepth' => $node->getAttribute('statementDepth'), + 'statementOrder' => $node->getAttribute('statementOrder'), + 'depth' => $node->getAttribute('expressionDepth'), + 'order' => $node->getAttribute('expressionOrder'), + 'variables' => $scope->getDefinedVariables(), + 'parentVariables' => $this->getParentVariables($scope), + ]) + ->build(), + ]; + } - /** - * @param Scope $scope - * @return array> - */ - private function getParentVariables(Scope $scope): array - { - $variables = []; - $parent = $scope->getParentScope(); - while ($parent !== null) { - $variables[] = $parent->getDefinedVariables(); - $parent = $parent->getParentScope(); - } + return []; + } - return $variables; - } + /** + * @param Scope $scope + * @return array> + */ + private function getParentVariables(Scope $scope): array + { + $variables = []; + $parent = $scope->getParentScope(); + while ($parent !== null) { + $variables[] = $parent->getDefinedVariables(); + $parent = $parent->getParentScope(); + } + return $variables; + } } diff --git a/src/Rules/Variables/IssetRule.php b/src/Rules/Variables/IssetRule.php index 78ab199e97..03fff60be3 100644 --- a/src/Rules/Variables/IssetRule.php +++ b/src/Rules/Variables/IssetRule.php @@ -1,4 +1,6 @@ -issetCheck = $issetCheck; - } - - public function getNodeType(): string - { - return Node\Expr\Isset_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $messages = []; - foreach ($node->vars as $var) { - $error = $this->issetCheck->check($var, $scope, 'in isset()'); - if ($error === null) { - continue; - } - $messages[] = $error; - } - - return $messages; - } - + private IssetCheck $issetCheck; + + public function __construct(IssetCheck $issetCheck) + { + $this->issetCheck = $issetCheck; + } + + public function getNodeType(): string + { + return Node\Expr\Isset_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + foreach ($node->vars as $var) { + $error = $this->issetCheck->check($var, $scope, 'in isset()'); + if ($error === null) { + continue; + } + $messages[] = $error; + } + + return $messages; + } } diff --git a/src/Rules/Variables/NullCoalesceRule.php b/src/Rules/Variables/NullCoalesceRule.php index 5c6790e3f8..34102520e1 100644 --- a/src/Rules/Variables/NullCoalesceRule.php +++ b/src/Rules/Variables/NullCoalesceRule.php @@ -1,4 +1,6 @@ -issetCheck = $issetCheck; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node instanceof Node\Expr\BinaryOp\Coalesce) { - $error = $this->issetCheck->check($node->left, $scope, 'on left side of ??'); - } elseif ($node instanceof Node\Expr\AssignOp\Coalesce) { - $error = $this->issetCheck->check($node->var, $scope, 'on left side of ??='); - } else { - return []; - } - - if ($error === null) { - return []; - } - - return [$error]; - } - + private IssetCheck $issetCheck; + + public function __construct(IssetCheck $issetCheck) + { + $this->issetCheck = $issetCheck; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof Node\Expr\BinaryOp\Coalesce) { + $error = $this->issetCheck->check($node->left, $scope, 'on left side of ??'); + } elseif ($node instanceof Node\Expr\AssignOp\Coalesce) { + $error = $this->issetCheck->check($node->var, $scope, 'on left side of ??='); + } else { + return []; + } + + if ($error === null) { + return []; + } + + return [$error]; + } } diff --git a/src/Rules/Variables/ThrowTypeRule.php b/src/Rules/Variables/ThrowTypeRule.php index 51b4c0bbfe..3a1e14ca6f 100644 --- a/src/Rules/Variables/ThrowTypeRule.php +++ b/src/Rules/Variables/ThrowTypeRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return \PhpParser\Node\Stmt\Throw_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $throwableType = new ObjectType(\Throwable::class); - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - 'Throwing object of an unknown class %s.', - static function (Type $type) use ($throwableType): bool { - return $throwableType->isSuperTypeOf($type)->yes(); - } - ); - - $foundType = $typeResult->getType(); - if ($foundType instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - - $isSuperType = $throwableType->isSuperTypeOf($foundType); - if ($isSuperType->yes()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Invalid type %s to throw.', - $foundType->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - + private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; + + public function __construct( + RuleLevelHelper $ruleLevelHelper + ) { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\Throw_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $throwableType = new ObjectType(\Throwable::class); + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + 'Throwing object of an unknown class %s.', + static function (Type $type) use ($throwableType): bool { + return $throwableType->isSuperTypeOf($type)->yes(); + } + ); + + $foundType = $typeResult->getType(); + if ($foundType instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + + $isSuperType = $throwableType->isSuperTypeOf($foundType); + if ($isSuperType->yes()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Invalid type %s to throw.', + $foundType->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/Variables/UnsetRule.php b/src/Rules/Variables/UnsetRule.php index 58ab8457d8..9a3b9d091b 100644 --- a/src/Rules/Variables/UnsetRule.php +++ b/src/Rules/Variables/UnsetRule.php @@ -1,4 +1,6 @@ -vars; - $errors = []; - - foreach ($functionArguments as $argument) { - $error = $this->canBeUnset($argument, $scope); - if ($error === null) { - continue; - } + public function processNode(Node $node, Scope $scope): array + { + $functionArguments = $node->vars; + $errors = []; - $errors[] = $error; - } + foreach ($functionArguments as $argument) { + $error = $this->canBeUnset($argument, $scope); + if ($error === null) { + continue; + } - return $errors; - } + $errors[] = $error; + } - private function canBeUnset(Node $node, Scope $scope): ?RuleError - { - if ($node instanceof Node\Expr\Variable && is_string($node->name)) { - $hasVariable = $scope->hasVariableType($node->name); - if ($hasVariable->no()) { - return RuleErrorBuilder::message( - sprintf('Call to function unset() contains undefined variable $%s.', $node->name) - )->line($node->getLine())->build(); - } - } elseif ($node instanceof Node\Expr\ArrayDimFetch && $node->dim !== null) { - $type = $scope->getType($node->var); - $dimType = $scope->getType($node->dim); + return $errors; + } - if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($dimType)->no()) { - return RuleErrorBuilder::message( - sprintf( - 'Cannot unset offset %s on %s.', - $dimType->describe(VerbosityLevel::value()), - $type->describe(VerbosityLevel::value()) - ) - )->line($node->getLine())->build(); - } + private function canBeUnset(Node $node, Scope $scope): ?RuleError + { + if ($node instanceof Node\Expr\Variable && is_string($node->name)) { + $hasVariable = $scope->hasVariableType($node->name); + if ($hasVariable->no()) { + return RuleErrorBuilder::message( + sprintf('Call to function unset() contains undefined variable $%s.', $node->name) + )->line($node->getLine())->build(); + } + } elseif ($node instanceof Node\Expr\ArrayDimFetch && $node->dim !== null) { + $type = $scope->getType($node->var); + $dimType = $scope->getType($node->dim); - return $this->canBeUnset($node->var, $scope); - } + if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($dimType)->no()) { + return RuleErrorBuilder::message( + sprintf( + 'Cannot unset offset %s on %s.', + $dimType->describe(VerbosityLevel::value()), + $type->describe(VerbosityLevel::value()) + ) + )->line($node->getLine())->build(); + } - return null; - } + return $this->canBeUnset($node->var, $scope); + } + return null; + } } diff --git a/src/Rules/Variables/VariableCertaintyInIssetRule.php b/src/Rules/Variables/VariableCertaintyInIssetRule.php index 242951798d..8f86a4c9f8 100644 --- a/src/Rules/Variables/VariableCertaintyInIssetRule.php +++ b/src/Rules/Variables/VariableCertaintyInIssetRule.php @@ -1,4 +1,6 @@ -vars as $var) { - $isSubNode = false; - while ( - $var instanceof Node\Expr\ArrayDimFetch - || $var instanceof Node\Expr\PropertyFetch - || ( - $var instanceof Node\Expr\StaticPropertyFetch - && $var->class instanceof Node\Expr - ) - ) { - if ($var instanceof Node\Expr\StaticPropertyFetch) { - $var = $var->class; - } else { - $var = $var->var; - } - $isSubNode = true; - } - - if (!$var instanceof Node\Expr\Variable || !is_string($var->name) || $var->name === '_SESSION') { - continue; - } + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + foreach ($node->vars as $var) { + $isSubNode = false; + while ( + $var instanceof Node\Expr\ArrayDimFetch + || $var instanceof Node\Expr\PropertyFetch + || ( + $var instanceof Node\Expr\StaticPropertyFetch + && $var->class instanceof Node\Expr + ) + ) { + if ($var instanceof Node\Expr\StaticPropertyFetch) { + $var = $var->class; + } else { + $var = $var->var; + } + $isSubNode = true; + } - $certainty = $scope->hasVariableType($var->name); - if ($certainty->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() is never defined.', - $var->name - ))->build(); - } elseif ($certainty->yes() && !$isSubNode) { - $variableType = $scope->getVariableType($var->name); - if ($variableType->isSuperTypeOf(new NullType())->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() always exists and is not nullable.', - $var->name - ))->build(); - } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() is always null.', - $var->name - ))->build(); - } - } - } + if (!$var instanceof Node\Expr\Variable || !is_string($var->name) || $var->name === '_SESSION') { + continue; + } - return $messages; - } + $certainty = $scope->hasVariableType($var->name); + if ($certainty->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in isset() is never defined.', + $var->name + ))->build(); + } elseif ($certainty->yes() && !$isSubNode) { + $variableType = $scope->getVariableType($var->name); + if ($variableType->isSuperTypeOf(new NullType())->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in isset() always exists and is not nullable.', + $var->name + ))->build(); + } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Variable $%s in isset() is always null.', + $var->name + ))->build(); + } + } + } + return $messages; + } } diff --git a/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php b/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php index 5a8b9e1323..c51deaa00c 100644 --- a/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php +++ b/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php @@ -1,4 +1,6 @@ -var; - $description = '??='; - } elseif ($node instanceof Node\Expr\BinaryOp\Coalesce) { - $var = $node->left; - $description = '??'; - } else { - return []; - } - - $isSubNode = false; - while ( - $var instanceof Node\Expr\ArrayDimFetch - || $var instanceof Node\Expr\PropertyFetch - || ( - $var instanceof Node\Expr\StaticPropertyFetch - && $var->class instanceof Node\Expr - ) - ) { - if ($var instanceof Node\Expr\StaticPropertyFetch) { - $var = $var->class; - } else { - $var = $var->var; - } - $isSubNode = true; - } + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof Node\Expr\AssignOp\Coalesce) { + $var = $node->var; + $description = '??='; + } elseif ($node instanceof Node\Expr\BinaryOp\Coalesce) { + $var = $node->left; + $description = '??'; + } else { + return []; + } - if (!$var instanceof Node\Expr\Variable || !is_string($var->name)) { - return []; - } + $isSubNode = false; + while ( + $var instanceof Node\Expr\ArrayDimFetch + || $var instanceof Node\Expr\PropertyFetch + || ( + $var instanceof Node\Expr\StaticPropertyFetch + && $var->class instanceof Node\Expr + ) + ) { + if ($var instanceof Node\Expr\StaticPropertyFetch) { + $var = $var->class; + } else { + $var = $var->var; + } + $isSubNode = true; + } - $certainty = $scope->hasVariableType($var->name); - if ($certainty->no()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s is never defined.', - $var->name, - $description - ))->build()]; - } elseif ($certainty->yes() && !$isSubNode) { - $variableType = $scope->getVariableType($var->name); - if ($variableType->isSuperTypeOf(new NullType())->no()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s always exists and is not nullable.', - $var->name, - $description - ))->build()]; - } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s is always null.', - $var->name, - $description - ))->build()]; - } - } + if (!$var instanceof Node\Expr\Variable || !is_string($var->name)) { + return []; + } - return []; - } + $certainty = $scope->hasVariableType($var->name); + if ($certainty->no()) { + return [RuleErrorBuilder::message(sprintf( + 'Variable $%s on left side of %s is never defined.', + $var->name, + $description + ))->build()]; + } elseif ($certainty->yes() && !$isSubNode) { + $variableType = $scope->getVariableType($var->name); + if ($variableType->isSuperTypeOf(new NullType())->no()) { + return [RuleErrorBuilder::message(sprintf( + 'Variable $%s on left side of %s always exists and is not nullable.', + $var->name, + $description + ))->build()]; + } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { + return [RuleErrorBuilder::message(sprintf( + 'Variable $%s on left side of %s is always null.', + $var->name, + $description + ))->build()]; + } + } + return []; + } } diff --git a/src/Rules/Variables/VariableCloningRule.php b/src/Rules/Variables/VariableCloningRule.php index 5caa3587f4..49c4583f78 100644 --- a/src/Rules/Variables/VariableCloningRule.php +++ b/src/Rules/Variables/VariableCloningRule.php @@ -1,4 +1,6 @@ -ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return Clone_::class; - } + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } - public function processNode(Node $node, Scope $scope): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - 'Cloning object of an unknown class %s.', - static function (Type $type): bool { - return $type->isCloneable()->yes(); - } - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - if ($type->isCloneable()->yes()) { - return []; - } + public function getNodeType(): string + { + return Clone_::class; + } - if ($node->expr instanceof Variable && is_string($node->expr->name)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot clone non-object variable $%s of type %s.', - $node->expr->name, - $type->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + public function processNode(Node $node, Scope $scope): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + 'Cloning object of an unknown class %s.', + static function (Type $type): bool { + return $type->isCloneable()->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + if ($type->isCloneable()->yes()) { + return []; + } - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot clone %s.', - $type->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } + if ($node->expr instanceof Variable && is_string($node->expr->name)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot clone non-object variable $%s of type %s.', + $node->expr->name, + $type->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot clone %s.', + $type->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]; + } } diff --git a/src/Rules/Whitespace/FileWhitespaceRule.php b/src/Rules/Whitespace/FileWhitespaceRule.php index 8829390f20..1e3c1c4a86 100644 --- a/src/Rules/Whitespace/FileWhitespaceRule.php +++ b/src/Rules/Whitespace/FileWhitespaceRule.php @@ -1,4 +1,6 @@ -getNodes(); - if (count($nodes) === 0) { - return []; - } - - $firstNode = $nodes[0]; - $messages = []; - if ($firstNode instanceof Node\Stmt\InlineHTML && $firstNode->value === "\xef\xbb\xbf") { - $messages[] = RuleErrorBuilder::message('File begins with UTF-8 BOM character. This may cause problems when running the code in the web browser.')->build(); - } - - $nodeTraverser = new NodeTraverser(); - $visitor = new class () extends \PhpParser\NodeVisitorAbstract { - - /** @var \PhpParser\Node[] */ - private $lastNodes = []; + public function processNode(Node $node, Scope $scope): array + { + $nodes = $node->getNodes(); + if (count($nodes) === 0) { + return []; + } - /** - * @param Node $node - * @return int|Node|null - */ - public function enterNode(Node $node) - { - if ($node instanceof Node\Stmt\Declare_) { - if ($node->stmts !== null && count($node->stmts) > 0) { - $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; - } - return null; - } - if ($node instanceof Node\Stmt\Namespace_) { - if (count($node->stmts) > 0) { - $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; - } - return null; - } - return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; - } + $firstNode = $nodes[0]; + $messages = []; + if ($firstNode instanceof Node\Stmt\InlineHTML && $firstNode->value === "\xef\xbb\xbf") { + $messages[] = RuleErrorBuilder::message('File begins with UTF-8 BOM character. This may cause problems when running the code in the web browser.')->build(); + } - /** - * @return Node[] - */ - public function getLastNodes(): array - { - return $this->lastNodes; - } + $nodeTraverser = new NodeTraverser(); + $visitor = new class() extends \PhpParser\NodeVisitorAbstract { + /** @var \PhpParser\Node[] */ + private $lastNodes = []; - }; - $nodeTraverser->addVisitor($visitor); - $nodeTraverser->traverse($nodes); + /** + * @param Node $node + * @return int|Node|null + */ + public function enterNode(Node $node) + { + if ($node instanceof Node\Stmt\Declare_) { + if ($node->stmts !== null && count($node->stmts) > 0) { + $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; + } + return null; + } + if ($node instanceof Node\Stmt\Namespace_) { + if (count($node->stmts) > 0) { + $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; + } + return null; + } + return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } - $lastNodes = $visitor->getLastNodes(); - $lastNodes[] = $nodes[count($nodes) - 1]; - foreach ($lastNodes as $lastNode) { - if (!$lastNode instanceof Node\Stmt\InlineHTML || Strings::match($lastNode->value, '#^(\s+)$#') === null) { - continue; - } + /** + * @return Node[] + */ + public function getLastNodes(): array + { + return $this->lastNodes; + } + }; + $nodeTraverser->addVisitor($visitor); + $nodeTraverser->traverse($nodes); - $messages[] = RuleErrorBuilder::message('File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.')->line($lastNode->getStartLine())->build(); - } + $lastNodes = $visitor->getLastNodes(); + $lastNodes[] = $nodes[count($nodes) - 1]; + foreach ($lastNodes as $lastNode) { + if (!$lastNode instanceof Node\Stmt\InlineHTML || Strings::match($lastNode->value, '#^(\s+)$#') === null) { + continue; + } - return $messages; - } + $messages[] = RuleErrorBuilder::message('File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.')->line($lastNode->getStartLine())->build(); + } + return $messages; + } } diff --git a/src/ShouldNotHappenException.php b/src/ShouldNotHappenException.php index 99575886db..f64003ef39 100644 --- a/src/ShouldNotHappenException.php +++ b/src/ShouldNotHappenException.php @@ -1,13 +1,13 @@ -= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } - if ($this->outputStream === null) { - $resource = fopen('php://memory', 'w', false); - if ($resource === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $this->outputStream = new StreamOutput($resource); - } - - return $this->outputStream; - } - - protected function getOutput(): Output - { - if ($this->output === null) { - $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $this->getOutputStream()); - $this->output = new SymfonyOutput($this->getOutputStream(), new SymfonyStyle($errorConsoleStyle)); - } - - return $this->output; - } - - protected function getOutputContent(): string - { - rewind($this->getOutputStream()->getStream()); - - $contents = stream_get_contents($this->getOutputStream()->getStream()); - if ($contents === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->rtrimMultiline($contents); - } - - protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors): AnalysisResult - { - if ($numFileErrors > 4 || $numFileErrors < 0 || $numGenericErrors > 2 || $numGenericErrors < 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $fileErrors = array_slice([ - new Error('Foo', self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 4), - new Error('Foo', self::DIRECTORY_PATH . '/foo.php', 1), - new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5), - new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2), - ], 0, $numFileErrors); - - $genericErrors = array_slice([ - 'first generic error', - 'second generic error', - ], 0, $numGenericErrors); - - return new AnalysisResult( - $fileErrors, - $genericErrors, - [], - [], - false, - null, - true - ); - } - - private function rtrimMultiline(string $output): string - { - $result = array_map(static function (string $line): string { - return rtrim($line, " \r\n"); - }, explode("\n", $output)); - - return implode("\n", $result); - } - + protected const DIRECTORY_PATH = '/data/folder/with space/and unicode 😃/project'; + + private ?StreamOutput $outputStream = null; + + private ?Output $output = null; + + private function getOutputStream(): StreamOutput + { + if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); + } + if ($this->outputStream === null) { + $resource = fopen('php://memory', 'w', false); + if ($resource === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $this->outputStream = new StreamOutput($resource); + } + + return $this->outputStream; + } + + protected function getOutput(): Output + { + if ($this->output === null) { + $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $this->getOutputStream()); + $this->output = new SymfonyOutput($this->getOutputStream(), new SymfonyStyle($errorConsoleStyle)); + } + + return $this->output; + } + + protected function getOutputContent(): string + { + rewind($this->getOutputStream()->getStream()); + + $contents = stream_get_contents($this->getOutputStream()->getStream()); + if ($contents === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->rtrimMultiline($contents); + } + + protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors): AnalysisResult + { + if ($numFileErrors > 4 || $numFileErrors < 0 || $numGenericErrors > 2 || $numGenericErrors < 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $fileErrors = array_slice([ + new Error('Foo', self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 4), + new Error('Foo', self::DIRECTORY_PATH . '/foo.php', 1), + new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5), + new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2), + ], 0, $numFileErrors); + + $genericErrors = array_slice([ + 'first generic error', + 'second generic error', + ], 0, $numGenericErrors); + + return new AnalysisResult( + $fileErrors, + $genericErrors, + [], + [], + false, + null, + true + ); + } + + private function rtrimMultiline(string $output): string + { + $result = array_map(static function (string $line): string { + return rtrim($line, " \r\n"); + }, explode("\n", $output)); + + return implode("\n", $result); + } } diff --git a/src/Testing/LevelsTestCase.php b/src/Testing/LevelsTestCase.php index 119242b3e7..59f1a1d88e 100644 --- a/src/Testing/LevelsTestCase.php +++ b/src/Testing/LevelsTestCase.php @@ -1,4 +1,6 @@ -> - */ - abstract public function dataTopics(): array; - - abstract public function getDataPath(): string; - - abstract public function getPhpStanExecutablePath(): string; - - abstract public function getPhpStanConfigPath(): ?string; - - protected function getResultSuffix(): string - { - return ''; - } - - protected function shouldAutoloadAnalysedFile(): bool - { - return true; - } - - /** - * @dataProvider dataTopics - * @param string $topic - */ - public function testLevels( - string $topic - ): void - { - $file = sprintf('%s' . DIRECTORY_SEPARATOR . '%s.php', $this->getDataPath(), $topic); - $command = escapeshellcmd($this->getPhpStanExecutablePath()); - $configPath = $this->getPhpStanConfigPath(); - $fileHelper = new FileHelper(__DIR__ . '/../..'); - - $previousMessages = []; - - $exceptions = []; - - foreach (range(0, 8) as $level) { - unset($outputLines); - exec(sprintf('%s %s clear-result-cache %s 2>&1', escapeshellarg(PHP_BINARY), $command, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : ''), $clearResultCacheOutputLines, $clearResultCacheExitCode); - if ($clearResultCacheExitCode !== 0) { - throw new \PHPStan\ShouldNotHappenException('Could not clear result cache: ' . implode("\n", $clearResultCacheOutputLines)); - } - exec(sprintf('%s %s analyse --no-progress --error-format=prettyJson --level=%d %s %s %s', escapeshellarg(PHP_BINARY), $command, $level, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : '', $this->shouldAutoloadAnalysedFile() ? sprintf('--autoload-file %s', escapeshellarg($file)) : '', escapeshellarg($file)), $outputLines); - - $output = implode("\n", $outputLines); - - try { - $actualJson = \Nette\Utils\Json::decode($output, \Nette\Utils\Json::FORCE_ARRAY); - } catch (\Nette\Utils\JsonException $e) { - throw new \Nette\Utils\JsonException(sprintf('Cannot decode: %s', $output)); - } - if (count($actualJson['files']) > 0) { - $normalizedFilePath = $fileHelper->normalizePath($file); - if (!isset($actualJson['files'][$normalizedFilePath])) { - $messagesBeforeDiffing = []; - } else { - $messagesBeforeDiffing = $actualJson['files'][$normalizedFilePath]['messages']; - } - - foreach ($this->getAdditionalAnalysedFiles() as $additionalAnalysedFile) { - $normalizedAdditionalFilePath = $fileHelper->normalizePath($additionalAnalysedFile); - if (!isset($actualJson['files'][$normalizedAdditionalFilePath])) { - continue; - } - - $messagesBeforeDiffing = array_merge($messagesBeforeDiffing, $actualJson['files'][$normalizedAdditionalFilePath]['messages']); - } - } else { - $messagesBeforeDiffing = []; - } - - $messages = []; - foreach ($messagesBeforeDiffing as $message) { - foreach ($previousMessages as $lastMessage) { - if ( - $message['message'] === $lastMessage['message'] - && $message['line'] === $lastMessage['line'] - ) { - continue 2; - } - } - - $messages[] = $message; - } - - $missingMessages = []; - foreach ($previousMessages as $previousMessage) { - foreach ($messagesBeforeDiffing as $message) { - if ( - $previousMessage['message'] === $message['message'] - && $previousMessage['line'] === $message['line'] - ) { - continue 2; - } - } - - $missingMessages[] = $previousMessage; - } - - $previousMessages = array_merge($previousMessages, $messages); - $expectedJsonFile = sprintf('%s/%s-%d%s.json', $this->getDataPath(), $topic, $level, $this->getResultSuffix()); - - $exception = $this->compareFiles($expectedJsonFile, $messages); - if ($exception !== null) { - $exceptions[] = $exception; - } - - $expectedJsonMissingFile = sprintf('%s/%s-%d-missing%s.json', $this->getDataPath(), $topic, $level, $this->getResultSuffix()); - $exception = $this->compareFiles($expectedJsonMissingFile, $missingMessages); - if ($exception === null) { - continue; - } - - $exceptions[] = $exception; - } - - if (count($exceptions) > 0) { - throw $exceptions[0]; - } - } - - /** - * @return string[] - */ - public function getAdditionalAnalysedFiles(): array - { - return []; - } - - /** - * @param string $expectedJsonFile - * @param string[] $expectedMessages - * @return \PHPUnit\Framework\AssertionFailedError|null - */ - private function compareFiles(string $expectedJsonFile, array $expectedMessages): ?\PHPUnit\Framework\AssertionFailedError - { - if (count($expectedMessages) === 0) { - try { - self::assertFileDoesNotExist($expectedJsonFile); - return null; - } catch (\PHPUnit\Framework\AssertionFailedError $e) { - unlink($expectedJsonFile); - return $e; - } - } - - $actualOutput = \Nette\Utils\Json::encode($expectedMessages, \Nette\Utils\Json::PRETTY); - - try { - $this->assertJsonStringEqualsJsonFile( - $expectedJsonFile, - $actualOutput - ); - } catch (\PHPUnit\Framework\AssertionFailedError $e) { - FileWriter::write($expectedJsonFile, $actualOutput); - return $e; - } - - return null; - } - - public static function assertFileDoesNotExist(string $filename, string $message = ''): void - { - if (!method_exists(parent::class, 'assertFileDoesNotExist')) { - parent::assertFileNotExists($filename, $message); - return; - } - - parent::assertFileDoesNotExist($filename, $message); - } - + /** + * @return array> + */ + abstract public function dataTopics(): array; + + abstract public function getDataPath(): string; + + abstract public function getPhpStanExecutablePath(): string; + + abstract public function getPhpStanConfigPath(): ?string; + + protected function getResultSuffix(): string + { + return ''; + } + + protected function shouldAutoloadAnalysedFile(): bool + { + return true; + } + + /** + * @dataProvider dataTopics + * @param string $topic + */ + public function testLevels( + string $topic + ): void { + $file = sprintf('%s' . DIRECTORY_SEPARATOR . '%s.php', $this->getDataPath(), $topic); + $command = escapeshellcmd($this->getPhpStanExecutablePath()); + $configPath = $this->getPhpStanConfigPath(); + $fileHelper = new FileHelper(__DIR__ . '/../..'); + + $previousMessages = []; + + $exceptions = []; + + foreach (range(0, 8) as $level) { + unset($outputLines); + exec(sprintf('%s %s clear-result-cache %s 2>&1', escapeshellarg(PHP_BINARY), $command, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : ''), $clearResultCacheOutputLines, $clearResultCacheExitCode); + if ($clearResultCacheExitCode !== 0) { + throw new \PHPStan\ShouldNotHappenException('Could not clear result cache: ' . implode("\n", $clearResultCacheOutputLines)); + } + exec(sprintf('%s %s analyse --no-progress --error-format=prettyJson --level=%d %s %s %s', escapeshellarg(PHP_BINARY), $command, $level, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : '', $this->shouldAutoloadAnalysedFile() ? sprintf('--autoload-file %s', escapeshellarg($file)) : '', escapeshellarg($file)), $outputLines); + + $output = implode("\n", $outputLines); + + try { + $actualJson = \Nette\Utils\Json::decode($output, \Nette\Utils\Json::FORCE_ARRAY); + } catch (\Nette\Utils\JsonException $e) { + throw new \Nette\Utils\JsonException(sprintf('Cannot decode: %s', $output)); + } + if (count($actualJson['files']) > 0) { + $normalizedFilePath = $fileHelper->normalizePath($file); + if (!isset($actualJson['files'][$normalizedFilePath])) { + $messagesBeforeDiffing = []; + } else { + $messagesBeforeDiffing = $actualJson['files'][$normalizedFilePath]['messages']; + } + + foreach ($this->getAdditionalAnalysedFiles() as $additionalAnalysedFile) { + $normalizedAdditionalFilePath = $fileHelper->normalizePath($additionalAnalysedFile); + if (!isset($actualJson['files'][$normalizedAdditionalFilePath])) { + continue; + } + + $messagesBeforeDiffing = array_merge($messagesBeforeDiffing, $actualJson['files'][$normalizedAdditionalFilePath]['messages']); + } + } else { + $messagesBeforeDiffing = []; + } + + $messages = []; + foreach ($messagesBeforeDiffing as $message) { + foreach ($previousMessages as $lastMessage) { + if ( + $message['message'] === $lastMessage['message'] + && $message['line'] === $lastMessage['line'] + ) { + continue 2; + } + } + + $messages[] = $message; + } + + $missingMessages = []; + foreach ($previousMessages as $previousMessage) { + foreach ($messagesBeforeDiffing as $message) { + if ( + $previousMessage['message'] === $message['message'] + && $previousMessage['line'] === $message['line'] + ) { + continue 2; + } + } + + $missingMessages[] = $previousMessage; + } + + $previousMessages = array_merge($previousMessages, $messages); + $expectedJsonFile = sprintf('%s/%s-%d%s.json', $this->getDataPath(), $topic, $level, $this->getResultSuffix()); + + $exception = $this->compareFiles($expectedJsonFile, $messages); + if ($exception !== null) { + $exceptions[] = $exception; + } + + $expectedJsonMissingFile = sprintf('%s/%s-%d-missing%s.json', $this->getDataPath(), $topic, $level, $this->getResultSuffix()); + $exception = $this->compareFiles($expectedJsonMissingFile, $missingMessages); + if ($exception === null) { + continue; + } + + $exceptions[] = $exception; + } + + if (count($exceptions) > 0) { + throw $exceptions[0]; + } + } + + /** + * @return string[] + */ + public function getAdditionalAnalysedFiles(): array + { + return []; + } + + /** + * @param string $expectedJsonFile + * @param string[] $expectedMessages + * @return \PHPUnit\Framework\AssertionFailedError|null + */ + private function compareFiles(string $expectedJsonFile, array $expectedMessages): ?\PHPUnit\Framework\AssertionFailedError + { + if (count($expectedMessages) === 0) { + try { + self::assertFileDoesNotExist($expectedJsonFile); + return null; + } catch (\PHPUnit\Framework\AssertionFailedError $e) { + unlink($expectedJsonFile); + return $e; + } + } + + $actualOutput = \Nette\Utils\Json::encode($expectedMessages, \Nette\Utils\Json::PRETTY); + + try { + $this->assertJsonStringEqualsJsonFile( + $expectedJsonFile, + $actualOutput + ); + } catch (\PHPUnit\Framework\AssertionFailedError $e) { + FileWriter::write($expectedJsonFile, $actualOutput); + return $e; + } + + return null; + } + + public static function assertFileDoesNotExist(string $filename, string $message = ''): void + { + if (!method_exists(parent::class, 'assertFileDoesNotExist')) { + parent::assertFileNotExists($filename, $message); + return; + } + + parent::assertFileDoesNotExist($filename, $message); + } } diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 5a1c9ca897..7159139fdb 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -1,4 +1,6 @@ -createTypeSpecifier( - new \PhpParser\PrettyPrinter\Standard(), - $this->createReflectionProvider(), - $this->getMethodTypeSpecifyingExtensions(), - $this->getStaticMethodTypeSpecifyingExtensions() - ); - } - - private function getAnalyser(): Analyser - { - if ($this->analyser === null) { - $registry = new Registry([ - $this->getRule(), - ]); - - $broker = $this->createBroker(); - $printer = new \PhpParser\PrettyPrinter\Standard(); - $typeSpecifier = $this->createTypeSpecifier( - $printer, - $broker, - $this->getMethodTypeSpecifyingExtensions(), - $this->getStaticMethodTypeSpecifyingExtensions() - ); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $currentWorkingDirectory = $fileHelper->normalizePath($currentWorkingDirectory, '/'); - $fileHelper = new FileHelper($currentWorkingDirectory); - $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), self::getContainer()->getByType(PhpDocStringResolver::class), self::getContainer()->getByType(PhpDocNodeResolver::class), $this->createMock(Cache::class), $anonymousClassNameHelper); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); - $nodeScopeResolver = new NodeScopeResolver( - $broker, - self::getReflectors()[0], - $this->getClassReflectionExtensionRegistryProvider(), - $this->getParser(), - $fileTypeMapper, - self::getContainer()->getByType(PhpVersion::class), - $phpDocInheritanceResolver, - $fileHelper, - $typeSpecifier, - self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), - $this->shouldPolluteScopeWithLoopInitialAssignments(), - $this->shouldPolluteCatchScopeWithTryAssignments(), - $this->shouldPolluteScopeWithAlwaysIterableForeach(), - [], - [], - true, - true - ); - $fileAnalyser = new FileAnalyser( - $this->createScopeFactory($broker, $typeSpecifier), - $nodeScopeResolver, - $this->getParser(), - new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), - true - ); - $this->analyser = new Analyser( - $fileAnalyser, - $registry, - $nodeScopeResolver, - 50 - ); - } - - return $this->analyser; - } - - /** - * @return \PHPStan\Type\MethodTypeSpecifyingExtension[] - */ - protected function getMethodTypeSpecifyingExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] - */ - protected function getStaticMethodTypeSpecifyingExtensions(): array - { - return []; - } - - /** - * @param string[] $files - * @param mixed[] $expectedErrors - */ - public function analyse(array $files, array $expectedErrors): void - { - $files = array_map([$this->getFileHelper(), 'normalizePath'], $files); - $analyserResult = $this->getAnalyser()->analyse($files); - if (count($analyserResult->getInternalErrors()) > 0) { - $this->fail(implode("\n", $analyserResult->getInternalErrors())); - } - $actualErrors = $analyserResult->getUnorderedErrors(); - - $strictlyTypedSprintf = static function (int $line, string $message, ?string $tip): string { - $message = sprintf('%02d: %s', $line, $message); - if ($tip !== null) { - $message .= "\n 💡 " . $tip; - } - - return $message; - }; - - $expectedErrors = array_map( - static function (array $error) use ($strictlyTypedSprintf): string { - if (!isset($error[0])) { - throw new \InvalidArgumentException('Missing expected error message.'); - } - if (!isset($error[1])) { - throw new \InvalidArgumentException('Missing expected file line.'); - } - return $strictlyTypedSprintf($error[1], $error[0], $error[2] ?? null); - }, - $expectedErrors - ); - - $actualErrors = array_map( - static function (Error $error) use ($strictlyTypedSprintf): string { - $line = $error->getLine(); - if ($line === null) { - return $strictlyTypedSprintf(-1, $error->getMessage(), $error->getTip()); - } - return $strictlyTypedSprintf($line, $error->getMessage(), $error->getTip()); - }, - $actualErrors - ); - - $this->assertSame(implode("\n", $expectedErrors) . "\n", implode("\n", $actualErrors) . "\n"); - } - - protected function shouldPolluteScopeWithLoopInitialAssignments(): bool - { - return false; - } - - protected function shouldPolluteCatchScopeWithTryAssignments(): bool - { - return false; - } - - protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool - { - return true; - } - + private ?\PHPStan\Analyser\Analyser $analyser = null; + + /** + * @return \PHPStan\Rules\Rule + * @phpstan-return TRule + */ + abstract protected function getRule(): Rule; + + protected function getTypeSpecifier(): TypeSpecifier + { + return $this->createTypeSpecifier( + new \PhpParser\PrettyPrinter\Standard(), + $this->createReflectionProvider(), + $this->getMethodTypeSpecifyingExtensions(), + $this->getStaticMethodTypeSpecifyingExtensions() + ); + } + + private function getAnalyser(): Analyser + { + if ($this->analyser === null) { + $registry = new Registry([ + $this->getRule(), + ]); + + $broker = $this->createBroker(); + $printer = new \PhpParser\PrettyPrinter\Standard(); + $typeSpecifier = $this->createTypeSpecifier( + $printer, + $broker, + $this->getMethodTypeSpecifyingExtensions(), + $this->getStaticMethodTypeSpecifyingExtensions() + ); + $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); + $fileHelper = new FileHelper($currentWorkingDirectory); + $currentWorkingDirectory = $fileHelper->normalizePath($currentWorkingDirectory, '/'); + $fileHelper = new FileHelper($currentWorkingDirectory); + $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); + $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); + $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), self::getContainer()->getByType(PhpDocStringResolver::class), self::getContainer()->getByType(PhpDocNodeResolver::class), $this->createMock(Cache::class), $anonymousClassNameHelper); + $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + $nodeScopeResolver = new NodeScopeResolver( + $broker, + self::getReflectors()[0], + $this->getClassReflectionExtensionRegistryProvider(), + $this->getParser(), + $fileTypeMapper, + self::getContainer()->getByType(PhpVersion::class), + $phpDocInheritanceResolver, + $fileHelper, + $typeSpecifier, + self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), + $this->shouldPolluteScopeWithLoopInitialAssignments(), + $this->shouldPolluteCatchScopeWithTryAssignments(), + $this->shouldPolluteScopeWithAlwaysIterableForeach(), + [], + [], + true, + true + ); + $fileAnalyser = new FileAnalyser( + $this->createScopeFactory($broker, $typeSpecifier), + $nodeScopeResolver, + $this->getParser(), + new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), + true + ); + $this->analyser = new Analyser( + $fileAnalyser, + $registry, + $nodeScopeResolver, + 50 + ); + } + + return $this->analyser; + } + + /** + * @return \PHPStan\Type\MethodTypeSpecifyingExtension[] + */ + protected function getMethodTypeSpecifyingExtensions(): array + { + return []; + } + + /** + * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] + */ + protected function getStaticMethodTypeSpecifyingExtensions(): array + { + return []; + } + + /** + * @param string[] $files + * @param mixed[] $expectedErrors + */ + public function analyse(array $files, array $expectedErrors): void + { + $files = array_map([$this->getFileHelper(), 'normalizePath'], $files); + $analyserResult = $this->getAnalyser()->analyse($files); + if (count($analyserResult->getInternalErrors()) > 0) { + $this->fail(implode("\n", $analyserResult->getInternalErrors())); + } + $actualErrors = $analyserResult->getUnorderedErrors(); + + $strictlyTypedSprintf = static function (int $line, string $message, ?string $tip): string { + $message = sprintf('%02d: %s', $line, $message); + if ($tip !== null) { + $message .= "\n 💡 " . $tip; + } + + return $message; + }; + + $expectedErrors = array_map( + static function (array $error) use ($strictlyTypedSprintf): string { + if (!isset($error[0])) { + throw new \InvalidArgumentException('Missing expected error message.'); + } + if (!isset($error[1])) { + throw new \InvalidArgumentException('Missing expected file line.'); + } + return $strictlyTypedSprintf($error[1], $error[0], $error[2] ?? null); + }, + $expectedErrors + ); + + $actualErrors = array_map( + static function (Error $error) use ($strictlyTypedSprintf): string { + $line = $error->getLine(); + if ($line === null) { + return $strictlyTypedSprintf(-1, $error->getMessage(), $error->getTip()); + } + return $strictlyTypedSprintf($line, $error->getMessage(), $error->getTip()); + }, + $actualErrors + ); + + $this->assertSame(implode("\n", $expectedErrors) . "\n", implode("\n", $actualErrors) . "\n"); + } + + protected function shouldPolluteScopeWithLoopInitialAssignments(): bool + { + return false; + } + + protected function shouldPolluteCatchScopeWithTryAssignments(): bool + { + return false; + } + + protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool + { + return true; + } } diff --git a/src/Testing/TestCase.php b/src/Testing/TestCase.php index b91ed8ac34..9ba32fc3f1 100644 --- a/src/Testing/TestCase.php +++ b/src/Testing/TestCase.php @@ -1,4 +1,6 @@ - */ - private static array $containers = []; - - private ?DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider = null; - - /** @var array{ClassReflector, FunctionReflector, ConstantReflector}|null */ - private static $reflectors; - - /** @var PhpStormStubsSourceStubber|null */ - private static $phpStormStubsSourceStubber; - - public static function getContainer(): Container - { - $additionalConfigFiles = static::getAdditionalConfigFiles(); - $cacheKey = sha1(implode("\n", $additionalConfigFiles)); - - if (!isset(self::$containers[$cacheKey])) { - $tmpDir = sys_get_temp_dir() . '/phpstan-tests'; - if (!@mkdir($tmpDir, 0777) && !is_dir($tmpDir)) { - self::fail(sprintf('Cannot create temp directory %s', $tmpDir)); - } - - if (self::$useStaticReflectionProvider) { - $additionalConfigFiles[] = __DIR__ . '/TestCase-staticReflection.neon'; - } - - $rootDir = __DIR__ . '/../..'; - $containerFactory = new ContainerFactory($rootDir); - $container = $containerFactory->create($tmpDir, array_merge([ - $containerFactory->getConfigDirectory() . '/config.level8.neon', - ], $additionalConfigFiles), []); - self::$containers[$cacheKey] = $container; - - foreach ($container->getParameter('bootstrapFiles') as $bootstrapFile) { - (static function (string $file) use ($container): void { - require_once $file; - })($bootstrapFile); - } - } - - return self::$containers[$cacheKey]; - } - - /** - * @return string[] - */ - public static function getAdditionalConfigFiles(): array - { - return []; - } - - public function getParser(): \PHPStan\Parser\Parser - { - /** @var \PHPStan\Parser\Parser $parser */ - $parser = self::getContainer()->getByType(CachedParser::class); - return $parser; - } - - /** - * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions - * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions - * @return \PHPStan\Broker\Broker - */ - public function createBroker( - array $dynamicMethodReturnTypeExtensions = [], - array $dynamicStaticMethodReturnTypeExtensions = [] - ): Broker - { - $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider( - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicMethodReturnTypeExtensions, $this->getDynamicMethodReturnTypeExtensions()), - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicStaticMethodReturnTypeExtensions, $this->getDynamicStaticMethodReturnTypeExtensions()), - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicFunctionReturnTypeExtensions()) - ); - $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider( - $this->getOperatorTypeSpecifyingExtensions() - ); - $reflectionProvider = $this->createReflectionProvider(); - $broker = new Broker( - $reflectionProvider, - $dynamicReturnTypeExtensionRegistryProvider, - $operatorTypeSpecifyingExtensionRegistryProvider, - self::getContainer()->getParameter('universalObjectCratesClasses') - ); - $dynamicReturnTypeExtensionRegistryProvider->setBroker($broker); - $dynamicReturnTypeExtensionRegistryProvider->setReflectionProvider($reflectionProvider); - $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($broker); - $this->getClassReflectionExtensionRegistryProvider()->setBroker($broker); - - return $broker; - } - - public function createReflectionProvider(): ReflectionProvider - { - $staticReflectionProvider = $this->createStaticReflectionProvider(); - return $this->createReflectionProviderByParameters( - $this->createRuntimeReflectionProvider($staticReflectionProvider), - $staticReflectionProvider, - self::$useStaticReflectionProvider - ); - } - - private function createReflectionProviderByParameters( - ReflectionProvider $runtimeReflectionProvider, - ReflectionProvider $staticReflectionProvider, - bool $disableRuntimeReflectionProvider - ): ReflectionProvider - { - $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); - $reflectionProviderFactory = new ReflectionProviderFactory( - $runtimeReflectionProvider, - $staticReflectionProvider, - $disableRuntimeReflectionProvider - ); - $reflectionProvider = $reflectionProviderFactory->create(); - $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); - - return $reflectionProvider; - } - - private static function getPhpStormStubsSourceStubber(): PhpStormStubsSourceStubber - { - if (self::$phpStormStubsSourceStubber === null) { - self::$phpStormStubsSourceStubber = self::getContainer()->getByType(PhpStormStubsSourceStubber::class); - } - - return self::$phpStormStubsSourceStubber; - } - - private function createRuntimeReflectionProvider(ReflectionProvider $actualReflectionProvider): ReflectionProvider - { - $functionCallStatementFinder = new FunctionCallStatementFinder(); - $parser = $this->getParser(); - $cache = new Cache(new MemoryCacheStorage()); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper(new FileHelper($currentWorkingDirectory), new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); - $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); - $fileTypeMapper = new FileTypeMapper($setterReflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); - $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); - $functionReflectionFactory = $this->getFunctionReflectionFactory( - $functionCallStatementFinder, - $cache - ); - $reflectionProvider = new ClassBlacklistReflectionProvider( - new RuntimeReflectionProvider( - $setterReflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionReflectionFactory, - $fileTypeMapper, - self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(NativeFunctionReflectionProvider::class), - self::getContainer()->getByType(StubPhpDocProvider::class), - self::getContainer()->getByType(PhpStormStubsSourceStubber::class) - ), - self::getPhpStormStubsSourceStubber(), - [ - '#^PhpParser\\\\#', - '#^PHPStan\\\\#', - '#^Hoa\\\\#', - ], - null - ); - $this->setUpReflectionProvider( - $actualReflectionProvider, - $setterReflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionCallStatementFinder, - $parser, - $cache, - $fileTypeMapper - ); - - return $reflectionProvider; - } - - private function setUpReflectionProvider( - ReflectionProvider $actualReflectionProvider, - ReflectionProvider\SetterReflectionProviderProvider $setterReflectionProviderProvider, - DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, - FunctionCallStatementFinder $functionCallStatementFinder, - \PHPStan\Parser\Parser $parser, - Cache $cache, - FileTypeMapper $fileTypeMapper - ): void - { - $methodReflectionFactory = new class($parser, $functionCallStatementFinder, $cache) implements PhpMethodReflectionFactory { - - private \PHPStan\Parser\Parser $parser; - - private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; - - private \PHPStan\Cache\Cache $cache; - - public ReflectionProvider $reflectionProvider; - - public function __construct( - Parser $parser, - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache - ) - { - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - } - - /** - * @param ClassReflection $declaringClass - * @param ClassReflection|null $declaringTrait - * @param \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $phpDocThrowType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param string|null $stubPhpDocString - * @param bool|null $isPure - * @return PhpMethodReflection - */ - public function create( - ClassReflection $declaringClass, - ?ClassReflection $declaringTrait, - \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $phpDocThrowType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - ?string $stubPhpDocString, - ?bool $isPure = null - ): PhpMethodReflection - { - return new PhpMethodReflection( - $declaringClass, - $declaringTrait, - $reflection, - $this->reflectionProvider, - $this->parser, - $this->functionCallStatementFinder, - $this->cache, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $stubPhpDocString, - $isPure - ); - } - - }; - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); - $annotationsMethodsClassReflectionExtension = new AnnotationsMethodsClassReflectionExtension(); - $annotationsPropertiesClassReflectionExtension = new AnnotationsPropertiesClassReflectionExtension(); - $signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class); - $methodReflectionFactory->reflectionProvider = $actualReflectionProvider; - $phpExtension = new PhpClassReflectionExtension(self::getContainer()->getByType(ScopeFactory::class), self::getContainer()->getByType(NodeScopeResolver::class), $methodReflectionFactory, $phpDocInheritanceResolver, $annotationsMethodsClassReflectionExtension, $annotationsPropertiesClassReflectionExtension, $signatureMapProvider, $parser, self::getContainer()->getByType(StubPhpDocProvider::class), $actualReflectionProvider, $fileTypeMapper, true, []); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($phpExtension); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new UniversalObjectCratesClassReflectionExtension([\stdClass::class])); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new MixinPropertiesClassReflectionExtension([])); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new SimpleXMLElementClassPropertyReflectionExtension()); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($annotationsPropertiesClassReflectionExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($phpExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new MixinMethodsClassReflectionExtension([])); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($annotationsMethodsClassReflectionExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new SoapClientMethodsClassReflectionExtension()); - - $setterReflectionProviderProvider->setReflectionProvider($actualReflectionProvider); - } - - private function createStaticReflectionProvider(): ReflectionProvider - { - $parser = $this->getParser(); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $cache = new Cache(new MemoryCacheStorage()); - $fileHelper = new FileHelper($currentWorkingDirectory); - $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); - $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); - $fileTypeMapper = new FileTypeMapper($setterReflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); - $functionCallStatementFinder = new FunctionCallStatementFinder(); - $functionReflectionFactory = $this->getFunctionReflectionFactory( - $functionCallStatementFinder, - $cache - ); - - [$classReflector, $functionReflector, $constantReflector] = self::getReflectors(); - - $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); - - $reflectionProvider = new BetterReflectionProvider( - $setterReflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $classReflector, - $fileTypeMapper, - self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(NativeFunctionReflectionProvider::class), - self::getContainer()->getByType(StubPhpDocProvider::class), - $functionReflectionFactory, - $relativePathHelper, - $anonymousClassNameHelper, - self::getContainer()->getByType(Standard::class), - $fileHelper, - $functionReflector, - $constantReflector, - self::getPhpStormStubsSourceStubber() - ); - - $this->setUpReflectionProvider( - $reflectionProvider, - $setterReflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionCallStatementFinder, - $parser, - $cache, - $fileTypeMapper - ); - - return $reflectionProvider; - } - - /** - * @return array{ClassReflector, FunctionReflector, ConstantReflector} - */ - public static function getReflectors(): array - { - if (self::$reflectors !== null) { - return self::$reflectors; - } - - if (!class_exists(ClassLoader::class)) { - self::fail('Composer ClassLoader is unknown'); - } - - $classLoaderReflection = new \ReflectionClass(ClassLoader::class); - if ($classLoaderReflection->getFileName() === false) { - self::fail('Unknown ClassLoader filename'); - } - - $composerProjectPath = dirname($classLoaderReflection->getFileName(), 3); - if (!is_file($composerProjectPath . '/composer.json')) { - self::fail(sprintf('composer.json not found in directory %s', $composerProjectPath)); - } - - $composerJsonAndInstalledJsonSourceLocatorMaker = self::getContainer()->getByType(ComposerJsonAndInstalledJsonSourceLocatorMaker::class); - $composerSourceLocator = $composerJsonAndInstalledJsonSourceLocatorMaker->create($composerProjectPath); - if ($composerSourceLocator === null) { - self::fail('Could not create composer source locator'); - } - - // these need to be synced with TestCase-staticReflection.neon file and TestCaseSourceLocatorFactory - - $locators = [ - $composerSourceLocator, - ]; - - $phpParser = new PhpParserDecorator(self::getContainer()->getByType(CachedParser::class)); - - /** @var FunctionReflector $functionReflector */ - $functionReflector = null; - $astLocator = new Locator($phpParser, static function () use (&$functionReflector): FunctionReflector { - return $functionReflector; - }); - $reflectionSourceStubber = new ReflectionSourceStubber(); - $locators[] = new PhpInternalSourceLocator($astLocator, self::getPhpStormStubsSourceStubber()); - $locators[] = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class)); - $locators[] = new PhpInternalSourceLocator($astLocator, $reflectionSourceStubber); - $locators[] = new EvaledCodeSourceLocator($astLocator, $reflectionSourceStubber); - $sourceLocator = new MemoizingSourceLocator(new AggregateSourceLocator($locators)); - - $classReflector = new MemoizingClassReflector($sourceLocator); - $functionReflector = new MemoizingFunctionReflector($sourceLocator, $classReflector); - $constantReflector = new MemoizingConstantReflector($sourceLocator, $classReflector); - - self::$reflectors = [$classReflector, $functionReflector, $constantReflector]; - - return self::$reflectors; - } - - private function getFunctionReflectionFactory( - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache - ): FunctionReflectionFactory - { - return new class($this->getParser(), $functionCallStatementFinder, $cache) implements FunctionReflectionFactory { - - private \PHPStan\Parser\Parser $parser; - - private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; - - private \PHPStan\Cache\Cache $cache; - - public function __construct( - Parser $parser, - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache - ) - { - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - } - - /** - * @param \ReflectionFunction $function - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $phpDocThrowType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param string|false $filename - * @param bool|null $isPure - * @return PhpFunctionReflection - */ - public function create( - \ReflectionFunction $function, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $phpDocThrowType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - $filename, - ?bool $isPure = null - ): PhpFunctionReflection - { - return new PhpFunctionReflection( - $function, - $this->parser, - $this->functionCallStatementFinder, - $this->cache, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $filename, - $isPure - ); - } - - }; - } - - public function getClassReflectionExtensionRegistryProvider(): DirectClassReflectionExtensionRegistryProvider - { - if ($this->classReflectionExtensionRegistryProvider === null) { - $this->classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); - } - - return $this->classReflectionExtensionRegistryProvider; - } - - public function createScopeFactory(Broker $broker, TypeSpecifier $typeSpecifier): ScopeFactory - { - $container = self::getContainer(); - - return new DirectScopeFactory( - MutatingScope::class, - $broker, - $broker->getDynamicReturnTypeExtensionRegistryProvider(), - $broker->getOperatorTypeSpecifyingExtensionRegistryProvider(), - new \PhpParser\PrettyPrinter\Standard(), - $typeSpecifier, - new PropertyReflectionFinder(), - $this->getParser(), - self::getContainer()->getByType(NodeScopeResolver::class), - $this->shouldTreatPhpDocTypesAsCertain(), - false, - $container - ); - } - - /** - * @param array $globalTypeAliases - */ - public function createTypeAliasResolver(array $globalTypeAliases, ReflectionProvider $reflectionProvider): TypeAliasResolver - { - $container = self::getContainer(); - - return new TypeAliasResolver( - $globalTypeAliases, - $container->getByType(TypeStringResolver::class), - $container->getByType(TypeNodeResolver::class), - $reflectionProvider - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return true; - } - - public function getCurrentWorkingDirectory(): string - { - return $this->getFileHelper()->normalizePath(__DIR__ . '/../..'); - } - - /** - * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] - */ - public function getDynamicMethodReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] - */ - public function getDynamicStaticMethodReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\OperatorTypeSpecifyingExtension[] - */ - public function getOperatorTypeSpecifyingExtensions(): array - { - return []; - } - - /** - * @param \PhpParser\PrettyPrinter\Standard $printer - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions - * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions - * @return \PHPStan\Analyser\TypeSpecifier - */ - public function createTypeSpecifier( - Standard $printer, - ReflectionProvider $reflectionProvider, - array $methodTypeSpecifyingExtensions = [], - array $staticMethodTypeSpecifyingExtensions = [] - ): TypeSpecifier - { - return new TypeSpecifier( - $printer, - $reflectionProvider, - true, - self::getContainer()->getServicesByTag(TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), - array_merge($methodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG)), - array_merge($staticMethodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG)) - ); - } - - public function getFileHelper(): FileHelper - { - return self::getContainer()->getByType(FileHelper::class); - } - - /** - * Provides a DIRECTORY_SEPARATOR agnostic assertion helper, to compare file paths. - * - * @param string $expected - * @param string $actual - * @param string $message - */ - protected function assertSamePaths(string $expected, string $actual, string $message = ''): void - { - $expected = $this->getFileHelper()->normalizePath($expected); - $actual = $this->getFileHelper()->normalizePath($actual); - - $this->assertSame($expected, $actual, $message); - } - - protected function skipIfNotOnWindows(): void - { - if (DIRECTORY_SEPARATOR === '\\') { - return; - } - - self::markTestSkipped(); - } - - protected function skipIfNotOnUnix(): void - { - if (DIRECTORY_SEPARATOR === '/') { - return; - } - - self::markTestSkipped(); - } - + /** @var bool */ + public static $useStaticReflectionProvider = false; + + /** @var array */ + private static array $containers = []; + + private ?DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider = null; + + /** @var array{ClassReflector, FunctionReflector, ConstantReflector}|null */ + private static $reflectors; + + /** @var PhpStormStubsSourceStubber|null */ + private static $phpStormStubsSourceStubber; + + public static function getContainer(): Container + { + $additionalConfigFiles = static::getAdditionalConfigFiles(); + $cacheKey = sha1(implode("\n", $additionalConfigFiles)); + + if (!isset(self::$containers[$cacheKey])) { + $tmpDir = sys_get_temp_dir() . '/phpstan-tests'; + if (!@mkdir($tmpDir, 0777) && !is_dir($tmpDir)) { + self::fail(sprintf('Cannot create temp directory %s', $tmpDir)); + } + + if (self::$useStaticReflectionProvider) { + $additionalConfigFiles[] = __DIR__ . '/TestCase-staticReflection.neon'; + } + + $rootDir = __DIR__ . '/../..'; + $containerFactory = new ContainerFactory($rootDir); + $container = $containerFactory->create($tmpDir, array_merge([ + $containerFactory->getConfigDirectory() . '/config.level8.neon', + ], $additionalConfigFiles), []); + self::$containers[$cacheKey] = $container; + + foreach ($container->getParameter('bootstrapFiles') as $bootstrapFile) { + (static function (string $file) use ($container): void { + require_once $file; + })($bootstrapFile); + } + } + + return self::$containers[$cacheKey]; + } + + /** + * @return string[] + */ + public static function getAdditionalConfigFiles(): array + { + return []; + } + + public function getParser(): \PHPStan\Parser\Parser + { + /** @var \PHPStan\Parser\Parser $parser */ + $parser = self::getContainer()->getByType(CachedParser::class); + return $parser; + } + + /** + * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + * @return \PHPStan\Broker\Broker + */ + public function createBroker( + array $dynamicMethodReturnTypeExtensions = [], + array $dynamicStaticMethodReturnTypeExtensions = [] + ): Broker { + $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider( + array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicMethodReturnTypeExtensions, $this->getDynamicMethodReturnTypeExtensions()), + array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicStaticMethodReturnTypeExtensions, $this->getDynamicStaticMethodReturnTypeExtensions()), + array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicFunctionReturnTypeExtensions()) + ); + $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider( + $this->getOperatorTypeSpecifyingExtensions() + ); + $reflectionProvider = $this->createReflectionProvider(); + $broker = new Broker( + $reflectionProvider, + $dynamicReturnTypeExtensionRegistryProvider, + $operatorTypeSpecifyingExtensionRegistryProvider, + self::getContainer()->getParameter('universalObjectCratesClasses') + ); + $dynamicReturnTypeExtensionRegistryProvider->setBroker($broker); + $dynamicReturnTypeExtensionRegistryProvider->setReflectionProvider($reflectionProvider); + $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($broker); + $this->getClassReflectionExtensionRegistryProvider()->setBroker($broker); + + return $broker; + } + + public function createReflectionProvider(): ReflectionProvider + { + $staticReflectionProvider = $this->createStaticReflectionProvider(); + return $this->createReflectionProviderByParameters( + $this->createRuntimeReflectionProvider($staticReflectionProvider), + $staticReflectionProvider, + self::$useStaticReflectionProvider + ); + } + + private function createReflectionProviderByParameters( + ReflectionProvider $runtimeReflectionProvider, + ReflectionProvider $staticReflectionProvider, + bool $disableRuntimeReflectionProvider + ): ReflectionProvider { + $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); + $reflectionProviderFactory = new ReflectionProviderFactory( + $runtimeReflectionProvider, + $staticReflectionProvider, + $disableRuntimeReflectionProvider + ); + $reflectionProvider = $reflectionProviderFactory->create(); + $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); + + return $reflectionProvider; + } + + private static function getPhpStormStubsSourceStubber(): PhpStormStubsSourceStubber + { + if (self::$phpStormStubsSourceStubber === null) { + self::$phpStormStubsSourceStubber = self::getContainer()->getByType(PhpStormStubsSourceStubber::class); + } + + return self::$phpStormStubsSourceStubber; + } + + private function createRuntimeReflectionProvider(ReflectionProvider $actualReflectionProvider): ReflectionProvider + { + $functionCallStatementFinder = new FunctionCallStatementFinder(); + $parser = $this->getParser(); + $cache = new Cache(new MemoryCacheStorage()); + $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); + $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); + $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); + $fileHelper = new FileHelper($currentWorkingDirectory); + $anonymousClassNameHelper = new AnonymousClassNameHelper(new FileHelper($currentWorkingDirectory), new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); + $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); + $fileTypeMapper = new FileTypeMapper($setterReflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); + $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); + $functionReflectionFactory = $this->getFunctionReflectionFactory( + $functionCallStatementFinder, + $cache + ); + $reflectionProvider = new ClassBlacklistReflectionProvider( + new RuntimeReflectionProvider( + $setterReflectionProviderProvider, + $classReflectionExtensionRegistryProvider, + $functionReflectionFactory, + $fileTypeMapper, + self::getContainer()->getByType(PhpVersion::class), + self::getContainer()->getByType(NativeFunctionReflectionProvider::class), + self::getContainer()->getByType(StubPhpDocProvider::class), + self::getContainer()->getByType(PhpStormStubsSourceStubber::class) + ), + self::getPhpStormStubsSourceStubber(), + [ + '#^PhpParser\\\\#', + '#^PHPStan\\\\#', + '#^Hoa\\\\#', + ], + null + ); + $this->setUpReflectionProvider( + $actualReflectionProvider, + $setterReflectionProviderProvider, + $classReflectionExtensionRegistryProvider, + $functionCallStatementFinder, + $parser, + $cache, + $fileTypeMapper + ); + + return $reflectionProvider; + } + + private function setUpReflectionProvider( + ReflectionProvider $actualReflectionProvider, + ReflectionProvider\SetterReflectionProviderProvider $setterReflectionProviderProvider, + DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, + FunctionCallStatementFinder $functionCallStatementFinder, + \PHPStan\Parser\Parser $parser, + Cache $cache, + FileTypeMapper $fileTypeMapper + ): void { + $methodReflectionFactory = new class($parser, $functionCallStatementFinder, $cache) implements PhpMethodReflectionFactory { + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; + + private \PHPStan\Cache\Cache $cache; + + public ReflectionProvider $reflectionProvider; + + public function __construct( + Parser $parser, + FunctionCallStatementFinder $functionCallStatementFinder, + Cache $cache + ) { + $this->parser = $parser; + $this->functionCallStatementFinder = $functionCallStatementFinder; + $this->cache = $cache; + } + + /** + * @param ClassReflection $declaringClass + * @param ClassReflection|null $declaringTrait + * @param \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection + * @param TemplateTypeMap $templateTypeMap + * @param Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $phpDocThrowType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param string|null $stubPhpDocString + * @param bool|null $isPure + * @return PhpMethodReflection + */ + public function create( + ClassReflection $declaringClass, + ?ClassReflection $declaringTrait, + \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $phpDocThrowType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?string $stubPhpDocString, + ?bool $isPure = null + ): PhpMethodReflection { + return new PhpMethodReflection( + $declaringClass, + $declaringTrait, + $reflection, + $this->reflectionProvider, + $this->parser, + $this->functionCallStatementFinder, + $this->cache, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $stubPhpDocString, + $isPure + ); + } + }; + $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + $annotationsMethodsClassReflectionExtension = new AnnotationsMethodsClassReflectionExtension(); + $annotationsPropertiesClassReflectionExtension = new AnnotationsPropertiesClassReflectionExtension(); + $signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class); + $methodReflectionFactory->reflectionProvider = $actualReflectionProvider; + $phpExtension = new PhpClassReflectionExtension(self::getContainer()->getByType(ScopeFactory::class), self::getContainer()->getByType(NodeScopeResolver::class), $methodReflectionFactory, $phpDocInheritanceResolver, $annotationsMethodsClassReflectionExtension, $annotationsPropertiesClassReflectionExtension, $signatureMapProvider, $parser, self::getContainer()->getByType(StubPhpDocProvider::class), $actualReflectionProvider, $fileTypeMapper, true, []); + $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($phpExtension); + $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new UniversalObjectCratesClassReflectionExtension([\stdClass::class])); + $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new MixinPropertiesClassReflectionExtension([])); + $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new SimpleXMLElementClassPropertyReflectionExtension()); + $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($annotationsPropertiesClassReflectionExtension); + $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($phpExtension); + $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new MixinMethodsClassReflectionExtension([])); + $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($annotationsMethodsClassReflectionExtension); + $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new SoapClientMethodsClassReflectionExtension()); + + $setterReflectionProviderProvider->setReflectionProvider($actualReflectionProvider); + } + + private function createStaticReflectionProvider(): ReflectionProvider + { + $parser = $this->getParser(); + $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); + $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); + $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); + $cache = new Cache(new MemoryCacheStorage()); + $fileHelper = new FileHelper($currentWorkingDirectory); + $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); + $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); + $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); + $fileTypeMapper = new FileTypeMapper($setterReflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); + $functionCallStatementFinder = new FunctionCallStatementFinder(); + $functionReflectionFactory = $this->getFunctionReflectionFactory( + $functionCallStatementFinder, + $cache + ); + + [$classReflector, $functionReflector, $constantReflector] = self::getReflectors(); + + $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); + + $reflectionProvider = new BetterReflectionProvider( + $setterReflectionProviderProvider, + $classReflectionExtensionRegistryProvider, + $classReflector, + $fileTypeMapper, + self::getContainer()->getByType(PhpVersion::class), + self::getContainer()->getByType(NativeFunctionReflectionProvider::class), + self::getContainer()->getByType(StubPhpDocProvider::class), + $functionReflectionFactory, + $relativePathHelper, + $anonymousClassNameHelper, + self::getContainer()->getByType(Standard::class), + $fileHelper, + $functionReflector, + $constantReflector, + self::getPhpStormStubsSourceStubber() + ); + + $this->setUpReflectionProvider( + $reflectionProvider, + $setterReflectionProviderProvider, + $classReflectionExtensionRegistryProvider, + $functionCallStatementFinder, + $parser, + $cache, + $fileTypeMapper + ); + + return $reflectionProvider; + } + + /** + * @return array{ClassReflector, FunctionReflector, ConstantReflector} + */ + public static function getReflectors(): array + { + if (self::$reflectors !== null) { + return self::$reflectors; + } + + if (!class_exists(ClassLoader::class)) { + self::fail('Composer ClassLoader is unknown'); + } + + $classLoaderReflection = new \ReflectionClass(ClassLoader::class); + if ($classLoaderReflection->getFileName() === false) { + self::fail('Unknown ClassLoader filename'); + } + + $composerProjectPath = dirname($classLoaderReflection->getFileName(), 3); + if (!is_file($composerProjectPath . '/composer.json')) { + self::fail(sprintf('composer.json not found in directory %s', $composerProjectPath)); + } + + $composerJsonAndInstalledJsonSourceLocatorMaker = self::getContainer()->getByType(ComposerJsonAndInstalledJsonSourceLocatorMaker::class); + $composerSourceLocator = $composerJsonAndInstalledJsonSourceLocatorMaker->create($composerProjectPath); + if ($composerSourceLocator === null) { + self::fail('Could not create composer source locator'); + } + + // these need to be synced with TestCase-staticReflection.neon file and TestCaseSourceLocatorFactory + + $locators = [ + $composerSourceLocator, + ]; + + $phpParser = new PhpParserDecorator(self::getContainer()->getByType(CachedParser::class)); + + /** @var FunctionReflector $functionReflector */ + $functionReflector = null; + $astLocator = new Locator($phpParser, static function () use (&$functionReflector): FunctionReflector { + return $functionReflector; + }); + $reflectionSourceStubber = new ReflectionSourceStubber(); + $locators[] = new PhpInternalSourceLocator($astLocator, self::getPhpStormStubsSourceStubber()); + $locators[] = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class)); + $locators[] = new PhpInternalSourceLocator($astLocator, $reflectionSourceStubber); + $locators[] = new EvaledCodeSourceLocator($astLocator, $reflectionSourceStubber); + $sourceLocator = new MemoizingSourceLocator(new AggregateSourceLocator($locators)); + + $classReflector = new MemoizingClassReflector($sourceLocator); + $functionReflector = new MemoizingFunctionReflector($sourceLocator, $classReflector); + $constantReflector = new MemoizingConstantReflector($sourceLocator, $classReflector); + + self::$reflectors = [$classReflector, $functionReflector, $constantReflector]; + + return self::$reflectors; + } + + private function getFunctionReflectionFactory( + FunctionCallStatementFinder $functionCallStatementFinder, + Cache $cache + ): FunctionReflectionFactory { + return new class($this->getParser(), $functionCallStatementFinder, $cache) implements FunctionReflectionFactory { + private \PHPStan\Parser\Parser $parser; + + private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; + + private \PHPStan\Cache\Cache $cache; + + public function __construct( + Parser $parser, + FunctionCallStatementFinder $functionCallStatementFinder, + Cache $cache + ) { + $this->parser = $parser; + $this->functionCallStatementFinder = $functionCallStatementFinder; + $this->cache = $cache; + } + + /** + * @param \ReflectionFunction $function + * @param TemplateTypeMap $templateTypeMap + * @param Type[] $phpDocParameterTypes + * @param Type|null $phpDocReturnType + * @param Type|null $phpDocThrowType + * @param string|null $deprecatedDescription + * @param bool $isDeprecated + * @param bool $isInternal + * @param bool $isFinal + * @param string|false $filename + * @param bool|null $isPure + * @return PhpFunctionReflection + */ + public function create( + \ReflectionFunction $function, + TemplateTypeMap $templateTypeMap, + array $phpDocParameterTypes, + ?Type $phpDocReturnType, + ?Type $phpDocThrowType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + $filename, + ?bool $isPure = null + ): PhpFunctionReflection { + return new PhpFunctionReflection( + $function, + $this->parser, + $this->functionCallStatementFinder, + $this->cache, + $templateTypeMap, + $phpDocParameterTypes, + $phpDocReturnType, + $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, + $isInternal, + $isFinal, + $filename, + $isPure + ); + } + }; + } + + public function getClassReflectionExtensionRegistryProvider(): DirectClassReflectionExtensionRegistryProvider + { + if ($this->classReflectionExtensionRegistryProvider === null) { + $this->classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); + } + + return $this->classReflectionExtensionRegistryProvider; + } + + public function createScopeFactory(Broker $broker, TypeSpecifier $typeSpecifier): ScopeFactory + { + $container = self::getContainer(); + + return new DirectScopeFactory( + MutatingScope::class, + $broker, + $broker->getDynamicReturnTypeExtensionRegistryProvider(), + $broker->getOperatorTypeSpecifyingExtensionRegistryProvider(), + new \PhpParser\PrettyPrinter\Standard(), + $typeSpecifier, + new PropertyReflectionFinder(), + $this->getParser(), + self::getContainer()->getByType(NodeScopeResolver::class), + $this->shouldTreatPhpDocTypesAsCertain(), + false, + $container + ); + } + + /** + * @param array $globalTypeAliases + */ + public function createTypeAliasResolver(array $globalTypeAliases, ReflectionProvider $reflectionProvider): TypeAliasResolver + { + $container = self::getContainer(); + + return new TypeAliasResolver( + $globalTypeAliases, + $container->getByType(TypeStringResolver::class), + $container->getByType(TypeNodeResolver::class), + $reflectionProvider + ); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return true; + } + + public function getCurrentWorkingDirectory(): string + { + return $this->getFileHelper()->normalizePath(__DIR__ . '/../..'); + } + + /** + * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] + */ + public function getDynamicMethodReturnTypeExtensions(): array + { + return []; + } + + /** + * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] + */ + public function getDynamicStaticMethodReturnTypeExtensions(): array + { + return []; + } + + /** + * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] + */ + public function getDynamicFunctionReturnTypeExtensions(): array + { + return []; + } + + /** + * @return \PHPStan\Type\OperatorTypeSpecifyingExtension[] + */ + public function getOperatorTypeSpecifyingExtensions(): array + { + return []; + } + + /** + * @param \PhpParser\PrettyPrinter\Standard $printer + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions + * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions + * @return \PHPStan\Analyser\TypeSpecifier + */ + public function createTypeSpecifier( + Standard $printer, + ReflectionProvider $reflectionProvider, + array $methodTypeSpecifyingExtensions = [], + array $staticMethodTypeSpecifyingExtensions = [] + ): TypeSpecifier { + return new TypeSpecifier( + $printer, + $reflectionProvider, + true, + self::getContainer()->getServicesByTag(TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), + array_merge($methodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG)), + array_merge($staticMethodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG)) + ); + } + + public function getFileHelper(): FileHelper + { + return self::getContainer()->getByType(FileHelper::class); + } + + /** + * Provides a DIRECTORY_SEPARATOR agnostic assertion helper, to compare file paths. + * + * @param string $expected + * @param string $actual + * @param string $message + */ + protected function assertSamePaths(string $expected, string $actual, string $message = ''): void + { + $expected = $this->getFileHelper()->normalizePath($expected); + $actual = $this->getFileHelper()->normalizePath($actual); + + $this->assertSame($expected, $actual, $message); + } + + protected function skipIfNotOnWindows(): void + { + if (DIRECTORY_SEPARATOR === '\\') { + return; + } + + self::markTestSkipped(); + } + + protected function skipIfNotOnUnix(): void + { + if (DIRECTORY_SEPARATOR === '/') { + return; + } + + self::markTestSkipped(); + } } diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index ca90ea6114..7bc29ea356 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -1,4 +1,6 @@ -container = $container; - $this->composerJsonAndInstalledJsonSourceLocatorMaker = $composerJsonAndInstalledJsonSourceLocatorMaker; - $this->autoloadSourceLocator = $autoloadSourceLocator; - $this->phpParser = $phpParser; - $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; - $this->reflectionSourceStubber = $reflectionSourceStubber; - } - - public function create(): SourceLocator - { - $classLoaderReflection = new \ReflectionClass(ClassLoader::class); - if ($classLoaderReflection->getFileName() === false) { - throw new \PHPStan\ShouldNotHappenException('Unknown ClassLoader filename'); - } - - $composerProjectPath = dirname($classLoaderReflection->getFileName(), 3); - if (!is_file($composerProjectPath . '/composer.json')) { - throw new \PHPStan\ShouldNotHappenException(sprintf('composer.json not found in directory %s', $composerProjectPath)); - } - - $composerSourceLocator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerProjectPath); - if ($composerSourceLocator === null) { - throw new \PHPStan\ShouldNotHappenException('Could not create composer source locator'); - } - - $locators = [ - $composerSourceLocator, - ]; - $astLocator = new Locator($this->phpParser, function (): FunctionReflector { - return $this->container->getService('testCaseFunctionReflector'); - }); - - $locators[] = new PhpInternalSourceLocator($astLocator, $this->phpstormStubsSourceStubber); - $locators[] = $this->autoloadSourceLocator; - $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); - $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); - - return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); - } - + private Container $container; + + private ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker; + + private AutoloadSourceLocator $autoloadSourceLocator; + + private \PhpParser\Parser $phpParser; + + private PhpStormStubsSourceStubber $phpstormStubsSourceStubber; + + private ReflectionSourceStubber $reflectionSourceStubber; + + public function __construct( + Container $container, + ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker, + AutoloadSourceLocator $autoloadSourceLocator, + \PhpParser\Parser $phpParser, + PhpStormStubsSourceStubber $phpstormStubsSourceStubber, + ReflectionSourceStubber $reflectionSourceStubber + ) { + $this->container = $container; + $this->composerJsonAndInstalledJsonSourceLocatorMaker = $composerJsonAndInstalledJsonSourceLocatorMaker; + $this->autoloadSourceLocator = $autoloadSourceLocator; + $this->phpParser = $phpParser; + $this->phpstormStubsSourceStubber = $phpstormStubsSourceStubber; + $this->reflectionSourceStubber = $reflectionSourceStubber; + } + + public function create(): SourceLocator + { + $classLoaderReflection = new \ReflectionClass(ClassLoader::class); + if ($classLoaderReflection->getFileName() === false) { + throw new \PHPStan\ShouldNotHappenException('Unknown ClassLoader filename'); + } + + $composerProjectPath = dirname($classLoaderReflection->getFileName(), 3); + if (!is_file($composerProjectPath . '/composer.json')) { + throw new \PHPStan\ShouldNotHappenException(sprintf('composer.json not found in directory %s', $composerProjectPath)); + } + + $composerSourceLocator = $this->composerJsonAndInstalledJsonSourceLocatorMaker->create($composerProjectPath); + if ($composerSourceLocator === null) { + throw new \PHPStan\ShouldNotHappenException('Could not create composer source locator'); + } + + $locators = [ + $composerSourceLocator, + ]; + $astLocator = new Locator($this->phpParser, function (): FunctionReflector { + return $this->container->getService('testCaseFunctionReflector'); + }); + + $locators[] = new PhpInternalSourceLocator($astLocator, $this->phpstormStubsSourceStubber); + $locators[] = $this->autoloadSourceLocator; + $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + + return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); + } } diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 50205e2e7e..3675926d29 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -1,4 +1,6 @@ -getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - - $printer = new \PhpParser\PrettyPrinter\Standard(); - $broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions); - Broker::registerInstance($broker); - $typeSpecifier = $this->createTypeSpecifier($printer, $broker, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($currentWorkingDirectory))); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); - $resolver = new NodeScopeResolver( - $broker, - self::getReflectors()[0], - $this->getClassReflectionExtensionRegistryProvider(), - $this->getParser(), - $fileTypeMapper, - self::getContainer()->getByType(PhpVersion::class), - $phpDocInheritanceResolver, - $fileHelper, - $typeSpecifier, - self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), - true, - $this->polluteCatchScopeWithTryAssignments, - true, - $this->getEarlyTerminatingMethodCalls(), - $this->getEarlyTerminatingFunctionCalls(), - true, - true - ); - $resolver->setAnalysedFiles(array_map(static function (string $file) use ($fileHelper): string { - return $fileHelper->normalizePath($file); - }, array_merge([$file], $this->getAdditionalAnalysedFiles()))); - - $scopeFactory = $this->createScopeFactory($broker, $typeSpecifier); - if (count($dynamicConstantNames) > 0) { - $reflectionProperty = new \ReflectionProperty(DirectScopeFactory::class, 'dynamicConstantNames'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($scopeFactory, $dynamicConstantNames); - } - $scope = $scopeFactory->create(ScopeContext::create($file)); - - $resolver->processNodes( - $this->getParser()->parseFile($file), - $scope, - $callback - ); - } - - /** - * @param string $assertType - * @param string $file - * @param mixed ...$args - */ - public function assertFileAsserts( - string $assertType, - string $file, - ...$args - ): void - { - if ($assertType === 'type') { - $expectedType = $args[0]; - $expected = $expectedType->getValue(); - $actualType = $args[1]; - $actual = $actualType->describe(VerbosityLevel::precise()); - $this->assertSame( - $expected, - $actual, - sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]) - ); - } elseif ($assertType === 'variableCertainty') { - $expectedCertainty = $args[0]; - $actualCertainty = $args[1]; - $variableName = $args[2]; - $this->assertTrue( - $expectedCertainty->equals($actualCertainty), - sprintf('Expected %s, actual certainty of variable $%s is %s', $expectedCertainty->describe(), $variableName, $actualCertainty->describe()) - ); - } - } - - /** - * @param string $file - * @return array - */ - public function gatherAssertTypes(string $file): array - { - $asserts = []; - $this->processFile($file, function (Node $node, Scope $scope) use (&$asserts, $file): void { - if (!$node instanceof Node\Expr\FuncCall) { - return; - } - - $nameNode = $node->name; - if (!$nameNode instanceof Name) { - return; - } - - $functionName = $nameNode->toString(); - if ($functionName === 'PHPStan\\Testing\\assertType') { - $expectedType = $scope->getType($node->args[0]->value); - $actualType = $scope->getType($node->args[1]->value); - $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; - } elseif ($functionName === 'PHPStan\\Testing\\assertNativeType') { - $nativeScope = $scope->doNotTreatPhpDocTypesAsCertain(); - $expectedType = $nativeScope->getNativeType($node->args[0]->value); - $actualType = $nativeScope->getNativeType($node->args[1]->value); - $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; - } elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') { - $certainty = $node->args[0]->value; - if (!$certainty instanceof StaticCall) { - $this->fail(sprintf('First argument of %s() must be TrinaryLogic call', $functionName)); - } - if (!$certainty->class instanceof Node\Name) { - $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); - } - - if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { - $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); - } - - if (!$certainty->name instanceof Node\Identifier) { - $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); - } - - // @phpstan-ignore-next-line - $expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); - $variable = $node->args[1]->value; - if (!$variable instanceof Node\Expr\Variable) { - $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); - } - if (!is_string($variable->name)) { - $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); - } - - $actualCertaintyValue = $scope->hasVariableType($variable->name); - $assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variable->name]; - } else { - return; - } - - if (count($node->args) !== 2) { - $this->fail(sprintf( - 'ERROR: Wrong %s() call on line %d.', - $functionName, - $node->getLine() - )); - } - - $asserts[$file . ':' . $node->getLine()] = $assert; - }); - - return $asserts; - } - - /** @return string[] */ - protected function getAdditionalAnalysedFiles(): array - { - return []; - } - - /** @return string[][] */ - protected function getEarlyTerminatingMethodCalls(): array - { - return []; - } - - /** @return string[] */ - protected function getEarlyTerminatingFunctionCalls(): array - { - return []; - } - + /** @var bool */ + protected $polluteCatchScopeWithTryAssignments = true; + + /** + * @param string $file + * @param callable(\PhpParser\Node, \PHPStan\Analyser\Scope): void $callback + * @param DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions + * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions + * @param string[] $dynamicConstantNames + */ + public function processFile( + string $file, + callable $callback, + array $dynamicMethodReturnTypeExtensions = [], + array $dynamicStaticMethodReturnTypeExtensions = [], + array $methodTypeSpecifyingExtensions = [], + array $staticMethodTypeSpecifyingExtensions = [], + array $dynamicConstantNames = [] + ): void { + $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); + $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); + + $printer = new \PhpParser\PrettyPrinter\Standard(); + $broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions); + Broker::registerInstance($broker); + $typeSpecifier = $this->createTypeSpecifier($printer, $broker, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); + $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); + $fileHelper = new FileHelper($currentWorkingDirectory); + $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($currentWorkingDirectory))); + $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + $resolver = new NodeScopeResolver( + $broker, + self::getReflectors()[0], + $this->getClassReflectionExtensionRegistryProvider(), + $this->getParser(), + $fileTypeMapper, + self::getContainer()->getByType(PhpVersion::class), + $phpDocInheritanceResolver, + $fileHelper, + $typeSpecifier, + self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), + true, + $this->polluteCatchScopeWithTryAssignments, + true, + $this->getEarlyTerminatingMethodCalls(), + $this->getEarlyTerminatingFunctionCalls(), + true, + true + ); + $resolver->setAnalysedFiles(array_map(static function (string $file) use ($fileHelper): string { + return $fileHelper->normalizePath($file); + }, array_merge([$file], $this->getAdditionalAnalysedFiles()))); + + $scopeFactory = $this->createScopeFactory($broker, $typeSpecifier); + if (count($dynamicConstantNames) > 0) { + $reflectionProperty = new \ReflectionProperty(DirectScopeFactory::class, 'dynamicConstantNames'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($scopeFactory, $dynamicConstantNames); + } + $scope = $scopeFactory->create(ScopeContext::create($file)); + + $resolver->processNodes( + $this->getParser()->parseFile($file), + $scope, + $callback + ); + } + + /** + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function assertFileAsserts( + string $assertType, + string $file, + ...$args + ): void { + if ($assertType === 'type') { + $expectedType = $args[0]; + $expected = $expectedType->getValue(); + $actualType = $args[1]; + $actual = $actualType->describe(VerbosityLevel::precise()); + $this->assertSame( + $expected, + $actual, + sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]) + ); + } elseif ($assertType === 'variableCertainty') { + $expectedCertainty = $args[0]; + $actualCertainty = $args[1]; + $variableName = $args[2]; + $this->assertTrue( + $expectedCertainty->equals($actualCertainty), + sprintf('Expected %s, actual certainty of variable $%s is %s', $expectedCertainty->describe(), $variableName, $actualCertainty->describe()) + ); + } + } + + /** + * @param string $file + * @return array + */ + public function gatherAssertTypes(string $file): array + { + $asserts = []; + $this->processFile($file, function (Node $node, Scope $scope) use (&$asserts, $file): void { + if (!$node instanceof Node\Expr\FuncCall) { + return; + } + + $nameNode = $node->name; + if (!$nameNode instanceof Name) { + return; + } + + $functionName = $nameNode->toString(); + if ($functionName === 'PHPStan\\Testing\\assertType') { + $expectedType = $scope->getType($node->args[0]->value); + $actualType = $scope->getType($node->args[1]->value); + $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; + } elseif ($functionName === 'PHPStan\\Testing\\assertNativeType') { + $nativeScope = $scope->doNotTreatPhpDocTypesAsCertain(); + $expectedType = $nativeScope->getNativeType($node->args[0]->value); + $actualType = $nativeScope->getNativeType($node->args[1]->value); + $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; + } elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') { + $certainty = $node->args[0]->value; + if (!$certainty instanceof StaticCall) { + $this->fail(sprintf('First argument of %s() must be TrinaryLogic call', $functionName)); + } + if (!$certainty->class instanceof Node\Name) { + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); + } + + if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); + } + + if (!$certainty->name instanceof Node\Identifier) { + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); + } + + // @phpstan-ignore-next-line + $expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); + $variable = $node->args[1]->value; + if (!$variable instanceof Node\Expr\Variable) { + $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); + } + if (!is_string($variable->name)) { + $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); + } + + $actualCertaintyValue = $scope->hasVariableType($variable->name); + $assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variable->name]; + } else { + return; + } + + if (count($node->args) !== 2) { + $this->fail(sprintf( + 'ERROR: Wrong %s() call on line %d.', + $functionName, + $node->getLine() + )); + } + + $asserts[$file . ':' . $node->getLine()] = $assert; + }); + + return $asserts; + } + + /** @return string[] */ + protected function getAdditionalAnalysedFiles(): array + { + return []; + } + + /** @return string[][] */ + protected function getEarlyTerminatingMethodCalls(): array + { + return []; + } + + /** @return string[] */ + protected function getEarlyTerminatingFunctionCalls(): array + { + return []; + } } diff --git a/src/Testing/functions.php b/src/Testing/functions.php index e9ce4f7cd6..242e3254bd 100644 --- a/src/Testing/functions.php +++ b/src/Testing/functions.php @@ -1,4 +1,6 @@ -value = $value; - } - - public static function createYes(): self - { - return self::create(self::YES); - } - - public static function createNo(): self - { - return self::create(self::NO); - } - - public static function createMaybe(): self - { - return self::create(self::MAYBE); - } - - public static function createFromBoolean(bool $value): self - { - return self::create($value ? self::YES : self::NO); - } - - private static function create(int $value): self - { - self::$registry[$value] = self::$registry[$value] ?? new self($value); - return self::$registry[$value]; - } - - public function yes(): bool - { - return $this->value === self::YES; - } - - public function maybe(): bool - { - return $this->value === self::MAYBE; - } - - public function no(): bool - { - return $this->value === self::NO; - } - - public function toBooleanType(): BooleanType - { - if ($this->value === self::MAYBE) { - return new BooleanType(); - } - - return new ConstantBooleanType($this->value === self::YES); - } - - public function and(self ...$operands): self - { - $operandValues = array_column($operands, 'value'); - $operandValues[] = $this->value; - return self::create(min($operandValues)); - } - - public function or(self ...$operands): self - { - $operandValues = array_column($operands, 'value'); - $operandValues[] = $this->value; - return self::create(max($operandValues)); - } - - public static function extremeIdentity(self ...$operands): self - { - $operandValues = array_column($operands, 'value'); - $min = min($operandValues); - $max = max($operandValues); - return self::create($min === $max ? $min : self::MAYBE); - } - - public static function maxMin(self ...$operands): self - { - $operandValues = array_column($operands, 'value'); - return self::create(max($operandValues) > 0 ? max($operandValues) : min($operandValues)); - } - - public function negate(): self - { - return self::create(-$this->value); - } - - public function equals(self $other): bool - { - return $this === $other; - } - - public function compareTo(self $other): ?self - { - if ($this->value > $other->value) { - return $this; - } elseif ($other->value > $this->value) { - return $other; - } - - return null; - } - - public function describe(): string - { - static $labels = [ - self::NO => 'No', - self::MAYBE => 'Maybe', - self::YES => 'Yes', - ]; - - return $labels[$this->value]; - } - - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return self::create($properties['value']); - } - + private const YES = 1; + private const MAYBE = 0; + private const NO = -1; + + private int $value; + + /** @var self[] */ + private static array $registry = []; + + private function __construct(int $value) + { + $this->value = $value; + } + + public static function createYes(): self + { + return self::create(self::YES); + } + + public static function createNo(): self + { + return self::create(self::NO); + } + + public static function createMaybe(): self + { + return self::create(self::MAYBE); + } + + public static function createFromBoolean(bool $value): self + { + return self::create($value ? self::YES : self::NO); + } + + private static function create(int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + public function yes(): bool + { + return $this->value === self::YES; + } + + public function maybe(): bool + { + return $this->value === self::MAYBE; + } + + public function no(): bool + { + return $this->value === self::NO; + } + + public function toBooleanType(): BooleanType + { + if ($this->value === self::MAYBE) { + return new BooleanType(); + } + + return new ConstantBooleanType($this->value === self::YES); + } + + public function and(self ...$operands): self + { + $operandValues = array_column($operands, 'value'); + $operandValues[] = $this->value; + return self::create(min($operandValues)); + } + + public function or(self ...$operands): self + { + $operandValues = array_column($operands, 'value'); + $operandValues[] = $this->value; + return self::create(max($operandValues)); + } + + public static function extremeIdentity(self ...$operands): self + { + $operandValues = array_column($operands, 'value'); + $min = min($operandValues); + $max = max($operandValues); + return self::create($min === $max ? $min : self::MAYBE); + } + + public static function maxMin(self ...$operands): self + { + $operandValues = array_column($operands, 'value'); + return self::create(max($operandValues) > 0 ? max($operandValues) : min($operandValues)); + } + + public function negate(): self + { + return self::create(-$this->value); + } + + public function equals(self $other): bool + { + return $this === $other; + } + + public function compareTo(self $other): ?self + { + if ($this->value > $other->value) { + return $this; + } elseif ($other->value > $this->value) { + return $other; + } + + return null; + } + + public function describe(): string + { + static $labels = [ + self::NO => 'No', + self::MAYBE => 'Maybe', + self::YES => 'Yes', + ]; + + return $labels[$this->value]; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return self::create($properties['value']); + } } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9cd022ad17..0b0f50f2c3 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -1,4 +1,6 @@ -isNumericString(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($this->equals($type)) { - return TrinaryLogic::createYes(); - } - - return $type->isNumericString(); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - return $otherType->isNumericString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function describe(\PHPStan\Type\VerbosityLevel $level): string - { - return 'numeric'; - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); - } - - public function getOffsetValueType(Type $offsetType): Type - { - if ($this->hasOffsetValueType($offsetType)->no()) { - return new ErrorType(); - } - - return new StringType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this; - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function toNumber(): Type - { - return new UnionType([ - $this->toInteger(), - $this->toFloat(), - ]); - } - - public function toInteger(): Type - { - return new IntegerType(); - } - - public function toFloat(): Type - { - return new FloatType(); - } - - public function toString(): Type - { - return $this; - } - - public function toArray(): Type - { - return new ConstantArrayType( - [new ConstantIntegerType(0)], - [$this], - 1 - ); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public static function __set_state(array $properties): Type - { - return new self(); - } - + use NonCallableTypeTrait; + use NonObjectTypeTrait; + use NonIterableTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + use NonGenericTypeTrait; + + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return $type->isNumericString(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + return $type->isNumericString(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isNumericString() + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return 'numeric'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + return new StringType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new UnionType([ + $this->toInteger(), + $this->toFloat(), + ]); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/Accessory/AccessoryType.php b/src/Type/Accessory/AccessoryType.php index 734ee43759..789c9fe600 100644 --- a/src/Type/Accessory/AccessoryType.php +++ b/src/Type/Accessory/AccessoryType.php @@ -1,4 +1,6 @@ -methodName = $methodName; - } - - public function getReferencedClasses(): array - { - return []; - } - - private function getCanonicalMethodName(): string - { - return strtolower($this->methodName); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->equals($type)); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $type->hasMethod($this->methodName); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); - } else { - $limit = TrinaryLogic::createMaybe(); - } - - return $limit->and($otherType->hasMethod($this->methodName)); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self - && $this->getCanonicalMethodName() === $type->getCanonicalMethodName(); - } - - public function describe(\PHPStan\Type\VerbosityLevel $level): string - { - return sprintf('hasMethod(%s)', $this->methodName); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - if ($this->getCanonicalMethodName() === strtolower($methodName)) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $method = new DummyMethodReflection($this->methodName); - return new CallbackUnresolvedMethodPrototypeReflection( - $method, - $method->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function isCallable(): TrinaryLogic - { - if ($this->getCanonicalMethodName() === '__invoke') { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [ - new TrivialParametersAcceptor(), - ]; - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public static function __set_state(array $properties): Type - { - return new self($properties['methodName']); - } - + use ObjectTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private string $methodName; + + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + public function getReferencedClasses(): array + { + return []; + } + + private function getCanonicalMethodName(): string + { + return strtolower($this->methodName); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->equals($type)); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $type->hasMethod($this->methodName); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + if ($otherType instanceof self) { + $limit = TrinaryLogic::createYes(); + } else { + $limit = TrinaryLogic::createMaybe(); + } + + return $limit->and($otherType->hasMethod($this->methodName)); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self + && $this->getCanonicalMethodName() === $type->getCanonicalMethodName(); + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return sprintf('hasMethod(%s)', $this->methodName); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + if ($this->getCanonicalMethodName() === strtolower($methodName)) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $method = new DummyMethodReflection($this->methodName); + return new CallbackUnresolvedMethodPrototypeReflection( + $method, + $method->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function isCallable(): TrinaryLogic + { + if ($this->getCanonicalMethodName() === '__invoke') { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [ + new TrivialParametersAcceptor(), + ]; + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self($properties['methodName']); + } } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 317c22ea26..95b29d8838 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -1,4 +1,6 @@ -offsetType = $offsetType; - } - - public function getOffsetType(): Type - { - return $this->offsetType; - } - - public function getReferencedClasses(): array - { - return []; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($this->equals($type)) { - return TrinaryLogic::createYes(); - } - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - return $otherType->isOffsetAccessible() - ->and($otherType->hasOffsetValueType($this->offsetType)) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self - && $this->offsetType->equals($type->offsetType); - } - - public function describe(\PHPStan\Type\VerbosityLevel $level): string - { - return sprintf('hasOffset(%s)', $this->offsetType->describe($level)); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - if ($offsetType instanceof ConstantScalarType && $offsetType->equals($this->offsetType)) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new MixedType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this; - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new MixedType(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public static function __set_state(array $properties): Type - { - return new self($properties['offsetType']); - } - + use MaybeCallableTypeTrait; + use MaybeIterableTypeTrait; + use MaybeObjectTypeTrait; + use TruthyBooleanTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private \PHPStan\Type\Type $offsetType; + + public function __construct(Type $offsetType) + { + $this->offsetType = $offsetType; + } + + public function getOffsetType(): Type + { + return $this->offsetType; + } + + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return $type->isOffsetAccessible() + ->and($type->hasOffsetValueType($this->offsetType)); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + return $type->isOffsetAccessible() + ->and($type->hasOffsetValueType($this->offsetType)); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isOffsetAccessible() + ->and($otherType->hasOffsetValueType($this->offsetType)) + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self + && $this->offsetType->equals($type->offsetType); + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return sprintf('hasOffset(%s)', $this->offsetType->describe($level)); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + if ($offsetType instanceof ConstantScalarType && $offsetType->equals($this->offsetType)) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new MixedType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this; + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new MixedType(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self($properties['offsetType']); + } } diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 40f5c4749d..79b47da85c 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -1,4 +1,6 @@ -propertyName = $propertyName; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return []; - } - - public function getPropertyName(): string - { - return $this->propertyName; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->equals($type)); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $type->hasProperty($this->propertyName); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); - } else { - $limit = TrinaryLogic::createMaybe(); - } - - return $limit->and($otherType->hasProperty($this->propertyName)); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self - && $this->propertyName === $type->propertyName; - } - - public function describe(\PHPStan\Type\VerbosityLevel $level): string - { - return sprintf('hasProperty(%s)', $this->propertyName); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - if ($this->propertyName === $propertyName) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [new TrivialParametersAcceptor()]; - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public static function __set_state(array $properties): Type - { - return new self($properties['propertyName']); - } - + use ObjectTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private string $propertyName; + + public function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function getPropertyName(): string + { + return $this->propertyName; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->equals($type)); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $type->hasProperty($this->propertyName); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + if ($otherType instanceof self) { + $limit = TrinaryLogic::createYes(); + } else { + $limit = TrinaryLogic::createMaybe(); + } + + return $limit->and($otherType->hasProperty($this->propertyName)); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self + && $this->propertyName === $type->propertyName; + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return sprintf('hasProperty(%s)', $this->propertyName); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + if ($this->propertyName === $propertyName) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [new TrivialParametersAcceptor()]; + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self($properties['propertyName']); + } } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index d65ae5f8ac..c48178bcc8 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -1,4 +1,6 @@ -isArray() - ->and($type->isIterableAtLeastOnce()); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($this->equals($type)) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return $type->isArray() - ->and($type->isIterableAtLeastOnce()); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - return $otherType->isArray() - ->and($otherType->isIterableAtLeastOnce()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function describe(\PHPStan\Type\VerbosityLevel $level): string - { - return 'nonEmpty'; - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new MixedType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this; - } - - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function getIterableKeyType(): Type - { - return new MixedType(); - } - - public function getIterableValueType(): Type - { - return new MixedType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new MixedType(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public static function __set_state(array $properties): Type - { - return new self(); - } - + use MaybeCallableTypeTrait; + use NonObjectTypeTrait; + use TruthyBooleanTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return $type->isArray() + ->and($type->isIterableAtLeastOnce()); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return $type->isArray() + ->and($type->isIterableAtLeastOnce()); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isArray() + ->and($otherType->isIterableAtLeastOnce()) + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return 'nonEmpty'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new MixedType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this; + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getIterableKeyType(): Type + { + return new MixedType(); + } + + public function getIterableValueType(): Type + { + return new MixedType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new MixedType(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 227145c96c..abc8ba4185 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -1,4 +1,6 @@ -describe(VerbosityLevel::value()) === '(int|string)') { - $keyType = new MixedType(); - } - $this->keyType = $keyType; - $this->itemType = $itemType; - } - - public function getKeyType(): Type - { - return $this->keyType; - } - - public function getItemType(): Type - { - return $this->itemType; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return array_merge( - $this->keyType->getReferencedClasses(), - $this->getItemType()->getReferencedClasses() - ); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - if ($type instanceof ConstantArrayType) { - $result = TrinaryLogic::createYes(); - $thisKeyType = $this->keyType; - $itemType = $this->getItemType(); - foreach ($type->getKeyTypes() as $i => $keyType) { - $valueType = $type->getValueTypes()[$i]; - $result = $result->and($thisKeyType->accepts($keyType, $strictTypes))->and($itemType->accepts($valueType, $strictTypes)); - } - - return $result; - } - - if ($type instanceof ArrayType) { - return $this->getItemType()->accepts($type->getItemType(), $strictTypes) - ->and($this->keyType->accepts($type->keyType, $strictTypes)); - } - - return TrinaryLogic::createNo(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return $this->getItemType()->isSuperTypeOf($type->getItemType()) - ->and($this->keyType->isSuperTypeOf($type->keyType)); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return $type instanceof self - && !$type instanceof ConstantArrayType - && $this->getItemType()->equals($type->getItemType()) - && $this->keyType->equals($type->keyType); - } - - public function describe(VerbosityLevel $level): string - { - $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed'; - $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed'; - - $valueHandler = function () use ($level, $isMixedKeyType, $isMixedItemType): string { - if ($isMixedKeyType || $this->keyType instanceof NeverType) { - if ($isMixedItemType || $this->itemType instanceof NeverType) { - return 'array'; - } - - return sprintf('array<%s>', $this->itemType->describe($level)); - } - - return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); - }; - - return $level->handle( - $valueHandler, - $valueHandler, - function () use ($level, $isMixedKeyType, $isMixedItemType): string { - if ($isMixedKeyType) { - if ($isMixedItemType) { - return 'array'; - } - - return sprintf('array<%s>', $this->itemType->describe($level)); - } - - return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); - } - ); - } - - public function generalizeValues(): self - { - return new self($this->keyType, TypeUtils::generalizeType($this->itemType)); - } - - public function getKeysArray(): self - { - return new self(new IntegerType(), $this->keyType); - } - - public function getValuesArray(): self - { - return new self(new IntegerType(), $this->itemType); - } - - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getIterableKeyType(): Type - { - $keyType = $this->keyType; - if ($keyType instanceof MixedType && !$keyType instanceof TemplateMixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - return $keyType; - } - - public function getIterableValueType(): Type - { - return $this->getItemType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - $offsetType = self::castToArrayKeyType($offsetType); - if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - $offsetType = self::castToArrayKeyType($offsetType); - if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()) { - return new ErrorType(); - } - - $type = $this->getItemType(); - if ($type instanceof ErrorType) { - return new MixedType(); - } - - return $type; - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type - { - if ($offsetType === null) { - $offsetType = new IntegerType(); - } - - return TypeCombinator::intersect(new self( - TypeCombinator::union($this->keyType, self::castToArrayKeyType($offsetType)), - $unionValues ? TypeCombinator::union($this->itemType, $valueType) : $valueType - ), new NonEmptyArrayType()); - } - - public function isCallable(): TrinaryLogic - { - return TrinaryLogic::createMaybe()->and((new StringType())->isSuperTypeOf($this->itemType)); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - if ($this->isCallable()->no()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return [new TrivialParametersAcceptor()]; - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return $this; - } - - public function count(): Type - { - return IntegerRangeType::fromInterval(0, null); - } - - public static function castToArrayKeyType(Type $offsetType): Type - { - return TypeTraverser::map($offsetType, static function (Type $offsetType, callable $traverse): Type { - if ($offsetType instanceof TemplateType) { - return $offsetType; - } - - if ($offsetType instanceof ConstantScalarType) { - /** @var int|string $offsetValue */ - $offsetValue = key([$offsetType->getValue() => null]); - return is_int($offsetValue) ? new ConstantIntegerType($offsetValue) : new ConstantStringType($offsetValue); - } - - if ($offsetType instanceof IntegerType) { - return $offsetType; - } - - if ($offsetType instanceof FloatType || $offsetType instanceof BooleanType || $offsetType->isNumericString()->yes()) { - return new IntegerType(); - } - - if ($offsetType instanceof StringType) { - return $offsetType; - } - - if ($offsetType instanceof UnionType || $offsetType instanceof IntersectionType) { - return $traverse($offsetType); - } - - return new UnionType([new IntegerType(), new StringType()]); - }); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ($receivedType instanceof ConstantArrayType && count($receivedType->getKeyTypes()) === 0) { - $keyType = $this->getKeyType(); - $typeMap = TemplateTypeMap::createEmpty(); - if ($keyType instanceof TemplateType) { - $typeMap = new TemplateTypeMap([ - $keyType->getName() => $keyType->getBound(), - ]); - } - - $itemType = $this->getItemType(); - if ($itemType instanceof TemplateType) { - $typeMap = $typeMap->union(new TemplateTypeMap([ - $itemType->getName() => $itemType->getBound(), - ])); - } - - return $typeMap; - } - - if ($receivedType->isArray()->yes()) { - $keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); - $itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType()); - - return $keyTypeMap->union($itemTypeMap); - } - - return TemplateTypeMap::createEmpty(); - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $keyVariance = $positionVariance; - $itemVariance = $positionVariance; - - if (!$positionVariance->contravariant()) { - $keyType = $this->getKeyType(); - if ($keyType instanceof TemplateType) { - $keyVariance = $keyType->getVariance(); - } - - $itemType = $this->getItemType(); - if ($itemType instanceof TemplateType) { - $itemVariance = $itemType->getVariance(); - } - } - - return array_merge( - $this->getKeyType()->getReferencedTemplateTypes($keyVariance), - $this->getItemType()->getReferencedTemplateTypes($itemVariance) - ); - } - - public function traverse(callable $cb): Type - { - $keyType = $cb($this->keyType); - $itemType = $cb($this->itemType); - - if ($keyType !== $this->keyType || $itemType !== $this->itemType) { - return new self($keyType, $itemType); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['keyType'], - $properties['itemType'] - ); - } - + use MaybeCallableTypeTrait; + use NonObjectTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonTypeTrait; + + private \PHPStan\Type\Type $keyType; + + private \PHPStan\Type\Type $itemType; + + public function __construct(Type $keyType, Type $itemType) + { + if ($keyType->describe(VerbosityLevel::value()) === '(int|string)') { + $keyType = new MixedType(); + } + $this->keyType = $keyType; + $this->itemType = $itemType; + } + + public function getKeyType(): Type + { + return $this->keyType; + } + + public function getItemType(): Type + { + return $this->itemType; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return array_merge( + $this->keyType->getReferencedClasses(), + $this->getItemType()->getReferencedClasses() + ); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + if ($type instanceof ConstantArrayType) { + $result = TrinaryLogic::createYes(); + $thisKeyType = $this->keyType; + $itemType = $this->getItemType(); + foreach ($type->getKeyTypes() as $i => $keyType) { + $valueType = $type->getValueTypes()[$i]; + $result = $result->and($thisKeyType->accepts($keyType, $strictTypes))->and($itemType->accepts($valueType, $strictTypes)); + } + + return $result; + } + + if ($type instanceof ArrayType) { + return $this->getItemType()->accepts($type->getItemType(), $strictTypes) + ->and($this->keyType->accepts($type->keyType, $strictTypes)); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return $this->getItemType()->isSuperTypeOf($type->getItemType()) + ->and($this->keyType->isSuperTypeOf($type->keyType)); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return $type instanceof self + && !$type instanceof ConstantArrayType + && $this->getItemType()->equals($type->getItemType()) + && $this->keyType->equals($type->keyType); + } + + public function describe(VerbosityLevel $level): string + { + $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed'; + $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed'; + + $valueHandler = function () use ($level, $isMixedKeyType, $isMixedItemType): string { + if ($isMixedKeyType || $this->keyType instanceof NeverType) { + if ($isMixedItemType || $this->itemType instanceof NeverType) { + return 'array'; + } + + return sprintf('array<%s>', $this->itemType->describe($level)); + } + + return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); + }; + + return $level->handle( + $valueHandler, + $valueHandler, + function () use ($level, $isMixedKeyType, $isMixedItemType): string { + if ($isMixedKeyType) { + if ($isMixedItemType) { + return 'array'; + } + + return sprintf('array<%s>', $this->itemType->describe($level)); + } + + return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); + } + ); + } + + public function generalizeValues(): self + { + return new self($this->keyType, TypeUtils::generalizeType($this->itemType)); + } + + public function getKeysArray(): self + { + return new self(new IntegerType(), $this->keyType); + } + + public function getValuesArray(): self + { + return new self(new IntegerType(), $this->itemType); + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getIterableKeyType(): Type + { + $keyType = $this->keyType; + if ($keyType instanceof MixedType && !$keyType instanceof TemplateMixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + return $keyType; + } + + public function getIterableValueType(): Type + { + return $this->getItemType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + $offsetType = self::castToArrayKeyType($offsetType); + if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + $offsetType = self::castToArrayKeyType($offsetType); + if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()) { + return new ErrorType(); + } + + $type = $this->getItemType(); + if ($type instanceof ErrorType) { + return new MixedType(); + } + + return $type; + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + if ($offsetType === null) { + $offsetType = new IntegerType(); + } + + return TypeCombinator::intersect(new self( + TypeCombinator::union($this->keyType, self::castToArrayKeyType($offsetType)), + $unionValues ? TypeCombinator::union($this->itemType, $valueType) : $valueType + ), new NonEmptyArrayType()); + } + + public function isCallable(): TrinaryLogic + { + return TrinaryLogic::createMaybe()->and((new StringType())->isSuperTypeOf($this->itemType)); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + if ($this->isCallable()->no()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return [new TrivialParametersAcceptor()]; + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return $this; + } + + public function count(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + + public static function castToArrayKeyType(Type $offsetType): Type + { + return TypeTraverser::map($offsetType, static function (Type $offsetType, callable $traverse): Type { + if ($offsetType instanceof TemplateType) { + return $offsetType; + } + + if ($offsetType instanceof ConstantScalarType) { + /** @var int|string $offsetValue */ + $offsetValue = key([$offsetType->getValue() => null]); + return is_int($offsetValue) ? new ConstantIntegerType($offsetValue) : new ConstantStringType($offsetValue); + } + + if ($offsetType instanceof IntegerType) { + return $offsetType; + } + + if ($offsetType instanceof FloatType || $offsetType instanceof BooleanType || $offsetType->isNumericString()->yes()) { + return new IntegerType(); + } + + if ($offsetType instanceof StringType) { + return $offsetType; + } + + if ($offsetType instanceof UnionType || $offsetType instanceof IntersectionType) { + return $traverse($offsetType); + } + + return new UnionType([new IntegerType(), new StringType()]); + }); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ($receivedType instanceof ConstantArrayType && count($receivedType->getKeyTypes()) === 0) { + $keyType = $this->getKeyType(); + $typeMap = TemplateTypeMap::createEmpty(); + if ($keyType instanceof TemplateType) { + $typeMap = new TemplateTypeMap([ + $keyType->getName() => $keyType->getBound(), + ]); + } + + $itemType = $this->getItemType(); + if ($itemType instanceof TemplateType) { + $typeMap = $typeMap->union(new TemplateTypeMap([ + $itemType->getName() => $itemType->getBound(), + ])); + } + + return $typeMap; + } + + if ($receivedType->isArray()->yes()) { + $keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); + $itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType()); + + return $keyTypeMap->union($itemTypeMap); + } + + return TemplateTypeMap::createEmpty(); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $keyVariance = $positionVariance; + $itemVariance = $positionVariance; + + if (!$positionVariance->contravariant()) { + $keyType = $this->getKeyType(); + if ($keyType instanceof TemplateType) { + $keyVariance = $keyType->getVariance(); + } + + $itemType = $this->getItemType(); + if ($itemType instanceof TemplateType) { + $itemVariance = $itemType->getVariance(); + } + } + + return array_merge( + $this->getKeyType()->getReferencedTemplateTypes($keyVariance), + $this->getItemType()->getReferencedTemplateTypes($itemVariance) + ); + } + + public function traverse(callable $cb): Type + { + $keyType = $cb($this->keyType); + $itemType = $cb($this->itemType); + + if ($keyType !== $this->keyType || $itemType !== $this->itemType) { + return new self($keyType, $itemType); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['keyType'], + $properties['itemType'] + ); + } } diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index 117581bcde..78f6622e89 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -1,4 +1,6 @@ -getTypes() as $type) { - $result = $getType($type); - if ($result instanceof ErrorType) { - continue; - } - - $resultTypes[] = $result; - } - - if (count($resultTypes) === 0) { - return new ErrorType(); - } - - return TypeCombinator::union(...$resultTypes); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $acceptingType->accepts($innerType, $strictTypes); - } - - return TrinaryLogic::createNo()->or(...$results); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - - foreach ($this->getTypes() as $type) { - $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType)); - } - - return $types; - } - - public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - - foreach ($this->getTypes() as $type) { - $types = $types->benevolentUnion($templateType->inferTemplateTypes($type)); - } - - return $types; - } - - public function traverse(callable $cb): Type - { - $types = []; - $changed = false; - - foreach ($this->getTypes() as $type) { - $newType = $cb($type); - if ($type !== $newType) { - $changed = true; - } - $types[] = $newType; - } - - if ($changed) { - return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types)); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types']); - } - + public function describe(VerbosityLevel $level): string + { + return '(' . parent::describe($level) . ')'; + } + + protected function unionTypes(callable $getType): Type + { + $resultTypes = []; + foreach ($this->getTypes() as $type) { + $result = $getType($type); + if ($result instanceof ErrorType) { + continue; + } + + $resultTypes[] = $result; + } + + if (count($resultTypes) === 0) { + return new ErrorType(); + } + + return TypeCombinator::union(...$resultTypes); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $acceptingType->accepts($innerType, $strictTypes); + } + + return TrinaryLogic::createNo()->or(...$results); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + + foreach ($this->getTypes() as $type) { + $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType)); + } + + return $types; + } + + public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + + foreach ($this->getTypes() as $type) { + $types = $types->benevolentUnion($templateType->inferTemplateTypes($type)); + } + + return $types; + } + + public function traverse(callable $cb): Type + { + $types = []; + $changed = false; + + foreach ($this->getTypes() as $type) { + $newType = $cb($type); + if ($type !== $newType) { + $changed = true; + } + $types[] = $newType; + } + + if ($changed) { + return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types)); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['types']); + } } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index a746c8590b..08d4f51f86 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -1,4 +1,6 @@ -toInteger(); - } - - public function toString(): Type - { - return TypeCombinator::union( - new ConstantStringType(''), - new ConstantStringType('1') - ); - } - - public function toInteger(): Type - { - return TypeCombinator::union( - new ConstantIntegerType(0), - new ConstantIntegerType(1) - ); - } - - public function toFloat(): Type - { - return TypeCombinator::union( - new ConstantFloatType(0.0), - new ConstantFloatType(1.0) - ); - } - - public function toArray(): Type - { - return new ConstantArrayType( - [new ConstantIntegerType(0)], - [$this], - 1 - ); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new ErrorType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return new ErrorType(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + use JustNullableTypeTrait; + use NonCallableTypeTrait; + use NonIterableTypeTrait; + use NonObjectTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonTypeTrait; + use NonGenericTypeTrait; + + public function describe(VerbosityLevel $level): string + { + return 'bool'; + } + + public function toNumber(): Type + { + return $this->toInteger(); + } + + public function toString(): Type + { + return TypeCombinator::union( + new ConstantStringType(''), + new ConstantStringType('1') + ); + } + + public function toInteger(): Type + { + return TypeCombinator::union( + new ConstantIntegerType(0), + new ConstantIntegerType(1) + ); + } + + public function toFloat(): Type + { + return TypeCombinator::union( + new ConstantFloatType(0.0), + new ConstantFloatType(1.0) + ); + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new ErrorType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return new ErrorType(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 8bfe8e422b..3a61957815 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -1,4 +1,6 @@ - */ - private array $parameters; - - private Type $returnType; - - private bool $variadic; - - private bool $isCommonCallable; - - /** - * @param array $parameters - * @param Type $returnType - * @param bool $variadic - */ - public function __construct( - ?array $parameters = null, - ?Type $returnType = null, - bool $variadic = true - ) - { - $this->parameters = $parameters ?? []; - $this->returnType = $returnType ?? new MixedType(); - $this->variadic = $variadic; - $this->isCommonCallable = $parameters === null && $returnType === null; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - $classes = []; - foreach ($this->parameters as $parameter) { - $classes = array_merge($classes, $parameter->getType()->getReferencedClasses()); - } - - return array_merge($classes, $this->returnType->getReferencedClasses()); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType && !$type instanceof self) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return $this->isSuperTypeOfInternal($type, true); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfInternal($type, false); - } - - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic - { - $isCallable = $type->isCallable(); - if ($isCallable->no() || $this->isCommonCallable) { - return $isCallable; - } - - static $scope; - if ($scope === null) { - $scope = new OutOfClassScope(); - } - - $variantsResult = null; - foreach ($type->getCallableParametersAcceptors($scope) as $variant) { - $isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny); - if ($variantsResult === null) { - $variantsResult = $isSuperType; - } else { - $variantsResult = $variantsResult->or($isSuperType); - } - } - - if ($variantsResult === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $isCallable->and($variantsResult); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf($this); - } - - return $otherType->isCallable() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'callable'; - }, - function () use ($level): string { - return sprintf( - 'callable(%s): %s', - implode(', ', array_map( - static function (NativeParameterReflection $param) use ($level): string { - return sprintf('%s%s', $param->isVariadic() ? '...' : '', $param->getType()->describe($level)); - }, - $this->getParameters() - )), - $this->returnType->describe($level) - ); - } - ); - } - - public function isCallable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [$this]; - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new ArrayType(new MixedType(), new MixedType()); - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - /** - * @return array - */ - public function getParameters(): array - { - return $this->parameters; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ($receivedType->isCallable()->no()) { - return TemplateTypeMap::createEmpty(); - } - - $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope()); - - $typeMap = TemplateTypeMap::createEmpty(); - - foreach ($parametersAcceptors as $parametersAcceptor) { - $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($receivedType, $parametersAcceptor)); - } - - return $typeMap; - } - - private function inferTemplateTypesOnParametersAcceptor(Type $receivedType, ParametersAcceptor $parametersAcceptor): TemplateTypeMap - { - $typeMap = TemplateTypeMap::createEmpty(); - $args = $parametersAcceptor->getParameters(); - $returnType = $parametersAcceptor->getReturnType(); - - foreach ($this->getParameters() as $i => $param) { - $argType = isset($args[$i]) ? $args[$i]->getType() : new NeverType(); - $paramType = $param->getType(); - $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); - } - - return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $references = $this->getReturnType()->getReferencedTemplateTypes( - $positionVariance->compose(TemplateTypeVariance::createCovariant()) - ); - - $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant()); - - foreach ($this->getParameters() as $param) { - foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) { - $references[] = $reference; - } - } - - return $references; - } - - public function traverse(callable $cb): Type - { - if ($this->isCommonCallable) { - return $this; - } - - $parameters = array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection { - $defaultValue = $param->getDefaultValue(); - return new NativeParameterReflection( - $param->getName(), - $param->isOptional(), - $cb($param->getType()), - $param->passedByReference(), - $param->isVariadic(), - $defaultValue !== null ? $cb($defaultValue) : null - ); - }, $this->getParameters()); - - return new self( - $parameters, - $cb($this->getReturnType()), - $this->isVariadic() - ); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isCommonCallable(): bool - { - return $this->isCommonCallable; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - (bool) $properties['isCommonCallable'] ? null : $properties['parameters'], - (bool) $properties['isCommonCallable'] ? null : $properties['returnType'], - $properties['variadic'] - ); - } - + use MaybeIterableTypeTrait; + use MaybeObjectTypeTrait; + use MaybeOffsetAccessibleTypeTrait; + use TruthyBooleanTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + /** @var array */ + private array $parameters; + + private Type $returnType; + + private bool $variadic; + + private bool $isCommonCallable; + + /** + * @param array $parameters + * @param Type $returnType + * @param bool $variadic + */ + public function __construct( + ?array $parameters = null, + ?Type $returnType = null, + bool $variadic = true + ) { + $this->parameters = $parameters ?? []; + $this->returnType = $returnType ?? new MixedType(); + $this->variadic = $variadic; + $this->isCommonCallable = $parameters === null && $returnType === null; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + $classes = []; + foreach ($this->parameters as $parameter) { + $classes = array_merge($classes, $parameter->getType()->getReferencedClasses()); + } + + return array_merge($classes, $this->returnType->getReferencedClasses()); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType && !$type instanceof self) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return $this->isSuperTypeOfInternal($type, true); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfInternal($type, false); + } + + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic + { + $isCallable = $type->isCallable(); + if ($isCallable->no() || $this->isCommonCallable) { + return $isCallable; + } + + static $scope; + if ($scope === null) { + $scope = new OutOfClassScope(); + } + + $variantsResult = null; + foreach ($type->getCallableParametersAcceptors($scope) as $variant) { + $isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny); + if ($variantsResult === null) { + $variantsResult = $isSuperType; + } else { + $variantsResult = $variantsResult->or($isSuperType); + } + } + + if ($variantsResult === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $isCallable->and($variantsResult); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isCallable() + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'callable'; + }, + function () use ($level): string { + return sprintf( + 'callable(%s): %s', + implode(', ', array_map( + static function (NativeParameterReflection $param) use ($level): string { + return sprintf('%s%s', $param->isVariadic() ? '...' : '', $param->getType()->describe($level)); + }, + $this->getParameters() + )), + $this->returnType->describe($level) + ); + } + ); + } + + public function isCallable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [$this]; + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new ArrayType(new MixedType(), new MixedType()); + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ($receivedType->isCallable()->no()) { + return TemplateTypeMap::createEmpty(); + } + + $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope()); + + $typeMap = TemplateTypeMap::createEmpty(); + + foreach ($parametersAcceptors as $parametersAcceptor) { + $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($receivedType, $parametersAcceptor)); + } + + return $typeMap; + } + + private function inferTemplateTypesOnParametersAcceptor(Type $receivedType, ParametersAcceptor $parametersAcceptor): TemplateTypeMap + { + $typeMap = TemplateTypeMap::createEmpty(); + $args = $parametersAcceptor->getParameters(); + $returnType = $parametersAcceptor->getReturnType(); + + foreach ($this->getParameters() as $i => $param) { + $argType = isset($args[$i]) ? $args[$i]->getType() : new NeverType(); + $paramType = $param->getType(); + $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); + } + + return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $references = $this->getReturnType()->getReferencedTemplateTypes( + $positionVariance->compose(TemplateTypeVariance::createCovariant()) + ); + + $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant()); + + foreach ($this->getParameters() as $param) { + foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + + public function traverse(callable $cb): Type + { + if ($this->isCommonCallable) { + return $this; + } + + $parameters = array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection { + $defaultValue = $param->getDefaultValue(); + return new NativeParameterReflection( + $param->getName(), + $param->isOptional(), + $cb($param->getType()), + $param->passedByReference(), + $param->isVariadic(), + $defaultValue !== null ? $cb($defaultValue) : null + ); + }, $this->getParameters()); + + return new self( + $parameters, + $cb($this->getReturnType()), + $this->isVariadic() + ); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isCommonCallable(): bool + { + return $this->isCommonCallable; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + (bool) $properties['isCommonCallable'] ? null : $properties['parameters'], + (bool) $properties['isCommonCallable'] ? null : $properties['returnType'], + $properties['variadic'] + ); + } } diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 85698a22b3..345cccbcad 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -1,4 +1,6 @@ -getParameters(); - $ourParameters = $ours->getParameters(); - - $result = null; - foreach ($theirParameters as $i => $theirParameter) { - if (!isset($ourParameters[$i])) { - if ($theirParameter->isOptional()) { - continue; - } - - return TrinaryLogic::createNo(); - } - - $ourParameter = $ourParameters[$i]; - $ourParameterType = $ourParameter->getType(); - if ($treatMixedAsAny) { - $isSuperType = $theirParameter->getType()->accepts($ourParameterType, true); - } else { - $isSuperType = $theirParameter->getType()->isSuperTypeOf($ourParameterType); - } - if ($result === null) { - $result = $isSuperType; - } else { - $result = $result->and($isSuperType); - } - } - - $theirReturnType = $theirs->getReturnType(); - if ($treatMixedAsAny) { - $isReturnTypeSuperType = $ours->getReturnType()->accepts($theirReturnType, true); - } else { - $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOf($theirReturnType); - } - if ($result === null) { - $result = $isReturnTypeSuperType; - } else { - $result = $result->and($isReturnTypeSuperType); - } - - return $result; - } - + public static function isParametersAcceptorSuperTypeOf( + ParametersAcceptor $ours, + ParametersAcceptor $theirs, + bool $treatMixedAsAny + ): TrinaryLogic { + $theirParameters = $theirs->getParameters(); + $ourParameters = $ours->getParameters(); + + $result = null; + foreach ($theirParameters as $i => $theirParameter) { + if (!isset($ourParameters[$i])) { + if ($theirParameter->isOptional()) { + continue; + } + + return TrinaryLogic::createNo(); + } + + $ourParameter = $ourParameters[$i]; + $ourParameterType = $ourParameter->getType(); + if ($treatMixedAsAny) { + $isSuperType = $theirParameter->getType()->accepts($ourParameterType, true); + } else { + $isSuperType = $theirParameter->getType()->isSuperTypeOf($ourParameterType); + } + if ($result === null) { + $result = $isSuperType; + } else { + $result = $result->and($isSuperType); + } + } + + $theirReturnType = $theirs->getReturnType(); + if ($treatMixedAsAny) { + $isReturnTypeSuperType = $ours->getReturnType()->accepts($theirReturnType, true); + } else { + $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOf($theirReturnType); + } + if ($result === null) { + $result = $isReturnTypeSuperType; + } else { + $result = $result->and($isReturnTypeSuperType); + } + + return $result; + } } diff --git a/src/Type/CircularTypeAliasDefinitionException.php b/src/Type/CircularTypeAliasDefinitionException.php index 62a946a020..0ffd749f46 100644 --- a/src/Type/CircularTypeAliasDefinitionException.php +++ b/src/Type/CircularTypeAliasDefinitionException.php @@ -1,8 +1,9 @@ -isAcceptedBy($this, $strictTypes); - } - - if ($type instanceof ConstantStringType) { - $broker = Broker::getInstance(); - return TrinaryLogic::createFromBoolean($broker->hasClass($type->getValue())); - } - - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof StringType) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof ConstantStringType) { - $broker = Broker::getInstance(); - return TrinaryLogic::createFromBoolean($broker->hasClass($type->getValue())); - } - - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + public function describe(VerbosityLevel $level): string + { + return 'class-string'; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + if ($type instanceof ConstantStringType) { + $broker = Broker::getInstance(); + return TrinaryLogic::createFromBoolean($broker->hasClass($type->getValue())); + } + + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof StringType) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof ConstantStringType) { + $broker = Broker::getInstance(); + return TrinaryLogic::createFromBoolean($broker->hasClass($type->getValue())); + } + + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 756e6c16a7..299717d2b3 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -1,4 +1,6 @@ - */ - private array $parameters; - - private Type $returnType; - - private bool $variadic; - - /** - * @param array $parameters - * @param Type $returnType - * @param bool $variadic - */ - public function __construct( - array $parameters, - Type $returnType, - bool $variadic - ) - { - $this->objectType = new ObjectType(\Closure::class); - $this->parameters = $parameters; - $this->returnType = $returnType; - $this->variadic = $variadic; - } - - public function getClassName(): string - { - return $this->objectType->getClassName(); - } - - public function getClassReflection(): ?ClassReflection - { - return $this->objectType->getClassReflection(); - } - - public function getAncestorWithClassName(string $className): ?TypeWithClassName - { - return $this->objectType->getAncestorWithClassName($className); - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - $classes = $this->objectType->getReferencedClasses(); - foreach ($this->parameters as $parameter) { - $classes = array_merge($classes, $parameter->getType()->getReferencedClasses()); - } - - return array_merge($classes, $this->returnType->getReferencedClasses()); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - if (!$type instanceof ClosureType) { - return $this->objectType->accepts($type, $strictTypes); - } - - return $this->isSuperTypeOfInternal($type, true); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfInternal($type, false); - } - - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic - { - if ($type instanceof self) { - return CallableTypeHelper::isParametersAcceptorSuperTypeOf( - $this, - $type, - $treatMixedAsAny - ); - } - - if ( - $type instanceof TypeWithClassName - && $type->getClassName() === \Closure::class - ) { - return TrinaryLogic::createMaybe(); - } - - return $this->objectType->isSuperTypeOf($type); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - return $this->returnType->equals($type->returnType); - } - - public function describe(VerbosityLevel $level): string - { - return sprintf( - 'Closure(%s): %s', - implode(', ', array_map(static function (ParameterReflection $parameter) use ($level): string { - return sprintf('%s%s', $parameter->isVariadic() ? '...' : '', $parameter->getType()->describe($level)); - }, $this->parameters)), - $this->returnType->describe($level) - ); - } - - public function canAccessProperties(): TrinaryLogic - { - return $this->objectType->canAccessProperties(); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return $this->objectType->hasProperty($propertyName); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->objectType->getProperty($propertyName, $scope); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope); - } - - public function canCallMethods(): TrinaryLogic - { - return $this->objectType->canCallMethods(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return $this->objectType->hasMethod($methodName); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - if ($methodName === 'call') { - return new ClosureCallUnresolvedMethodPrototypeReflection( - $this->objectType->getUnresolvedMethodPrototype($methodName, $scope), - $this - ); - } - - return $this->objectType->getUnresolvedMethodPrototype($methodName, $scope); - } - - public function canAccessConstants(): TrinaryLogic - { - return $this->objectType->canAccessConstants(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return $this->objectType->hasConstant($constantName); - } - - public function getConstant(string $constantName): ConstantReflection - { - return $this->objectType->getConstant($constantName); - } - - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getIterableKeyType(): Type - { - return new ErrorType(); - } - - public function getIterableValueType(): Type - { - return new ErrorType(); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new ErrorType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return new ErrorType(); - } - - public function isCallable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [$this]; - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function toBoolean(): BooleanType - { - return new ConstantBooleanType(true); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new ConstantArrayType( - [new ConstantIntegerType(0)], - [$this], - 1 - ); - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return TemplateTypeMap::createEmpty(); - } - - /** - * @return array - */ - public function getParameters(): array - { - return $this->parameters; - } - - public function isVariadic(): bool - { - return $this->variadic; - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ($receivedType->isCallable()->no()) { - return TemplateTypeMap::createEmpty(); - } - - $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope()); - - $typeMap = TemplateTypeMap::createEmpty(); - - foreach ($parametersAcceptors as $parametersAcceptor) { - $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($receivedType, $parametersAcceptor)); - } - - return $typeMap; - } - - private function inferTemplateTypesOnParametersAcceptor(Type $receivedType, ParametersAcceptor $parametersAcceptor): TemplateTypeMap - { - $typeMap = TemplateTypeMap::createEmpty(); - $args = $parametersAcceptor->getParameters(); - $returnType = $parametersAcceptor->getReturnType(); - - foreach ($this->getParameters() as $i => $param) { - $argType = isset($args[$i]) ? $args[$i]->getType() : new NeverType(); - $paramType = $param->getType(); - $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); - } - - return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); - } - - public function traverse(callable $cb): Type - { - return new self( - array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection { - $defaultValue = $param->getDefaultValue(); - return new NativeParameterReflection( - $param->getName(), - $param->isOptional(), - $cb($param->getType()), - $param->passedByReference(), - $param->isVariadic(), - $defaultValue !== null ? $cb($defaultValue) : null - ); - }, $this->getParameters()), - $cb($this->getReturnType()), - $this->isVariadic() - ); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['parameters'], - $properties['returnType'], - $properties['variadic'] - ); - } - + use NonGenericTypeTrait; + use UndecidedComparisonTypeTrait; + + private ObjectType $objectType; + + /** @var array */ + private array $parameters; + + private Type $returnType; + + private bool $variadic; + + /** + * @param array $parameters + * @param Type $returnType + * @param bool $variadic + */ + public function __construct( + array $parameters, + Type $returnType, + bool $variadic + ) { + $this->objectType = new ObjectType(\Closure::class); + $this->parameters = $parameters; + $this->returnType = $returnType; + $this->variadic = $variadic; + } + + public function getClassName(): string + { + return $this->objectType->getClassName(); + } + + public function getClassReflection(): ?ClassReflection + { + return $this->objectType->getClassReflection(); + } + + public function getAncestorWithClassName(string $className): ?TypeWithClassName + { + return $this->objectType->getAncestorWithClassName($className); + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + $classes = $this->objectType->getReferencedClasses(); + foreach ($this->parameters as $parameter) { + $classes = array_merge($classes, $parameter->getType()->getReferencedClasses()); + } + + return array_merge($classes, $this->returnType->getReferencedClasses()); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + if (!$type instanceof ClosureType) { + return $this->objectType->accepts($type, $strictTypes); + } + + return $this->isSuperTypeOfInternal($type, true); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfInternal($type, false); + } + + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic + { + if ($type instanceof self) { + return CallableTypeHelper::isParametersAcceptorSuperTypeOf( + $this, + $type, + $treatMixedAsAny + ); + } + + if ( + $type instanceof TypeWithClassName + && $type->getClassName() === \Closure::class + ) { + return TrinaryLogic::createMaybe(); + } + + return $this->objectType->isSuperTypeOf($type); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + return $this->returnType->equals($type->returnType); + } + + public function describe(VerbosityLevel $level): string + { + return sprintf( + 'Closure(%s): %s', + implode(', ', array_map(static function (ParameterReflection $parameter) use ($level): string { + return sprintf('%s%s', $parameter->isVariadic() ? '...' : '', $parameter->getType()->describe($level)); + }, $this->parameters)), + $this->returnType->describe($level) + ); + } + + public function canAccessProperties(): TrinaryLogic + { + return $this->objectType->canAccessProperties(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return $this->objectType->hasProperty($propertyName); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->objectType->getProperty($propertyName, $scope); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope); + } + + public function canCallMethods(): TrinaryLogic + { + return $this->objectType->canCallMethods(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return $this->objectType->hasMethod($methodName); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + if ($methodName === 'call') { + return new ClosureCallUnresolvedMethodPrototypeReflection( + $this->objectType->getUnresolvedMethodPrototype($methodName, $scope), + $this + ); + } + + return $this->objectType->getUnresolvedMethodPrototype($methodName, $scope); + } + + public function canAccessConstants(): TrinaryLogic + { + return $this->objectType->canAccessConstants(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return $this->objectType->hasConstant($constantName); + } + + public function getConstant(string $constantName): ConstantReflection + { + return $this->objectType->getConstant($constantName); + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getIterableKeyType(): Type + { + return new ErrorType(); + } + + public function getIterableValueType(): Type + { + return new ErrorType(); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new ErrorType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return new ErrorType(); + } + + public function isCallable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [$this]; + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function toBoolean(): BooleanType + { + return new ConstantBooleanType(true); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function getTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function isVariadic(): bool + { + return $this->variadic; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ($receivedType->isCallable()->no()) { + return TemplateTypeMap::createEmpty(); + } + + $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope()); + + $typeMap = TemplateTypeMap::createEmpty(); + + foreach ($parametersAcceptors as $parametersAcceptor) { + $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($receivedType, $parametersAcceptor)); + } + + return $typeMap; + } + + private function inferTemplateTypesOnParametersAcceptor(Type $receivedType, ParametersAcceptor $parametersAcceptor): TemplateTypeMap + { + $typeMap = TemplateTypeMap::createEmpty(); + $args = $parametersAcceptor->getParameters(); + $returnType = $parametersAcceptor->getReturnType(); + + foreach ($this->getParameters() as $i => $param) { + $argType = isset($args[$i]) ? $args[$i]->getType() : new NeverType(); + $paramType = $param->getType(); + $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); + } + + return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); + } + + public function traverse(callable $cb): Type + { + return new self( + array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection { + $defaultValue = $param->getDefaultValue(); + return new NativeParameterReflection( + $param->getName(), + $param->isOptional(), + $cb($param->getType()), + $param->passedByReference(), + $param->isVariadic(), + $defaultValue !== null ? $cb($defaultValue) : null + ); + }, $this->getParameters()), + $cb($this->getReturnType()), + $this->isVariadic() + ); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['parameters'], + $properties['returnType'], + $properties['variadic'] + ); + } } diff --git a/src/Type/CommentHelper.php b/src/Type/CommentHelper.php index 3f9e3501ae..83d1eb50be 100644 --- a/src/Type/CommentHelper.php +++ b/src/Type/CommentHelper.php @@ -1,4 +1,6 @@ -getDocComment(); + if ($phpDoc !== null) { + return $phpDoc->getText(); + } - public static function getDocComment(Node $node): ?string - { - $phpDoc = $node->getDocComment(); - if ($phpDoc !== null) { - return $phpDoc->getText(); - } - - return null; - } - + return null; + } } diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index f220f3676b..9958712b05 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -1,4 +1,6 @@ -isAcceptedBy($otherType, $strictTypes); - } - + public static function accepts(CompoundType $compoundType, Type $otherType, bool $strictTypes): TrinaryLogic + { + return $compoundType->isAcceptedBy($otherType, $strictTypes); + } } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 3cbd5e1000..83353cf3b8 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1,4 +1,6 @@ - */ - private array $keyTypes; - - /** @var array */ - private array $valueTypes; - - private int $nextAutoIndex; - - /** @var int[] */ - private array $optionalKeys; - - /** @var self[]|null */ - private ?array $allArrays = null; - - /** - * @param array $keyTypes - * @param array $valueTypes - * @param int $nextAutoIndex - * @param int[] $optionalKeys - */ - public function __construct( - array $keyTypes, - array $valueTypes, - int $nextAutoIndex = 0, - array $optionalKeys = [] - ) - { - assert(count($keyTypes) === count($valueTypes)); - - parent::__construct( - count($keyTypes) > 0 ? TypeCombinator::union(...$keyTypes) : new NeverType(), - count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType() - ); - - $this->keyTypes = $keyTypes; - $this->valueTypes = $valueTypes; - $this->nextAutoIndex = $nextAutoIndex; - $this->optionalKeys = $optionalKeys; - } - - public function isEmpty(): bool - { - return count($this->keyTypes) === 0; - } - - public function getNextAutoIndex(): int - { - return $this->nextAutoIndex; - } - - /** - * @return int[] - */ - public function getOptionalKeys(): array - { - return $this->optionalKeys; - } - - /** - * @return self[] - */ - public function getAllArrays(): array - { - if ($this->allArrays !== null) { - return $this->allArrays; - } - - if (count($this->optionalKeys) <= 10) { - $optionalKeysCombinations = $this->powerSet($this->optionalKeys); - } else { - $optionalKeysCombinations = [ - [], - $this->optionalKeys, - ]; - } - - $requiredKeys = []; - foreach (array_keys($this->keyTypes) as $i) { - if (in_array($i, $this->optionalKeys, true)) { - continue; - } - $requiredKeys[] = $i; - } - - $arrays = []; - foreach ($optionalKeysCombinations as $combination) { - $keys = array_merge($requiredKeys, $combination); - $builder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($keys as $i) { - $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i]); - } - - $array = $builder->getArray(); - if (!$array instanceof ConstantArrayType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $arrays[] = $array; - } - - return $this->allArrays = $arrays; - } - - /** - * @template T - * @param T[] $in - * @return T[][] - */ - private function powerSet(array $in): array - { - $count = count($in); - $members = pow(2, $count); - $return = []; - for ($i = 0; $i < $members; $i++) { - $b = sprintf('%0' . $count . 'b', $i); - $out = []; - for ($j = 0; $j < $count; $j++) { - if ($b[$j] !== '1') { - continue; - } - - $out[] = $in[$j]; - } - $return[] = $out; - } - - return $return; - } - - public function getKeyType(): Type - { - if (count($this->keyTypes) > 1) { - return new UnionType($this->keyTypes); - } - - return parent::getKeyType(); - } - - /** - * @return array - */ - public function getKeyTypes(): array - { - return $this->keyTypes; - } - - /** - * @return array - */ - public function getValueTypes(): array - { - return $this->valueTypes; - } - - public function isOptionalKey(int $i): bool - { - return in_array($i, $this->optionalKeys, true); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof MixedType && !$type instanceof TemplateMixedType) { - return $type->isAcceptedBy($this, $strictTypes); - } - - if ($type instanceof self && count($this->keyTypes) === 0) { - return TrinaryLogic::createFromBoolean(count($type->keyTypes) === 0); - } - - $result = TrinaryLogic::createYes(); - foreach ($this->keyTypes as $i => $keyType) { - $valueType = $this->valueTypes[$i]; - $hasOffset = $type->hasOffsetValueType($keyType); - if ($hasOffset->no()) { - if ($this->isOptionalKey($i)) { - continue; - } - return $hasOffset; - } - if ($hasOffset->maybe() && $this->isOptionalKey($i)) { - $hasOffset = TrinaryLogic::createYes(); - } - - $result = $result->and($hasOffset); - $otherValueType = $type->getOffsetValueType($keyType); - $acceptsValue = $valueType->accepts($otherValueType, $strictTypes); - if ($acceptsValue->no()) { - return $acceptsValue; - } - $result = $result->and($acceptsValue); - } - - return $result->and($type->isArray()); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - if (count($this->keyTypes) === 0) { - if (count($type->keyTypes) > 0) { - if (count($type->optionalKeys) > 0) { - return TrinaryLogic::createMaybe(); - } - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createYes(); - } - - $results = []; - foreach ($this->keyTypes as $i => $keyType) { - $hasOffset = $type->hasOffsetValueType($keyType); - if ($hasOffset->no()) { - if (!$this->isOptionalKey($i)) { - return TrinaryLogic::createNo(); - } - - $results[] = TrinaryLogic::createMaybe(); - continue; - } - $results[] = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); - } - - return TrinaryLogic::createYes()->and(...$results); - } - - if ($type instanceof ArrayType) { - $result = TrinaryLogic::createMaybe(); - if (count($this->keyTypes) === 0) { - return $result; - } - - return $result->and( - $this->getKeyType()->isSuperTypeOf($type->getKeyType()), - $this->getItemType()->isSuperTypeOf($type->getItemType()) - ); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if (count($this->keyTypes) !== count($type->keyTypes)) { - return false; - } - - foreach ($this->keyTypes as $i => $keyType) { - $valueType = $this->valueTypes[$i]; - if (!$valueType->equals($type->valueTypes[$i])) { - return false; - } - if (!$keyType->equals($type->keyTypes[$i])) { - return false; - } - } - - if ($this->optionalKeys !== $type->optionalKeys) { - return false; - } - - return true; - } - - public function isCallable(): TrinaryLogic - { - $typeAndMethod = $this->findTypeAndMethodName(); - if ($typeAndMethod === null) { - return TrinaryLogic::createNo(); - } - - return $typeAndMethod->getCertainty(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - $typeAndMethodName = $this->findTypeAndMethodName(); - if ($typeAndMethodName === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($typeAndMethodName->isUnknown() || !$typeAndMethodName->getCertainty()->yes()) { - return [new TrivialParametersAcceptor()]; - } - - $method = $typeAndMethodName->getType() - ->getMethod($typeAndMethodName->getMethod(), $scope); - - if (!$scope->canCallMethod($method)) { - return [new InaccessibleMethod($method)]; - } - - return $method->getVariants(); - } - - public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod - { - if (count($this->keyTypes) !== 2) { - return null; - } - - if ($this->keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) { - return null; - } - - if ($this->keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no()) { - return null; - } - - [$classOrObject, $method] = $this->valueTypes; - - if (!$method instanceof ConstantStringType) { - return ConstantArrayTypeAndMethod::createUnknown(); - } - - if ($classOrObject instanceof ConstantStringType) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($classOrObject->getValue())) { - return ConstantArrayTypeAndMethod::createUnknown(); - } - $type = new ObjectType($broker->getClass($classOrObject->getValue())->getName()); - } elseif ($classOrObject instanceof GenericClassStringType) { - $type = $classOrObject->getGenericType(); - } elseif ((new \PHPStan\Type\ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) { - $type = $classOrObject; - } else { - return ConstantArrayTypeAndMethod::createUnknown(); - } - - $has = $type->hasMethod($method->getValue()); - if (!$has->no()) { - if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) { - $has = $has->and(TrinaryLogic::createMaybe()); - } - - return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); - } - - return null; - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - $offsetType = ArrayType::castToArrayKeyType($offsetType); - if ($offsetType instanceof UnionType) { - $results = []; - foreach ($offsetType->getTypes() as $innerType) { - $results[] = $this->hasOffsetValueType($innerType); - } - - return TrinaryLogic::extremeIdentity(...$results); - } - - $result = TrinaryLogic::createNo(); - foreach ($this->keyTypes as $i => $keyType) { - if ( - $keyType instanceof ConstantIntegerType - && $offsetType instanceof StringType - && !$offsetType instanceof ConstantStringType - ) { - return TrinaryLogic::createMaybe(); - } - - $has = $keyType->isSuperTypeOf($offsetType); - if ($has->yes()) { - if ($this->isOptionalKey($i)) { - return TrinaryLogic::createMaybe(); - } - return TrinaryLogic::createYes(); - } - if (!$has->maybe()) { - continue; - } - - $result = TrinaryLogic::createMaybe(); - } - - return $result; - } - - public function getOffsetValueType(Type $offsetType): Type - { - $offsetType = ArrayType::castToArrayKeyType($offsetType); - $matchingValueTypes = []; - foreach ($this->keyTypes as $i => $keyType) { - if ($keyType->isSuperTypeOf($offsetType)->no()) { - continue; - } - - $matchingValueTypes[] = $this->valueTypes[$i]; - } - - if (count($matchingValueTypes) > 0) { - $type = TypeCombinator::union(...$matchingValueTypes); - if ($type instanceof ErrorType) { - return new MixedType(); - } - - return $type; - } - - return new ErrorType(); // undefined offset - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = false): Type - { - $builder = ConstantArrayTypeBuilder::createFromConstantArray($this); - $builder->setOffsetValueType($offsetType, $valueType); - - return $builder->getArray(); - } - - public function unsetOffset(Type $offsetType): Type - { - $offsetType = ArrayType::castToArrayKeyType($offsetType); - if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { - foreach ($this->keyTypes as $i => $keyType) { - if ($keyType->getValue() === $offsetType->getValue()) { - $keyTypes = $this->keyTypes; - unset($keyTypes[$i]); - $valueTypes = $this->valueTypes; - unset($valueTypes[$i]); - - $newKeyTypes = []; - $newValueTypes = []; - $newOptionalKeys = []; - - $k = 0; - foreach ($keyTypes as $j => $newKeyType) { - $newKeyTypes[] = $newKeyType; - $newValueTypes[] = $valueTypes[$j]; - if (in_array($j, $this->optionalKeys, true)) { - $newOptionalKeys[] = $k; - } - $k++; - } - - return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndex, $newOptionalKeys); - } - } - } - - $arrays = []; - foreach ($this->getAllArrays() as $tmp) { - $arrays[] = new self($tmp->keyTypes, $tmp->valueTypes, $tmp->nextAutoIndex, array_keys($tmp->keyTypes)); - } - - return TypeUtils::generalizeType(TypeCombinator::union(...$arrays)); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - $keysCount = count($this->keyTypes); - if ($keysCount === 0) { - return TrinaryLogic::createNo(); - } - - $optionalKeysCount = count($this->optionalKeys); - if ($optionalKeysCount === 0) { - return TrinaryLogic::createYes(); - } - - if ($optionalKeysCount < $keysCount) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function removeLast(): self - { - if (count($this->keyTypes) === 0) { - return $this; - } - - $i = count($this->keyTypes) - 1; - - $keyTypes = $this->keyTypes; - $valueTypes = $this->valueTypes; - $optionalKeys = $this->optionalKeys; - unset($optionalKeys[$i]); - - $removedKeyType = array_pop($keyTypes); - array_pop($valueTypes); - $nextAutoindex = $removedKeyType instanceof ConstantIntegerType - ? $removedKeyType->getValue() - : $this->nextAutoIndex; - - return new self( - $keyTypes, - $valueTypes, - $nextAutoindex, - array_values($optionalKeys) - ); - } - - public function removeFirst(): ArrayType - { - $builder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($this->keyTypes as $i => $keyType) { - if ($i === 0) { - continue; - } - - $valueType = $this->valueTypes[$i]; - if ($keyType instanceof ConstantIntegerType) { - $keyType = null; - } - - $builder->setOffsetValueType($keyType, $valueType); - } - - return $builder->getArray(); - } - - public function slice(int $offset, ?int $limit, bool $preserveKeys = false): self - { - if (count($this->keyTypes) === 0) { - return $this; - } - - $keyTypes = array_slice($this->keyTypes, $offset, $limit); - $valueTypes = array_slice($this->valueTypes, $offset, $limit); - - if (!$preserveKeys) { - $i = 0; - /** @var array $keyTypes */ - $keyTypes = array_map(static function ($keyType) use (&$i) { - if ($keyType instanceof ConstantIntegerType) { - $i++; - return new ConstantIntegerType($i - 1); - } - - return $keyType; - }, $keyTypes); - } - - /** @var int|float $nextAutoIndex */ - $nextAutoIndex = 0; - foreach ($keyTypes as $keyType) { - if (!$keyType instanceof ConstantIntegerType) { - continue; - } - - /** @var int|float $nextAutoIndex */ - $nextAutoIndex = max($nextAutoIndex, $keyType->getValue() + 1); - } - - return new self( - $keyTypes, - $valueTypes, - (int) $nextAutoIndex, - [] - ); - } - - public function toBoolean(): BooleanType - { - return $this->count()->toBoolean(); - } - - public function generalize(): Type - { - if (count($this->keyTypes) === 0) { - return $this; - } - - $arrayType = new ArrayType( - TypeUtils::generalizeType($this->getKeyType()), - TypeUtils::generalizeType($this->getItemType()) - ); - - if (count($this->keyTypes) > count($this->optionalKeys)) { - return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); - } - - return $arrayType; - } - - /** - * @return self - */ - public function generalizeValues(): ArrayType - { - $valueTypes = []; - foreach ($this->valueTypes as $valueType) { - $valueTypes[] = TypeUtils::generalizeType($valueType); - } - - return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); - } - - /** - * @return self - */ - public function getKeysArray(): ArrayType - { - $keyTypes = []; - $valueTypes = []; - $optionalKeys = []; - $autoIndex = 0; - - foreach ($this->keyTypes as $i => $keyType) { - $keyTypes[] = new ConstantIntegerType($i); - $valueTypes[] = $keyType; - $autoIndex++; - - if (!$this->isOptionalKey($i)) { - continue; - } - - $optionalKeys[] = $i; - } - - return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys); - } - - /** - * @return self - */ - public function getValuesArray(): ArrayType - { - $keyTypes = []; - $valueTypes = []; - $optionalKeys = []; - $autoIndex = 0; - - foreach ($this->valueTypes as $i => $valueType) { - $keyTypes[] = new ConstantIntegerType($i); - $valueTypes[] = $valueType; - $autoIndex++; - - if (!$this->isOptionalKey($i)) { - continue; - } - - $optionalKeys[] = $i; - } - - return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys); - } - - public function count(): Type - { - $optionalKeysCount = count($this->optionalKeys); - $totalKeysCount = count($this->getKeyTypes()); - if ($optionalKeysCount === 0) { - return new ConstantIntegerType($totalKeysCount); - } - - return IntegerRangeType::fromInterval($totalKeysCount - $optionalKeysCount, $totalKeysCount); - } - - public function describe(VerbosityLevel $level): string - { - $describeValue = function (bool $truncate) use ($level): string { - $items = []; - $values = []; - $exportValuesOnly = true; - foreach ($this->keyTypes as $i => $keyType) { - $valueType = $this->valueTypes[$i]; - if ($keyType->getValue() !== $i) { - $exportValuesOnly = false; - } - - $isOptional = $this->isOptionalKey($i); - if ($isOptional) { - $exportValuesOnly = false; - } - - $items[] = sprintf('%s%s => %s', $isOptional ? '?' : '', var_export($keyType->getValue(), true), $valueType->describe($level)); - $values[] = $valueType->describe($level); - } - - $append = ''; - if ($truncate && count($items) > self::DESCRIBE_LIMIT) { - $items = array_slice($items, 0, self::DESCRIBE_LIMIT); - $values = array_slice($values, 0, self::DESCRIBE_LIMIT); - $append = ', ...'; - } - - return sprintf( - 'array(%s%s)', - implode(', ', $exportValuesOnly ? $values : $items), - $append - ); - }; - return $level->handle( - function () use ($level): string { - return parent::describe($level); - }, - static function () use ($describeValue): string { - return $describeValue(true); - }, - static function () use ($describeValue): string { - return $describeValue(false); - } - ); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ($receivedType instanceof self) { - $typeMap = TemplateTypeMap::createEmpty(); - foreach ($this->keyTypes as $i => $keyType) { - $valueType = $this->valueTypes[$i]; - if ($receivedType->hasOffsetValueType($keyType)->no()) { - continue; - } - $receivedValueType = $receivedType->getOffsetValueType($keyType); - $typeMap = $typeMap->union($valueType->inferTemplateTypes($receivedValueType)); - } - - return $typeMap; - } - - return parent::inferTemplateTypes($receivedType); - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $variance = $positionVariance->compose(TemplateTypeVariance::createInvariant()); - $references = []; - - foreach ($this->keyTypes as $type) { - foreach ($type->getReferencedTemplateTypes($variance) as $reference) { - $references[] = $reference; - } - } - - foreach ($this->valueTypes as $type) { - foreach ($type->getReferencedTemplateTypes($variance) as $reference) { - $references[] = $reference; - } - } - - return $references; - } - - public function traverse(callable $cb): Type - { - $valueTypes = []; - - $stillOriginal = true; - foreach ($this->valueTypes as $valueType) { - $transformedValueType = $cb($valueType); - if ($transformedValueType !== $valueType) { - $stillOriginal = false; - } - - $valueTypes[] = $transformedValueType; - } - - if ($stillOriginal) { - return $this; - } - - return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); - } - - public function isKeysSupersetOf(self $otherArray): bool - { - if (count($this->keyTypes) === 0) { - return count($otherArray->keyTypes) === 0; - } - - if (count($otherArray->keyTypes) === 0) { - return false; - } - - $otherKeys = $otherArray->keyTypes; - foreach ($this->keyTypes as $keyType) { - foreach ($otherArray->keyTypes as $j => $otherKeyType) { - if (!$keyType->equals($otherKeyType)) { - continue; - } - - unset($otherKeys[$j]); - continue 2; - } - } - - return count($otherKeys) === 0; - } - - public function mergeWith(self $otherArray): self - { - // only call this after verifying isKeysSupersetOf - $valueTypes = $this->valueTypes; - $optionalKeys = $this->optionalKeys; - foreach ($this->keyTypes as $i => $keyType) { - $otherIndex = $otherArray->getKeyIndex($keyType); - if ($otherIndex === null) { - $optionalKeys[] = $i; - continue; - } - if ($otherArray->isOptionalKey($otherIndex)) { - $optionalKeys[] = $i; - } - $otherValueType = $otherArray->valueTypes[$otherIndex]; - $valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $otherValueType); - } - - $optionalKeys = array_values(array_unique($optionalKeys)); - - return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $optionalKeys); - } - - /** - * @param ConstantIntegerType|ConstantStringType $otherKeyType - * @return int|null - */ - private function getKeyIndex($otherKeyType): ?int - { - foreach ($this->keyTypes as $i => $keyType) { - if ($keyType->equals($otherKeyType)) { - return $i; - } - } - - return null; - } - - public function makeOffsetRequired(Type $offsetType): self - { - $offsetType = ArrayType::castToArrayKeyType($offsetType); - $optionalKeys = $this->optionalKeys; - foreach ($this->keyTypes as $i => $keyType) { - if (!$keyType->equals($offsetType)) { - continue; - } - - foreach ($optionalKeys as $j => $key) { - if ($i === $key) { - unset($optionalKeys[$j]); - return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndex, array_values($optionalKeys)); - } - } - - break; - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndex'], $properties['optionalKeys'] ?? []); - } - + private const DESCRIBE_LIMIT = 8; + + /** @var array */ + private array $keyTypes; + + /** @var array */ + private array $valueTypes; + + private int $nextAutoIndex; + + /** @var int[] */ + private array $optionalKeys; + + /** @var self[]|null */ + private ?array $allArrays = null; + + /** + * @param array $keyTypes + * @param array $valueTypes + * @param int $nextAutoIndex + * @param int[] $optionalKeys + */ + public function __construct( + array $keyTypes, + array $valueTypes, + int $nextAutoIndex = 0, + array $optionalKeys = [] + ) { + assert(count($keyTypes) === count($valueTypes)); + + parent::__construct( + count($keyTypes) > 0 ? TypeCombinator::union(...$keyTypes) : new NeverType(), + count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType() + ); + + $this->keyTypes = $keyTypes; + $this->valueTypes = $valueTypes; + $this->nextAutoIndex = $nextAutoIndex; + $this->optionalKeys = $optionalKeys; + } + + public function isEmpty(): bool + { + return count($this->keyTypes) === 0; + } + + public function getNextAutoIndex(): int + { + return $this->nextAutoIndex; + } + + /** + * @return int[] + */ + public function getOptionalKeys(): array + { + return $this->optionalKeys; + } + + /** + * @return self[] + */ + public function getAllArrays(): array + { + if ($this->allArrays !== null) { + return $this->allArrays; + } + + if (count($this->optionalKeys) <= 10) { + $optionalKeysCombinations = $this->powerSet($this->optionalKeys); + } else { + $optionalKeysCombinations = [ + [], + $this->optionalKeys, + ]; + } + + $requiredKeys = []; + foreach (array_keys($this->keyTypes) as $i) { + if (in_array($i, $this->optionalKeys, true)) { + continue; + } + $requiredKeys[] = $i; + } + + $arrays = []; + foreach ($optionalKeysCombinations as $combination) { + $keys = array_merge($requiredKeys, $combination); + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($keys as $i) { + $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i]); + } + + $array = $builder->getArray(); + if (!$array instanceof ConstantArrayType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $arrays[] = $array; + } + + return $this->allArrays = $arrays; + } + + /** + * @template T + * @param T[] $in + * @return T[][] + */ + private function powerSet(array $in): array + { + $count = count($in); + $members = pow(2, $count); + $return = []; + for ($i = 0; $i < $members; $i++) { + $b = sprintf('%0' . $count . 'b', $i); + $out = []; + for ($j = 0; $j < $count; $j++) { + if ($b[$j] !== '1') { + continue; + } + + $out[] = $in[$j]; + } + $return[] = $out; + } + + return $return; + } + + public function getKeyType(): Type + { + if (count($this->keyTypes) > 1) { + return new UnionType($this->keyTypes); + } + + return parent::getKeyType(); + } + + /** + * @return array + */ + public function getKeyTypes(): array + { + return $this->keyTypes; + } + + /** + * @return array + */ + public function getValueTypes(): array + { + return $this->valueTypes; + } + + public function isOptionalKey(int $i): bool + { + return in_array($i, $this->optionalKeys, true); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof MixedType && !$type instanceof TemplateMixedType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + if ($type instanceof self && count($this->keyTypes) === 0) { + return TrinaryLogic::createFromBoolean(count($type->keyTypes) === 0); + } + + $result = TrinaryLogic::createYes(); + foreach ($this->keyTypes as $i => $keyType) { + $valueType = $this->valueTypes[$i]; + $hasOffset = $type->hasOffsetValueType($keyType); + if ($hasOffset->no()) { + if ($this->isOptionalKey($i)) { + continue; + } + return $hasOffset; + } + if ($hasOffset->maybe() && $this->isOptionalKey($i)) { + $hasOffset = TrinaryLogic::createYes(); + } + + $result = $result->and($hasOffset); + $otherValueType = $type->getOffsetValueType($keyType); + $acceptsValue = $valueType->accepts($otherValueType, $strictTypes); + if ($acceptsValue->no()) { + return $acceptsValue; + } + $result = $result->and($acceptsValue); + } + + return $result->and($type->isArray()); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + if (count($this->keyTypes) === 0) { + if (count($type->keyTypes) > 0) { + if (count($type->optionalKeys) > 0) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createYes(); + } + + $results = []; + foreach ($this->keyTypes as $i => $keyType) { + $hasOffset = $type->hasOffsetValueType($keyType); + if ($hasOffset->no()) { + if (!$this->isOptionalKey($i)) { + return TrinaryLogic::createNo(); + } + + $results[] = TrinaryLogic::createMaybe(); + continue; + } + $results[] = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); + } + + return TrinaryLogic::createYes()->and(...$results); + } + + if ($type instanceof ArrayType) { + $result = TrinaryLogic::createMaybe(); + if (count($this->keyTypes) === 0) { + return $result; + } + + return $result->and( + $this->getKeyType()->isSuperTypeOf($type->getKeyType()), + $this->getItemType()->isSuperTypeOf($type->getItemType()) + ); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if (count($this->keyTypes) !== count($type->keyTypes)) { + return false; + } + + foreach ($this->keyTypes as $i => $keyType) { + $valueType = $this->valueTypes[$i]; + if (!$valueType->equals($type->valueTypes[$i])) { + return false; + } + if (!$keyType->equals($type->keyTypes[$i])) { + return false; + } + } + + if ($this->optionalKeys !== $type->optionalKeys) { + return false; + } + + return true; + } + + public function isCallable(): TrinaryLogic + { + $typeAndMethod = $this->findTypeAndMethodName(); + if ($typeAndMethod === null) { + return TrinaryLogic::createNo(); + } + + return $typeAndMethod->getCertainty(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + $typeAndMethodName = $this->findTypeAndMethodName(); + if ($typeAndMethodName === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($typeAndMethodName->isUnknown() || !$typeAndMethodName->getCertainty()->yes()) { + return [new TrivialParametersAcceptor()]; + } + + $method = $typeAndMethodName->getType() + ->getMethod($typeAndMethodName->getMethod(), $scope); + + if (!$scope->canCallMethod($method)) { + return [new InaccessibleMethod($method)]; + } + + return $method->getVariants(); + } + + public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod + { + if (count($this->keyTypes) !== 2) { + return null; + } + + if ($this->keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) { + return null; + } + + if ($this->keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no()) { + return null; + } + + [$classOrObject, $method] = $this->valueTypes; + + if (!$method instanceof ConstantStringType) { + return ConstantArrayTypeAndMethod::createUnknown(); + } + + if ($classOrObject instanceof ConstantStringType) { + $broker = Broker::getInstance(); + if (!$broker->hasClass($classOrObject->getValue())) { + return ConstantArrayTypeAndMethod::createUnknown(); + } + $type = new ObjectType($broker->getClass($classOrObject->getValue())->getName()); + } elseif ($classOrObject instanceof GenericClassStringType) { + $type = $classOrObject->getGenericType(); + } elseif ((new \PHPStan\Type\ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) { + $type = $classOrObject; + } else { + return ConstantArrayTypeAndMethod::createUnknown(); + } + + $has = $type->hasMethod($method->getValue()); + if (!$has->no()) { + if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) { + $has = $has->and(TrinaryLogic::createMaybe()); + } + + return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); + } + + return null; + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + $offsetType = ArrayType::castToArrayKeyType($offsetType); + if ($offsetType instanceof UnionType) { + $results = []; + foreach ($offsetType->getTypes() as $innerType) { + $results[] = $this->hasOffsetValueType($innerType); + } + + return TrinaryLogic::extremeIdentity(...$results); + } + + $result = TrinaryLogic::createNo(); + foreach ($this->keyTypes as $i => $keyType) { + if ( + $keyType instanceof ConstantIntegerType + && $offsetType instanceof StringType + && !$offsetType instanceof ConstantStringType + ) { + return TrinaryLogic::createMaybe(); + } + + $has = $keyType->isSuperTypeOf($offsetType); + if ($has->yes()) { + if ($this->isOptionalKey($i)) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createYes(); + } + if (!$has->maybe()) { + continue; + } + + $result = TrinaryLogic::createMaybe(); + } + + return $result; + } + + public function getOffsetValueType(Type $offsetType): Type + { + $offsetType = ArrayType::castToArrayKeyType($offsetType); + $matchingValueTypes = []; + foreach ($this->keyTypes as $i => $keyType) { + if ($keyType->isSuperTypeOf($offsetType)->no()) { + continue; + } + + $matchingValueTypes[] = $this->valueTypes[$i]; + } + + if (count($matchingValueTypes) > 0) { + $type = TypeCombinator::union(...$matchingValueTypes); + if ($type instanceof ErrorType) { + return new MixedType(); + } + + return $type; + } + + return new ErrorType(); // undefined offset + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = false): Type + { + $builder = ConstantArrayTypeBuilder::createFromConstantArray($this); + $builder->setOffsetValueType($offsetType, $valueType); + + return $builder->getArray(); + } + + public function unsetOffset(Type $offsetType): Type + { + $offsetType = ArrayType::castToArrayKeyType($offsetType); + if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { + foreach ($this->keyTypes as $i => $keyType) { + if ($keyType->getValue() === $offsetType->getValue()) { + $keyTypes = $this->keyTypes; + unset($keyTypes[$i]); + $valueTypes = $this->valueTypes; + unset($valueTypes[$i]); + + $newKeyTypes = []; + $newValueTypes = []; + $newOptionalKeys = []; + + $k = 0; + foreach ($keyTypes as $j => $newKeyType) { + $newKeyTypes[] = $newKeyType; + $newValueTypes[] = $valueTypes[$j]; + if (in_array($j, $this->optionalKeys, true)) { + $newOptionalKeys[] = $k; + } + $k++; + } + + return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndex, $newOptionalKeys); + } + } + } + + $arrays = []; + foreach ($this->getAllArrays() as $tmp) { + $arrays[] = new self($tmp->keyTypes, $tmp->valueTypes, $tmp->nextAutoIndex, array_keys($tmp->keyTypes)); + } + + return TypeUtils::generalizeType(TypeCombinator::union(...$arrays)); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + $keysCount = count($this->keyTypes); + if ($keysCount === 0) { + return TrinaryLogic::createNo(); + } + + $optionalKeysCount = count($this->optionalKeys); + if ($optionalKeysCount === 0) { + return TrinaryLogic::createYes(); + } + + if ($optionalKeysCount < $keysCount) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function removeLast(): self + { + if (count($this->keyTypes) === 0) { + return $this; + } + + $i = count($this->keyTypes) - 1; + + $keyTypes = $this->keyTypes; + $valueTypes = $this->valueTypes; + $optionalKeys = $this->optionalKeys; + unset($optionalKeys[$i]); + + $removedKeyType = array_pop($keyTypes); + array_pop($valueTypes); + $nextAutoindex = $removedKeyType instanceof ConstantIntegerType + ? $removedKeyType->getValue() + : $this->nextAutoIndex; + + return new self( + $keyTypes, + $valueTypes, + $nextAutoindex, + array_values($optionalKeys) + ); + } + + public function removeFirst(): ArrayType + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($this->keyTypes as $i => $keyType) { + if ($i === 0) { + continue; + } + + $valueType = $this->valueTypes[$i]; + if ($keyType instanceof ConstantIntegerType) { + $keyType = null; + } + + $builder->setOffsetValueType($keyType, $valueType); + } + + return $builder->getArray(); + } + + public function slice(int $offset, ?int $limit, bool $preserveKeys = false): self + { + if (count($this->keyTypes) === 0) { + return $this; + } + + $keyTypes = array_slice($this->keyTypes, $offset, $limit); + $valueTypes = array_slice($this->valueTypes, $offset, $limit); + + if (!$preserveKeys) { + $i = 0; + /** @var array $keyTypes */ + $keyTypes = array_map(static function ($keyType) use (&$i) { + if ($keyType instanceof ConstantIntegerType) { + $i++; + return new ConstantIntegerType($i - 1); + } + + return $keyType; + }, $keyTypes); + } + + /** @var int|float $nextAutoIndex */ + $nextAutoIndex = 0; + foreach ($keyTypes as $keyType) { + if (!$keyType instanceof ConstantIntegerType) { + continue; + } + + /** @var int|float $nextAutoIndex */ + $nextAutoIndex = max($nextAutoIndex, $keyType->getValue() + 1); + } + + return new self( + $keyTypes, + $valueTypes, + (int) $nextAutoIndex, + [] + ); + } + + public function toBoolean(): BooleanType + { + return $this->count()->toBoolean(); + } + + public function generalize(): Type + { + if (count($this->keyTypes) === 0) { + return $this; + } + + $arrayType = new ArrayType( + TypeUtils::generalizeType($this->getKeyType()), + TypeUtils::generalizeType($this->getItemType()) + ); + + if (count($this->keyTypes) > count($this->optionalKeys)) { + return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + + return $arrayType; + } + + /** + * @return self + */ + public function generalizeValues(): ArrayType + { + $valueTypes = []; + foreach ($this->valueTypes as $valueType) { + $valueTypes[] = TypeUtils::generalizeType($valueType); + } + + return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); + } + + /** + * @return self + */ + public function getKeysArray(): ArrayType + { + $keyTypes = []; + $valueTypes = []; + $optionalKeys = []; + $autoIndex = 0; + + foreach ($this->keyTypes as $i => $keyType) { + $keyTypes[] = new ConstantIntegerType($i); + $valueTypes[] = $keyType; + $autoIndex++; + + if (!$this->isOptionalKey($i)) { + continue; + } + + $optionalKeys[] = $i; + } + + return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys); + } + + /** + * @return self + */ + public function getValuesArray(): ArrayType + { + $keyTypes = []; + $valueTypes = []; + $optionalKeys = []; + $autoIndex = 0; + + foreach ($this->valueTypes as $i => $valueType) { + $keyTypes[] = new ConstantIntegerType($i); + $valueTypes[] = $valueType; + $autoIndex++; + + if (!$this->isOptionalKey($i)) { + continue; + } + + $optionalKeys[] = $i; + } + + return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys); + } + + public function count(): Type + { + $optionalKeysCount = count($this->optionalKeys); + $totalKeysCount = count($this->getKeyTypes()); + if ($optionalKeysCount === 0) { + return new ConstantIntegerType($totalKeysCount); + } + + return IntegerRangeType::fromInterval($totalKeysCount - $optionalKeysCount, $totalKeysCount); + } + + public function describe(VerbosityLevel $level): string + { + $describeValue = function (bool $truncate) use ($level): string { + $items = []; + $values = []; + $exportValuesOnly = true; + foreach ($this->keyTypes as $i => $keyType) { + $valueType = $this->valueTypes[$i]; + if ($keyType->getValue() !== $i) { + $exportValuesOnly = false; + } + + $isOptional = $this->isOptionalKey($i); + if ($isOptional) { + $exportValuesOnly = false; + } + + $items[] = sprintf('%s%s => %s', $isOptional ? '?' : '', var_export($keyType->getValue(), true), $valueType->describe($level)); + $values[] = $valueType->describe($level); + } + + $append = ''; + if ($truncate && count($items) > self::DESCRIBE_LIMIT) { + $items = array_slice($items, 0, self::DESCRIBE_LIMIT); + $values = array_slice($values, 0, self::DESCRIBE_LIMIT); + $append = ', ...'; + } + + return sprintf( + 'array(%s%s)', + implode(', ', $exportValuesOnly ? $values : $items), + $append + ); + }; + return $level->handle( + function () use ($level): string { + return parent::describe($level); + }, + static function () use ($describeValue): string { + return $describeValue(true); + }, + static function () use ($describeValue): string { + return $describeValue(false); + } + ); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ($receivedType instanceof self) { + $typeMap = TemplateTypeMap::createEmpty(); + foreach ($this->keyTypes as $i => $keyType) { + $valueType = $this->valueTypes[$i]; + if ($receivedType->hasOffsetValueType($keyType)->no()) { + continue; + } + $receivedValueType = $receivedType->getOffsetValueType($keyType); + $typeMap = $typeMap->union($valueType->inferTemplateTypes($receivedValueType)); + } + + return $typeMap; + } + + return parent::inferTemplateTypes($receivedType); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $variance = $positionVariance->compose(TemplateTypeVariance::createInvariant()); + $references = []; + + foreach ($this->keyTypes as $type) { + foreach ($type->getReferencedTemplateTypes($variance) as $reference) { + $references[] = $reference; + } + } + + foreach ($this->valueTypes as $type) { + foreach ($type->getReferencedTemplateTypes($variance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + + public function traverse(callable $cb): Type + { + $valueTypes = []; + + $stillOriginal = true; + foreach ($this->valueTypes as $valueType) { + $transformedValueType = $cb($valueType); + if ($transformedValueType !== $valueType) { + $stillOriginal = false; + } + + $valueTypes[] = $transformedValueType; + } + + if ($stillOriginal) { + return $this; + } + + return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); + } + + public function isKeysSupersetOf(self $otherArray): bool + { + if (count($this->keyTypes) === 0) { + return count($otherArray->keyTypes) === 0; + } + + if (count($otherArray->keyTypes) === 0) { + return false; + } + + $otherKeys = $otherArray->keyTypes; + foreach ($this->keyTypes as $keyType) { + foreach ($otherArray->keyTypes as $j => $otherKeyType) { + if (!$keyType->equals($otherKeyType)) { + continue; + } + + unset($otherKeys[$j]); + continue 2; + } + } + + return count($otherKeys) === 0; + } + + public function mergeWith(self $otherArray): self + { + // only call this after verifying isKeysSupersetOf + $valueTypes = $this->valueTypes; + $optionalKeys = $this->optionalKeys; + foreach ($this->keyTypes as $i => $keyType) { + $otherIndex = $otherArray->getKeyIndex($keyType); + if ($otherIndex === null) { + $optionalKeys[] = $i; + continue; + } + if ($otherArray->isOptionalKey($otherIndex)) { + $optionalKeys[] = $i; + } + $otherValueType = $otherArray->valueTypes[$otherIndex]; + $valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $otherValueType); + } + + $optionalKeys = array_values(array_unique($optionalKeys)); + + return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $optionalKeys); + } + + /** + * @param ConstantIntegerType|ConstantStringType $otherKeyType + * @return int|null + */ + private function getKeyIndex($otherKeyType): ?int + { + foreach ($this->keyTypes as $i => $keyType) { + if ($keyType->equals($otherKeyType)) { + return $i; + } + } + + return null; + } + + public function makeOffsetRequired(Type $offsetType): self + { + $offsetType = ArrayType::castToArrayKeyType($offsetType); + $optionalKeys = $this->optionalKeys; + foreach ($this->keyTypes as $i => $keyType) { + if (!$keyType->equals($offsetType)) { + continue; + } + + foreach ($optionalKeys as $j => $key) { + if ($i === $key) { + unset($optionalKeys[$j]); + return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndex, array_values($optionalKeys)); + } + } + + break; + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndex'], $properties['optionalKeys'] ?? []); + } } diff --git a/src/Type/Constant/ConstantArrayTypeAndMethod.php b/src/Type/Constant/ConstantArrayTypeAndMethod.php index 548254e1e0..4a26168c2c 100644 --- a/src/Type/Constant/ConstantArrayTypeAndMethod.php +++ b/src/Type/Constant/ConstantArrayTypeAndMethod.php @@ -1,4 +1,6 @@ -type = $type; - $this->method = $method; - $this->certainty = $certainty; - } - - public static function createConcrete( - Type $type, - string $method, - TrinaryLogic $certainty - ): self - { - if ($certainty->no()) { - throw new \PHPStan\ShouldNotHappenException(); - } - return new self($type, $method, $certainty); - } - - public static function createUnknown(): self - { - return new self(null, null, TrinaryLogic::createMaybe()); - } - - public function isUnknown(): bool - { - return $this->type === null; - } - - public function getType(): Type - { - if ($this->type === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->type; - } - - public function getMethod(): string - { - if ($this->method === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->method; - } - - public function getCertainty(): TrinaryLogic - { - return $this->certainty; - } - + private ?\PHPStan\Type\Type $type; + + private ?string $method; + + private TrinaryLogic $certainty; + + private function __construct( + ?Type $type, + ?string $method, + TrinaryLogic $certainty + ) { + $this->type = $type; + $this->method = $method; + $this->certainty = $certainty; + } + + public static function createConcrete( + Type $type, + string $method, + TrinaryLogic $certainty + ): self { + if ($certainty->no()) { + throw new \PHPStan\ShouldNotHappenException(); + } + return new self($type, $method, $certainty); + } + + public static function createUnknown(): self + { + return new self(null, null, TrinaryLogic::createMaybe()); + } + + public function isUnknown(): bool + { + return $this->type === null; + } + + public function getType(): Type + { + if ($this->type === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->type; + } + + public function getMethod(): string + { + if ($this->method === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->method; + } + + public function getCertainty(): TrinaryLogic + { + return $this->certainty; + } } diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index b5260b4164..c868f9ea49 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -1,4 +1,6 @@ - */ - private array $keyTypes; - - /** @var array */ - private array $valueTypes; - - /** @var array */ - private array $optionalKeys; - - private int $nextAutoIndex; - - private bool $degradeToGeneralArray = false; - - /** - * @param array $keyTypes - * @param array $valueTypes - * @param array $optionalKeys - * @param int $nextAutoIndex - */ - private function __construct( - array $keyTypes, - array $valueTypes, - int $nextAutoIndex, - array $optionalKeys - ) - { - $this->keyTypes = $keyTypes; - $this->valueTypes = $valueTypes; - $this->nextAutoIndex = $nextAutoIndex; - $this->optionalKeys = $optionalKeys; - } - - public static function createEmpty(): self - { - return new self([], [], 0, []); - } - - public static function createFromConstantArray(ConstantArrayType $startArrayType): self - { - return new self( - $startArrayType->getKeyTypes(), - $startArrayType->getValueTypes(), - $startArrayType->getNextAutoIndex(), - $startArrayType->getOptionalKeys() - ); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $optional = false): void - { - if ($offsetType === null) { - $offsetType = new ConstantIntegerType($this->nextAutoIndex); - } else { - $offsetType = ArrayType::castToArrayKeyType($offsetType); - } - - if ( - !$this->degradeToGeneralArray - && ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) - ) { - /** @var ConstantIntegerType|ConstantStringType $keyType */ - foreach ($this->keyTypes as $i => $keyType) { - if ($keyType->getValue() === $offsetType->getValue()) { - $this->valueTypes[$i] = $valueType; - $this->optionalKeys = array_values(array_filter($this->optionalKeys, static function (int $index) use ($i): bool { - return $index !== $i; - })); - return; - } - } - - $this->keyTypes[] = $offsetType; - $this->valueTypes[] = $valueType; - - if ($optional) { - $this->optionalKeys[] = count($this->keyTypes) - 1; - } - - /** @var int|float $newNextAutoIndex */ - $newNextAutoIndex = $offsetType instanceof ConstantIntegerType - ? max($this->nextAutoIndex, $offsetType->getValue() + 1) - : $this->nextAutoIndex; - if (!is_float($newNextAutoIndex)) { - $this->nextAutoIndex = $newNextAutoIndex; - } - return; - } - - $this->keyTypes[] = TypeUtils::generalizeType($offsetType); - $this->valueTypes[] = $valueType; - $this->degradeToGeneralArray = true; - } - - public function degradeToGeneralArray(): void - { - $this->degradeToGeneralArray = true; - } - - public function getArray(): ArrayType - { - if (!$this->degradeToGeneralArray) { - /** @var array $keyTypes */ - $keyTypes = $this->keyTypes; - return new ConstantArrayType($keyTypes, $this->valueTypes, $this->nextAutoIndex, $this->optionalKeys); - } - - return new ArrayType( - TypeCombinator::union(...$this->keyTypes), - TypeCombinator::union(...$this->valueTypes) - ); - } - + /** @var array */ + private array $keyTypes; + + /** @var array */ + private array $valueTypes; + + /** @var array */ + private array $optionalKeys; + + private int $nextAutoIndex; + + private bool $degradeToGeneralArray = false; + + /** + * @param array $keyTypes + * @param array $valueTypes + * @param array $optionalKeys + * @param int $nextAutoIndex + */ + private function __construct( + array $keyTypes, + array $valueTypes, + int $nextAutoIndex, + array $optionalKeys + ) { + $this->keyTypes = $keyTypes; + $this->valueTypes = $valueTypes; + $this->nextAutoIndex = $nextAutoIndex; + $this->optionalKeys = $optionalKeys; + } + + public static function createEmpty(): self + { + return new self([], [], 0, []); + } + + public static function createFromConstantArray(ConstantArrayType $startArrayType): self + { + return new self( + $startArrayType->getKeyTypes(), + $startArrayType->getValueTypes(), + $startArrayType->getNextAutoIndex(), + $startArrayType->getOptionalKeys() + ); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $optional = false): void + { + if ($offsetType === null) { + $offsetType = new ConstantIntegerType($this->nextAutoIndex); + } else { + $offsetType = ArrayType::castToArrayKeyType($offsetType); + } + + if ( + !$this->degradeToGeneralArray + && ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) + ) { + /** @var ConstantIntegerType|ConstantStringType $keyType */ + foreach ($this->keyTypes as $i => $keyType) { + if ($keyType->getValue() === $offsetType->getValue()) { + $this->valueTypes[$i] = $valueType; + $this->optionalKeys = array_values(array_filter($this->optionalKeys, static function (int $index) use ($i): bool { + return $index !== $i; + })); + return; + } + } + + $this->keyTypes[] = $offsetType; + $this->valueTypes[] = $valueType; + + if ($optional) { + $this->optionalKeys[] = count($this->keyTypes) - 1; + } + + /** @var int|float $newNextAutoIndex */ + $newNextAutoIndex = $offsetType instanceof ConstantIntegerType + ? max($this->nextAutoIndex, $offsetType->getValue() + 1) + : $this->nextAutoIndex; + if (!is_float($newNextAutoIndex)) { + $this->nextAutoIndex = $newNextAutoIndex; + } + return; + } + + $this->keyTypes[] = TypeUtils::generalizeType($offsetType); + $this->valueTypes[] = $valueType; + $this->degradeToGeneralArray = true; + } + + public function degradeToGeneralArray(): void + { + $this->degradeToGeneralArray = true; + } + + public function getArray(): ArrayType + { + if (!$this->degradeToGeneralArray) { + /** @var array $keyTypes */ + $keyTypes = $this->keyTypes; + return new ConstantArrayType($keyTypes, $this->valueTypes, $this->nextAutoIndex, $this->optionalKeys); + } + + return new ArrayType( + TypeCombinator::union(...$this->keyTypes), + TypeCombinator::union(...$this->valueTypes) + ); + } } diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index 87103f2601..f421927fe2 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -1,4 +1,6 @@ -value = $value; - } - - public function getValue(): bool - { - return $this->value; - } - - public function describe(VerbosityLevel $level): string - { - return $this->value ? 'true' : 'false'; - } - - public function getSmallerType(): Type - { - if ($this->value) { - return StaticTypeFactory::falsey(); - } - return new NeverType(); - } - - public function getSmallerOrEqualType(): Type - { - if ($this->value) { - return new MixedType(); - } - return StaticTypeFactory::falsey(); - } - - public function getGreaterType(): Type - { - if ($this->value) { - return new NeverType(); - } - return StaticTypeFactory::truthy(); - } - - public function getGreaterOrEqualType(): Type - { - if ($this->value) { - return StaticTypeFactory::truthy(); - } - return new MixedType(); - } - - public function toBoolean(): BooleanType - { - return $this; - } - - public function toNumber(): Type - { - return new ConstantIntegerType((int) $this->value); - } - - public function toString(): Type - { - return new ConstantStringType((string) $this->value); - } - - public function toInteger(): Type - { - return new ConstantIntegerType((int) $this->value); - } - - public function toFloat(): Type - { - return new ConstantFloatType((float) $this->value); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - + use ConstantScalarTypeTrait; + + private bool $value; + + public function __construct(bool $value) + { + $this->value = $value; + } + + public function getValue(): bool + { + return $this->value; + } + + public function describe(VerbosityLevel $level): string + { + return $this->value ? 'true' : 'false'; + } + + public function getSmallerType(): Type + { + if ($this->value) { + return StaticTypeFactory::falsey(); + } + return new NeverType(); + } + + public function getSmallerOrEqualType(): Type + { + if ($this->value) { + return new MixedType(); + } + return StaticTypeFactory::falsey(); + } + + public function getGreaterType(): Type + { + if ($this->value) { + return new NeverType(); + } + return StaticTypeFactory::truthy(); + } + + public function getGreaterOrEqualType(): Type + { + if ($this->value) { + return StaticTypeFactory::truthy(); + } + return new MixedType(); + } + + public function toBoolean(): BooleanType + { + return $this; + } + + public function toNumber(): Type + { + return new ConstantIntegerType((int) $this->value); + } + + public function toString(): Type + { + return new ConstantStringType((string) $this->value); + } + + public function toInteger(): Type + { + return new ConstantIntegerType((int) $this->value); + } + + public function toFloat(): Type + { + return new ConstantFloatType((float) $this->value); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['value']); + } } diff --git a/src/Type/Constant/ConstantFloatType.php b/src/Type/Constant/ConstantFloatType.php index 69cfeb7376..59bdfa3eba 100644 --- a/src/Type/Constant/ConstantFloatType.php +++ b/src/Type/Constant/ConstantFloatType.php @@ -1,4 +1,6 @@ -value = $value; - } - - public function getValue(): float - { - return $this->value; - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'float'; - }, - function (): string { - $formatted = (string) $this->value; - if (strpos($formatted, '.') === false) { - $formatted .= '.0'; - } - - return $formatted; - } - ); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - if (!$this->equals($type)) { - if ($this->describe(VerbosityLevel::value()) === $type->describe(VerbosityLevel::value())) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createYes(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function toString(): Type - { - return new ConstantStringType((string) $this->value); - } - - public function toInteger(): Type - { - return new ConstantIntegerType((int) $this->value); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - + use ConstantScalarTypeTrait; + use ConstantScalarToBooleanTrait; + use ConstantNumericComparisonTypeTrait; + + private float $value; + + public function __construct(float $value) + { + $this->value = $value; + } + + public function getValue(): float + { + return $this->value; + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'float'; + }, + function (): string { + $formatted = (string) $this->value; + if (strpos($formatted, '.') === false) { + $formatted .= '.0'; + } + + return $formatted; + } + ); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + if (!$this->equals($type)) { + if ($this->describe(VerbosityLevel::value()) === $type->describe(VerbosityLevel::value())) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createYes(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function toString(): Type + { + return new ConstantStringType((string) $this->value); + } + + public function toInteger(): Type + { + return new ConstantIntegerType((int) $this->value); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['value']); + } } diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 63c18dec12..edb100cb49 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -1,4 +1,6 @@ -value = $value; - } - - public function getValue(): int - { - return $this->value; - } - - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); - } - - if ($type instanceof IntegerRangeType) { - $min = $type->getMin(); - $max = $type->getMax(); - if (($min === null || $min <= $this->value) && ($max === null || $this->value <= $max)) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'int'; - }, - function (): string { - return sprintf('%s', $this->value); - } - ); - } - - public function toFloat(): Type - { - return new ConstantFloatType($this->value); - } - - public function toString(): Type - { - return new ConstantStringType((string) $this->value); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - + use ConstantScalarTypeTrait; + use ConstantScalarToBooleanTrait; + use ConstantNumericComparisonTypeTrait; + + private int $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function getValue(): int + { + return $this->value; + } + + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + } + + if ($type instanceof IntegerRangeType) { + $min = $type->getMin(); + $max = $type->getMax(); + if (($min === null || $min <= $this->value) && ($max === null || $this->value <= $max)) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'int'; + }, + function (): string { + return sprintf('%s', $this->value); + } + ); + } + + public function toFloat(): Type + { + return new ConstantFloatType($this->value); + } + + public function toString(): Type + { + return new ConstantStringType((string) $this->value); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['value']); + } } diff --git a/src/Type/Constant/ConstantScalarToBooleanTrait.php b/src/Type/Constant/ConstantScalarToBooleanTrait.php index 4b2e0ff624..4ab019658d 100644 --- a/src/Type/Constant/ConstantScalarToBooleanTrait.php +++ b/src/Type/Constant/ConstantScalarToBooleanTrait.php @@ -1,4 +1,6 @@ -value); - } - + public function toBoolean(): BooleanType + { + return new ConstantBooleanType((bool) $this->value); + } } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 44bc6af9b5..d1e3c0d222 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -1,4 +1,6 @@ -value = $value; - $this->isClassString = $isClassString; - } - - public function getValue(): string - { - return $this->value; - } - - public function isClassString(): bool - { - return $this->isClassString; - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'string'; - }, - function (): string { - if ($this->isClassString) { - return var_export($this->value, true); - } - - try { - $truncatedValue = \Nette\Utils\Strings::truncate($this->value, self::DESCRIBE_LIMIT); - } catch (\Nette\Utils\RegexpException $e) { - $truncatedValue = substr($this->value, 0, self::DESCRIBE_LIMIT) . "\u{2026}"; - } - - return var_export( - $truncatedValue, - true - ); - }, - function (): string { - return var_export($this->value, true); - } - ); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof GenericClassStringType) { - $genericType = $type->getGenericType(); - if ($genericType instanceof MixedType) { - return TrinaryLogic::createMaybe(); - } - if ($genericType instanceof StaticType) { - $genericType = $genericType->getStaticObjectType(); - } - - // We are transforming constant class-string to ObjectType. But we need to filter out - // an uncertainty originating in possible ObjectType's class subtypes. - $objectType = new ObjectType($this->getValue()); - - // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType - // uncertainty into account. - if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); - } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); - } - - // Explicitly handle the uncertainty for Yes & Maybe. - if ($isSuperType->yes()) { - return TrinaryLogic::createMaybe(); - } - return TrinaryLogic::createNo(); - } - if ($type instanceof ClassStringType) { - $broker = Broker::getInstance(); - - return $broker->hasClass($this->getValue()) ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); - } - - if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function isCallable(): TrinaryLogic - { - if ($this->value === '') { - return TrinaryLogic::createNo(); - } - - $broker = Broker::getInstance(); - - // 'my_function' - if ($broker->hasFunction(new Name($this->value), null)) { - return TrinaryLogic::createYes(); - } - - // 'MyClass::myStaticFunction' - $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); - if ($matches !== null) { - if (!$broker->hasClass($matches[1])) { - return TrinaryLogic::createMaybe(); - } - - $classRef = $broker->getClass($matches[1]); - if ($classRef->hasMethod($matches[2])) { - return TrinaryLogic::createYes(); - } - - if (!$classRef->getNativeReflection()->isFinal()) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createNo(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - $broker = Broker::getInstance(); - - // 'my_function' - $functionName = new Name($this->value); - if ($broker->hasFunction($functionName, null)) { - return $broker->getFunction($functionName, null)->getVariants(); - } - - // 'MyClass::myStaticFunction' - $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); - if ($matches !== null) { - if (!$broker->hasClass($matches[1])) { - return [new TrivialParametersAcceptor()]; - } - - $classReflection = $broker->getClass($matches[1]); - if ($classReflection->hasMethod($matches[2])) { - $method = $classReflection->getMethod($matches[2], $scope); - if (!$scope->canCallMethod($method)) { - return [new InaccessibleMethod($method)]; - } - - return $method->getVariants(); - } - - if (!$classReflection->getNativeReflection()->isFinal()) { - return [new TrivialParametersAcceptor()]; - } - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function toNumber(): Type - { - if (is_numeric($this->value)) { - /** @var mixed $value */ - $value = $this->value; - $value = +$value; - if (is_float($value)) { - return new ConstantFloatType($value); - } - - return new ConstantIntegerType($value); - } - - return new ErrorType(); - } - - public function toInteger(): Type - { - $type = $this->toNumber(); - if ($type instanceof ErrorType) { - return $type; - } - - return $type->toInteger(); - } - - public function toFloat(): Type - { - $type = $this->toNumber(); - if ($type instanceof ErrorType) { - return $type; - } - - return $type->toFloat(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean(is_numeric($this->getValue())); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - if ($offsetType instanceof ConstantIntegerType) { - return TrinaryLogic::createFromBoolean( - $offsetType->getValue() < strlen($this->value) - ); - } - - return parent::hasOffsetValueType($offsetType); - } - - public function getOffsetValueType(Type $offsetType): Type - { - if ($offsetType instanceof ConstantIntegerType) { - if ($offsetType->getValue() < strlen($this->value)) { - return new self($this->value[$offsetType->getValue()]); - } - - return new ErrorType(); - } - - return parent::getOffsetValueType($offsetType); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - $valueStringType = $valueType->toString(); - if ($valueStringType instanceof ErrorType) { - return new ErrorType(); - } - if ( - $offsetType instanceof ConstantIntegerType - && $valueStringType instanceof ConstantStringType - ) { - $value = $this->value; - $value[$offsetType->getValue()] = $valueStringType->getValue(); - - return new self($value); - } - - return parent::setOffsetValueType($offsetType, $valueType); - } - - public function append(self $otherString): self - { - return new self($this->getValue() . $otherString->getValue()); - } - - public function generalize(): Type - { - if ($this->isClassString) { - return new ClassStringType(); - } - return new StringType(); - } - - public function getSmallerType(): Type - { - $subtractedTypes = [ - new ConstantBooleanType(true), - IntegerRangeType::createAllGreaterThanOrEqualTo((float) $this->value), - ]; - - if ($this->value === '') { - $subtractedTypes[] = new NullType(); - $subtractedTypes[] = new StringType(); - } - - if (!(bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(false); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getSmallerOrEqualType(): Type - { - $subtractedTypes = [ - IntegerRangeType::createAllGreaterThan((float) $this->value), - ]; - - if (!(bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(true); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterType(): Type - { - $subtractedTypes = [ - new ConstantBooleanType(false), - IntegerRangeType::createAllSmallerThanOrEqualTo((float) $this->value), - ]; - - if ((bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(true); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterOrEqualType(): Type - { - $subtractedTypes = [ - IntegerRangeType::createAllSmallerThan((float) $this->value), - ]; - - if ((bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(false); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value'], $properties['isClassString'] ?? false); - } - + use ConstantScalarTypeTrait; + use ConstantScalarToBooleanTrait; + + private const DESCRIBE_LIMIT = 20; + + private string $value; + + private bool $isClassString; + + public function __construct(string $value, bool $isClassString = false) + { + $this->value = $value; + $this->isClassString = $isClassString; + } + + public function getValue(): string + { + return $this->value; + } + + public function isClassString(): bool + { + return $this->isClassString; + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'string'; + }, + function (): string { + if ($this->isClassString) { + return var_export($this->value, true); + } + + try { + $truncatedValue = \Nette\Utils\Strings::truncate($this->value, self::DESCRIBE_LIMIT); + } catch (\Nette\Utils\RegexpException $e) { + $truncatedValue = substr($this->value, 0, self::DESCRIBE_LIMIT) . "\u{2026}"; + } + + return var_export( + $truncatedValue, + true + ); + }, + function (): string { + return var_export($this->value, true); + } + ); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof GenericClassStringType) { + $genericType = $type->getGenericType(); + if ($genericType instanceof MixedType) { + return TrinaryLogic::createMaybe(); + } + if ($genericType instanceof StaticType) { + $genericType = $genericType->getStaticObjectType(); + } + + // We are transforming constant class-string to ObjectType. But we need to filter out + // an uncertainty originating in possible ObjectType's class subtypes. + $objectType = new ObjectType($this->getValue()); + + // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType + // uncertainty into account. + if ($genericType instanceof TemplateType) { + $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + } else { + $isSuperType = $genericType->isSuperTypeOf($objectType); + } + + // Explicitly handle the uncertainty for Yes & Maybe. + if ($isSuperType->yes()) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createNo(); + } + if ($type instanceof ClassStringType) { + $broker = Broker::getInstance(); + + return $broker->hasClass($this->getValue()) ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + } + + if ($type instanceof self) { + return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function isCallable(): TrinaryLogic + { + if ($this->value === '') { + return TrinaryLogic::createNo(); + } + + $broker = Broker::getInstance(); + + // 'my_function' + if ($broker->hasFunction(new Name($this->value), null)) { + return TrinaryLogic::createYes(); + } + + // 'MyClass::myStaticFunction' + $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); + if ($matches !== null) { + if (!$broker->hasClass($matches[1])) { + return TrinaryLogic::createMaybe(); + } + + $classRef = $broker->getClass($matches[1]); + if ($classRef->hasMethod($matches[2])) { + return TrinaryLogic::createYes(); + } + + if (!$classRef->getNativeReflection()->isFinal()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createNo(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + $broker = Broker::getInstance(); + + // 'my_function' + $functionName = new Name($this->value); + if ($broker->hasFunction($functionName, null)) { + return $broker->getFunction($functionName, null)->getVariants(); + } + + // 'MyClass::myStaticFunction' + $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); + if ($matches !== null) { + if (!$broker->hasClass($matches[1])) { + return [new TrivialParametersAcceptor()]; + } + + $classReflection = $broker->getClass($matches[1]); + if ($classReflection->hasMethod($matches[2])) { + $method = $classReflection->getMethod($matches[2], $scope); + if (!$scope->canCallMethod($method)) { + return [new InaccessibleMethod($method)]; + } + + return $method->getVariants(); + } + + if (!$classReflection->getNativeReflection()->isFinal()) { + return [new TrivialParametersAcceptor()]; + } + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function toNumber(): Type + { + if (is_numeric($this->value)) { + /** @var mixed $value */ + $value = $this->value; + $value = +$value; + if (is_float($value)) { + return new ConstantFloatType($value); + } + + return new ConstantIntegerType($value); + } + + return new ErrorType(); + } + + public function toInteger(): Type + { + $type = $this->toNumber(); + if ($type instanceof ErrorType) { + return $type; + } + + return $type->toInteger(); + } + + public function toFloat(): Type + { + $type = $this->toNumber(); + if ($type instanceof ErrorType) { + return $type; + } + + return $type->toFloat(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean(is_numeric($this->getValue())); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + if ($offsetType instanceof ConstantIntegerType) { + return TrinaryLogic::createFromBoolean( + $offsetType->getValue() < strlen($this->value) + ); + } + + return parent::hasOffsetValueType($offsetType); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($offsetType instanceof ConstantIntegerType) { + if ($offsetType->getValue() < strlen($this->value)) { + return new self($this->value[$offsetType->getValue()]); + } + + return new ErrorType(); + } + + return parent::getOffsetValueType($offsetType); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + $valueStringType = $valueType->toString(); + if ($valueStringType instanceof ErrorType) { + return new ErrorType(); + } + if ( + $offsetType instanceof ConstantIntegerType + && $valueStringType instanceof ConstantStringType + ) { + $value = $this->value; + $value[$offsetType->getValue()] = $valueStringType->getValue(); + + return new self($value); + } + + return parent::setOffsetValueType($offsetType, $valueType); + } + + public function append(self $otherString): self + { + return new self($this->getValue() . $otherString->getValue()); + } + + public function generalize(): Type + { + if ($this->isClassString) { + return new ClassStringType(); + } + return new StringType(); + } + + public function getSmallerType(): Type + { + $subtractedTypes = [ + new ConstantBooleanType(true), + IntegerRangeType::createAllGreaterThanOrEqualTo((float) $this->value), + ]; + + if ($this->value === '') { + $subtractedTypes[] = new NullType(); + $subtractedTypes[] = new StringType(); + } + + if (!(bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(false); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getSmallerOrEqualType(): Type + { + $subtractedTypes = [ + IntegerRangeType::createAllGreaterThan((float) $this->value), + ]; + + if (!(bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(true); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterType(): Type + { + $subtractedTypes = [ + new ConstantBooleanType(false), + IntegerRangeType::createAllSmallerThanOrEqualTo((float) $this->value), + ]; + + if ((bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(true); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterOrEqualType(): Type + { + $subtractedTypes = [ + IntegerRangeType::createAllSmallerThan((float) $this->value), + ]; + + if ((bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(false); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['value'], $properties['isClassString'] ?? false); + } } diff --git a/src/Type/ConstantScalarType.php b/src/Type/ConstantScalarType.php index 1e06686e75..c850de0330 100644 --- a/src/Type/ConstantScalarType.php +++ b/src/Type/ConstantScalarType.php @@ -1,13 +1,13 @@ - $v) { + $arrayBuilder->setOffsetValueType(self::getTypeFromValue($k), self::getTypeFromValue($v)); + } + return $arrayBuilder->getArray(); + } - /** - * @param mixed $value - */ - public static function getTypeFromValue($value): Type - { - if (is_int($value)) { - return new ConstantIntegerType($value); - } elseif (is_float($value)) { - return new ConstantFloatType($value); - } elseif (is_bool($value)) { - return new ConstantBooleanType($value); - } elseif ($value === null) { - return new NullType(); - } elseif (is_string($value)) { - return new ConstantStringType($value); - } elseif (is_array($value)) { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($value as $k => $v) { - $arrayBuilder->setOffsetValueType(self::getTypeFromValue($k), self::getTypeFromValue($v)); - } - return $arrayBuilder->getArray(); - } - - return new MixedType(); - } - + return new MixedType(); + } } diff --git a/src/Type/DynamicFunctionReturnTypeExtension.php b/src/Type/DynamicFunctionReturnTypeExtension.php index aa0d2befcd..4f1623b74e 100644 --- a/src/Type/DynamicFunctionReturnTypeExtension.php +++ b/src/Type/DynamicFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -setBroker($broker); - } - - $this->reflectionProvider = $reflectionProvider; - $this->dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions; - $this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions; - $this->dynamicFunctionReturnTypeExtensions = $dynamicFunctionReturnTypeExtensions; - } - - /** - * @param string $className - * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] - */ - public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array - { - if ($this->dynamicMethodReturnTypeExtensionsByClass === null) { - $byClass = []; - foreach ($this->dynamicMethodReturnTypeExtensions as $extension) { - $byClass[$extension->getClass()][] = $extension; - } - - $this->dynamicMethodReturnTypeExtensionsByClass = $byClass; - } - return $this->getDynamicExtensionsForType($this->dynamicMethodReturnTypeExtensionsByClass, $className); - } - - /** - * @param string $className - * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] - */ - public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array - { - if ($this->dynamicStaticMethodReturnTypeExtensionsByClass === null) { - $byClass = []; - foreach ($this->dynamicStaticMethodReturnTypeExtensions as $extension) { - $byClass[$extension->getClass()][] = $extension; - } - - $this->dynamicStaticMethodReturnTypeExtensionsByClass = $byClass; - } - return $this->getDynamicExtensionsForType($this->dynamicStaticMethodReturnTypeExtensionsByClass, $className); - } - - /** - * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[][]|\PHPStan\Type\DynamicStaticMethodReturnTypeExtension[][] $extensions - * @param string $className - * @return mixed[] - */ - private function getDynamicExtensionsForType(array $extensions, string $className): array - { - if (!$this->reflectionProvider->hasClass($className)) { - return []; - } - - $extensionsForClass = [[]]; - $class = $this->reflectionProvider->getClass($className); - foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { - if (!isset($extensions[$extensionClassName])) { - continue; - } - - $extensionsForClass[] = $extensions[$extensionClassName]; - } - - return array_merge(...$extensionsForClass); - } - - /** - * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return $this->dynamicFunctionReturnTypeExtensions; - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + /** @var \PHPStan\Type\DynamicMethodReturnTypeExtension[] */ + private array $dynamicMethodReturnTypeExtensions; + + /** @var \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] */ + private array $dynamicStaticMethodReturnTypeExtensions; + + /** @var \PHPStan\Type\DynamicFunctionReturnTypeExtension[] */ + private array $dynamicFunctionReturnTypeExtensions; + + /** @var \PHPStan\Type\DynamicMethodReturnTypeExtension[][]|null */ + private ?array $dynamicMethodReturnTypeExtensionsByClass = null; + + /** @var \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[][]|null */ + private ?array $dynamicStaticMethodReturnTypeExtensionsByClass = null; + + /** + * @param \PHPStan\Broker\Broker $broker + * @param ReflectionProvider $reflectionProvider + * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicFunctionReturnTypeExtension[] $dynamicFunctionReturnTypeExtensions + */ + public function __construct( + Broker $broker, + ReflectionProvider $reflectionProvider, + array $dynamicMethodReturnTypeExtensions, + array $dynamicStaticMethodReturnTypeExtensions, + array $dynamicFunctionReturnTypeExtensions + ) { + foreach (array_merge($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions, $dynamicFunctionReturnTypeExtensions) as $extension) { + if (!($extension instanceof BrokerAwareExtension)) { + continue; + } + + $extension->setBroker($broker); + } + + $this->reflectionProvider = $reflectionProvider; + $this->dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions; + $this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions; + $this->dynamicFunctionReturnTypeExtensions = $dynamicFunctionReturnTypeExtensions; + } + + /** + * @param string $className + * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] + */ + public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array + { + if ($this->dynamicMethodReturnTypeExtensionsByClass === null) { + $byClass = []; + foreach ($this->dynamicMethodReturnTypeExtensions as $extension) { + $byClass[$extension->getClass()][] = $extension; + } + + $this->dynamicMethodReturnTypeExtensionsByClass = $byClass; + } + return $this->getDynamicExtensionsForType($this->dynamicMethodReturnTypeExtensionsByClass, $className); + } + + /** + * @param string $className + * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] + */ + public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array + { + if ($this->dynamicStaticMethodReturnTypeExtensionsByClass === null) { + $byClass = []; + foreach ($this->dynamicStaticMethodReturnTypeExtensions as $extension) { + $byClass[$extension->getClass()][] = $extension; + } + + $this->dynamicStaticMethodReturnTypeExtensionsByClass = $byClass; + } + return $this->getDynamicExtensionsForType($this->dynamicStaticMethodReturnTypeExtensionsByClass, $className); + } + + /** + * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[][]|\PHPStan\Type\DynamicStaticMethodReturnTypeExtension[][] $extensions + * @param string $className + * @return mixed[] + */ + private function getDynamicExtensionsForType(array $extensions, string $className): array + { + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $extensionsForClass = [[]]; + $class = $this->reflectionProvider->getClass($className); + foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { + if (!isset($extensions[$extensionClassName])) { + continue; + } + + $extensionsForClass[] = $extensions[$extensionClassName]; + } + + return array_merge(...$extensionsForClass); + } + + /** + * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] + */ + public function getDynamicFunctionReturnTypeExtensions(): array + { + return $this->dynamicFunctionReturnTypeExtensions; + } } diff --git a/src/Type/DynamicStaticMethodReturnTypeExtension.php b/src/Type/DynamicStaticMethodReturnTypeExtension.php index 4508b33ae2..cea2c9150d 100644 --- a/src/Type/DynamicStaticMethodReturnTypeExtension.php +++ b/src/Type/DynamicStaticMethodReturnTypeExtension.php @@ -1,4 +1,6 @@ -handle( - function () use ($level): string { - return parent::describe($level); - }, - function () use ($level): string { - return parent::describe($level); - }, - static function (): string { - return '*ERROR*'; - } - ); - } - - public function getIterableKeyType(): Type - { - return new ErrorType(); - } - - public function getIterableValueType(): Type - { - return new ErrorType(); - } - - public function subtract(Type $type): Type - { - return new self(); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + public function __construct() + { + parent::__construct(); + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + function () use ($level): string { + return parent::describe($level); + }, + function () use ($level): string { + return parent::describe($level); + }, + static function (): string { + return '*ERROR*'; + } + ); + } + + public function getIterableKeyType(): Type + { + return new ErrorType(); + } + + public function getIterableValueType(): Type + { + return new ErrorType(); + } + + public function subtract(Type $type): Type + { + return new self(); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 48a155beee..eacaaef916 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -1,4 +1,6 @@ - */ - private array $resolvedPhpDocBlockCache = []; - - /** @var array */ - private array $alreadyProcessedDependentFiles = []; - - /** @var array */ - private array $docKeys = []; - - public function __construct( - ReflectionProviderProvider $reflectionProviderProvider, - Parser $phpParser, - PhpDocStringResolver $phpDocStringResolver, - PhpDocNodeResolver $phpDocNodeResolver, - Cache $cache, - AnonymousClassNameHelper $anonymousClassNameHelper - ) - { - $this->reflectionProviderProvider = $reflectionProviderProvider; - $this->phpParser = $phpParser; - $this->phpDocStringResolver = $phpDocStringResolver; - $this->phpDocNodeResolver = $phpDocNodeResolver; - $this->cache = $cache; - $this->anonymousClassNameHelper = $anonymousClassNameHelper; - } - - public function getResolvedPhpDoc( - string $fileName, - ?string $className, - ?string $traitName, - ?string $functionName, - string $docComment - ): ResolvedPhpDocBlock - { - if ($className === null && $traitName !== null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $phpDocKey = $this->getPhpDocKey($fileName, $className, $traitName, $functionName, $docComment); - if (isset($this->resolvedPhpDocBlockCache[$phpDocKey])) { - return $this->resolvedPhpDocBlockCache[$phpDocKey]; - } - - $phpDocMap = []; - - if (!isset($this->inProcess[$fileName])) { - $phpDocMap = $this->getResolvedPhpDocMap($fileName); - } - - if (isset($phpDocMap[$phpDocKey])) { - return $this->createResolvedPhpDocBlock($phpDocKey, $phpDocMap[$phpDocKey], $fileName); - } - - if (!isset($this->inProcess[$fileName][$phpDocKey])) { // wrong $fileName due to traits - return ResolvedPhpDocBlock::createEmpty(); - } - - if ($this->inProcess[$fileName][$phpDocKey] === false) { // PHPDoc has cyclic dependency - return ResolvedPhpDocBlock::createEmpty(); - } - - if (is_callable($this->inProcess[$fileName][$phpDocKey])) { - $resolveCallback = $this->inProcess[$fileName][$phpDocKey]; - $this->inProcess[$fileName][$phpDocKey] = false; - $this->inProcess[$fileName][$phpDocKey] = $resolveCallback(); - } - - return $this->createResolvedPhpDocBlock($phpDocKey, $this->inProcess[$fileName][$phpDocKey], $fileName); - } - - private function createResolvedPhpDocBlock(string $phpDocKey, NameScopedPhpDocString $nameScopedPhpDocString, string $fileName): ResolvedPhpDocBlock - { - $phpDocString = $nameScopedPhpDocString->getPhpDocString(); - $phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString); - $nameScope = $nameScopedPhpDocString->getNameScope(); - $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); - $templateTypeScope = $nameScope->getTemplateTypeScope(); - - if ($templateTypeScope !== null) { - $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { - return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); - }, $templateTags)); - $nameScope = $nameScope->withTemplateTypeMap( - new TemplateTypeMap(array_merge( - $nameScope->getTemplateTypeMap()->getTypes(), - $templateTypeMap->getTypes() - )) - ); - $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); - $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { - return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); - }, $templateTags)); - $nameScope = $nameScope->withTemplateTypeMap( - new TemplateTypeMap(array_merge( - $nameScope->getTemplateTypeMap()->getTypes(), - $templateTypeMap->getTypes() - )) - ); - } else { - $templateTypeMap = TemplateTypeMap::createEmpty(); - } - - $this->resolvedPhpDocBlockCache[$phpDocKey] = ResolvedPhpDocBlock::create( - $phpDocNode, - $phpDocString, - $fileName, - $nameScope, - $templateTypeMap, - $templateTags, - $this->phpDocNodeResolver - ); - - return $this->resolvedPhpDocBlockCache[$phpDocKey]; - } - - private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode - { - $phpDocParserVersion = 'Version unknown'; - try { - $phpDocParserVersion = \Jean85\PrettyVersions::getVersion('phpstan/phpdoc-parser')->getPrettyVersion(); - } catch (\OutOfBoundsException $e) { - // skip - } - $cacheKey = sprintf('phpdocstring-%s', $phpDocString); - $phpDocNodeSerializedString = $this->cache->load($cacheKey, $phpDocParserVersion); - if ($phpDocNodeSerializedString !== null) { - $unserializeResult = @unserialize($phpDocNodeSerializedString); - if ($unserializeResult === false) { - $error = error_get_last(); - if ($error !== null) { - throw new \PHPStan\ShouldNotHappenException(sprintf('unserialize() error: %s', $error['message'])); - } - - throw new \PHPStan\ShouldNotHappenException('Unknown unserialize() error'); - } - - return $unserializeResult; - } - - $phpDocNode = $this->phpDocStringResolver->resolve($phpDocString); - if ($this->shouldPhpDocNodeBeCachedToDisk($phpDocNode)) { - $this->cache->save($cacheKey, $phpDocParserVersion, serialize($phpDocNode)); - } - - return $phpDocNode; - } - - private function shouldPhpDocNodeBeCachedToDisk(PhpDocNode $phpDocNode): bool - { - foreach ($phpDocNode->getTags() as $phpDocTag) { - if (!$phpDocTag->value instanceof InvalidTagValueNode) { - continue; - } - - return false; - } - - return true; - } - - /** - * @param string $fileName - * @return \PHPStan\PhpDoc\NameScopedPhpDocString[] - */ - private function getResolvedPhpDocMap(string $fileName): array - { - if (!isset($this->memoryCache[$fileName])) { - $cacheKey = sprintf('%s-phpdocstring-v9-type-alias', $fileName); - $variableCacheKey = implode(',', array_map(static function (array $file): string { - return sprintf('%s-%d', $file['filename'], $file['modifiedTime']); - }, $this->getCachedDependentFilesWithTimestamps($fileName))); - $map = $this->cache->load($cacheKey, $variableCacheKey); - - if ($map === null) { - $map = $this->createResolvedPhpDocMap($fileName); - $this->cache->save($cacheKey, $variableCacheKey, $map); - } - - $this->memoryCache[$fileName] = $map; - } - - return $this->memoryCache[$fileName]; - } - - /** - * @param string $fileName - * @return \PHPStan\PhpDoc\NameScopedPhpDocString[] - */ - private function createResolvedPhpDocMap(string $fileName): array - { - $phpDocMap = $this->createFilePhpDocMap($fileName, null, null); - $resolvedPhpDocMap = []; - - try { - $this->inProcess[$fileName] = $phpDocMap; - - foreach ($phpDocMap as $phpDocKey => $resolveCallback) { - $this->inProcess[$fileName][$phpDocKey] = false; - $this->inProcess[$fileName][$phpDocKey] = $data = $resolveCallback(); - $resolvedPhpDocMap[$phpDocKey] = $data; - } - - } finally { - unset($this->inProcess[$fileName]); - } - - return $resolvedPhpDocMap; - } - - /** - * @param string $fileName - * @param string|null $lookForTrait - * @param string|null $traitUseClass - * @param array $traitMethodAliases - * @return (callable(): \PHPStan\PhpDoc\NameScopedPhpDocString)[] - */ - private function createFilePhpDocMap( - string $fileName, - ?string $lookForTrait, - ?string $traitUseClass, - array $traitMethodAliases = [] - ): array - { - /** @var (callable(): \PHPStan\PhpDoc\NameScopedPhpDocString)[] $phpDocMap */ - $phpDocMap = []; - - /** @var (callable(): TemplateTypeMap)[] $typeMapStack */ - $typeMapStack = []; - - /** @var array> $typeAliasStack */ - $typeAliasStack = []; - - /** @var string[] $classStack */ - $classStack = []; - if ($lookForTrait !== null && $traitUseClass !== null) { - $classStack[] = $traitUseClass; - $typeAliasStack[] = []; - } - $namespace = null; - $functionName = null; - $uses = []; - $this->processNodes( - $this->phpParser->parseFile($fileName), - function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAliases, &$phpDocMap, &$classStack, &$typeAliasStack, &$namespace, &$functionName, &$uses, &$typeMapStack): ?int { - $resolvableTemplateTypes = false; - if ($node instanceof Node\Stmt\ClassLike) { - if ($lookForTrait !== null) { - if (!$node instanceof Node\Stmt\Trait_) { - return self::SKIP_NODE; - } - if ((string) $node->namespacedName !== $lookForTrait) { - return self::SKIP_NODE; - } - } else { - if ($node->name === null) { - if (!$node instanceof Node\Stmt\Class_) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $className = $this->anonymousClassNameHelper->getAnonymousClassName($node, $fileName); - } elseif ((bool) $node->getAttribute('anonymousClass', false)) { - $className = $node->name->name; - } else { - $className = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); - } - $classStack[] = $className; - $typeAliasStack[] = $this->getTypeAliasesMap($node->getDocComment()); - $functionName = null; - $resolvableTemplateTypes = true; - } - } elseif ($node instanceof Node\Stmt\TraitUse) { - $resolvableTemplateTypes = true; - } elseif ($node instanceof Node\Stmt\ClassMethod) { - $functionName = $node->name->name; - if (array_key_exists($functionName, $traitMethodAliases)) { - $functionName = $traitMethodAliases[$functionName]; - } - $resolvableTemplateTypes = true; - } elseif ( - $node instanceof Node\Param - && $node->flags !== 0 - ) { - $resolvableTemplateTypes = true; - } elseif ($node instanceof Node\Stmt\Function_) { - $functionName = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); - $resolvableTemplateTypes = true; - } elseif ($node instanceof Node\Stmt\Property) { - $resolvableTemplateTypes = true; - } elseif ( - !$node instanceof Node\Stmt - && !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignRef - ) { - return null; - } - - foreach (array_reverse($node->getComments()) as $comment) { - if (!$comment instanceof Doc) { - continue; - } - - $phpDocString = $comment->getText(); - $className = $classStack[count($classStack) - 1] ?? null; - $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; - $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; - - $phpDocKey = $this->getPhpDocKey($fileName, $className, $lookForTrait, $functionName, $phpDocString); - $phpDocMap[$phpDocKey] = static function () use ($phpDocString, $namespace, $uses, $className, $functionName, $typeMapCb, $typeAliasesMap, $resolvableTemplateTypes): NameScopedPhpDocString { - $nameScope = new NameScope( - $namespace, - $uses, - $className, - $functionName, - ($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty())->map(static function (string $name, Type $type) use ($className, $resolvableTemplateTypes): Type { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type { - if (!$type instanceof TemplateType) { - return $traverse($type); - } - - if (!$resolvableTemplateTypes) { - return $traverse($type->toArgument()); - } - - $scope = $type->getScope(); - - if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) { - return $traverse($type->toArgument()); - } - - return $traverse($type); - }); - }), - $typeAliasesMap - ); - return new NameScopedPhpDocString($phpDocString, $nameScope); - }; - - if (!($node instanceof Node\Stmt\ClassLike) && !($node instanceof Node\FunctionLike)) { - continue; - } - - $typeMapStack[] = function () use ($fileName, $className, $lookForTrait, $functionName, $phpDocString, $typeMapCb): TemplateTypeMap { - $resolvedPhpDoc = $this->getResolvedPhpDoc( - $fileName, - $className, - $lookForTrait, - $functionName, - $phpDocString - ); - return new TemplateTypeMap(array_merge( - $typeMapCb !== null ? $typeMapCb()->getTypes() : [], - $resolvedPhpDoc->getTemplateTypeMap()->getTypes() - )); - }; - - return self::POP_TYPE_MAP_STACK; - } - - if ($node instanceof \PhpParser\Node\Stmt\Namespace_) { - $namespace = (string) $node->name; - } elseif ($node instanceof \PhpParser\Node\Stmt\Use_ && $node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { - foreach ($node->uses as $use) { - $uses[strtolower($use->getAlias()->name)] = (string) $use->name; - } - } elseif ($node instanceof \PhpParser\Node\Stmt\GroupUse) { - $prefix = (string) $node->prefix; - foreach ($node->uses as $use) { - if ($node->type !== \PhpParser\Node\Stmt\Use_::TYPE_NORMAL && $use->type !== \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { - continue; - } - - $uses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name); - } - } elseif ($node instanceof Node\Stmt\TraitUse) { - $traitMethodAliases = []; - foreach ($node->adaptations as $traitUseAdaptation) { - if (!$traitUseAdaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { - continue; - } - - if ($traitUseAdaptation->trait === null) { - continue; - } - - if ($traitUseAdaptation->newName === null) { - continue; - } - - $traitMethodAliases[$traitUseAdaptation->trait->toString()][$traitUseAdaptation->method->toString()] = $traitUseAdaptation->newName->toString(); - } - - $useDocComment = null; - if ($node->getDocComment() !== null) { - $useDocComment = $node->getDocComment()->getText(); - } - - foreach ($node->traits as $traitName) { - /** @var class-string $traitName */ - $traitName = (string) $traitName; - $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); - if (!$reflectionProvider->hasClass($traitName)) { - continue; - } - - $traitReflection = $reflectionProvider->getClass($traitName); - if (!$traitReflection->isTrait()) { - continue; - } - if ($traitReflection->getFileName() === false) { - continue; - } - if (!file_exists($traitReflection->getFileName())) { - continue; - } - - $className = $classStack[count($classStack) - 1] ?? null; - if ($className === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $traitPhpDocMap = $this->createFilePhpDocMap( - $traitReflection->getFileName(), - $traitName, - $className, - $traitMethodAliases[$traitName] ?? [] - ); - $finalTraitPhpDocMap = []; - foreach ($traitPhpDocMap as $phpDocKey => $callback) { - $finalTraitPhpDocMap[$phpDocKey] = function () use ($callback, $traitReflection, $fileName, $className, $lookForTrait, $useDocComment): NameScopedPhpDocString { - /** @var NameScopedPhpDocString $original */ - $original = $callback(); - if (!$traitReflection->isGeneric()) { - return $original; - } - - $traitTemplateTypeMap = $traitReflection->getTemplateTypeMap(); - - $useType = null; - if ($useDocComment !== null) { - $useTags = $this->getResolvedPhpDoc( - $fileName, - $className, - $lookForTrait, - null, - $useDocComment - )->getUsesTags(); - foreach ($useTags as $useTag) { - $useTagType = $useTag->getType(); - if (!$useTagType instanceof GenericObjectType) { - continue; - } - - if ($useTagType->getClassName() !== $traitReflection->getName()) { - continue; - } - - $useType = $useTagType; - break; - } - } - - if ($useType === null) { - return new NameScopedPhpDocString( - $original->getPhpDocString(), - $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->resolveToBounds()) - ); - } - - $transformedTraitTypeMap = $traitReflection->typeMapFromList($useType->getTypes()); - - return new NameScopedPhpDocString( - $original->getPhpDocString(), - $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->map(static function (string $name, Type $type) use ($transformedTraitTypeMap): Type { - return TemplateTypeHelper::resolveTemplateTypes($type, $transformedTraitTypeMap); - })) - ); - }; - } - $phpDocMap = array_merge($phpDocMap, $finalTraitPhpDocMap); - } - } - - return null; - }, - static function (\PhpParser\Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionName, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack): void { - if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) { - if (count($classStack) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - array_pop($classStack); - - if (count($typeAliasStack) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - array_pop($typeAliasStack); - } elseif ($node instanceof \PhpParser\Node\Stmt\Namespace_) { - $namespace = null; - $uses = []; - } elseif ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { - $functionName = null; - } - if ($callbackResult !== self::POP_TYPE_MAP_STACK) { - return; - } - - if (count($typeMapStack) === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - array_pop($typeMapStack); - } - ); - - if (count($typeMapStack) > 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $phpDocMap; - } - - /** - * @param Doc|null $docComment - * @return array - */ - private function getTypeAliasesMap(?Doc $docComment): array - { - if ($docComment === null) { - return []; - } - - $phpDocNode = $this->phpDocStringResolver->resolve($docComment->getText()); - $nameScope = new NameScope(null, []); - - $aliasesMap = []; - foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope)) as $key) { - $aliasesMap[$key] = true; - } - - foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope)) as $key) { - $aliasesMap[$key] = true; - } - - return $aliasesMap; - } - - /** - * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node - * @param \Closure(\PhpParser\Node $node): mixed $nodeCallback - * @param \Closure(\PhpParser\Node $node, mixed $callbackResult): void $endNodeCallback - */ - private function processNodes($node, \Closure $nodeCallback, \Closure $endNodeCallback): void - { - if ($node instanceof Node) { - $callbackResult = $nodeCallback($node); - if ($callbackResult === self::SKIP_NODE) { - return; - } - foreach ($node->getSubNodeNames() as $subNodeName) { - $subNode = $node->{$subNodeName}; - $this->processNodes($subNode, $nodeCallback, $endNodeCallback); - } - $endNodeCallback($node, $callbackResult); - } elseif (is_array($node)) { - foreach ($node as $subNode) { - $this->processNodes($subNode, $nodeCallback, $endNodeCallback); - } - } - } - - private function getPhpDocKey( - string $file, - ?string $class, - ?string $trait, - ?string $function, - string $docComment - ): string - { - $cacheKey = md5($docComment); - if (!isset($this->docKeys[$cacheKey])) { - $this->docKeys[$cacheKey] = \Nette\Utils\Strings::replace($docComment, '#\s+#', ' '); - } - $docComment = $this->docKeys[$cacheKey]; - - if ($class === null && $trait === null && $function === null) { - return md5(sprintf('%s-%s', $file, $docComment)); - } - - return md5(sprintf('%s-%s-%s-%s', $class, $trait, $function, $docComment)); - } - - /** - * @param string $fileName - * @return array - */ - private function getCachedDependentFilesWithTimestamps(string $fileName): array - { - $cacheKey = sprintf('dependentFilesTimestamps-%s', $fileName); - $fileModifiedTime = filemtime($fileName); - if ($fileModifiedTime === false) { - $fileModifiedTime = time(); - } - $variableCacheKey = sprintf('%d', $fileModifiedTime); - /** @var array|null $cachedFilesTimestamps */ - $cachedFilesTimestamps = $this->cache->load($cacheKey, $variableCacheKey); - if ($cachedFilesTimestamps !== null) { - $useCached = true; - foreach ($cachedFilesTimestamps as $cachedFile) { - $cachedFilename = $cachedFile['filename']; - $cachedTimestamp = $cachedFile['modifiedTime']; - - if (!file_exists($cachedFilename)) { - $useCached = false; - break; - } - - $currentTimestamp = filemtime($cachedFilename); - if ($currentTimestamp === false) { - $useCached = false; - break; - } - - if ($currentTimestamp !== $cachedTimestamp) { - $useCached = false; - break; - } - } - - if ($useCached) { - return $cachedFilesTimestamps; - } - } - - $filesTimestamps = []; - foreach ($this->getDependentFiles($fileName) as $dependentFile) { - $dependentFileModifiedTime = filemtime($dependentFile); - if ($dependentFileModifiedTime === false) { - $dependentFileModifiedTime = time(); - } - - $filesTimestamps[] = [ - 'filename' => $dependentFile, - 'modifiedTime' => $dependentFileModifiedTime, - ]; - } - - $this->cache->save($cacheKey, $variableCacheKey, $filesTimestamps); - - return $filesTimestamps; - } - - /** - * @param string $fileName - * @return string[] - */ - private function getDependentFiles(string $fileName): array - { - $dependentFiles = [$fileName]; - - if (isset($this->alreadyProcessedDependentFiles[$fileName])) { - return $dependentFiles; - } - - $this->alreadyProcessedDependentFiles[$fileName] = true; - - $this->processNodes( - $this->phpParser->parseFile($fileName), - function (Node $node) use (&$dependentFiles) { - if ($node instanceof Node\Stmt\Declare_) { - return null; - } - if ($node instanceof Node\Stmt\Namespace_) { - return null; - } - - if (!$node instanceof Node\Stmt\Class_ && !$node instanceof Node\Stmt\Trait_) { - return null; - } - - foreach ($node->stmts as $stmt) { - if (!$stmt instanceof Node\Stmt\TraitUse) { - continue; - } - - foreach ($stmt->traits as $traitName) { - $traitName = (string) $traitName; - if (!trait_exists($traitName)) { - continue; - } - - $traitReflection = new \ReflectionClass($traitName); - if ($traitReflection->getFileName() === false) { - continue; - } - if (!file_exists($traitReflection->getFileName())) { - continue; - } - - foreach ($this->getDependentFiles($traitReflection->getFileName()) as $traitFileName) { - $dependentFiles[] = $traitFileName; - } - } - } - - return null; - }, - static function (): void { - } - ); - - unset($this->alreadyProcessedDependentFiles[$fileName]); - - return $dependentFiles; - } - + private const SKIP_NODE = 1; + private const POP_TYPE_MAP_STACK = 2; + + private ReflectionProviderProvider $reflectionProviderProvider; + + private \PHPStan\Parser\Parser $phpParser; + + private \PHPStan\PhpDoc\PhpDocStringResolver $phpDocStringResolver; + + private \PHPStan\PhpDoc\PhpDocNodeResolver $phpDocNodeResolver; + + private \PHPStan\Cache\Cache $cache; + + private \PHPStan\Broker\AnonymousClassNameHelper $anonymousClassNameHelper; + + /** @var \PHPStan\PhpDoc\NameScopedPhpDocString[][] */ + private array $memoryCache = []; + + /** @var (false|(callable(): \PHPStan\PhpDoc\NameScopedPhpDocString)|\PHPStan\PhpDoc\NameScopedPhpDocString)[][] */ + private array $inProcess = []; + + /** @var array */ + private array $resolvedPhpDocBlockCache = []; + + /** @var array */ + private array $alreadyProcessedDependentFiles = []; + + /** @var array */ + private array $docKeys = []; + + public function __construct( + ReflectionProviderProvider $reflectionProviderProvider, + Parser $phpParser, + PhpDocStringResolver $phpDocStringResolver, + PhpDocNodeResolver $phpDocNodeResolver, + Cache $cache, + AnonymousClassNameHelper $anonymousClassNameHelper + ) { + $this->reflectionProviderProvider = $reflectionProviderProvider; + $this->phpParser = $phpParser; + $this->phpDocStringResolver = $phpDocStringResolver; + $this->phpDocNodeResolver = $phpDocNodeResolver; + $this->cache = $cache; + $this->anonymousClassNameHelper = $anonymousClassNameHelper; + } + + public function getResolvedPhpDoc( + string $fileName, + ?string $className, + ?string $traitName, + ?string $functionName, + string $docComment + ): ResolvedPhpDocBlock { + if ($className === null && $traitName !== null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $phpDocKey = $this->getPhpDocKey($fileName, $className, $traitName, $functionName, $docComment); + if (isset($this->resolvedPhpDocBlockCache[$phpDocKey])) { + return $this->resolvedPhpDocBlockCache[$phpDocKey]; + } + + $phpDocMap = []; + + if (!isset($this->inProcess[$fileName])) { + $phpDocMap = $this->getResolvedPhpDocMap($fileName); + } + + if (isset($phpDocMap[$phpDocKey])) { + return $this->createResolvedPhpDocBlock($phpDocKey, $phpDocMap[$phpDocKey], $fileName); + } + + if (!isset($this->inProcess[$fileName][$phpDocKey])) { // wrong $fileName due to traits + return ResolvedPhpDocBlock::createEmpty(); + } + + if ($this->inProcess[$fileName][$phpDocKey] === false) { // PHPDoc has cyclic dependency + return ResolvedPhpDocBlock::createEmpty(); + } + + if (is_callable($this->inProcess[$fileName][$phpDocKey])) { + $resolveCallback = $this->inProcess[$fileName][$phpDocKey]; + $this->inProcess[$fileName][$phpDocKey] = false; + $this->inProcess[$fileName][$phpDocKey] = $resolveCallback(); + } + + return $this->createResolvedPhpDocBlock($phpDocKey, $this->inProcess[$fileName][$phpDocKey], $fileName); + } + + private function createResolvedPhpDocBlock(string $phpDocKey, NameScopedPhpDocString $nameScopedPhpDocString, string $fileName): ResolvedPhpDocBlock + { + $phpDocString = $nameScopedPhpDocString->getPhpDocString(); + $phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString); + $nameScope = $nameScopedPhpDocString->getNameScope(); + $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); + $templateTypeScope = $nameScope->getTemplateTypeScope(); + + if ($templateTypeScope !== null) { + $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { + return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); + }, $templateTags)); + $nameScope = $nameScope->withTemplateTypeMap( + new TemplateTypeMap(array_merge( + $nameScope->getTemplateTypeMap()->getTypes(), + $templateTypeMap->getTypes() + )) + ); + $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); + $templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type { + return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag); + }, $templateTags)); + $nameScope = $nameScope->withTemplateTypeMap( + new TemplateTypeMap(array_merge( + $nameScope->getTemplateTypeMap()->getTypes(), + $templateTypeMap->getTypes() + )) + ); + } else { + $templateTypeMap = TemplateTypeMap::createEmpty(); + } + + $this->resolvedPhpDocBlockCache[$phpDocKey] = ResolvedPhpDocBlock::create( + $phpDocNode, + $phpDocString, + $fileName, + $nameScope, + $templateTypeMap, + $templateTags, + $this->phpDocNodeResolver + ); + + return $this->resolvedPhpDocBlockCache[$phpDocKey]; + } + + private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode + { + $phpDocParserVersion = 'Version unknown'; + try { + $phpDocParserVersion = \Jean85\PrettyVersions::getVersion('phpstan/phpdoc-parser')->getPrettyVersion(); + } catch (\OutOfBoundsException $e) { + // skip + } + $cacheKey = sprintf('phpdocstring-%s', $phpDocString); + $phpDocNodeSerializedString = $this->cache->load($cacheKey, $phpDocParserVersion); + if ($phpDocNodeSerializedString !== null) { + $unserializeResult = @unserialize($phpDocNodeSerializedString); + if ($unserializeResult === false) { + $error = error_get_last(); + if ($error !== null) { + throw new \PHPStan\ShouldNotHappenException(sprintf('unserialize() error: %s', $error['message'])); + } + + throw new \PHPStan\ShouldNotHappenException('Unknown unserialize() error'); + } + + return $unserializeResult; + } + + $phpDocNode = $this->phpDocStringResolver->resolve($phpDocString); + if ($this->shouldPhpDocNodeBeCachedToDisk($phpDocNode)) { + $this->cache->save($cacheKey, $phpDocParserVersion, serialize($phpDocNode)); + } + + return $phpDocNode; + } + + private function shouldPhpDocNodeBeCachedToDisk(PhpDocNode $phpDocNode): bool + { + foreach ($phpDocNode->getTags() as $phpDocTag) { + if (!$phpDocTag->value instanceof InvalidTagValueNode) { + continue; + } + + return false; + } + + return true; + } + + /** + * @param string $fileName + * @return \PHPStan\PhpDoc\NameScopedPhpDocString[] + */ + private function getResolvedPhpDocMap(string $fileName): array + { + if (!isset($this->memoryCache[$fileName])) { + $cacheKey = sprintf('%s-phpdocstring-v9-type-alias', $fileName); + $variableCacheKey = implode(',', array_map(static function (array $file): string { + return sprintf('%s-%d', $file['filename'], $file['modifiedTime']); + }, $this->getCachedDependentFilesWithTimestamps($fileName))); + $map = $this->cache->load($cacheKey, $variableCacheKey); + + if ($map === null) { + $map = $this->createResolvedPhpDocMap($fileName); + $this->cache->save($cacheKey, $variableCacheKey, $map); + } + + $this->memoryCache[$fileName] = $map; + } + + return $this->memoryCache[$fileName]; + } + + /** + * @param string $fileName + * @return \PHPStan\PhpDoc\NameScopedPhpDocString[] + */ + private function createResolvedPhpDocMap(string $fileName): array + { + $phpDocMap = $this->createFilePhpDocMap($fileName, null, null); + $resolvedPhpDocMap = []; + + try { + $this->inProcess[$fileName] = $phpDocMap; + + foreach ($phpDocMap as $phpDocKey => $resolveCallback) { + $this->inProcess[$fileName][$phpDocKey] = false; + $this->inProcess[$fileName][$phpDocKey] = $data = $resolveCallback(); + $resolvedPhpDocMap[$phpDocKey] = $data; + } + } finally { + unset($this->inProcess[$fileName]); + } + + return $resolvedPhpDocMap; + } + + /** + * @param string $fileName + * @param string|null $lookForTrait + * @param string|null $traitUseClass + * @param array $traitMethodAliases + * @return (callable(): \PHPStan\PhpDoc\NameScopedPhpDocString)[] + */ + private function createFilePhpDocMap( + string $fileName, + ?string $lookForTrait, + ?string $traitUseClass, + array $traitMethodAliases = [] + ): array { + /** @var (callable(): \PHPStan\PhpDoc\NameScopedPhpDocString)[] $phpDocMap */ + $phpDocMap = []; + + /** @var (callable(): TemplateTypeMap)[] $typeMapStack */ + $typeMapStack = []; + + /** @var array> $typeAliasStack */ + $typeAliasStack = []; + + /** @var string[] $classStack */ + $classStack = []; + if ($lookForTrait !== null && $traitUseClass !== null) { + $classStack[] = $traitUseClass; + $typeAliasStack[] = []; + } + $namespace = null; + $functionName = null; + $uses = []; + $this->processNodes( + $this->phpParser->parseFile($fileName), + function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAliases, &$phpDocMap, &$classStack, &$typeAliasStack, &$namespace, &$functionName, &$uses, &$typeMapStack): ?int { + $resolvableTemplateTypes = false; + if ($node instanceof Node\Stmt\ClassLike) { + if ($lookForTrait !== null) { + if (!$node instanceof Node\Stmt\Trait_) { + return self::SKIP_NODE; + } + if ((string) $node->namespacedName !== $lookForTrait) { + return self::SKIP_NODE; + } + } else { + if ($node->name === null) { + if (!$node instanceof Node\Stmt\Class_) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $className = $this->anonymousClassNameHelper->getAnonymousClassName($node, $fileName); + } elseif ((bool) $node->getAttribute('anonymousClass', false)) { + $className = $node->name->name; + } else { + $className = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); + } + $classStack[] = $className; + $typeAliasStack[] = $this->getTypeAliasesMap($node->getDocComment()); + $functionName = null; + $resolvableTemplateTypes = true; + } + } elseif ($node instanceof Node\Stmt\TraitUse) { + $resolvableTemplateTypes = true; + } elseif ($node instanceof Node\Stmt\ClassMethod) { + $functionName = $node->name->name; + if (array_key_exists($functionName, $traitMethodAliases)) { + $functionName = $traitMethodAliases[$functionName]; + } + $resolvableTemplateTypes = true; + } elseif ( + $node instanceof Node\Param + && $node->flags !== 0 + ) { + $resolvableTemplateTypes = true; + } elseif ($node instanceof Node\Stmt\Function_) { + $functionName = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); + $resolvableTemplateTypes = true; + } elseif ($node instanceof Node\Stmt\Property) { + $resolvableTemplateTypes = true; + } elseif ( + !$node instanceof Node\Stmt + && !$node instanceof Node\Expr\Assign + && !$node instanceof Node\Expr\AssignRef + ) { + return null; + } + + foreach (array_reverse($node->getComments()) as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $phpDocString = $comment->getText(); + $className = $classStack[count($classStack) - 1] ?? null; + $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; + $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; + + $phpDocKey = $this->getPhpDocKey($fileName, $className, $lookForTrait, $functionName, $phpDocString); + $phpDocMap[$phpDocKey] = static function () use ($phpDocString, $namespace, $uses, $className, $functionName, $typeMapCb, $typeAliasesMap, $resolvableTemplateTypes): NameScopedPhpDocString { + $nameScope = new NameScope( + $namespace, + $uses, + $className, + $functionName, + ($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty())->map(static function (string $name, Type $type) use ($className, $resolvableTemplateTypes): Type { + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type { + if (!$type instanceof TemplateType) { + return $traverse($type); + } + + if (!$resolvableTemplateTypes) { + return $traverse($type->toArgument()); + } + + $scope = $type->getScope(); + + if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) { + return $traverse($type->toArgument()); + } + + return $traverse($type); + }); + }), + $typeAliasesMap + ); + return new NameScopedPhpDocString($phpDocString, $nameScope); + }; + + if (!($node instanceof Node\Stmt\ClassLike) && !($node instanceof Node\FunctionLike)) { + continue; + } + + $typeMapStack[] = function () use ($fileName, $className, $lookForTrait, $functionName, $phpDocString, $typeMapCb): TemplateTypeMap { + $resolvedPhpDoc = $this->getResolvedPhpDoc( + $fileName, + $className, + $lookForTrait, + $functionName, + $phpDocString + ); + return new TemplateTypeMap(array_merge( + $typeMapCb !== null ? $typeMapCb()->getTypes() : [], + $resolvedPhpDoc->getTemplateTypeMap()->getTypes() + )); + }; + + return self::POP_TYPE_MAP_STACK; + } + + if ($node instanceof \PhpParser\Node\Stmt\Namespace_) { + $namespace = (string) $node->name; + } elseif ($node instanceof \PhpParser\Node\Stmt\Use_ && $node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { + foreach ($node->uses as $use) { + $uses[strtolower($use->getAlias()->name)] = (string) $use->name; + } + } elseif ($node instanceof \PhpParser\Node\Stmt\GroupUse) { + $prefix = (string) $node->prefix; + foreach ($node->uses as $use) { + if ($node->type !== \PhpParser\Node\Stmt\Use_::TYPE_NORMAL && $use->type !== \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { + continue; + } + + $uses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name); + } + } elseif ($node instanceof Node\Stmt\TraitUse) { + $traitMethodAliases = []; + foreach ($node->adaptations as $traitUseAdaptation) { + if (!$traitUseAdaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + continue; + } + + if ($traitUseAdaptation->trait === null) { + continue; + } + + if ($traitUseAdaptation->newName === null) { + continue; + } + + $traitMethodAliases[$traitUseAdaptation->trait->toString()][$traitUseAdaptation->method->toString()] = $traitUseAdaptation->newName->toString(); + } + + $useDocComment = null; + if ($node->getDocComment() !== null) { + $useDocComment = $node->getDocComment()->getText(); + } + + foreach ($node->traits as $traitName) { + /** @var class-string $traitName */ + $traitName = (string) $traitName; + $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); + if (!$reflectionProvider->hasClass($traitName)) { + continue; + } + + $traitReflection = $reflectionProvider->getClass($traitName); + if (!$traitReflection->isTrait()) { + continue; + } + if ($traitReflection->getFileName() === false) { + continue; + } + if (!file_exists($traitReflection->getFileName())) { + continue; + } + + $className = $classStack[count($classStack) - 1] ?? null; + if ($className === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $traitPhpDocMap = $this->createFilePhpDocMap( + $traitReflection->getFileName(), + $traitName, + $className, + $traitMethodAliases[$traitName] ?? [] + ); + $finalTraitPhpDocMap = []; + foreach ($traitPhpDocMap as $phpDocKey => $callback) { + $finalTraitPhpDocMap[$phpDocKey] = function () use ($callback, $traitReflection, $fileName, $className, $lookForTrait, $useDocComment): NameScopedPhpDocString { + /** @var NameScopedPhpDocString $original */ + $original = $callback(); + if (!$traitReflection->isGeneric()) { + return $original; + } + + $traitTemplateTypeMap = $traitReflection->getTemplateTypeMap(); + + $useType = null; + if ($useDocComment !== null) { + $useTags = $this->getResolvedPhpDoc( + $fileName, + $className, + $lookForTrait, + null, + $useDocComment + )->getUsesTags(); + foreach ($useTags as $useTag) { + $useTagType = $useTag->getType(); + if (!$useTagType instanceof GenericObjectType) { + continue; + } + + if ($useTagType->getClassName() !== $traitReflection->getName()) { + continue; + } + + $useType = $useTagType; + break; + } + } + + if ($useType === null) { + return new NameScopedPhpDocString( + $original->getPhpDocString(), + $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->resolveToBounds()) + ); + } + + $transformedTraitTypeMap = $traitReflection->typeMapFromList($useType->getTypes()); + + return new NameScopedPhpDocString( + $original->getPhpDocString(), + $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->map(static function (string $name, Type $type) use ($transformedTraitTypeMap): Type { + return TemplateTypeHelper::resolveTemplateTypes($type, $transformedTraitTypeMap); + })) + ); + }; + } + $phpDocMap = array_merge($phpDocMap, $finalTraitPhpDocMap); + } + } + + return null; + }, + static function (\PhpParser\Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionName, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack): void { + if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) { + if (count($classStack) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + array_pop($classStack); + + if (count($typeAliasStack) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + array_pop($typeAliasStack); + } elseif ($node instanceof \PhpParser\Node\Stmt\Namespace_) { + $namespace = null; + $uses = []; + } elseif ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { + $functionName = null; + } + if ($callbackResult !== self::POP_TYPE_MAP_STACK) { + return; + } + + if (count($typeMapStack) === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + array_pop($typeMapStack); + } + ); + + if (count($typeMapStack) > 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $phpDocMap; + } + + /** + * @param Doc|null $docComment + * @return array + */ + private function getTypeAliasesMap(?Doc $docComment): array + { + if ($docComment === null) { + return []; + } + + $phpDocNode = $this->phpDocStringResolver->resolve($docComment->getText()); + $nameScope = new NameScope(null, []); + + $aliasesMap = []; + foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope)) as $key) { + $aliasesMap[$key] = true; + } + + foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope)) as $key) { + $aliasesMap[$key] = true; + } + + return $aliasesMap; + } + + /** + * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node + * @param \Closure(\PhpParser\Node $node): mixed $nodeCallback + * @param \Closure(\PhpParser\Node $node, mixed $callbackResult): void $endNodeCallback + */ + private function processNodes($node, \Closure $nodeCallback, \Closure $endNodeCallback): void + { + if ($node instanceof Node) { + $callbackResult = $nodeCallback($node); + if ($callbackResult === self::SKIP_NODE) { + return; + } + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->{$subNodeName}; + $this->processNodes($subNode, $nodeCallback, $endNodeCallback); + } + $endNodeCallback($node, $callbackResult); + } elseif (is_array($node)) { + foreach ($node as $subNode) { + $this->processNodes($subNode, $nodeCallback, $endNodeCallback); + } + } + } + + private function getPhpDocKey( + string $file, + ?string $class, + ?string $trait, + ?string $function, + string $docComment + ): string { + $cacheKey = md5($docComment); + if (!isset($this->docKeys[$cacheKey])) { + $this->docKeys[$cacheKey] = \Nette\Utils\Strings::replace($docComment, '#\s+#', ' '); + } + $docComment = $this->docKeys[$cacheKey]; + + if ($class === null && $trait === null && $function === null) { + return md5(sprintf('%s-%s', $file, $docComment)); + } + + return md5(sprintf('%s-%s-%s-%s', $class, $trait, $function, $docComment)); + } + + /** + * @param string $fileName + * @return array + */ + private function getCachedDependentFilesWithTimestamps(string $fileName): array + { + $cacheKey = sprintf('dependentFilesTimestamps-%s', $fileName); + $fileModifiedTime = filemtime($fileName); + if ($fileModifiedTime === false) { + $fileModifiedTime = time(); + } + $variableCacheKey = sprintf('%d', $fileModifiedTime); + /** @var array|null $cachedFilesTimestamps */ + $cachedFilesTimestamps = $this->cache->load($cacheKey, $variableCacheKey); + if ($cachedFilesTimestamps !== null) { + $useCached = true; + foreach ($cachedFilesTimestamps as $cachedFile) { + $cachedFilename = $cachedFile['filename']; + $cachedTimestamp = $cachedFile['modifiedTime']; + + if (!file_exists($cachedFilename)) { + $useCached = false; + break; + } + + $currentTimestamp = filemtime($cachedFilename); + if ($currentTimestamp === false) { + $useCached = false; + break; + } + + if ($currentTimestamp !== $cachedTimestamp) { + $useCached = false; + break; + } + } + + if ($useCached) { + return $cachedFilesTimestamps; + } + } + + $filesTimestamps = []; + foreach ($this->getDependentFiles($fileName) as $dependentFile) { + $dependentFileModifiedTime = filemtime($dependentFile); + if ($dependentFileModifiedTime === false) { + $dependentFileModifiedTime = time(); + } + + $filesTimestamps[] = [ + 'filename' => $dependentFile, + 'modifiedTime' => $dependentFileModifiedTime, + ]; + } + + $this->cache->save($cacheKey, $variableCacheKey, $filesTimestamps); + + return $filesTimestamps; + } + + /** + * @param string $fileName + * @return string[] + */ + private function getDependentFiles(string $fileName): array + { + $dependentFiles = [$fileName]; + + if (isset($this->alreadyProcessedDependentFiles[$fileName])) { + return $dependentFiles; + } + + $this->alreadyProcessedDependentFiles[$fileName] = true; + + $this->processNodes( + $this->phpParser->parseFile($fileName), + function (Node $node) use (&$dependentFiles) { + if ($node instanceof Node\Stmt\Declare_) { + return null; + } + if ($node instanceof Node\Stmt\Namespace_) { + return null; + } + + if (!$node instanceof Node\Stmt\Class_ && !$node instanceof Node\Stmt\Trait_) { + return null; + } + + foreach ($node->stmts as $stmt) { + if (!$stmt instanceof Node\Stmt\TraitUse) { + continue; + } + + foreach ($stmt->traits as $traitName) { + $traitName = (string) $traitName; + if (!trait_exists($traitName)) { + continue; + } + + $traitReflection = new \ReflectionClass($traitName); + if ($traitReflection->getFileName() === false) { + continue; + } + if (!file_exists($traitReflection->getFileName())) { + continue; + } + + foreach ($this->getDependentFiles($traitReflection->getFileName()) as $traitFileName) { + $dependentFiles[] = $traitFileName; + } + } + } + + return null; + }, + static function (): void { + } + ); + + unset($this->alreadyProcessedDependentFiles[$fileName]); + + return $dependentFiles; + } } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 2a50a41d59..105b0b1bae 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -1,4 +1,6 @@ -isAcceptedBy(new UnionType([ - $this, - new IntegerType(), - ]), $strictTypes); - } - - return TrinaryLogic::createNo(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return get_class($type) === static::class; - } - - public function describe(VerbosityLevel $level): string - { - return 'float'; - } - - public function toNumber(): Type - { - return $this; - } - - public function toFloat(): Type - { - return $this; - } - - public function toInteger(): Type - { - return new IntegerType(); - } - - public function toString(): Type - { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - - public function toArray(): Type - { - return new ConstantArrayType( - [new ConstantIntegerType(0)], - [$this], - 1 - ); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new ErrorType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return new ErrorType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + use NonCallableTypeTrait; + use NonIterableTypeTrait; + use NonObjectTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonTypeTrait; + use NonGenericTypeTrait; + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof self || $type instanceof IntegerType) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isAcceptedBy(new UnionType([ + $this, + new IntegerType(), + ]), $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return get_class($type) === static::class; + } + + public function describe(VerbosityLevel $level): string + { + return 'float'; + } + + public function toNumber(): Type + { + return $this; + } + + public function toFloat(): Type + { + return $this; + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toString(): Type + { + return new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]); + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new ErrorType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return new ErrorType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/FunctionTypeSpecifyingExtension.php b/src/Type/FunctionTypeSpecifyingExtension.php index d9bbd6c0cf..844972b5f1 100644 --- a/src/Type/FunctionTypeSpecifyingExtension.php +++ b/src/Type/FunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -type = $type; - } - - public function getReferencedClasses(): array - { - return $this->type->getReferencedClasses(); - } - - public function getGenericType(): Type - { - return $this->type; - } - - public function describe(VerbosityLevel $level): string - { - return sprintf('%s<%s>', parent::describe($level), $this->type->describe($level)); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isAcceptedBy($this, $strictTypes); - } - - if ($type instanceof ConstantStringType) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getValue())) { - return TrinaryLogic::createNo(); - } - - $objectType = new ObjectType($type->getValue()); - } elseif ($type instanceof self) { - $objectType = $type->type; - } elseif ($type instanceof ClassStringType) { - $objectType = new ObjectWithoutClassType(); - } elseif ($type instanceof StringType) { - return TrinaryLogic::createMaybe(); - } else { - return TrinaryLogic::createNo(); - } - - return $this->type->accepts($objectType, $strictTypes); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - if ($type instanceof ConstantStringType) { - $genericType = $this->type; - if ($genericType instanceof MixedType) { - return TrinaryLogic::createYes(); - } - - if ($genericType instanceof StaticType) { - $genericType = $genericType->getStaticObjectType(); - } - - // We are transforming constant class-string to ObjectType. But we need to filter out - // an uncertainty originating in possible ObjectType's class subtypes. - $objectType = new ObjectType($type->getValue()); - - // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType - // uncertainty into account. - if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); - } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); - } - - // Explicitly handle the uncertainty for Maybe. - if ($isSuperType->maybe()) { - return TrinaryLogic::createNo(); - } - return $isSuperType; - } elseif ($type instanceof self) { - return $this->type->isSuperTypeOf($type->type); - } elseif ($type instanceof StringType) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - public function traverse(callable $cb): Type - { - $newType = $cb($this->type); - if ($newType === $this->type) { - return $this; - } - - return new self($newType); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ($receivedType instanceof ConstantStringType) { - $typeToInfer = new ObjectType($receivedType->getValue()); - } elseif ($receivedType instanceof self) { - $typeToInfer = $receivedType->type; - } elseif ($receivedType instanceof ClassStringType) { - $typeToInfer = $this->type; - if ($typeToInfer instanceof TemplateType) { - $typeToInfer = $typeToInfer->getBound(); - } - - $typeToInfer = TypeCombinator::intersect($typeToInfer, new ObjectWithoutClassType()); - } else { - return TemplateTypeMap::createEmpty(); - } - - return $this->type->inferTemplateTypes($typeToInfer); - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant()); - - return $this->type->getReferencedTemplateTypes($variance); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if (!parent::equals($type)) { - return false; - } - - if (!$this->type->equals($type->type)) { - return false; - } - - return true; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['type']); - } - + private Type $type; + + public function __construct(Type $type) + { + $this->type = $type; + } + + public function getReferencedClasses(): array + { + return $this->type->getReferencedClasses(); + } + + public function getGenericType(): Type + { + return $this->type; + } + + public function describe(VerbosityLevel $level): string + { + return sprintf('%s<%s>', parent::describe($level), $this->type->describe($level)); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + if ($type instanceof ConstantStringType) { + $broker = Broker::getInstance(); + if (!$broker->hasClass($type->getValue())) { + return TrinaryLogic::createNo(); + } + + $objectType = new ObjectType($type->getValue()); + } elseif ($type instanceof self) { + $objectType = $type->type; + } elseif ($type instanceof ClassStringType) { + $objectType = new ObjectWithoutClassType(); + } elseif ($type instanceof StringType) { + return TrinaryLogic::createMaybe(); + } else { + return TrinaryLogic::createNo(); + } + + return $this->type->accepts($objectType, $strictTypes); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + if ($type instanceof ConstantStringType) { + $genericType = $this->type; + if ($genericType instanceof MixedType) { + return TrinaryLogic::createYes(); + } + + if ($genericType instanceof StaticType) { + $genericType = $genericType->getStaticObjectType(); + } + + // We are transforming constant class-string to ObjectType. But we need to filter out + // an uncertainty originating in possible ObjectType's class subtypes. + $objectType = new ObjectType($type->getValue()); + + // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType + // uncertainty into account. + if ($genericType instanceof TemplateType) { + $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + } else { + $isSuperType = $genericType->isSuperTypeOf($objectType); + } + + // Explicitly handle the uncertainty for Maybe. + if ($isSuperType->maybe()) { + return TrinaryLogic::createNo(); + } + return $isSuperType; + } elseif ($type instanceof self) { + return $this->type->isSuperTypeOf($type->type); + } elseif ($type instanceof StringType) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + public function traverse(callable $cb): Type + { + $newType = $cb($this->type); + if ($newType === $this->type) { + return $this; + } + + return new self($newType); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ($receivedType instanceof ConstantStringType) { + $typeToInfer = new ObjectType($receivedType->getValue()); + } elseif ($receivedType instanceof self) { + $typeToInfer = $receivedType->type; + } elseif ($receivedType instanceof ClassStringType) { + $typeToInfer = $this->type; + if ($typeToInfer instanceof TemplateType) { + $typeToInfer = $typeToInfer->getBound(); + } + + $typeToInfer = TypeCombinator::intersect($typeToInfer, new ObjectWithoutClassType()); + } else { + return TemplateTypeMap::createEmpty(); + } + + return $this->type->inferTemplateTypes($typeToInfer); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant()); + + return $this->type->getReferencedTemplateTypes($variance); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if (!parent::equals($type)) { + return false; + } + + if (!$this->type->equals($type->type)) { + return false; + } + + return true; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['type']); + } } diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 923748bd35..eb58552a58 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -1,4 +1,6 @@ - */ - private array $types; - - private ?ClassReflection $classReflection; - - /** - * @param array $types - */ - public function __construct( - string $mainType, - array $types, - ?Type $subtractedType = null, - ?ClassReflection $classReflection = null - ) - { - parent::__construct($mainType, $subtractedType, $classReflection); - $this->types = $types; - $this->classReflection = $classReflection; - } - - public function describe(VerbosityLevel $level): string - { - return sprintf( - '%s<%s>', - parent::describe($level), - implode(', ', array_map(static function (Type $type) use ($level): string { - return $type->describe($level); - }, $this->types)) - ); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if (!parent::equals($type)) { - return false; - } - - if (count($this->types) !== count($type->types)) { - return false; - } - - foreach ($this->types as $i => $genericType) { - $otherGenericType = $type->types[$i]; - if (!$genericType->equals($otherGenericType)) { - return false; - } - } - - return true; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - $classes = parent::getReferencedClasses(); - foreach ($this->types as $type) { - foreach ($type->getReferencedClasses() as $referencedClass) { - $classes[] = $referencedClass; - } - } - - return $classes; - } - - /** @return array */ - public function getTypes(): array - { - return $this->types; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isAcceptedBy($this, $strictTypes); - } - - return $this->isSuperTypeOfInternal($type, true); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfInternal($type, false); - } - - private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - $nakedSuperTypeOf = parent::isSuperTypeOf($type); - if ($nakedSuperTypeOf->no()) { - return $nakedSuperTypeOf; - } - - if (!$type instanceof ObjectType) { - return $nakedSuperTypeOf; - } - - $ancestor = $type->getAncestorWithClassName($this->getClassName()); - if ($ancestor === null) { - return $nakedSuperTypeOf; - } - if (!$ancestor instanceof self) { - if ($acceptsContext) { - return $nakedSuperTypeOf; - } - - return $nakedSuperTypeOf->and(TrinaryLogic::createMaybe()); - } - - if (count($this->types) !== count($ancestor->types)) { - return TrinaryLogic::createNo(); - } - - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return $nakedSuperTypeOf; - } - - $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); - $results = []; - foreach ($typeList as $i => $templateType) { - if (!isset($ancestor->types[$i])) { - continue; - } - if (!isset($this->types[$i])) { - continue; - } - if ($templateType instanceof ErrorType) { - continue; - } - if (!$templateType instanceof TemplateType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); - } - - if (count($results) === 0) { - return $nakedSuperTypeOf; - } - - return $nakedSuperTypeOf->and(...$results); - } - - public function getClassReflection(): ?ClassReflection - { - if ($this->classReflection !== null) { - return $this->classReflection; - } - - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->getClassName())) { - return null; - } - - return $this->classReflection = $broker->getClass($this->getClassName())->withTypes($this->types); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $prototype = parent::getUnresolvedPropertyPrototype($propertyName, $scope); - - return $prototype->doNotResolveTemplateTypeMapToBounds(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $prototype = parent::getUnresolvedMethodPrototype($methodName, $scope); - - return $prototype->doNotResolveTemplateTypeMapToBounds(); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if (!$receivedType instanceof TypeWithClassName) { - return TemplateTypeMap::createEmpty(); - } - - $ancestor = $receivedType->getAncestorWithClassName($this->getClassName()); - - if ($ancestor === null) { - return TemplateTypeMap::createEmpty(); - } - $ancestorClassReflection = $ancestor->getClassReflection(); - if ($ancestorClassReflection === null) { - return TemplateTypeMap::createEmpty(); - } - - $otherTypes = $ancestorClassReflection->typeMapToList($ancestorClassReflection->getActiveTemplateTypeMap()); - $typeMap = TemplateTypeMap::createEmpty(); - - foreach ($this->getTypes() as $i => $type) { - $other = $otherTypes[$i] ?? new ErrorType(); - $typeMap = $typeMap->union($type->inferTemplateTypes($other)); - } - - return $typeMap; - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $classReflection = $this->getClassReflection(); - if ($classReflection !== null) { - $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); - } else { - $typeList = []; - } - - $references = []; - - foreach ($this->types as $i => $type) { - $variance = $positionVariance->compose( - isset($typeList[$i]) && $typeList[$i] instanceof TemplateType - ? $typeList[$i]->getVariance() - : TemplateTypeVariance::createInvariant() - ); - foreach ($type->getReferencedTemplateTypes($variance) as $reference) { - $references[] = $reference; - } - } - - return $references; - } - - public function traverse(callable $cb): Type - { - $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null; - - $typesChanged = false; - $types = []; - foreach ($this->types as $type) { - $newType = $cb($type); - $types[] = $newType; - if ($newType === $type) { - continue; - } - - $typesChanged = true; - } - - if ($subtractedType !== $this->getSubtractedType() || $typesChanged) { - return $this->recreate($this->getClassName(), $types, $subtractedType); - } - - return $this; - } - - /** - * @param string $className - * @param Type[] $types - * @param Type|null $subtractedType - * @return self - */ - protected function recreate(string $className, array $types, ?Type $subtractedType): self - { - return new self( - $className, - $types, - $subtractedType - ); - } - - public function changeSubtractedType(?Type $subtractedType): Type - { - return new self($this->getClassName(), $this->types, $subtractedType); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['className'], - $properties['types'], - $properties['subtractedType'] ?? null - ); - } - + /** @var array */ + private array $types; + + private ?ClassReflection $classReflection; + + /** + * @param array $types + */ + public function __construct( + string $mainType, + array $types, + ?Type $subtractedType = null, + ?ClassReflection $classReflection = null + ) { + parent::__construct($mainType, $subtractedType, $classReflection); + $this->types = $types; + $this->classReflection = $classReflection; + } + + public function describe(VerbosityLevel $level): string + { + return sprintf( + '%s<%s>', + parent::describe($level), + implode(', ', array_map(static function (Type $type) use ($level): string { + return $type->describe($level); + }, $this->types)) + ); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if (!parent::equals($type)) { + return false; + } + + if (count($this->types) !== count($type->types)) { + return false; + } + + foreach ($this->types as $i => $genericType) { + $otherGenericType = $type->types[$i]; + if (!$genericType->equals($otherGenericType)) { + return false; + } + } + + return true; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + $classes = parent::getReferencedClasses(); + foreach ($this->types as $type) { + foreach ($type->getReferencedClasses() as $referencedClass) { + $classes[] = $referencedClass; + } + } + + return $classes; + } + + /** @return array */ + public function getTypes(): array + { + return $this->types; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + return $this->isSuperTypeOfInternal($type, true); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfInternal($type, false); + } + + private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + $nakedSuperTypeOf = parent::isSuperTypeOf($type); + if ($nakedSuperTypeOf->no()) { + return $nakedSuperTypeOf; + } + + if (!$type instanceof ObjectType) { + return $nakedSuperTypeOf; + } + + $ancestor = $type->getAncestorWithClassName($this->getClassName()); + if ($ancestor === null) { + return $nakedSuperTypeOf; + } + if (!$ancestor instanceof self) { + if ($acceptsContext) { + return $nakedSuperTypeOf; + } + + return $nakedSuperTypeOf->and(TrinaryLogic::createMaybe()); + } + + if (count($this->types) !== count($ancestor->types)) { + return TrinaryLogic::createNo(); + } + + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return $nakedSuperTypeOf; + } + + $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); + $results = []; + foreach ($typeList as $i => $templateType) { + if (!isset($ancestor->types[$i])) { + continue; + } + if (!isset($this->types[$i])) { + continue; + } + if ($templateType instanceof ErrorType) { + continue; + } + if (!$templateType instanceof TemplateType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); + } + + if (count($results) === 0) { + return $nakedSuperTypeOf; + } + + return $nakedSuperTypeOf->and(...$results); + } + + public function getClassReflection(): ?ClassReflection + { + if ($this->classReflection !== null) { + return $this->classReflection; + } + + $broker = Broker::getInstance(); + if (!$broker->hasClass($this->getClassName())) { + return null; + } + + return $this->classReflection = $broker->getClass($this->getClassName())->withTypes($this->types); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $prototype = parent::getUnresolvedPropertyPrototype($propertyName, $scope); + + return $prototype->doNotResolveTemplateTypeMapToBounds(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $prototype = parent::getUnresolvedMethodPrototype($methodName, $scope); + + return $prototype->doNotResolveTemplateTypeMapToBounds(); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if (!$receivedType instanceof TypeWithClassName) { + return TemplateTypeMap::createEmpty(); + } + + $ancestor = $receivedType->getAncestorWithClassName($this->getClassName()); + + if ($ancestor === null) { + return TemplateTypeMap::createEmpty(); + } + $ancestorClassReflection = $ancestor->getClassReflection(); + if ($ancestorClassReflection === null) { + return TemplateTypeMap::createEmpty(); + } + + $otherTypes = $ancestorClassReflection->typeMapToList($ancestorClassReflection->getActiveTemplateTypeMap()); + $typeMap = TemplateTypeMap::createEmpty(); + + foreach ($this->getTypes() as $i => $type) { + $other = $otherTypes[$i] ?? new ErrorType(); + $typeMap = $typeMap->union($type->inferTemplateTypes($other)); + } + + return $typeMap; + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $classReflection = $this->getClassReflection(); + if ($classReflection !== null) { + $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); + } else { + $typeList = []; + } + + $references = []; + + foreach ($this->types as $i => $type) { + $variance = $positionVariance->compose( + isset($typeList[$i]) && $typeList[$i] instanceof TemplateType + ? $typeList[$i]->getVariance() + : TemplateTypeVariance::createInvariant() + ); + foreach ($type->getReferencedTemplateTypes($variance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + + public function traverse(callable $cb): Type + { + $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null; + + $typesChanged = false; + $types = []; + foreach ($this->types as $type) { + $newType = $cb($type); + $types[] = $newType; + if ($newType === $type) { + continue; + } + + $typesChanged = true; + } + + if ($subtractedType !== $this->getSubtractedType() || $typesChanged) { + return $this->recreate($this->getClassName(), $types, $subtractedType); + } + + return $this; + } + + /** + * @param string $className + * @param Type[] $types + * @param Type|null $subtractedType + * @return self + */ + protected function recreate(string $className, array $types, ?Type $subtractedType): self + { + return new self( + $className, + $types, + $subtractedType + ); + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + return new self($this->getClassName(), $this->types, $subtractedType); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['className'], + $properties['types'], + $properties['subtractedType'] ?? null + ); + } } diff --git a/src/Type/Generic/TemplateBenevolentUnionType.php b/src/Type/Generic/TemplateBenevolentUnionType.php index e829d6229f..86f88ad85b 100644 --- a/src/Type/Generic/TemplateBenevolentUnionType.php +++ b/src/Type/Generic/TemplateBenevolentUnionType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - BenevolentUnionType $bound - ) - { - parent::__construct($bound->getTypes()); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof BenevolentUnionType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + BenevolentUnionType $bound + ) { + parent::__construct($bound->getTypes()); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof BenevolentUnionType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } } diff --git a/src/Type/Generic/TemplateGenericObjectType.php b/src/Type/Generic/TemplateGenericObjectType.php index 8e20f21f95..a1b9f90dab 100644 --- a/src/Type/Generic/TemplateGenericObjectType.php +++ b/src/Type/Generic/TemplateGenericObjectType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - GenericObjectType $bound - ) - { - parent::__construct($bound->getClassName(), $bound->getTypes()); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof GenericObjectType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - - protected function recreate(string $className, array $types, ?Type $subtractedType): GenericObjectType - { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $this->getBound() - ); - } - + use UndecidedComparisonCompoundTypeTrait; + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + GenericObjectType $bound + ) { + parent::__construct($bound->getClassName(), $bound->getTypes()); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof GenericObjectType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function recreate(string $className, array $types, ?Type $subtractedType): GenericObjectType + { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $this->getBound() + ); + } } diff --git a/src/Type/Generic/TemplateIntegerType.php b/src/Type/Generic/TemplateIntegerType.php index b510840c6d..49dee1934c 100644 --- a/src/Type/Generic/TemplateIntegerType.php +++ b/src/Type/Generic/TemplateIntegerType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - use UndecidedComparisonCompoundTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - IntegerType $bound - ) - { - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof IntegerType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - - protected function shouldGeneralizeInferredType(): bool - { - return false; - } - + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + IntegerType $bound + ) { + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof IntegerType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } } diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 43d36e3e77..5efa2a25ee 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - MixedType $bound - ) - { - parent::__construct(true); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic - { - return $this->isSuperTypeOf($type); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - $isSuperType = $this->isSuperTypeOf($acceptingType); - if ($isSuperType->no()) { - return $isSuperType; - } - return TrinaryLogic::createYes(); - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof MixedType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + MixedType $bound + ) { + parent::__construct(true); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + { + return $this->isSuperTypeOf($type); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + $isSuperType = $this->isSuperTypeOf($acceptingType); + if ($isSuperType->no()) { + return $isSuperType; + } + return TrinaryLogic::createYes(); + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof MixedType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } } diff --git a/src/Type/Generic/TemplateObjectType.php b/src/Type/Generic/TemplateObjectType.php index b2a89b964f..266083f992 100644 --- a/src/Type/Generic/TemplateObjectType.php +++ b/src/Type/Generic/TemplateObjectType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - ObjectType $bound - ) - { - parent::__construct($bound->getClassName()); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof ObjectType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - + use UndecidedComparisonCompoundTypeTrait; + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + ObjectType $bound + ) { + parent::__construct($bound->getClassName()); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof ObjectType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } } diff --git a/src/Type/Generic/TemplateObjectWithoutClassType.php b/src/Type/Generic/TemplateObjectWithoutClassType.php index 44c2ebd7bc..7c90fbd17f 100644 --- a/src/Type/Generic/TemplateObjectWithoutClassType.php +++ b/src/Type/Generic/TemplateObjectWithoutClassType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - ObjectWithoutClassType $bound - ) - { - parent::__construct(); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof ObjectWithoutClassType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - + use UndecidedComparisonCompoundTypeTrait; + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + ObjectWithoutClassType $bound + ) { + parent::__construct(); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof ObjectWithoutClassType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } } diff --git a/src/Type/Generic/TemplateStringType.php b/src/Type/Generic/TemplateStringType.php index a761e0964e..b204149bd2 100644 --- a/src/Type/Generic/TemplateStringType.php +++ b/src/Type/Generic/TemplateStringType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - use UndecidedComparisonCompoundTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - StringType $bound - ) - { - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof StringType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - - protected function shouldGeneralizeInferredType(): bool - { - return false; - } - + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + StringType $bound + ) { + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof StringType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } } diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index a319af224a..f8a7d43aa8 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -1,4 +1,6 @@ -getTypes() as $type) { - if ($this->accepts($left, $type, $strictTypes)->yes()) { - return TrinaryLogic::createYes(); - } - } - - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createFromBoolean($left->equals($right)) - ->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType()))); - } - - public function isArgument(): bool - { - return true; - } - - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self(); - } - + public function accepts(TemplateType $left, Type $right, bool $strictTypes): TrinaryLogic + { + if ($right instanceof IntersectionType) { + foreach ($right->getTypes() as $type) { + if ($this->accepts($left, $type, $strictTypes)->yes()) { + return TrinaryLogic::createYes(); + } + } + + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createFromBoolean($left->equals($right)) + ->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType()))); + } + + public function isArgument(): bool + { + return true; + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self(); + } } diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index e6172790f8..691399d926 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -1,4 +1,6 @@ -getName(), $tag->getBound(), $tag->getVariance()); - } - + public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): TemplateType + { + $strategy = new TemplateTypeParameterStrategy(); + + if ($bound === null) { + return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true)); + } + + $boundClass = get_class($bound); + if ($bound instanceof ObjectType && ($boundClass === ObjectType::class || $bound instanceof TemplateType)) { + return new TemplateObjectType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof GenericObjectType && ($boundClass === GenericObjectType::class || $bound instanceof TemplateType)) { + return new TemplateGenericObjectType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof ObjectWithoutClassType && ($boundClass === ObjectWithoutClassType::class || $bound instanceof TemplateType)) { + return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof StringType && ($boundClass === StringType::class || $bound instanceof TemplateType)) { + return new TemplateStringType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof IntegerType && ($boundClass === IntegerType::class || $bound instanceof TemplateType)) { + return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof MixedType && ($boundClass === MixedType::class || $bound instanceof TemplateType)) { + return new TemplateMixedType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof UnionType) { + if ($boundClass === UnionType::class || $bound instanceof TemplateUnionType) { + return new TemplateUnionType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof BenevolentUnionType) { + return new TemplateBenevolentUnionType($scope, $strategy, $variance, $name, $bound); + } + } + + return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true)); + } + + public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): TemplateType + { + return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance()); + } } diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 9c8165bcdc..41082f08f8 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -1,4 +1,6 @@ -isArgument()) { + $newType = $standins->getType($type->getName()); + if ($newType === null) { + return $traverse($type); + } - /** - * Replaces template types with standin types - */ - public static function resolveTemplateTypes(Type $type, TemplateTypeMap $standins): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins): Type { - if ($type instanceof TemplateType && !$type->isArgument()) { - $newType = $standins->getType($type->getName()); - if ($newType === null) { - return $traverse($type); - } - - if ($newType instanceof ErrorType) { - return $traverse($type->getBound()); - } - - return $newType; - } + if ($newType instanceof ErrorType) { + return $traverse($type->getBound()); + } - return $traverse($type); - }); - } + return $newType; + } - public static function resolveToBounds(Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { - if ($type instanceof TemplateType) { - return $traverse($type->getBound()); - } + return $traverse($type); + }); + } - return $traverse($type); - }); - } + public static function resolveToBounds(Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof TemplateType) { + return $traverse($type->getBound()); + } - /** - * @template T of Type - * @param T $type - * @return T - */ - public static function toArgument(Type $type): Type - { - /** @var T */ - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { - if ($type instanceof TemplateType) { - return $traverse($type->toArgument()); - } + return $traverse($type); + }); + } - return $traverse($type); - }); - } + /** + * @template T of Type + * @param T $type + * @return T + */ + public static function toArgument(Type $type): Type + { + /** @var T */ + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof TemplateType) { + return $traverse($type->toArgument()); + } - public static function generalizeType(Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { - if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) { - return $type->generalize(); - } + return $traverse($type); + }); + } - return $traverse($type); - }); - } + public static function generalizeType(Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) { + return $type->generalize(); + } + return $traverse($type); + }); + } } diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 6a34242aed..a6404d30c6 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -1,4 +1,6 @@ - */ - private array $types; - - /** @param array $types */ - public function __construct(array $types) - { - $this->types = $types; - } - - public static function createEmpty(): self - { - $empty = self::$empty; - - if ($empty !== null) { - return $empty; - } - - $empty = new self([]); - self::$empty = $empty; - - return $empty; - } - - public function isEmpty(): bool - { - return count($this->types) === 0; - } - - public function count(): int - { - return count($this->types); - } - - /** @return array */ - public function getTypes(): array - { - return $this->types; - } - - public function hasType(string $name): bool - { - return array_key_exists($name, $this->types); - } - - public function getType(string $name): ?Type - { - return $this->types[$name] ?? null; - } - - public function unsetType(string $name): self - { - if (!$this->hasType($name)) { - return $this; - } - - $types = $this->types; - - unset($types[$name]); - - if (count($types) === 0) { - return self::createEmpty(); - } - - return new self($types); - } - - public function union(self $other): self - { - $result = $this->types; - - foreach ($other->types as $name => $type) { - if (isset($result[$name])) { - $result[$name] = TypeCombinator::union($result[$name], $type); - } else { - $result[$name] = $type; - } - } - - return new self($result); - } - - public function benevolentUnion(self $other): self - { - $result = $this->types; - - foreach ($other->types as $name => $type) { - if (isset($result[$name])) { - $result[$name] = TypeUtils::toBenevolentUnion(TypeCombinator::union($result[$name], $type)); - } else { - $result[$name] = $type; - } - } - - return new self($result); - } - - public function intersect(self $other): self - { - $result = $this->types; - - foreach ($other->types as $name => $type) { - if (isset($result[$name])) { - $result[$name] = TypeCombinator::intersect($result[$name], $type); - } else { - $result[$name] = $type; - } - } - - return new self($result); - } - - /** @param callable(string,Type):Type $cb */ - public function map(callable $cb): self - { - $types = []; - foreach ($this->types as $name => $type) { - $types[$name] = $cb($name, $type); - } - - return new self($types); - } - - public function resolveToBounds(): self - { - return $this->map(static function (string $name, Type $type): Type { - $type = TemplateTypeHelper::resolveToBounds($type); - if ($type instanceof MixedType && $type->isExplicitMixed()) { - return new MixedType(false); - } - - return $type; - }); - } - - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['types'] - ); - } - + private static ?TemplateTypeMap $empty = null; + + /** @var array */ + private array $types; + + /** @param array $types */ + public function __construct(array $types) + { + $this->types = $types; + } + + public static function createEmpty(): self + { + $empty = self::$empty; + + if ($empty !== null) { + return $empty; + } + + $empty = new self([]); + self::$empty = $empty; + + return $empty; + } + + public function isEmpty(): bool + { + return count($this->types) === 0; + } + + public function count(): int + { + return count($this->types); + } + + /** @return array */ + public function getTypes(): array + { + return $this->types; + } + + public function hasType(string $name): bool + { + return array_key_exists($name, $this->types); + } + + public function getType(string $name): ?Type + { + return $this->types[$name] ?? null; + } + + public function unsetType(string $name): self + { + if (!$this->hasType($name)) { + return $this; + } + + $types = $this->types; + + unset($types[$name]); + + if (count($types) === 0) { + return self::createEmpty(); + } + + return new self($types); + } + + public function union(self $other): self + { + $result = $this->types; + + foreach ($other->types as $name => $type) { + if (isset($result[$name])) { + $result[$name] = TypeCombinator::union($result[$name], $type); + } else { + $result[$name] = $type; + } + } + + return new self($result); + } + + public function benevolentUnion(self $other): self + { + $result = $this->types; + + foreach ($other->types as $name => $type) { + if (isset($result[$name])) { + $result[$name] = TypeUtils::toBenevolentUnion(TypeCombinator::union($result[$name], $type)); + } else { + $result[$name] = $type; + } + } + + return new self($result); + } + + public function intersect(self $other): self + { + $result = $this->types; + + foreach ($other->types as $name => $type) { + if (isset($result[$name])) { + $result[$name] = TypeCombinator::intersect($result[$name], $type); + } else { + $result[$name] = $type; + } + } + + return new self($result); + } + + /** @param callable(string,Type):Type $cb */ + public function map(callable $cb): self + { + $types = []; + foreach ($this->types as $name => $type) { + $types[$name] = $cb($name, $type); + } + + return new self($types); + } + + public function resolveToBounds(): self + { + return $this->map(static function (string $name, Type $type): Type { + $type = TemplateTypeHelper::resolveToBounds($type); + if ($type instanceof MixedType && $type->isExplicitMixed()) { + return new MixedType(false); + } + + return $type; + }); + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['types'] + ); + } } diff --git a/src/Type/Generic/TemplateTypeParameterStrategy.php b/src/Type/Generic/TemplateTypeParameterStrategy.php index 89e69d6051..5c10726857 100644 --- a/src/Type/Generic/TemplateTypeParameterStrategy.php +++ b/src/Type/Generic/TemplateTypeParameterStrategy.php @@ -1,4 +1,6 @@ -getBound()->accepts($right, $strictTypes); - } - - public function isArgument(): bool - { - return false; - } - - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self(); - } - + public function accepts(TemplateType $left, Type $right, bool $strictTypes): TrinaryLogic + { + if ($right instanceof CompoundType) { + return CompoundTypeHelper::accepts($right, $left, $strictTypes); + } + + return $left->getBound()->accepts($right, $strictTypes); + } + + public function isArgument(): bool + { + return false; + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self(); + } } diff --git a/src/Type/Generic/TemplateTypeReference.php b/src/Type/Generic/TemplateTypeReference.php index a88e5c3ce1..84a05a2ee2 100644 --- a/src/Type/Generic/TemplateTypeReference.php +++ b/src/Type/Generic/TemplateTypeReference.php @@ -1,28 +1,28 @@ -type = $type; - $this->positionVariance = $positionVariance; - } + private TemplateTypeVariance $positionVariance; - public function getType(): TemplateType - { - return $this->type; - } + public function __construct(TemplateType $type, TemplateTypeVariance $positionVariance) + { + $this->type = $type; + $this->positionVariance = $positionVariance; + } - public function getPositionVariance(): TemplateTypeVariance - { - return $this->positionVariance; - } + public function getType(): TemplateType + { + return $this->type; + } + public function getPositionVariance(): TemplateTypeVariance + { + return $this->positionVariance; + } } diff --git a/src/Type/Generic/TemplateTypeScope.php b/src/Type/Generic/TemplateTypeScope.php index 8e58ac9ba4..df3183559c 100644 --- a/src/Type/Generic/TemplateTypeScope.php +++ b/src/Type/Generic/TemplateTypeScope.php @@ -1,73 +1,73 @@ -className = $className; - $this->functionName = $functionName; - } - - public function getClassName(): ?string - { - return $this->className; - } - - public function getFunctionName(): ?string - { - return $this->functionName; - } - - public function equals(self $other): bool - { - return $this->className === $other->className - && $this->functionName === $other->functionName; - } - - public function describe(): string - { - if ($this->className === null) { - return sprintf('function %s()', $this->functionName); - } - - if ($this->functionName === null) { - return sprintf('class %s', $this->className); - } - - return sprintf('method %s::%s()', $this->className, $this->functionName); - } - - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['className'], - $properties['functionName'] - ); - } - + private ?string $className; + + private ?string $functionName; + + public static function createWithFunction(string $functionName): self + { + return new self(null, $functionName); + } + + public static function createWithMethod(string $className, string $functionName): self + { + return new self($className, $functionName); + } + + public static function createWithClass(string $className): self + { + return new self($className, null); + } + + private function __construct(?string $className, ?string $functionName) + { + $this->className = $className; + $this->functionName = $functionName; + } + + public function getClassName(): ?string + { + return $this->className; + } + + public function getFunctionName(): ?string + { + return $this->functionName; + } + + public function equals(self $other): bool + { + return $this->className === $other->className + && $this->functionName === $other->functionName; + } + + public function describe(): string + { + if ($this->className === null) { + return sprintf('function %s()', $this->functionName); + } + + if ($this->functionName === null) { + return sprintf('class %s', $this->className); + } + + return sprintf('method %s::%s()', $this->className, $this->functionName); + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['className'], + $properties['functionName'] + ); + } } diff --git a/src/Type/Generic/TemplateTypeStrategy.php b/src/Type/Generic/TemplateTypeStrategy.php index d90dc732e1..3a1c1d51d4 100644 --- a/src/Type/Generic/TemplateTypeStrategy.php +++ b/src/Type/Generic/TemplateTypeStrategy.php @@ -1,4 +1,6 @@ -name; - } - - public function getScope(): TemplateTypeScope - { - return $this->scope; - } - - /** @return TBound */ - public function getBound(): Type - { - return $this->bound; - } - - public function describe(VerbosityLevel $level): string - { - $basicDescription = function () use ($level): string { - if ($this->bound instanceof MixedType) { // @phpstan-ignore-line - $boundDescription = ''; - } else { // @phpstan-ignore-line - $boundDescription = sprintf(' of %s', $this->bound->describe($level)); - } - return sprintf( - '%s%s', - $this->name, - $boundDescription - ); - }; - - return $level->handle( - $basicDescription, - $basicDescription, - function () use ($basicDescription): string { - return sprintf('%s (%s, %s)', $basicDescription(), $this->scope->describe(), $this->isArgument() ? 'argument' : 'parameter'); - } - ); - } - - public function isArgument(): bool - { - return $this->strategy->isArgument(); - } - - public function toArgument(): TemplateType - { - return new self( - $this->scope, - new TemplateTypeArgumentStrategy(), - $this->variance, - $this->name, - TemplateTypeHelper::toArgument($this->getBound()) - ); - } - - public function isValidVariance(Type $a, Type $b): TrinaryLogic - { - return $this->variance->isValidVariance($a, $b); - } - - public function subtract(Type $type): Type - { - return $this; - } - - public function getTypeWithoutSubtractedType(): Type - { - return $this; - } - - public function changeSubtractedType(?Type $subtractedType): Type - { - return $this; - } - - public function equals(Type $type): bool - { - return $type instanceof self - && $type->scope->equals($this->scope) - && $type->name === $this->name - && $this->bound->equals($type->bound); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->strategy->accepts($this, $type, $strictTypes); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return $this->getBound()->isSuperTypeOf($type) - ->and(TrinaryLogic::createMaybe()); - } - - public function isSubTypeOf(Type $type): TrinaryLogic - { - /** @var Type $bound */ - $bound = $this->getBound(); - if ( - !$type instanceof $bound - && !$this instanceof $type - && !$type instanceof TemplateType - && ($type instanceof UnionType || $type instanceof IntersectionType) - ) { - return $type->isSuperTypeOf($this); - } - - if (!$type instanceof TemplateType) { - return $type->isSuperTypeOf($this->getBound()); - } - - if ($this->equals($type)) { - return TrinaryLogic::createYes(); - } - - if ($type->getBound()->isSuperTypeOf($this->getBound())->no() && - $this->getBound()->isSuperTypeOf($type->getBound())->no()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if ( - $receivedType instanceof TemplateType - && $this->getBound()->isSuperTypeOf($receivedType->getBound())->yes() - ) { - return new TemplateTypeMap([ - $this->name => $receivedType, - ]); - } - - $map = $this->getBound()->inferTemplateTypes($receivedType); - $resolvedBound = TemplateTypeHelper::resolveTemplateTypes($this->getBound(), $map); - if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) { - return (new TemplateTypeMap([ - $this->name => $this->shouldGeneralizeInferredType() ? TemplateTypeHelper::generalizeType($receivedType) : $receivedType, - ]))->union($map); - } - - return $map; - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - return [new TemplateTypeReference($this, $positionVariance)]; - } - - public function getVariance(): TemplateTypeVariance - { - return $this->variance; - } - - protected function shouldGeneralizeInferredType(): bool - { - return true; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['scope'], - $properties['strategy'], - $properties['variance'], - $properties['name'], - $properties['bound'] - ); - } - + private string $name; + + private TemplateTypeScope $scope; + + private TemplateTypeStrategy $strategy; + + private TemplateTypeVariance $variance; + + /** @var TBound */ + private Type $bound; + + public function getName(): string + { + return $this->name; + } + + public function getScope(): TemplateTypeScope + { + return $this->scope; + } + + /** @return TBound */ + public function getBound(): Type + { + return $this->bound; + } + + public function describe(VerbosityLevel $level): string + { + $basicDescription = function () use ($level): string { + if ($this->bound instanceof MixedType) { // @phpstan-ignore-line + $boundDescription = ''; + } else { // @phpstan-ignore-line + $boundDescription = sprintf(' of %s', $this->bound->describe($level)); + } + return sprintf( + '%s%s', + $this->name, + $boundDescription + ); + }; + + return $level->handle( + $basicDescription, + $basicDescription, + function () use ($basicDescription): string { + return sprintf('%s (%s, %s)', $basicDescription(), $this->scope->describe(), $this->isArgument() ? 'argument' : 'parameter'); + } + ); + } + + public function isArgument(): bool + { + return $this->strategy->isArgument(); + } + + public function toArgument(): TemplateType + { + return new self( + $this->scope, + new TemplateTypeArgumentStrategy(), + $this->variance, + $this->name, + TemplateTypeHelper::toArgument($this->getBound()) + ); + } + + public function isValidVariance(Type $a, Type $b): TrinaryLogic + { + return $this->variance->isValidVariance($a, $b); + } + + public function subtract(Type $type): Type + { + return $this; + } + + public function getTypeWithoutSubtractedType(): Type + { + return $this; + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + return $this; + } + + public function equals(Type $type): bool + { + return $type instanceof self + && $type->scope->equals($this->scope) + && $type->name === $this->name + && $this->bound->equals($type->bound); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + return $this->strategy->accepts($this, $type, $strictTypes); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return $this->getBound()->isSuperTypeOf($type) + ->and(TrinaryLogic::createMaybe()); + } + + public function isSubTypeOf(Type $type): TrinaryLogic + { + /** @var Type $bound */ + $bound = $this->getBound(); + if ( + !$type instanceof $bound + && !$this instanceof $type + && !$type instanceof TemplateType + && ($type instanceof UnionType || $type instanceof IntersectionType) + ) { + return $type->isSuperTypeOf($this); + } + + if (!$type instanceof TemplateType) { + return $type->isSuperTypeOf($this->getBound()); + } + + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + if ($type->getBound()->isSuperTypeOf($this->getBound())->no() && + $this->getBound()->isSuperTypeOf($type->getBound())->no()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if ( + $receivedType instanceof TemplateType + && $this->getBound()->isSuperTypeOf($receivedType->getBound())->yes() + ) { + return new TemplateTypeMap([ + $this->name => $receivedType, + ]); + } + + $map = $this->getBound()->inferTemplateTypes($receivedType); + $resolvedBound = TemplateTypeHelper::resolveTemplateTypes($this->getBound(), $map); + if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) { + return (new TemplateTypeMap([ + $this->name => $this->shouldGeneralizeInferredType() ? TemplateTypeHelper::generalizeType($receivedType) : $receivedType, + ]))->union($map); + } + + return $map; + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + return [new TemplateTypeReference($this, $positionVariance)]; + } + + public function getVariance(): TemplateTypeVariance + { + return $this->variance; + } + + protected function shouldGeneralizeInferredType(): bool + { + return true; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['scope'], + $properties['strategy'], + $properties['variance'], + $properties['name'], + $properties['bound'] + ); + } } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 0e52c0126b..9f11a806b9 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -1,4 +1,6 @@ -value = $value; - } - - private static function create(int $value): self - { - self::$registry[$value] = self::$registry[$value] ?? new self($value); - return self::$registry[$value]; - } - - public static function createInvariant(): self - { - return self::create(self::INVARIANT); - } - - public static function createCovariant(): self - { - return self::create(self::COVARIANT); - } - - public static function createContravariant(): self - { - return self::create(self::CONTRAVARIANT); - } - - public static function createStatic(): self - { - return self::create(self::STATIC); - } - - public function invariant(): bool - { - return $this->value === self::INVARIANT; - } - - public function covariant(): bool - { - return $this->value === self::COVARIANT; - } - - public function contravariant(): bool - { - return $this->value === self::CONTRAVARIANT; - } - - public function static(): bool - { - return $this->value === self::STATIC; - } - - public function compose(self $other): self - { - if ($this->contravariant()) { - if ($other->contravariant()) { - return self::createCovariant(); - } - if ($other->covariant()) { - return self::createContravariant(); - } - return self::createInvariant(); - } - - if ($this->covariant()) { - if ($other->contravariant()) { - return self::createCovariant(); - } - if ($other->covariant()) { - return self::createCovariant(); - } - return self::createInvariant(); - } - - return $other; - } - - public function isValidVariance(Type $a, Type $b): TrinaryLogic - { - if ($a instanceof MixedType && !$a instanceof TemplateType) { - return TrinaryLogic::createYes(); - } - - if ($a instanceof BenevolentUnionType) { - if (!$a->isSuperTypeOf($b)->no()) { - return TrinaryLogic::createYes(); - } - } - - if ($b instanceof BenevolentUnionType) { - if (!$b->isSuperTypeOf($a)->no()) { - return TrinaryLogic::createYes(); - } - } - - if ($b instanceof MixedType && !$b instanceof TemplateType) { - return TrinaryLogic::createYes(); - } - - if ($this->invariant()) { - return TrinaryLogic::createFromBoolean($a->equals($b)); - } - - if ($this->covariant()) { - return $a->isSuperTypeOf($b); - } - - if ($this->contravariant()) { - return $b->isSuperTypeOf($a); - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function equals(self $other): bool - { - return $other->value === $this->value; - } - - public function validPosition(self $other): bool - { - return $other->value === $this->value - || $other->invariant() - || $this->static(); - } - - public function describe(): string - { - switch ($this->value) { - case self::INVARIANT: - return 'invariant'; - case self::COVARIANT: - return 'covariant'; - case self::CONTRAVARIANT: - return 'contravariant'; - case self::STATIC: - return 'static'; - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - /** - * @param array{value: int} $properties - * @return self - */ - public static function __set_state(array $properties): self - { - return new self($properties['value']); - } - + private const INVARIANT = 1; + private const COVARIANT = 2; + private const CONTRAVARIANT = 3; + private const STATIC = 4; + + /** @var self[] */ + private static array $registry; + + private int $value; + + private function __construct(int $value) + { + $this->value = $value; + } + + private static function create(int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + public static function createInvariant(): self + { + return self::create(self::INVARIANT); + } + + public static function createCovariant(): self + { + return self::create(self::COVARIANT); + } + + public static function createContravariant(): self + { + return self::create(self::CONTRAVARIANT); + } + + public static function createStatic(): self + { + return self::create(self::STATIC); + } + + public function invariant(): bool + { + return $this->value === self::INVARIANT; + } + + public function covariant(): bool + { + return $this->value === self::COVARIANT; + } + + public function contravariant(): bool + { + return $this->value === self::CONTRAVARIANT; + } + + public function static(): bool + { + return $this->value === self::STATIC; + } + + public function compose(self $other): self + { + if ($this->contravariant()) { + if ($other->contravariant()) { + return self::createCovariant(); + } + if ($other->covariant()) { + return self::createContravariant(); + } + return self::createInvariant(); + } + + if ($this->covariant()) { + if ($other->contravariant()) { + return self::createCovariant(); + } + if ($other->covariant()) { + return self::createCovariant(); + } + return self::createInvariant(); + } + + return $other; + } + + public function isValidVariance(Type $a, Type $b): TrinaryLogic + { + if ($a instanceof MixedType && !$a instanceof TemplateType) { + return TrinaryLogic::createYes(); + } + + if ($a instanceof BenevolentUnionType) { + if (!$a->isSuperTypeOf($b)->no()) { + return TrinaryLogic::createYes(); + } + } + + if ($b instanceof BenevolentUnionType) { + if (!$b->isSuperTypeOf($a)->no()) { + return TrinaryLogic::createYes(); + } + } + + if ($b instanceof MixedType && !$b instanceof TemplateType) { + return TrinaryLogic::createYes(); + } + + if ($this->invariant()) { + return TrinaryLogic::createFromBoolean($a->equals($b)); + } + + if ($this->covariant()) { + return $a->isSuperTypeOf($b); + } + + if ($this->contravariant()) { + return $b->isSuperTypeOf($a); + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function validPosition(self $other): bool + { + return $other->value === $this->value + || $other->invariant() + || $this->static(); + } + + public function describe(): string + { + switch ($this->value) { + case self::INVARIANT: + return 'invariant'; + case self::COVARIANT: + return 'covariant'; + case self::CONTRAVARIANT: + return 'contravariant'; + case self::STATIC: + return 'static'; + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + /** + * @param array{value: int} $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self($properties['value']); + } } diff --git a/src/Type/Generic/TemplateUnionType.php b/src/Type/Generic/TemplateUnionType.php index 48f1bf1de8..51e546b566 100644 --- a/src/Type/Generic/TemplateUnionType.php +++ b/src/Type/Generic/TemplateUnionType.php @@ -1,4 +1,6 @@ - */ - use TemplateTypeTrait; - - public function __construct( - TemplateTypeScope $scope, - TemplateTypeStrategy $templateTypeStrategy, - TemplateTypeVariance $templateTypeVariance, - string $name, - UnionType $bound - ) - { - parent::__construct($bound->getTypes()); - - $this->scope = $scope; - $this->strategy = $templateTypeStrategy; - $this->variance = $templateTypeVariance; - $this->name = $name; - $this->bound = $bound; - } - - public function traverse(callable $cb): Type - { - $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof UnionType) { - return new self( - $this->scope, - $this->strategy, - $this->variance, - $this->name, - $newBound - ); - } - - return $this; - } - + /** @use TemplateTypeTrait */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + UnionType $bound + ) { + parent::__construct($bound->getTypes()); + + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof UnionType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } } diff --git a/src/Type/GenericTypeVariableResolver.php b/src/Type/GenericTypeVariableResolver.php index 1643770ae8..ff5170ced5 100644 --- a/src/Type/GenericTypeVariableResolver.php +++ b/src/Type/GenericTypeVariableResolver.php @@ -1,29 +1,28 @@ -getAncestorWithClassName($genericClassName); + if ($ancestor === null) { + return null; + } - public static function getType( - TypeWithClassName $type, - string $genericClassName, - string $typeVariableName - ): ?Type - { - $ancestor = $type->getAncestorWithClassName($genericClassName); - if ($ancestor === null) { - return null; - } - - $classReflection = $ancestor->getClassReflection(); - if ($classReflection === null) { - return null; - } - - $templateTypeMap = $classReflection->getActiveTemplateTypeMap(); + $classReflection = $ancestor->getClassReflection(); + if ($classReflection === null) { + return null; + } - return $templateTypeMap->getType($typeVariableName); - } + $templateTypeMap = $classReflection->getActiveTemplateTypeMap(); + return $templateTypeMap->getType($typeVariableName); + } } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 0c8d9b0278..29a2502ad6 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -1,4 +1,6 @@ -min = $min; - $this->max = $max; - } - - public static function fromInterval(?int $min, ?int $max, int $shift = 0): Type - { - if ($min !== null && $max !== null) { - if ($min > $max) { - return new NeverType(); - } - if ($min === $max) { - return new ConstantIntegerType($min + $shift); - } - } - - if ($min === null && $max === null) { - return new IntegerType(); - } - - return (new self($min, $max))->shift($shift); - } - - protected static function isDisjoint(?int $minA, ?int $maxA, ?int $minB, ?int $maxB, bool $touchingIsDisjoint = true): bool - { - $offset = $touchingIsDisjoint ? 0 : 1; - return $minA !== null && $maxB !== null && $minA > $maxB + $offset - || $maxA !== null && $minB !== null && $maxA + $offset < $minB; - } - - /** - * Return the range of integers smaller than the given value - * - * @param int|float $value - * @return Type - */ - public static function createAllSmallerThan($value): Type - { - if (is_int($value)) { - return self::fromInterval(null, $value, -1); - } - - if ($value > PHP_INT_MAX) { - return new IntegerType(); - } - - if ($value <= PHP_INT_MIN) { - return new NeverType(); - } - - return self::fromInterval(null, (int) ceil($value), -1); - } - - /** - * Return the range of integers smaller than or equal to the given value - * - * @param int|float $value - * @return Type - */ - public static function createAllSmallerThanOrEqualTo($value): Type - { - if (is_int($value)) { - return self::fromInterval(null, $value); - } - - if ($value >= PHP_INT_MAX) { - return new IntegerType(); - } - - if ($value < PHP_INT_MIN) { - return new NeverType(); - } - - return self::fromInterval(null, (int) floor($value)); - } - - /** - * Return the range of integers greater than the given value - * - * @param int|float $value - * @return Type - */ - public static function createAllGreaterThan($value): Type - { - if (is_int($value)) { - return self::fromInterval($value, null, 1); - } - - if ($value < PHP_INT_MIN) { - return new IntegerType(); - } - - if ($value >= PHP_INT_MAX) { - return new NeverType(); - } - - return self::fromInterval((int) floor($value), null, 1); - } - - /** - * Return the range of integers greater than or equal to the given value - * - * @param int|float $value - * @return Type - */ - public static function createAllGreaterThanOrEqualTo($value): Type - { - if (is_int($value)) { - return self::fromInterval($value, null); - } - - if ($value <= PHP_INT_MIN) { - return new IntegerType(); - } - - if ($value > PHP_INT_MAX) { - return new NeverType(); - } - - return self::fromInterval((int) ceil($value), null); - } - - public function getMin(): ?int - { - return $this->min; - } - - - public function getMax(): ?int - { - return $this->max; - } - - - public function describe(VerbosityLevel $level): string - { - return sprintf('int<%s, %s>', $this->min ?? 'min', $this->max ?? 'max'); - } - - - public function shift(int $amount): Type - { - if ($amount === 0) { - return $this; - } - - $min = $this->min; - $max = $this->max; - - if ($amount < 0) { - if ($max !== null) { - if ($max < PHP_INT_MIN - $amount) { - return new NeverType(); - } - $max += $amount; - } - if ($min !== null) { - $min = $min < PHP_INT_MIN - $amount ? null : $min + $amount; - } - } else { - if ($min !== null) { - if ($min > PHP_INT_MAX - $amount) { - return new NeverType(); - } - $min += $amount; - } - if ($max !== null) { - $max = $max > PHP_INT_MAX - $amount ? null : $max + $amount; - } - } - - return self::fromInterval($min, $max); - } - - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof parent) { - return $this->isSuperTypeOf($type); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return TrinaryLogic::createNo(); - } - - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self || $type instanceof ConstantIntegerType) { - if ($type instanceof self) { - $typeMin = $type->min; - $typeMax = $type->max; - } else { - $typeMin = $type->getValue(); - $typeMax = $type->getValue(); - } - - if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) { - return TrinaryLogic::createNo(); - } - - if ( - ($this->min === null || $typeMin !== null && $this->min <= $typeMin) - && ($this->max === null || $typeMax !== null && $this->max >= $typeMax) - ) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof parent) { - return $otherType->isSuperTypeOf($this); - } - - if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - return $type instanceof self && $this->min === $type->min && $this->max === $type->max; - } - - - public function generalize(): Type - { - return new parent(); - } - - public function isSmallerThan(Type $otherType): TrinaryLogic - { - if ($this->min === null) { - $minIsSmaller = TrinaryLogic::createYes(); - } else { - $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType); - } - - if ($this->max === null) { - $maxIsSmaller = TrinaryLogic::createNo(); - } else { - $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType); - } - - return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); - } - - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic - { - if ($this->min === null) { - $minIsSmaller = TrinaryLogic::createYes(); - } else { - $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType); - } - - if ($this->max === null) { - $maxIsSmaller = TrinaryLogic::createNo(); - } else { - $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType); - } - - return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); - } - - public function isGreaterThan(Type $otherType): TrinaryLogic - { - if ($this->min === null) { - $minIsSmaller = TrinaryLogic::createNo(); - } else { - $minIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->min))); - } - - if ($this->max === null) { - $maxIsSmaller = TrinaryLogic::createYes(); - } else { - $maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max))); - } - - return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); - } - - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic - { - if ($this->min === null) { - $minIsSmaller = TrinaryLogic::createNo(); - } else { - $minIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->min))); - } - - if ($this->max === null) { - $maxIsSmaller = TrinaryLogic::createYes(); - } else { - $maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max))); - } - - return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); - } - - public function getSmallerType(): Type - { - $subtractedTypes = [ - new ConstantBooleanType(true), - ]; - - if ($this->max !== null) { - $subtractedTypes[] = self::createAllGreaterThanOrEqualTo($this->max); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getSmallerOrEqualType(): Type - { - $subtractedTypes = []; - - if ($this->max !== null) { - $subtractedTypes[] = self::createAllGreaterThan($this->max); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterType(): Type - { - $subtractedTypes = [ - new NullType(), - new ConstantBooleanType(false), - ]; - - if ($this->min !== null) { - $subtractedTypes[] = self::createAllSmallerThanOrEqualTo($this->min); - } - - if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) { - $subtractedTypes[] = new ConstantBooleanType(true); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterOrEqualType(): Type - { - $subtractedTypes = []; - - if ($this->min !== null) { - $subtractedTypes[] = self::createAllSmallerThan($this->min); - } - - if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) { - $subtractedTypes[] = new NullType(); - $subtractedTypes[] = new ConstantBooleanType(false); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function toNumber(): Type - { - return new parent(); - } - - public function toBoolean(): BooleanType - { - $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this); - if ($isZero->no()) { - return new ConstantBooleanType(true); - } - - if ($isZero->maybe()) { - return new BooleanType(); - } - - return new ConstantBooleanType(false); - } - - /** - * Return the union with another type, but only if it can be expressed in a simpler way than using UnionType - * - * @param Type $otherType - * @return Type|null - */ - public function tryUnion(Type $otherType): ?Type - { - if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) { - if ($otherType instanceof self) { - $otherMin = $otherType->min; - $otherMax = $otherType->max; - } else { - $otherMin = $otherType->getValue(); - $otherMax = $otherType->getValue(); - } - - if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) { - return null; - } - - return self::fromInterval( - $this->min !== null && $otherMin !== null ? min($this->min, $otherMin) : null, - $this->max !== null && $otherMax !== null ? max($this->max, $otherMax) : null - ); - } - - if (get_class($otherType) === parent::class) { - return $otherType; - } - - return null; - } - - /** - * Return the intersection with another type, but only if it can be expressed in a simpler way than using - * IntersectionType - * - * @param Type $otherType - * @return Type|null - */ - public function tryIntersect(Type $otherType): ?Type - { - if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) { - if ($otherType instanceof self) { - $otherMin = $otherType->min; - $otherMax = $otherType->max; - } else { - $otherMin = $otherType->getValue(); - $otherMax = $otherType->getValue(); - } - - if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) { - return new NeverType(); - } - - if ($this->min === null) { - $newMin = $otherMin; - } elseif ($otherMin === null) { - $newMin = $this->min; - } else { - $newMin = max($this->min, $otherMin); - } - - if ($this->max === null) { - $newMax = $otherMax; - } elseif ($otherMax === null) { - $newMax = $this->max; - } else { - $newMax = min($this->max, $otherMax); - } - - return self::fromInterval($newMin, $newMax); - } - - if (get_class($otherType) === parent::class) { - return $this; - } - - return null; - } - - /** - * Return the different with another type, or null if it cannot be represented. - * - * @param Type $typeToRemove - * @return Type|null - */ - public function tryRemove(Type $typeToRemove): ?Type - { - if (get_class($typeToRemove) === parent::class) { - return new NeverType(); - } - - if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { - if ($typeToRemove instanceof IntegerRangeType) { - $removeMin = $typeToRemove->min; - $removeMax = $typeToRemove->max; - } else { - $removeMin = $typeToRemove->getValue(); - $removeMax = $typeToRemove->getValue(); - } - - if ( - $this->min !== null && $removeMax !== null && $removeMax < $this->min - || $this->max !== null && $removeMin !== null && $this->max < $removeMin - ) { - return $this; - } - - if ($removeMin !== null && $removeMin !== PHP_INT_MIN) { - $lowerPart = self::fromInterval($this->min, $removeMin - 1); - } else { - $lowerPart = null; - } - if ($removeMax !== null && $removeMax !== PHP_INT_MAX) { - $upperPart = self::fromInterval($removeMax + 1, $this->max); - } else { - $upperPart = null; - } - - if ($lowerPart !== null && $upperPart !== null) { - return TypeCombinator::union($lowerPart, $upperPart); - } - - return $lowerPart ?? $upperPart; - } - - return null; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['min'], $properties['max']); - } - + private ?int $min; + + private ?int $max; + + private function __construct(?int $min, ?int $max) + { + assert($min === null || $max === null || $min <= $max); + assert($min !== null || $max !== null); + + $this->min = $min; + $this->max = $max; + } + + public static function fromInterval(?int $min, ?int $max, int $shift = 0): Type + { + if ($min !== null && $max !== null) { + if ($min > $max) { + return new NeverType(); + } + if ($min === $max) { + return new ConstantIntegerType($min + $shift); + } + } + + if ($min === null && $max === null) { + return new IntegerType(); + } + + return (new self($min, $max))->shift($shift); + } + + protected static function isDisjoint(?int $minA, ?int $maxA, ?int $minB, ?int $maxB, bool $touchingIsDisjoint = true): bool + { + $offset = $touchingIsDisjoint ? 0 : 1; + return $minA !== null && $maxB !== null && $minA > $maxB + $offset + || $maxA !== null && $minB !== null && $maxA + $offset < $minB; + } + + /** + * Return the range of integers smaller than the given value + * + * @param int|float $value + * @return Type + */ + public static function createAllSmallerThan($value): Type + { + if (is_int($value)) { + return self::fromInterval(null, $value, -1); + } + + if ($value > PHP_INT_MAX) { + return new IntegerType(); + } + + if ($value <= PHP_INT_MIN) { + return new NeverType(); + } + + return self::fromInterval(null, (int) ceil($value), -1); + } + + /** + * Return the range of integers smaller than or equal to the given value + * + * @param int|float $value + * @return Type + */ + public static function createAllSmallerThanOrEqualTo($value): Type + { + if (is_int($value)) { + return self::fromInterval(null, $value); + } + + if ($value >= PHP_INT_MAX) { + return new IntegerType(); + } + + if ($value < PHP_INT_MIN) { + return new NeverType(); + } + + return self::fromInterval(null, (int) floor($value)); + } + + /** + * Return the range of integers greater than the given value + * + * @param int|float $value + * @return Type + */ + public static function createAllGreaterThan($value): Type + { + if (is_int($value)) { + return self::fromInterval($value, null, 1); + } + + if ($value < PHP_INT_MIN) { + return new IntegerType(); + } + + if ($value >= PHP_INT_MAX) { + return new NeverType(); + } + + return self::fromInterval((int) floor($value), null, 1); + } + + /** + * Return the range of integers greater than or equal to the given value + * + * @param int|float $value + * @return Type + */ + public static function createAllGreaterThanOrEqualTo($value): Type + { + if (is_int($value)) { + return self::fromInterval($value, null); + } + + if ($value <= PHP_INT_MIN) { + return new IntegerType(); + } + + if ($value > PHP_INT_MAX) { + return new NeverType(); + } + + return self::fromInterval((int) ceil($value), null); + } + + public function getMin(): ?int + { + return $this->min; + } + + + public function getMax(): ?int + { + return $this->max; + } + + + public function describe(VerbosityLevel $level): string + { + return sprintf('int<%s, %s>', $this->min ?? 'min', $this->max ?? 'max'); + } + + + public function shift(int $amount): Type + { + if ($amount === 0) { + return $this; + } + + $min = $this->min; + $max = $this->max; + + if ($amount < 0) { + if ($max !== null) { + if ($max < PHP_INT_MIN - $amount) { + return new NeverType(); + } + $max += $amount; + } + if ($min !== null) { + $min = $min < PHP_INT_MIN - $amount ? null : $min + $amount; + } + } else { + if ($min !== null) { + if ($min > PHP_INT_MAX - $amount) { + return new NeverType(); + } + $min += $amount; + } + if ($max !== null) { + $max = $max > PHP_INT_MAX - $amount ? null : $max + $amount; + } + } + + return self::fromInterval($min, $max); + } + + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof parent) { + return $this->isSuperTypeOf($type); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self || $type instanceof ConstantIntegerType) { + if ($type instanceof self) { + $typeMin = $type->min; + $typeMax = $type->max; + } else { + $typeMin = $type->getValue(); + $typeMax = $type->getValue(); + } + + if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) { + return TrinaryLogic::createNo(); + } + + if ( + ($this->min === null || $typeMin !== null && $this->min <= $typeMin) + && ($this->max === null || $typeMax !== null && $this->max >= $typeMax) + ) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof parent) { + return $otherType->isSuperTypeOf($this); + } + + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self && $this->min === $type->min && $this->max === $type->max; + } + + + public function generalize(): Type + { + return new parent(); + } + + public function isSmallerThan(Type $otherType): TrinaryLogic + { + if ($this->min === null) { + $minIsSmaller = TrinaryLogic::createYes(); + } else { + $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType); + } + + if ($this->max === null) { + $maxIsSmaller = TrinaryLogic::createNo(); + } else { + $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType); + } + + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); + } + + public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + { + if ($this->min === null) { + $minIsSmaller = TrinaryLogic::createYes(); + } else { + $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType); + } + + if ($this->max === null) { + $maxIsSmaller = TrinaryLogic::createNo(); + } else { + $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType); + } + + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); + } + + public function isGreaterThan(Type $otherType): TrinaryLogic + { + if ($this->min === null) { + $minIsSmaller = TrinaryLogic::createNo(); + } else { + $minIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->min))); + } + + if ($this->max === null) { + $maxIsSmaller = TrinaryLogic::createYes(); + } else { + $maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max))); + } + + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); + } + + public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + { + if ($this->min === null) { + $minIsSmaller = TrinaryLogic::createNo(); + } else { + $minIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->min))); + } + + if ($this->max === null) { + $maxIsSmaller = TrinaryLogic::createYes(); + } else { + $maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max))); + } + + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); + } + + public function getSmallerType(): Type + { + $subtractedTypes = [ + new ConstantBooleanType(true), + ]; + + if ($this->max !== null) { + $subtractedTypes[] = self::createAllGreaterThanOrEqualTo($this->max); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getSmallerOrEqualType(): Type + { + $subtractedTypes = []; + + if ($this->max !== null) { + $subtractedTypes[] = self::createAllGreaterThan($this->max); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterType(): Type + { + $subtractedTypes = [ + new NullType(), + new ConstantBooleanType(false), + ]; + + if ($this->min !== null) { + $subtractedTypes[] = self::createAllSmallerThanOrEqualTo($this->min); + } + + if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) { + $subtractedTypes[] = new ConstantBooleanType(true); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterOrEqualType(): Type + { + $subtractedTypes = []; + + if ($this->min !== null) { + $subtractedTypes[] = self::createAllSmallerThan($this->min); + } + + if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) { + $subtractedTypes[] = new NullType(); + $subtractedTypes[] = new ConstantBooleanType(false); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function toNumber(): Type + { + return new parent(); + } + + public function toBoolean(): BooleanType + { + $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this); + if ($isZero->no()) { + return new ConstantBooleanType(true); + } + + if ($isZero->maybe()) { + return new BooleanType(); + } + + return new ConstantBooleanType(false); + } + + /** + * Return the union with another type, but only if it can be expressed in a simpler way than using UnionType + * + * @param Type $otherType + * @return Type|null + */ + public function tryUnion(Type $otherType): ?Type + { + if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) { + if ($otherType instanceof self) { + $otherMin = $otherType->min; + $otherMax = $otherType->max; + } else { + $otherMin = $otherType->getValue(); + $otherMax = $otherType->getValue(); + } + + if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) { + return null; + } + + return self::fromInterval( + $this->min !== null && $otherMin !== null ? min($this->min, $otherMin) : null, + $this->max !== null && $otherMax !== null ? max($this->max, $otherMax) : null + ); + } + + if (get_class($otherType) === parent::class) { + return $otherType; + } + + return null; + } + + /** + * Return the intersection with another type, but only if it can be expressed in a simpler way than using + * IntersectionType + * + * @param Type $otherType + * @return Type|null + */ + public function tryIntersect(Type $otherType): ?Type + { + if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) { + if ($otherType instanceof self) { + $otherMin = $otherType->min; + $otherMax = $otherType->max; + } else { + $otherMin = $otherType->getValue(); + $otherMax = $otherType->getValue(); + } + + if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) { + return new NeverType(); + } + + if ($this->min === null) { + $newMin = $otherMin; + } elseif ($otherMin === null) { + $newMin = $this->min; + } else { + $newMin = max($this->min, $otherMin); + } + + if ($this->max === null) { + $newMax = $otherMax; + } elseif ($otherMax === null) { + $newMax = $this->max; + } else { + $newMax = min($this->max, $otherMax); + } + + return self::fromInterval($newMin, $newMax); + } + + if (get_class($otherType) === parent::class) { + return $this; + } + + return null; + } + + /** + * Return the different with another type, or null if it cannot be represented. + * + * @param Type $typeToRemove + * @return Type|null + */ + public function tryRemove(Type $typeToRemove): ?Type + { + if (get_class($typeToRemove) === parent::class) { + return new NeverType(); + } + + if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { + if ($typeToRemove instanceof IntegerRangeType) { + $removeMin = $typeToRemove->min; + $removeMax = $typeToRemove->max; + } else { + $removeMin = $typeToRemove->getValue(); + $removeMax = $typeToRemove->getValue(); + } + + if ( + $this->min !== null && $removeMax !== null && $removeMax < $this->min + || $this->max !== null && $removeMin !== null && $this->max < $removeMin + ) { + return $this; + } + + if ($removeMin !== null && $removeMin !== PHP_INT_MIN) { + $lowerPart = self::fromInterval($this->min, $removeMin - 1); + } else { + $lowerPart = null; + } + if ($removeMax !== null && $removeMax !== PHP_INT_MAX) { + $upperPart = self::fromInterval($removeMax + 1, $this->max); + } else { + $upperPart = null; + } + + if ($lowerPart !== null && $upperPart !== null) { + return TypeCombinator::union($lowerPart, $upperPart); + } + + return $lowerPart ?? $upperPart; + } + + return null; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['min'], $properties['max']); + } } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index ec483a1fef..6c32bfab03 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -1,4 +1,6 @@ -types = UnionTypeHelper::sortTypes($types); - } - - /** - * @return Type[] - */ - public function getTypes(): array - { - return $this->types; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return UnionTypeHelper::getReferencedClasses($this->types); - } - - public function accepts(Type $otherType, bool $strictTypes): TrinaryLogic - { - foreach ($this->types as $type) { - if (!$type->accepts($otherType, $strictTypes)->yes()) { - return TrinaryLogic::createNo(); - } - } - - return TrinaryLogic::createYes(); - } - - public function isSuperTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof IntersectionType && $this->equals($otherType)) { - return TrinaryLogic::createYes(); - } - - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $innerType->isSuperTypeOf($otherType); - } - - return TrinaryLogic::createYes()->and(...$results); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof self || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf($this); - } - - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $otherType->isSuperTypeOf($innerType); - } - - return TrinaryLogic::maxMin(...$results); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - if ($acceptingType instanceof self || $acceptingType instanceof UnionType) { - return $acceptingType->isSuperTypeOf($this); - } - - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $acceptingType->accepts($innerType, $strictTypes); - } - - return TrinaryLogic::maxMin(...$results); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if (count($this->types) !== count($type->types)) { - return false; - } - - foreach ($this->types as $i => $innerType) { - if (!$innerType->equals($type->types[$i])) { - return false; - } - } - - return true; - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - function () use ($level): string { - $typeNames = []; - foreach ($this->types as $type) { - if ($type instanceof AccessoryType) { - continue; - } - $typeNames[] = TypeUtils::generalizeType($type)->describe($level); - } - - return implode('&', $typeNames); - }, - function () use ($level): string { - $typeNames = []; - foreach ($this->types as $type) { - if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType) { - continue; - } - $typeNames[] = $type->describe($level); - } - - return implode('&', $typeNames); - }, - function () use ($level): string { - $typeNames = []; - foreach ($this->types as $type) { - $typeNames[] = $type->describe($level); - } - - return implode('&', $typeNames); - } - ); - } - - public function canAccessProperties(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->canAccessProperties(); - }); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($propertyName): TrinaryLogic { - return $type->hasProperty($propertyName); - }); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $propertyPrototypes = []; - foreach ($this->types as $type) { - if (!$type->hasProperty($propertyName)->yes()) { - continue; - } - - $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this); - } - - $propertiesCount = count($propertyPrototypes); - if ($propertiesCount === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($propertiesCount === 1) { - return $propertyPrototypes[0]; - } - - return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); - } - - public function canCallMethods(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->canCallMethods(); - }); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($methodName): TrinaryLogic { - return $type->hasMethod($methodName); - }); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $methodPrototypes = []; - foreach ($this->types as $type) { - if (!$type->hasMethod($methodName)->yes()) { - continue; - } - - $methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this); - } - - $methodsCount = count($methodPrototypes); - if ($methodsCount === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($methodsCount === 1) { - return $methodPrototypes[0]; - } - - return new IntersectionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes); - } - - public function canAccessConstants(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->canAccessConstants(); - }); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($constantName): TrinaryLogic { - return $type->hasConstant($constantName); - }); - } - - public function getConstant(string $constantName): ConstantReflection - { - foreach ($this->types as $type) { - if ($type->hasConstant($constantName)->yes()) { - return $type->getConstant($constantName); - } - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function isIterable(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isIterable(); - }); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isIterableAtLeastOnce(); - }); - } - - public function getIterableKeyType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getIterableKeyType(); - }); - } - - public function getIterableValueType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getIterableValueType(); - }); - } - - public function isArray(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isArray(); - }); - } - - public function isNumericString(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isNumericString(); - }); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isOffsetAccessible(); - }); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($offsetType): TrinaryLogic { - return $type->hasOffsetValueType($offsetType); - }); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return $this->intersectTypes(static function (Type $type) use ($offsetType): Type { - return $type->getOffsetValueType($offsetType); - }); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this->intersectTypes(static function (Type $type) use ($offsetType, $valueType): Type { - return $type->setOffsetValueType($offsetType, $valueType); - }); - } - - public function isCallable(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isCallable(); - }); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - if ($this->isCallable()->no()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return [new TrivialParametersAcceptor()]; - } - - public function isCloneable(): TrinaryLogic - { - return $this->intersectResults(static function (Type $type): TrinaryLogic { - return $type->isCloneable(); - }); - } - - public function isSmallerThan(Type $otherType): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $type->isSmallerThan($otherType); - }); - } - - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $type->isSmallerThanOrEqual($otherType); - }); - } - - public function isGreaterThan(Type $otherType): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $otherType->isSmallerThan($type); - }); - } - - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic - { - return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $otherType->isSmallerThanOrEqual($type); - }); - } - - public function getSmallerType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getSmallerType(); - }); - } - - public function getSmallerOrEqualType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getSmallerOrEqualType(); - }); - } - - public function getGreaterType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getGreaterType(); - }); - } - - public function getGreaterOrEqualType(): Type - { - return $this->intersectTypes(static function (Type $type): Type { - return $type->getGreaterOrEqualType(); - }); - } - - public function toBoolean(): BooleanType - { - $type = $this->intersectTypes(static function (Type $type): BooleanType { - return $type->toBoolean(); - }); - - if (!$type instanceof BooleanType) { - return new BooleanType(); - } - - return $type; - } - - public function toNumber(): Type - { - $type = $this->intersectTypes(static function (Type $type): Type { - return $type->toNumber(); - }); - - return $type; - } - - public function toString(): Type - { - $type = $this->intersectTypes(static function (Type $type): Type { - return $type->toString(); - }); - - return $type; - } - - public function toInteger(): Type - { - $type = $this->intersectTypes(static function (Type $type): Type { - return $type->toInteger(); - }); - - return $type; - } - - public function toFloat(): Type - { - $type = $this->intersectTypes(static function (Type $type): Type { - return $type->toFloat(); - }); - - return $type; - } - - public function toArray(): Type - { - $type = $this->intersectTypes(static function (Type $type): Type { - return $type->toArray(); - }); - - return $type; - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - - foreach ($this->types as $type) { - $types = $types->intersect($type->inferTemplateTypes($receivedType)); - } - - return $types; - } - - public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - - foreach ($this->types as $type) { - $types = $types->intersect($templateType->inferTemplateTypes($type)); - } - - return $types; - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $references = []; - - foreach ($this->types as $type) { - foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { - $references[] = $reference; - } - } - - return $references; - } - - public function traverse(callable $cb): Type - { - $types = []; - $changed = false; - - foreach ($this->types as $type) { - $newType = $cb($type); - if ($type !== $newType) { - $changed = true; - } - $types[] = $newType; - } - - if ($changed) { - return TypeCombinator::intersect(...$types); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types']); - } - - /** - * @param callable(Type $type): TrinaryLogic $getResult - * @return TrinaryLogic - */ - private function intersectResults(callable $getResult): TrinaryLogic - { - $operands = array_map($getResult, $this->types); - return TrinaryLogic::maxMin(...$operands); - } - - /** - * @param callable(Type $type): Type $getType - * @return Type - */ - private function intersectTypes(callable $getType): Type - { - $operands = array_map($getType, $this->types); - return TypeCombinator::intersect(...$operands); - } - + /** @var \PHPStan\Type\Type[] */ + private array $types; + + /** + * @param Type[] $types + */ + public function __construct(array $types) + { + $this->types = UnionTypeHelper::sortTypes($types); + } + + /** + * @return Type[] + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return UnionTypeHelper::getReferencedClasses($this->types); + } + + public function accepts(Type $otherType, bool $strictTypes): TrinaryLogic + { + foreach ($this->types as $type) { + if (!$type->accepts($otherType, $strictTypes)->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createYes(); + } + + public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof IntersectionType && $this->equals($otherType)) { + return TrinaryLogic::createYes(); + } + + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $innerType->isSuperTypeOf($otherType); + } + + return TrinaryLogic::createYes()->and(...$results); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof self || $otherType instanceof UnionType) { + return $otherType->isSuperTypeOf($this); + } + + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $otherType->isSuperTypeOf($innerType); + } + + return TrinaryLogic::maxMin(...$results); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + if ($acceptingType instanceof self || $acceptingType instanceof UnionType) { + return $acceptingType->isSuperTypeOf($this); + } + + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $acceptingType->accepts($innerType, $strictTypes); + } + + return TrinaryLogic::maxMin(...$results); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if (count($this->types) !== count($type->types)) { + return false; + } + + foreach ($this->types as $i => $innerType) { + if (!$innerType->equals($type->types[$i])) { + return false; + } + } + + return true; + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + function () use ($level): string { + $typeNames = []; + foreach ($this->types as $type) { + if ($type instanceof AccessoryType) { + continue; + } + $typeNames[] = TypeUtils::generalizeType($type)->describe($level); + } + + return implode('&', $typeNames); + }, + function () use ($level): string { + $typeNames = []; + foreach ($this->types as $type) { + if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType) { + continue; + } + $typeNames[] = $type->describe($level); + } + + return implode('&', $typeNames); + }, + function () use ($level): string { + $typeNames = []; + foreach ($this->types as $type) { + $typeNames[] = $type->describe($level); + } + + return implode('&', $typeNames); + } + ); + } + + public function canAccessProperties(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->canAccessProperties(); + }); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($propertyName): TrinaryLogic { + return $type->hasProperty($propertyName); + }); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + + public function canCallMethods(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->canCallMethods(); + }); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($methodName): TrinaryLogic { + return $type->hasMethod($methodName); + }); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $methodPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasMethod($methodName)->yes()) { + continue; + } + + $methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this); + } + + $methodsCount = count($methodPrototypes); + if ($methodsCount === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($methodsCount === 1) { + return $methodPrototypes[0]; + } + + return new IntersectionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes); + } + + public function canAccessConstants(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->canAccessConstants(); + }); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($constantName): TrinaryLogic { + return $type->hasConstant($constantName); + }); + } + + public function getConstant(string $constantName): ConstantReflection + { + foreach ($this->types as $type) { + if ($type->hasConstant($constantName)->yes()) { + return $type->getConstant($constantName); + } + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function isIterable(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isIterable(); + }); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isIterableAtLeastOnce(); + }); + } + + public function getIterableKeyType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getIterableKeyType(); + }); + } + + public function getIterableValueType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getIterableValueType(); + }); + } + + public function isArray(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isArray(); + }); + } + + public function isNumericString(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isNumericString(); + }); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isOffsetAccessible(); + }); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($offsetType): TrinaryLogic { + return $type->hasOffsetValueType($offsetType); + }); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return $this->intersectTypes(static function (Type $type) use ($offsetType): Type { + return $type->getOffsetValueType($offsetType); + }); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this->intersectTypes(static function (Type $type) use ($offsetType, $valueType): Type { + return $type->setOffsetValueType($offsetType, $valueType); + }); + } + + public function isCallable(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isCallable(); + }); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + if ($this->isCallable()->no()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return [new TrivialParametersAcceptor()]; + } + + public function isCloneable(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isCloneable(); + }); + } + + public function isSmallerThan(Type $otherType): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $type->isSmallerThan($otherType); + }); + } + + public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $type->isSmallerThanOrEqual($otherType); + }); + } + + public function isGreaterThan(Type $otherType): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $otherType->isSmallerThan($type); + }); + } + + public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + { + return $this->intersectResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $otherType->isSmallerThanOrEqual($type); + }); + } + + public function getSmallerType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getSmallerType(); + }); + } + + public function getSmallerOrEqualType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getSmallerOrEqualType(); + }); + } + + public function getGreaterType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getGreaterType(); + }); + } + + public function getGreaterOrEqualType(): Type + { + return $this->intersectTypes(static function (Type $type): Type { + return $type->getGreaterOrEqualType(); + }); + } + + public function toBoolean(): BooleanType + { + $type = $this->intersectTypes(static function (Type $type): BooleanType { + return $type->toBoolean(); + }); + + if (!$type instanceof BooleanType) { + return new BooleanType(); + } + + return $type; + } + + public function toNumber(): Type + { + $type = $this->intersectTypes(static function (Type $type): Type { + return $type->toNumber(); + }); + + return $type; + } + + public function toString(): Type + { + $type = $this->intersectTypes(static function (Type $type): Type { + return $type->toString(); + }); + + return $type; + } + + public function toInteger(): Type + { + $type = $this->intersectTypes(static function (Type $type): Type { + return $type->toInteger(); + }); + + return $type; + } + + public function toFloat(): Type + { + $type = $this->intersectTypes(static function (Type $type): Type { + return $type->toFloat(); + }); + + return $type; + } + + public function toArray(): Type + { + $type = $this->intersectTypes(static function (Type $type): Type { + return $type->toArray(); + }); + + return $type; + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + + foreach ($this->types as $type) { + $types = $types->intersect($type->inferTemplateTypes($receivedType)); + } + + return $types; + } + + public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + + foreach ($this->types as $type) { + $types = $types->intersect($templateType->inferTemplateTypes($type)); + } + + return $types; + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $references = []; + + foreach ($this->types as $type) { + foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + + public function traverse(callable $cb): Type + { + $types = []; + $changed = false; + + foreach ($this->types as $type) { + $newType = $cb($type); + if ($type !== $newType) { + $changed = true; + } + $types[] = $newType; + } + + if ($changed) { + return TypeCombinator::intersect(...$types); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['types']); + } + + /** + * @param callable(Type $type): TrinaryLogic $getResult + * @return TrinaryLogic + */ + private function intersectResults(callable $getResult): TrinaryLogic + { + $operands = array_map($getResult, $this->types); + return TrinaryLogic::maxMin(...$operands); + } + + /** + * @param callable(Type $type): Type $getType + * @return Type + */ + private function intersectTypes(callable $getType): Type + { + $operands = array_map($getType, $this->types); + return TypeCombinator::intersect(...$operands); + } } diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index e1de4d5fc4..60efc5ed0b 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -1,4 +1,6 @@ -keyType = $keyType; - $this->itemType = $itemType; - } - - public function getKeyType(): Type - { - return $this->keyType; - } - - public function getItemType(): Type - { - return $this->itemType; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return array_merge( - $this->keyType->getReferencedClasses(), - $this->getItemType()->getReferencedClasses() - ); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof ConstantArrayType && $type->isEmpty()) { - return TrinaryLogic::createYes(); - } - if ($type->isIterable()->yes()) { - return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes) - ->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes)); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return TrinaryLogic::createNo(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $type->isIterable() - ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())) - ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); - } - - public function isSuperTypeOfMixed(Type $type): TrinaryLogic - { - return $type->isIterable() - ->and($this->isNestedTypeSuperTypeOf($this->getIterableValueType(), $type->getIterableValueType())) - ->and($this->isNestedTypeSuperTypeOf($this->getIterableKeyType(), $type->getIterableKeyType())); - } - - private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic - { - if (!$a instanceof MixedType || !$b instanceof MixedType) { - return $a->isSuperTypeOf($b); - } - - if ($a instanceof TemplateMixedType || $b instanceof TemplateMixedType) { - return $a->isSuperTypeOf($b); - } - - if ($a->isExplicitMixed()) { - if ($b->isExplicitMixed()) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createYes(); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf(new UnionType([ - new ArrayType($this->keyType, $this->itemType), - new IntersectionType([ - new ObjectType(\Traversable::class), - $this, - ]), - ])); - } - - if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); - } else { - $limit = TrinaryLogic::createMaybe(); - } - - if ($otherType instanceof ConstantArrayType && $otherType->isEmpty()) { - return TrinaryLogic::createMaybe(); - } - - return $limit->and( - $otherType->isIterable(), - $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), - $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType) - ); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - return $this->keyType->equals($type->keyType) - && $this->itemType->equals($type->itemType); - } - - public function describe(VerbosityLevel $level): string - { - $isMixedKeyType = $this->keyType instanceof MixedType && !$this->keyType instanceof TemplateType; - $isMixedItemType = $this->itemType instanceof MixedType && !$this->itemType instanceof TemplateType; - - if ($isMixedKeyType) { - if ($isMixedItemType) { - return 'iterable'; - } - - return sprintf('iterable<%s>', $this->itemType->describe($level)); - } - - return sprintf('iterable<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - if ($this->getIterableKeyType()->isSuperTypeOf($offsetType)->no()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new ArrayType($this->keyType, $this->getItemType()); - } - - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getIterableKeyType(): Type - { - return $this->keyType; - } - - public function getIterableValueType(): Type - { - return $this->getItemType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { - return $receivedType->inferTemplateTypesOn($this); - } - - if (!$receivedType->isIterable()->yes()) { - return TemplateTypeMap::createEmpty(); - } - - $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); - $valueTypeMap = $this->getIterableValueType()->inferTemplateTypes($receivedType->getIterableValueType()); - - return $keyTypeMap->union($valueTypeMap); - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - return array_merge( - $this->getIterableKeyType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()), - $this->getIterableValueType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()) - ); - } - - public function traverse(callable $cb): Type - { - $keyType = $cb($this->keyType); - $itemType = $cb($this->itemType); - - if ($keyType !== $this->keyType || $itemType !== $this->itemType) { - return new self($keyType, $itemType); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['keyType'], $properties['itemType']); - } - + use MaybeCallableTypeTrait; + use MaybeObjectTypeTrait; + use MaybeOffsetAccessibleTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private \PHPStan\Type\Type $keyType; + + private \PHPStan\Type\Type $itemType; + + public function __construct( + Type $keyType, + Type $itemType + ) { + $this->keyType = $keyType; + $this->itemType = $itemType; + } + + public function getKeyType(): Type + { + return $this->keyType; + } + + public function getItemType(): Type + { + return $this->itemType; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return array_merge( + $this->keyType->getReferencedClasses(), + $this->getItemType()->getReferencedClasses() + ); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof ConstantArrayType && $type->isEmpty()) { + return TrinaryLogic::createYes(); + } + if ($type->isIterable()->yes()) { + return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes) + ->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes)); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $type->isIterable() + ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())) + ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); + } + + public function isSuperTypeOfMixed(Type $type): TrinaryLogic + { + return $type->isIterable() + ->and($this->isNestedTypeSuperTypeOf($this->getIterableValueType(), $type->getIterableValueType())) + ->and($this->isNestedTypeSuperTypeOf($this->getIterableKeyType(), $type->getIterableKeyType())); + } + + private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic + { + if (!$a instanceof MixedType || !$b instanceof MixedType) { + return $a->isSuperTypeOf($b); + } + + if ($a instanceof TemplateMixedType || $b instanceof TemplateMixedType) { + return $a->isSuperTypeOf($b); + } + + if ($a->isExplicitMixed()) { + if ($b->isExplicitMixed()) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createYes(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { + return $otherType->isSuperTypeOf(new UnionType([ + new ArrayType($this->keyType, $this->itemType), + new IntersectionType([ + new ObjectType(\Traversable::class), + $this, + ]), + ])); + } + + if ($otherType instanceof self) { + $limit = TrinaryLogic::createYes(); + } else { + $limit = TrinaryLogic::createMaybe(); + } + + if ($otherType instanceof ConstantArrayType && $otherType->isEmpty()) { + return TrinaryLogic::createMaybe(); + } + + return $limit->and( + $otherType->isIterable(), + $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), + $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType) + ); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + return $this->keyType->equals($type->keyType) + && $this->itemType->equals($type->itemType); + } + + public function describe(VerbosityLevel $level): string + { + $isMixedKeyType = $this->keyType instanceof MixedType && !$this->keyType instanceof TemplateType; + $isMixedItemType = $this->itemType instanceof MixedType && !$this->itemType instanceof TemplateType; + + if ($isMixedKeyType) { + if ($isMixedItemType) { + return 'iterable'; + } + + return sprintf('iterable<%s>', $this->itemType->describe($level)); + } + + return sprintf('iterable<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level)); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + if ($this->getIterableKeyType()->isSuperTypeOf($offsetType)->no()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new ArrayType($this->keyType, $this->getItemType()); + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getIterableKeyType(): Type + { + return $this->keyType; + } + + public function getIterableValueType(): Type + { + return $this->getItemType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + return $receivedType->inferTemplateTypesOn($this); + } + + if (!$receivedType->isIterable()->yes()) { + return TemplateTypeMap::createEmpty(); + } + + $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); + $valueTypeMap = $this->getIterableValueType()->inferTemplateTypes($receivedType->getIterableValueType()); + + return $keyTypeMap->union($valueTypeMap); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + return array_merge( + $this->getIterableKeyType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()), + $this->getIterableValueType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()) + ); + } + + public function traverse(callable $cb): Type + { + $keyType = $cb($this->keyType); + $itemType = $cb($this->itemType); + + if ($keyType !== $this->keyType || $itemType !== $this->itemType) { + return new self($keyType, $itemType); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['keyType'], $properties['itemType']); + } } diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index d56223fa0f..eb683d3ffc 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -1,4 +1,6 @@ -isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return get_class($type) === static::class; - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof static) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return get_class($type) === static::class; + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } } diff --git a/src/Type/MethodTypeSpecifyingExtension.php b/src/Type/MethodTypeSpecifyingExtension.php index 2776d8db62..1c84860edb 100644 --- a/src/Type/MethodTypeSpecifyingExtension.php +++ b/src/Type/MethodTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -isExplicitMixed = $isExplicitMixed; - $this->subtractedType = $subtractedType; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return []; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic - { - if ($this->subtractedType === null) { - if ($this->isExplicitMixed) { - if ($type->isExplicitMixed) { - return TrinaryLogic::createYes(); - } - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createYes(); - } - - if ($type->subtractedType === null) { - return TrinaryLogic::createMaybe(); - } - - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); - if ($isSuperType->yes()) { - if ($this->isExplicitMixed) { - if ($type->isExplicitMixed) { - return TrinaryLogic::createYes(); - } - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($this->subtractedType === null || $type instanceof NeverType) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof self) { - if ($type->subtractedType === null) { - return TrinaryLogic::createMaybe(); - } - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); - if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createMaybe(); - } - - return $this->subtractedType->isSuperTypeOf($type)->negate(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return new MixedType(); - } - - public function isCallable(): TrinaryLogic - { - if ( - $this->subtractedType !== null - && $this->subtractedType->isCallable()->yes() - ) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [new TrivialParametersAcceptor()]; - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if ($this->subtractedType === null) { - if ($type->subtractedType === null) { - return true; - } - - return false; - } - - if ($type->subtractedType === null) { - return false; - } - - return $this->subtractedType->equals($type->subtractedType); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) { - return TrinaryLogic::createYes(); - } - - if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($otherType); - if ($isSuperType->yes()) { - return TrinaryLogic::createNo(); - } - } - - return TrinaryLogic::createMaybe(); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - $isSuperType = $this->isSuperTypeOf($acceptingType); - if ($isSuperType->no()) { - return $isSuperType; - } - return TrinaryLogic::createYes(); - } - - public function canAccessProperties(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $property = new DummyPropertyReflection(); - return new CallbackUnresolvedPropertyPrototypeReflection( - $property, - $property->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canCallMethods(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $method = new DummyMethodReflection($methodName); - return new CallbackUnresolvedMethodPrototypeReflection( - $method, - $method->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canAccessConstants(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function getConstant(string $constantName): ConstantReflection - { - return new DummyConstantReflection($constantName); - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'mixed'; - }, - static function (): string { - return 'mixed'; - }, - function () use ($level): string { - $description = 'mixed'; - if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - }, - function () use ($level): string { - $description = 'mixed'; - if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); - } - - if ($this->isExplicitMixed) { - $description .= '=explicit'; - } else { - $description .= '=implicit'; - } - - return $description; - } - ); - } - - public function toBoolean(): BooleanType - { - if ($this->subtractedType !== null && StaticTypeFactory::falsey()->equals($this->subtractedType)) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - public function toNumber(): Type - { - return new UnionType([ - $this->toInteger(), - $this->toFloat(), - ]); - } - - public function toInteger(): Type - { - return new IntegerType(); - } - - public function toFloat(): Type - { - return new FloatType(); - } - - public function toString(): Type - { - return new StringType(); - } - - public function toArray(): Type - { - return new ArrayType(new MixedType(), new MixedType()); - } - - public function isExplicitMixed(): bool - { - return $this->isExplicitMixed; - } - - public function subtract(Type $type): Type - { - if ($type instanceof self && !$type instanceof TemplateType) { - return new NeverType(); - } - if ($this->subtractedType !== null) { - $type = TypeCombinator::union($this->subtractedType, $type); - } - - return new self($this->isExplicitMixed, $type); - } - - public function getTypeWithoutSubtractedType(): Type - { - return new self($this->isExplicitMixed); - } - - public function changeSubtractedType(?Type $subtractedType): Type - { - return new self($this->isExplicitMixed, $subtractedType); - } - - public function getSubtractedType(): ?Type - { - return $this->subtractedType; - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['isExplicitMixed'], - $properties['subtractedType'] ?? null - ); - } - + use MaybeIterableTypeTrait; + use MaybeOffsetAccessibleTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private bool $isExplicitMixed; + + private ?\PHPStan\Type\Type $subtractedType; + + public function __construct( + bool $isExplicitMixed = false, + ?Type $subtractedType = null + ) { + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + $this->isExplicitMixed = $isExplicitMixed; + $this->subtractedType = $subtractedType; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + { + if ($this->subtractedType === null) { + if ($this->isExplicitMixed) { + if ($type->isExplicitMixed) { + return TrinaryLogic::createYes(); + } + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createYes(); + } + + if ($type->subtractedType === null) { + return TrinaryLogic::createMaybe(); + } + + $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + if ($isSuperType->yes()) { + if ($this->isExplicitMixed) { + if ($type->isExplicitMixed) { + return TrinaryLogic::createYes(); + } + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($this->subtractedType === null || $type instanceof NeverType) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof self) { + if ($type->subtractedType === null) { + return TrinaryLogic::createMaybe(); + } + $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + if ($isSuperType->yes()) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + return $this->subtractedType->isSuperTypeOf($type)->negate(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return new MixedType(); + } + + public function isCallable(): TrinaryLogic + { + if ( + $this->subtractedType !== null + && $this->subtractedType->isCallable()->yes() + ) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [new TrivialParametersAcceptor()]; + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if ($this->subtractedType === null) { + if ($type->subtractedType === null) { + return true; + } + + return false; + } + + if ($type->subtractedType === null) { + return false; + } + + return $this->subtractedType->equals($type->subtractedType); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) { + return TrinaryLogic::createYes(); + } + + if ($this->subtractedType !== null) { + $isSuperType = $this->subtractedType->isSuperTypeOf($otherType); + if ($isSuperType->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createMaybe(); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + $isSuperType = $this->isSuperTypeOf($acceptingType); + if ($isSuperType->no()) { + return $isSuperType; + } + return TrinaryLogic::createYes(); + } + + public function canAccessProperties(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canCallMethods(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $method = new DummyMethodReflection($methodName); + return new CallbackUnresolvedMethodPrototypeReflection( + $method, + $method->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canAccessConstants(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getConstant(string $constantName): ConstantReflection + { + return new DummyConstantReflection($constantName); + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'mixed'; + }, + static function (): string { + return 'mixed'; + }, + function () use ($level): string { + $description = 'mixed'; + if ($this->subtractedType !== null) { + $description .= sprintf('~%s', $this->subtractedType->describe($level)); + } + + return $description; + }, + function () use ($level): string { + $description = 'mixed'; + if ($this->subtractedType !== null) { + $description .= sprintf('~%s', $this->subtractedType->describe($level)); + } + + if ($this->isExplicitMixed) { + $description .= '=explicit'; + } else { + $description .= '=implicit'; + } + + return $description; + } + ); + } + + public function toBoolean(): BooleanType + { + if ($this->subtractedType !== null && StaticTypeFactory::falsey()->equals($this->subtractedType)) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + public function toNumber(): Type + { + return new UnionType([ + $this->toInteger(), + $this->toFloat(), + ]); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return new StringType(); + } + + public function toArray(): Type + { + return new ArrayType(new MixedType(), new MixedType()); + } + + public function isExplicitMixed(): bool + { + return $this->isExplicitMixed; + } + + public function subtract(Type $type): Type + { + if ($type instanceof self && !$type instanceof TemplateType) { + return new NeverType(); + } + if ($this->subtractedType !== null) { + $type = TypeCombinator::union($this->subtractedType, $type); + } + + return new self($this->isExplicitMixed, $type); + } + + public function getTypeWithoutSubtractedType(): Type + { + return new self($this->isExplicitMixed); + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + return new self($this->isExplicitMixed, $subtractedType); + } + + public function getSubtractedType(): ?Type + { + return $this->subtractedType; + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['isExplicitMixed'], + $properties['subtractedType'] ?? null + ); + } } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 40ae2df67b..b2f4e273a7 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -1,4 +1,6 @@ -isExplicit = $isExplicit; - } - - public function isExplicit(): bool - { - return $this->isExplicit; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return []; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isSubTypeOf($acceptingType); - } - - public function describe(VerbosityLevel $level): string - { - return '*NEVER*'; - } - - public function canAccessProperties(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function canCallMethods(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function canAccessConstants(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getConstant(string $constantName): ConstantReflection - { - throw new \PHPStan\ShouldNotHappenException(); - } - - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getIterableKeyType(): Type - { - return new NeverType(); - } - - public function getIterableValueType(): Type - { - return new NeverType(); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new NeverType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return new NeverType(); - } - - public function isCallable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return [new TrivialParametersAcceptor()]; - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function toNumber(): Type - { - return $this; - } - - public function toString(): Type - { - return $this; - } - - public function toInteger(): Type - { - return $this; - } - - public function toFloat(): Type - { - return $this; - } - - public function toArray(): Type - { - return $this; - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['isExplicit']); - } - + use FalseyBooleanTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + private bool $isExplicit; + + public function __construct(bool $isExplicit = false) + { + $this->isExplicit = $isExplicit; + } + + public function isExplicit(): bool + { + return $this->isExplicit; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function describe(VerbosityLevel $level): string + { + return '*NEVER*'; + } + + public function canAccessProperties(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function canCallMethods(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function canAccessConstants(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getConstant(string $constantName): ConstantReflection + { + throw new \PHPStan\ShouldNotHappenException(); + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getIterableKeyType(): Type + { + return new NeverType(); + } + + public function getIterableValueType(): Type + { + return new NeverType(); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new NeverType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return new NeverType(); + } + + public function isCallable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return [new TrivialParametersAcceptor()]; + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function toNumber(): Type + { + return $this; + } + + public function toString(): Type + { + return $this; + } + + public function toInteger(): Type + { + return $this; + } + + public function toFloat(): Type + { + return $this; + } + + public function toArray(): Type + { + return $this; + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['isExplicit']); + } } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 1731f72b92..6c376f1357 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -1,4 +1,6 @@ -isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function isSmallerThan(Type $otherType): TrinaryLogic - { - if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean(null < $otherType->getValue()); - } - - if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThan($this); - } - - return TrinaryLogic::createMaybe(); - } - - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic - { - if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean(null <= $otherType->getValue()); - } - - if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThanOrEqual($this); - } - - return TrinaryLogic::createMaybe(); - } - - public function describe(VerbosityLevel $level): string - { - return 'null'; - } - - public function toNumber(): Type - { - return new ConstantIntegerType(0); - } - - public function toString(): Type - { - return new ConstantStringType(''); - } - - public function toInteger(): Type - { - return $this->toNumber(); - } - - public function toFloat(): Type - { - return $this->toNumber()->toFloat(); - } - - public function toArray(): Type - { - return new ConstantArrayType([], []); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return new ErrorType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - $array = new ConstantArrayType([], []); - return $array->setOffsetValueType($offsetType, $valueType); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getSmallerType(): Type - { - return new NeverType(); - } - - public function getSmallerOrEqualType(): Type - { - // All falsey types except '0' - return new UnionType([ - new NullType(), - new ConstantBooleanType(false), - new ConstantIntegerType(0), - new ConstantFloatType(0.0), - new ConstantStringType(''), - new ConstantArrayType([], []), - ]); - } - - public function getGreaterType(): Type - { - // All truthy types, but also '0' - return new MixedType(false, new UnionType([ - new NullType(), - new ConstantBooleanType(false), - new ConstantIntegerType(0), - new ConstantFloatType(0.0), - new ConstantStringType(''), - new ConstantArrayType([], []), - ])); - } - - public function getGreaterOrEqualType(): Type - { - return new MixedType(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + use NonCallableTypeTrait; + use NonIterableTypeTrait; + use NonObjectTypeTrait; + use FalseyBooleanTypeTrait; + use NonGenericTypeTrait; + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + /** + * @return null + */ + public function getValue() + { + return null; + } + + public function generalize(): Type + { + return $this; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function isSmallerThan(Type $otherType): TrinaryLogic + { + if ($otherType instanceof ConstantScalarType) { + return TrinaryLogic::createFromBoolean(null < $otherType->getValue()); + } + + if ($otherType instanceof CompoundType) { + return $otherType->isGreaterThan($this); + } + + return TrinaryLogic::createMaybe(); + } + + public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + { + if ($otherType instanceof ConstantScalarType) { + return TrinaryLogic::createFromBoolean(null <= $otherType->getValue()); + } + + if ($otherType instanceof CompoundType) { + return $otherType->isGreaterThanOrEqual($this); + } + + return TrinaryLogic::createMaybe(); + } + + public function describe(VerbosityLevel $level): string + { + return 'null'; + } + + public function toNumber(): Type + { + return new ConstantIntegerType(0); + } + + public function toString(): Type + { + return new ConstantStringType(''); + } + + public function toInteger(): Type + { + return $this->toNumber(); + } + + public function toFloat(): Type + { + return $this->toNumber()->toFloat(); + } + + public function toArray(): Type + { + return new ConstantArrayType([], []); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new ErrorType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + $array = new ConstantArrayType([], []); + return $array->setOffsetValueType($offsetType, $valueType); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getSmallerType(): Type + { + return new NeverType(); + } + + public function getSmallerOrEqualType(): Type + { + // All falsey types except '0' + return new UnionType([ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new ConstantStringType(''), + new ConstantArrayType([], []), + ]); + } + + public function getGreaterType(): Type + { + // All truthy types, but also '0' + return new MixedType(false, new UnionType([ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new ConstantStringType(''), + new ConstantArrayType([], []), + ])); + } + + public function getGreaterOrEqualType(): Type + { + return new MixedType(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 7b834d7a84..d4ccc6fcc5 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1,4 +1,6 @@ -> */ - private static array $superTypes = []; + private const EXTRA_OFFSET_CLASSES = ['SimpleXMLElement', 'DOMNodeList', 'Threaded']; + + private string $className; - private ?self $cachedParent = null; + private ?\PHPStan\Type\Type $subtractedType; + + private ?ClassReflection $classReflection; - /** @var self[]|null */ - private ?array $cachedInterfaces = null; - - /** @var array>> */ - private static array $methods = []; - - /** @var array>> */ - private static array $properties = []; - - /** @var array> */ - private static array $ancestors = []; - - /** @var array */ - private array $currentAncestors = []; - - public function __construct( - string $className, - ?Type $subtractedType = null, - ?ClassReflection $classReflection = null - ) - { - if ($subtractedType instanceof NeverType) { - $subtractedType = null; - } - - $this->className = $className; - $this->subtractedType = $subtractedType; - $this->classReflection = $classReflection; - } - - private static function createFromReflection(ClassReflection $reflection): self - { - if (!$reflection->isGeneric()) { - return new ObjectType($reflection->getName()); - } - - return new GenericObjectType( - $reflection->getName(), - $reflection->typeMapToList($reflection->getActiveTemplateTypeMap()) - ); - } - - public function getClassName(): string - { - return $this->className; - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return TrinaryLogic::createMaybe(); - } - - if ($classReflection->hasProperty($propertyName)) { - return TrinaryLogic::createYes(); - } - - if ($classReflection->isFinal()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - if (!$scope->isInClass()) { - $canAccessProperty = 'no'; - } else { - $canAccessProperty = $scope->getClassReflection()->getName(); - } - $description = $this->describeCache(); - - if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) { - return self::$properties[$description][$propertyName][$canAccessProperty]; - } - - $nakedClassReflection = $this->getNakedClassReflection(); - if ($nakedClassReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - if (!$nakedClassReflection->hasProperty($propertyName)) { - $nakedClassReflection = $this->getClassReflection(); - } - - if ($nakedClassReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - $property = $nakedClassReflection->getProperty($propertyName, $scope); - - $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); - $resolvedClassReflection = null; - if ($ancestor !== null) { - $resolvedClassReflection = $ancestor->getClassReflection(); - if ($ancestor !== $this) { - $property = $ancestor->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty(); - } - } - if ($resolvedClassReflection === null) { - $resolvedClassReflection = $property->getDeclaringClass(); - } - - return self::$properties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection( - $property, - $resolvedClassReflection, - true, - $this - ); - } - - public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - $classReflection = $this->getNakedClassReflection(); - if ($classReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - if (!$classReflection->hasProperty($propertyName)) { - $classReflection = $this->getClassReflection(); - } - - if ($classReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - return $classReflection->getProperty($propertyName, $scope); - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return [$this->className]; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof StaticType) { - return $this->checkSubclassAcceptability($type->getBaseClass()); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - if ($type instanceof ClosureType) { - return $this->isInstanceOf(\Closure::class); - } - - if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); - } - - if (!$type instanceof TypeWithClassName) { - return TrinaryLogic::createNo(); - } - - return $this->checkSubclassAcceptability($type->getClassName()); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - $thisDescription = $this->describeCache(); - - if ($type instanceof self) { - $description = $type->describeCache(); - } else { - $description = $type->describe(VerbosityLevel::cache()); - } - - if (isset(self::$superTypes[$thisDescription][$description])) { - return self::$superTypes[$thisDescription][$description]; - } - - if ($type instanceof CompoundType) { - return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); - } - - if ($type instanceof ObjectWithoutClassType) { - if ($type->getSubtractedType() !== null) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); - if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); - } - } - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); - } - - if (!$type instanceof TypeWithClassName) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); - } - - if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($type); - if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); - } - } - - if ( - $type instanceof SubtractableType - && $type->getSubtractedType() !== null - ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); - if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); - } - } - - $thisClassName = $this->className; - $thatClassName = $type->getClassName(); - - if ($thatClassName === $thisClassName) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); - } - - $broker = Broker::getInstance(); - - if ($this->getClassReflection() === null || !$broker->hasClass($thatClassName)) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); - } - - $thisClassReflection = $this->getClassReflection(); - $thatClassReflection = $broker->getClass($thatClassName); - - if ($thisClassReflection->getName() === $thatClassReflection->getName()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); - } - - if ($thatClassReflection->isSubclassOf($thisClassName)) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); - } - - if ($thisClassReflection->isSubclassOf($thatClassName)) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); - } - - if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); - } - - if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); - } - - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if ($this->className !== $type->className) { - return false; - } - - if ($this->subtractedType === null) { - if ($type->subtractedType === null) { - return true; - } - - return false; - } - - if ($type->subtractedType === null) { - return false; - } - - return $this->subtractedType->equals($type->subtractedType); - } - - private function checkSubclassAcceptability(string $thatClass): TrinaryLogic - { - if ($this->className === $thatClass) { - return TrinaryLogic::createYes(); - } - - $broker = Broker::getInstance(); - - if ($this->getClassReflection() === null || !$broker->hasClass($thatClass)) { - return TrinaryLogic::createNo(); - } - - $thisReflection = $this->getClassReflection(); - $thatReflection = $broker->getClass($thatClass); - - if ($thisReflection->getName() === $thatReflection->getName()) { - // class alias - return TrinaryLogic::createYes(); - } - - if ($thisReflection->isInterface() && $thatReflection->isInterface()) { - return TrinaryLogic::createFromBoolean( - $thatReflection->implementsInterface($this->className) - ); - } - - return TrinaryLogic::createFromBoolean( - $thatReflection->isSubclassOf($this->className) - ); - } - - public function describe(VerbosityLevel $level): string - { - $preciseNameCallback = function (): string { - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { - return $this->className; - } - - return $broker->getClassName($this->className); - }; - - $preciseWithSubtracted = function () use ($level): string { - $description = $this->className; - if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - }; - - return $level->handle( - $preciseNameCallback, - $preciseNameCallback, - $preciseWithSubtracted, - function () use ($preciseWithSubtracted): string { - return $preciseWithSubtracted() . '-' . static::class . '-' . $this->describeAdditionalCacheKey(); - } - ); - } - - protected function describeAdditionalCacheKey(): string - { - return ''; - } - - private function describeCache(): string - { - if (static::class !== self::class) { - return $this->describe(VerbosityLevel::cache()); - } - - $description = $this->className; - if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); - } - - return $description; - } - - public function toNumber(): Type - { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { - return new UnionType([ - new FloatType(), - new IntegerType(), - ]); - } - - return new ErrorType(); - } - - public function toInteger(): Type - { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { - return new IntegerType(); - } - - if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) { - return new IntegerType(); - } - - return new ErrorType(); - } - - public function toFloat(): Type - { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { - return new FloatType(); - } - return new ErrorType(); - } - - public function toString(): Type - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return new ErrorType(); - } - - if ($classReflection->hasNativeMethod('__toString')) { - return ParametersAcceptorSelector::selectSingle($this->getMethod('__toString', new OutOfClassScope())->getVariants())->getReturnType(); - } - - return new ErrorType(); - } - - public function toArray(): Type - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return new ArrayType(new MixedType(), new MixedType()); - } - - $broker = Broker::getInstance(); - - if ( - !$classReflection->getNativeReflection()->isUserDefined() - || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( - $broker, - $broker->getUniversalObjectCratesClasses(), - $classReflection - ) - ) { - return new ArrayType(new MixedType(), new MixedType()); - } - $arrayKeys = []; - $arrayValues = []; - - do { - foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) { - if ($nativeProperty->isStatic()) { - continue; - } - - $declaringClass = $broker->getClass($nativeProperty->getDeclaringClass()->getName()); - $property = $declaringClass->getNativeProperty($nativeProperty->getName()); - - $keyName = $nativeProperty->getName(); - if ($nativeProperty->isPrivate()) { - $keyName = sprintf( - "\0%s\0%s", - $declaringClass->getName(), - $keyName - ); - } elseif ($nativeProperty->isProtected()) { - $keyName = sprintf( - "\0*\0%s", - $keyName - ); - } - - $arrayKeys[] = new ConstantStringType($keyName); - $arrayValues[] = $property->getReadableType(); - } - - $classReflection = $classReflection->getParentClass(); - } while ($classReflection !== false); - - return new ConstantArrayType($arrayKeys, $arrayValues); - } - - public function toBoolean(): BooleanType - { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { - return new BooleanType(); - } - - return new ConstantBooleanType(true); - } - - public function canAccessProperties(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function canCallMethods(): TrinaryLogic - { - if (strtolower($this->className) === 'stdclass') { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createYes(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return TrinaryLogic::createMaybe(); - } - - if ($classReflection->hasMethod($methodName)) { - return TrinaryLogic::createYes(); - } - - if ($classReflection->isFinal()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - if (!$scope->isInClass()) { - $canCallMethod = 'no'; - } else { - $canCallMethod = $scope->getClassReflection()->getName(); - } - $description = $this->describeCache(); - if (isset(self::$methods[$description][$methodName][$canCallMethod])) { - return self::$methods[$description][$methodName][$canCallMethod]; - } - - $nakedClassReflection = $this->getNakedClassReflection(); - if ($nakedClassReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - if (!$nakedClassReflection->hasMethod($methodName)) { - $nakedClassReflection = $this->getClassReflection(); - } - - if ($nakedClassReflection === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - $method = $nakedClassReflection->getMethod($methodName, $scope); - - $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName()); - $resolvedClassReflection = null; - if ($ancestor !== null) { - $resolvedClassReflection = $ancestor->getClassReflection(); - if ($ancestor !== $this) { - $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod(); - } - } - if ($resolvedClassReflection === null) { - $resolvedClassReflection = $method->getDeclaringClass(); - } - - return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection( - $method, - $resolvedClassReflection, - true, - $this - ); - } - - public function canAccessConstants(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - $class = $this->getClassReflection(); - if ($class === null) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createFromBoolean( - $class->hasConstant($constantName) - ); - } - - public function getConstant(string $constantName): ConstantReflection - { - $class = $this->getClassReflection(); - if ($class === null) { - throw new \PHPStan\Broker\ClassNotFoundException($this->className); - } - - return $class->getConstant($constantName); - } - - public function isIterable(): TrinaryLogic - { - return $this->isInstanceOf(\Traversable::class); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return $this->isInstanceOf(\Traversable::class) - ->and(TrinaryLogic::createMaybe()); - } - - public function getIterableKeyType(): Type - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return new ErrorType(); - } - - if ($this->isInstanceOf(\Iterator::class)->yes()) { - return ParametersAcceptorSelector::selectSingle($this->getMethod('key', new OutOfClassScope())->getVariants())->getReturnType(); - } - - if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { - $keyType = RecursionGuard::run($this, function (): Type { - return ParametersAcceptorSelector::selectSingle( - $this->getMethod('getIterator', new OutOfClassScope())->getVariants() - )->getReturnType()->getIterableKeyType(); - }); - if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) { - return $keyType; - } - } - - if ($this->isInstanceOf(\Traversable::class)->yes()) { - $tKey = GenericTypeVariableResolver::getType($this, \Traversable::class, 'TKey'); - if ($tKey !== null) { - return $tKey; - } - - return new MixedType(); - } - - return new ErrorType(); - } - - public function getIterableValueType(): Type - { - if ($this->isInstanceOf(\Iterator::class)->yes()) { - return ParametersAcceptorSelector::selectSingle( - $this->getMethod('current', new OutOfClassScope())->getVariants() - )->getReturnType(); - } - - if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { - $valueType = RecursionGuard::run($this, function (): Type { - return ParametersAcceptorSelector::selectSingle( - $this->getMethod('getIterator', new OutOfClassScope())->getVariants() - )->getReturnType()->getIterableValueType(); - }); - if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) { - return $valueType; - } - } - - if ($this->isInstanceOf(\Traversable::class)->yes()) { - $tValue = GenericTypeVariableResolver::getType($this, \Traversable::class, 'TValue'); - if ($tValue !== null) { - return $tValue; - } - - return new MixedType(); - } - - return new ErrorType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - private function isExtraOffsetAccessibleClass(): TrinaryLogic - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return TrinaryLogic::createMaybe(); - } - - foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) { - if ($classReflection->getName() === $extraOffsetClass) { - return TrinaryLogic::createYes(); - } - if ($classReflection->isSubclassOf($extraOffsetClass)) { - return TrinaryLogic::createYes(); - } - } - - if ($classReflection->isInterface()) { - return TrinaryLogic::createMaybe(); - } - - if ($classReflection->isFinal()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return $this->isInstanceOf(\ArrayAccess::class)->or( - $this->isExtraOffsetAccessibleClass() - ); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { - $acceptedOffsetType = RecursionGuard::run($this, function (): Type { - $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); - if (count($parameters) < 2) { - throw new \PHPStan\ShouldNotHappenException(sprintf( - 'Method %s::%s() has less than 2 parameters.', - $this->className, - 'offsetSet' - )); - } - - $offsetParameter = $parameters[0]; - - return $offsetParameter->getType(); - }); - - if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); - } - - return $this->isExtraOffsetAccessibleClass() - ->and(TrinaryLogic::createMaybe()); - } - - public function getOffsetValueType(Type $offsetType): Type - { - if (!$this->isExtraOffsetAccessibleClass()->no()) { - return new MixedType(); - } - - if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { - return RecursionGuard::run($this, function (): Type { - return ParametersAcceptorSelector::selectSingle($this->getMethod('offsetGet', new OutOfClassScope())->getVariants())->getReturnType(); - }); - } - - return new ErrorType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - if ($this->isOffsetAccessible()->no()) { - return new ErrorType(); - } - - if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { - $acceptedValueType = new NeverType(); - $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type { - $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); - if (count($parameters) < 2) { - throw new \PHPStan\ShouldNotHappenException(sprintf( - 'Method %s::%s() has less than 2 parameters.', - $this->className, - 'offsetSet' - )); - } - - $offsetParameter = $parameters[0]; - $acceptedValueType = $parameters[1]->getType(); - - return $offsetParameter->getType(); - }); - - if ($offsetType === null) { - $offsetType = new NullType(); - } - - if ( - (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes()) - || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes()) - ) { - return new ErrorType(); - } - } - - // in the future we may return intersection of $this and OffsetAccessibleType() - return $this; - } - - public function isCallable(): TrinaryLogic - { - $parametersAcceptors = $this->findCallableParametersAcceptors(); - if ($parametersAcceptors === null) { - return TrinaryLogic::createNo(); - } - - if ( - count($parametersAcceptors) === 1 - && $parametersAcceptors[0] instanceof TrivialParametersAcceptor - ) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createYes(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - if ($this->className === \Closure::class) { - return [new TrivialParametersAcceptor()]; - } - $parametersAcceptors = $this->findCallableParametersAcceptors(); - if ($parametersAcceptors === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $parametersAcceptors; - } - - /** - * @return \PHPStan\Reflection\ParametersAcceptor[]|null - */ - private function findCallableParametersAcceptors(): ?array - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return [new TrivialParametersAcceptor()]; - } - - if ($classReflection->hasNativeMethod('__invoke')) { - return $this->getMethod('__invoke', new OutOfClassScope())->getVariants(); - } - - if (!$classReflection->getNativeReflection()->isFinal()) { - return [new TrivialParametersAcceptor()]; - } - - return null; - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['className'], - $properties['subtractedType'] ?? null - ); - } - - public function isInstanceOf(string $className): TrinaryLogic - { - $classReflection = $this->getClassReflection(); - if ($classReflection === null) { - return TrinaryLogic::createMaybe(); - } - - if ($classReflection->isSubclassOf($className) || $classReflection->getName() === $className) { - return TrinaryLogic::createYes(); - } - - if ($classReflection->isInterface()) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - public function subtract(Type $type): Type - { - if ($this->subtractedType !== null) { - $type = TypeCombinator::union($this->subtractedType, $type); - } - - return $this->changeSubtractedType($type); - } - - public function getTypeWithoutSubtractedType(): Type - { - return $this->changeSubtractedType(null); - } - - public function changeSubtractedType(?Type $subtractedType): Type - { - return new self($this->className, $subtractedType); - } - - public function getSubtractedType(): ?Type - { - return $this->subtractedType; - } - - public function traverse(callable $cb): Type - { - $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null; - - if ($subtractedType !== $this->subtractedType) { - return new self( - $this->className, - $subtractedType - ); - } - - return $this; - } - - public function getNakedClassReflection(): ?ClassReflection - { - if ($this->classReflection !== null) { - return $this->classReflection; - } - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { - return null; - } - - $this->classReflection = $broker->getClass($this->className); - - return $this->classReflection; - } - - public function getClassReflection(): ?ClassReflection - { - if ($this->classReflection !== null) { - return $this->classReflection; - } - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { - return null; - } - - $classReflection = $broker->getClass($this->className); - if ($classReflection->isGeneric()) { - return $this->classReflection = $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->resolveToBounds()->getTypes())); - } - - return $this->classReflection = $classReflection; - } - - /** - * @param string $className - * @return self|null - */ - public function getAncestorWithClassName(string $className): ?TypeWithClassName - { - if (isset($this->currentAncestors[$className])) { - return $this->currentAncestors[$className]; - } - - $thisReflection = $this->getClassReflection(); - if ($thisReflection === null) { - return null; - } - - $description = $this->describeCache() . '-' . $thisReflection->getCacheKey(); - if (isset(self::$ancestors[$description][$className])) { - return self::$ancestors[$description][$className]; - } - - $broker = Broker::getInstance(); - if (!$broker->hasClass($className)) { - return null; - } - $theirReflection = $broker->getClass($className); - - if ($theirReflection->getName() === $thisReflection->getName()) { - return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this; - } - - foreach ($this->getInterfaces() as $interface) { - $ancestor = $interface->getAncestorWithClassName($className); - if ($ancestor !== null) { - return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; - } - } - - $parent = $this->getParent(); - if ($parent !== null) { - $ancestor = $parent->getAncestorWithClassName($className); - if ($ancestor !== null) { - return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; - } - } - - return null; - } - - private function getParent(): ?ObjectType - { - if ($this->cachedParent !== null) { - return $this->cachedParent; - } - $thisReflection = $this->getClassReflection(); - if ($thisReflection === null) { - return null; - } - - $parentReflection = $thisReflection->getParentClass(); - if ($parentReflection === false) { - return null; - } - - return $this->cachedParent = self::createFromReflection($parentReflection); - } - - /** @return ObjectType[] */ - private function getInterfaces(): array - { - if ($this->cachedInterfaces !== null) { - return $this->cachedInterfaces; - } - $thisReflection = $this->getClassReflection(); - if ($thisReflection === null) { - return $this->cachedInterfaces = []; - } - - return $this->cachedInterfaces = array_map(static function (ClassReflection $interfaceReflection): self { - return self::createFromReflection($interfaceReflection); - }, $thisReflection->getInterfaces()); - } + /** @var array> */ + private static array $superTypes = []; + private ?self $cachedParent = null; + + /** @var self[]|null */ + private ?array $cachedInterfaces = null; + + /** @var array>> */ + private static array $methods = []; + + /** @var array>> */ + private static array $properties = []; + + /** @var array> */ + private static array $ancestors = []; + + /** @var array */ + private array $currentAncestors = []; + + public function __construct( + string $className, + ?Type $subtractedType = null, + ?ClassReflection $classReflection = null + ) { + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + $this->className = $className; + $this->subtractedType = $subtractedType; + $this->classReflection = $classReflection; + } + + private static function createFromReflection(ClassReflection $reflection): self + { + if (!$reflection->isGeneric()) { + return new ObjectType($reflection->getName()); + } + + return new GenericObjectType( + $reflection->getName(), + $reflection->typeMapToList($reflection->getActiveTemplateTypeMap()) + ); + } + + public function getClassName(): string + { + return $this->className; + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->hasProperty($propertyName)) { + return TrinaryLogic::createYes(); + } + + if ($classReflection->isFinal()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + if (!$scope->isInClass()) { + $canAccessProperty = 'no'; + } else { + $canAccessProperty = $scope->getClassReflection()->getName(); + } + $description = $this->describeCache(); + + if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) { + return self::$properties[$description][$propertyName][$canAccessProperty]; + } + + $nakedClassReflection = $this->getNakedClassReflection(); + if ($nakedClassReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + if (!$nakedClassReflection->hasProperty($propertyName)) { + $nakedClassReflection = $this->getClassReflection(); + } + + if ($nakedClassReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + $property = $nakedClassReflection->getProperty($propertyName, $scope); + + $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); + $resolvedClassReflection = null; + if ($ancestor !== null) { + $resolvedClassReflection = $ancestor->getClassReflection(); + if ($ancestor !== $this) { + $property = $ancestor->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty(); + } + } + if ($resolvedClassReflection === null) { + $resolvedClassReflection = $property->getDeclaringClass(); + } + + return self::$properties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection( + $property, + $resolvedClassReflection, + true, + $this + ); + } + + public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + $classReflection = $this->getNakedClassReflection(); + if ($classReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + if (!$classReflection->hasProperty($propertyName)) { + $classReflection = $this->getClassReflection(); + } + + if ($classReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + return $classReflection->getProperty($propertyName, $scope); + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return [$this->className]; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof StaticType) { + return $this->checkSubclassAcceptability($type->getBaseClass()); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + if ($type instanceof ClosureType) { + return $this->isInstanceOf(\Closure::class); + } + + if ($type instanceof ObjectWithoutClassType) { + return TrinaryLogic::createMaybe(); + } + + if (!$type instanceof TypeWithClassName) { + return TrinaryLogic::createNo(); + } + + return $this->checkSubclassAcceptability($type->getClassName()); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + $thisDescription = $this->describeCache(); + + if ($type instanceof self) { + $description = $type->describeCache(); + } else { + $description = $type->describe(VerbosityLevel::cache()); + } + + if (isset(self::$superTypes[$thisDescription][$description])) { + return self::$superTypes[$thisDescription][$description]; + } + + if ($type instanceof CompoundType) { + return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); + } + + if ($type instanceof ObjectWithoutClassType) { + if ($type->getSubtractedType() !== null) { + $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + if ($isSuperType->yes()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + } + } + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + } + + if (!$type instanceof TypeWithClassName) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + } + + if ($this->subtractedType !== null) { + $isSuperType = $this->subtractedType->isSuperTypeOf($type); + if ($isSuperType->yes()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + } + } + + if ( + $type instanceof SubtractableType + && $type->getSubtractedType() !== null + ) { + $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + if ($isSuperType->yes()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + } + } + + $thisClassName = $this->className; + $thatClassName = $type->getClassName(); + + if ($thatClassName === $thisClassName) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); + } + + $broker = Broker::getInstance(); + + if ($this->getClassReflection() === null || !$broker->hasClass($thatClassName)) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + } + + $thisClassReflection = $this->getClassReflection(); + $thatClassReflection = $broker->getClass($thatClassName); + + if ($thisClassReflection->getName() === $thatClassReflection->getName()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); + } + + if ($thatClassReflection->isSubclassOf($thisClassName)) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); + } + + if ($thisClassReflection->isSubclassOf($thatClassName)) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + } + + if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + } + + if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + } + + return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if ($this->className !== $type->className) { + return false; + } + + if ($this->subtractedType === null) { + if ($type->subtractedType === null) { + return true; + } + + return false; + } + + if ($type->subtractedType === null) { + return false; + } + + return $this->subtractedType->equals($type->subtractedType); + } + + private function checkSubclassAcceptability(string $thatClass): TrinaryLogic + { + if ($this->className === $thatClass) { + return TrinaryLogic::createYes(); + } + + $broker = Broker::getInstance(); + + if ($this->getClassReflection() === null || !$broker->hasClass($thatClass)) { + return TrinaryLogic::createNo(); + } + + $thisReflection = $this->getClassReflection(); + $thatReflection = $broker->getClass($thatClass); + + if ($thisReflection->getName() === $thatReflection->getName()) { + // class alias + return TrinaryLogic::createYes(); + } + + if ($thisReflection->isInterface() && $thatReflection->isInterface()) { + return TrinaryLogic::createFromBoolean( + $thatReflection->implementsInterface($this->className) + ); + } + + return TrinaryLogic::createFromBoolean( + $thatReflection->isSubclassOf($this->className) + ); + } + + public function describe(VerbosityLevel $level): string + { + $preciseNameCallback = function (): string { + $broker = Broker::getInstance(); + if (!$broker->hasClass($this->className)) { + return $this->className; + } + + return $broker->getClassName($this->className); + }; + + $preciseWithSubtracted = function () use ($level): string { + $description = $this->className; + if ($this->subtractedType !== null) { + $description .= sprintf('~%s', $this->subtractedType->describe($level)); + } + + return $description; + }; + + return $level->handle( + $preciseNameCallback, + $preciseNameCallback, + $preciseWithSubtracted, + function () use ($preciseWithSubtracted): string { + return $preciseWithSubtracted() . '-' . static::class . '-' . $this->describeAdditionalCacheKey(); + } + ); + } + + protected function describeAdditionalCacheKey(): string + { + return ''; + } + + private function describeCache(): string + { + if (static::class !== self::class) { + return $this->describe(VerbosityLevel::cache()); + } + + $description = $this->className; + if ($this->subtractedType !== null) { + $description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); + } + + return $description; + } + + public function toNumber(): Type + { + if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + return new UnionType([ + new FloatType(), + new IntegerType(), + ]); + } + + return new ErrorType(); + } + + public function toInteger(): Type + { + if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + return new IntegerType(); + } + + if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) { + return new IntegerType(); + } + + return new ErrorType(); + } + + public function toFloat(): Type + { + if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + return new FloatType(); + } + return new ErrorType(); + } + + public function toString(): Type + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return new ErrorType(); + } + + if ($classReflection->hasNativeMethod('__toString')) { + return ParametersAcceptorSelector::selectSingle($this->getMethod('__toString', new OutOfClassScope())->getVariants())->getReturnType(); + } + + return new ErrorType(); + } + + public function toArray(): Type + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return new ArrayType(new MixedType(), new MixedType()); + } + + $broker = Broker::getInstance(); + + if ( + !$classReflection->getNativeReflection()->isUserDefined() + || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( + $broker, + $broker->getUniversalObjectCratesClasses(), + $classReflection + ) + ) { + return new ArrayType(new MixedType(), new MixedType()); + } + $arrayKeys = []; + $arrayValues = []; + + do { + foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) { + if ($nativeProperty->isStatic()) { + continue; + } + + $declaringClass = $broker->getClass($nativeProperty->getDeclaringClass()->getName()); + $property = $declaringClass->getNativeProperty($nativeProperty->getName()); + + $keyName = $nativeProperty->getName(); + if ($nativeProperty->isPrivate()) { + $keyName = sprintf( + "\0%s\0%s", + $declaringClass->getName(), + $keyName + ); + } elseif ($nativeProperty->isProtected()) { + $keyName = sprintf( + "\0*\0%s", + $keyName + ); + } + + $arrayKeys[] = new ConstantStringType($keyName); + $arrayValues[] = $property->getReadableType(); + } + + $classReflection = $classReflection->getParentClass(); + } while ($classReflection !== false); + + return new ConstantArrayType($arrayKeys, $arrayValues); + } + + public function toBoolean(): BooleanType + { + if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + return new BooleanType(); + } + + return new ConstantBooleanType(true); + } + + public function canAccessProperties(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function canCallMethods(): TrinaryLogic + { + if (strtolower($this->className) === 'stdclass') { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createYes(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->hasMethod($methodName)) { + return TrinaryLogic::createYes(); + } + + if ($classReflection->isFinal()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + if (!$scope->isInClass()) { + $canCallMethod = 'no'; + } else { + $canCallMethod = $scope->getClassReflection()->getName(); + } + $description = $this->describeCache(); + if (isset(self::$methods[$description][$methodName][$canCallMethod])) { + return self::$methods[$description][$methodName][$canCallMethod]; + } + + $nakedClassReflection = $this->getNakedClassReflection(); + if ($nakedClassReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + if (!$nakedClassReflection->hasMethod($methodName)) { + $nakedClassReflection = $this->getClassReflection(); + } + + if ($nakedClassReflection === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + $method = $nakedClassReflection->getMethod($methodName, $scope); + + $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName()); + $resolvedClassReflection = null; + if ($ancestor !== null) { + $resolvedClassReflection = $ancestor->getClassReflection(); + if ($ancestor !== $this) { + $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod(); + } + } + if ($resolvedClassReflection === null) { + $resolvedClassReflection = $method->getDeclaringClass(); + } + + return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection( + $method, + $resolvedClassReflection, + true, + $this + ); + } + + public function canAccessConstants(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + $class = $this->getClassReflection(); + if ($class === null) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createFromBoolean( + $class->hasConstant($constantName) + ); + } + + public function getConstant(string $constantName): ConstantReflection + { + $class = $this->getClassReflection(); + if ($class === null) { + throw new \PHPStan\Broker\ClassNotFoundException($this->className); + } + + return $class->getConstant($constantName); + } + + public function isIterable(): TrinaryLogic + { + return $this->isInstanceOf(\Traversable::class); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return $this->isInstanceOf(\Traversable::class) + ->and(TrinaryLogic::createMaybe()); + } + + public function getIterableKeyType(): Type + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return new ErrorType(); + } + + if ($this->isInstanceOf(\Iterator::class)->yes()) { + return ParametersAcceptorSelector::selectSingle($this->getMethod('key', new OutOfClassScope())->getVariants())->getReturnType(); + } + + if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { + $keyType = RecursionGuard::run($this, function (): Type { + return ParametersAcceptorSelector::selectSingle( + $this->getMethod('getIterator', new OutOfClassScope())->getVariants() + )->getReturnType()->getIterableKeyType(); + }); + if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) { + return $keyType; + } + } + + if ($this->isInstanceOf(\Traversable::class)->yes()) { + $tKey = GenericTypeVariableResolver::getType($this, \Traversable::class, 'TKey'); + if ($tKey !== null) { + return $tKey; + } + + return new MixedType(); + } + + return new ErrorType(); + } + + public function getIterableValueType(): Type + { + if ($this->isInstanceOf(\Iterator::class)->yes()) { + return ParametersAcceptorSelector::selectSingle( + $this->getMethod('current', new OutOfClassScope())->getVariants() + )->getReturnType(); + } + + if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { + $valueType = RecursionGuard::run($this, function (): Type { + return ParametersAcceptorSelector::selectSingle( + $this->getMethod('getIterator', new OutOfClassScope())->getVariants() + )->getReturnType()->getIterableValueType(); + }); + if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) { + return $valueType; + } + } + + if ($this->isInstanceOf(\Traversable::class)->yes()) { + $tValue = GenericTypeVariableResolver::getType($this, \Traversable::class, 'TValue'); + if ($tValue !== null) { + return $tValue; + } + + return new MixedType(); + } + + return new ErrorType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + private function isExtraOffsetAccessibleClass(): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) { + if ($classReflection->getName() === $extraOffsetClass) { + return TrinaryLogic::createYes(); + } + if ($classReflection->isSubclassOf($extraOffsetClass)) { + return TrinaryLogic::createYes(); + } + } + + if ($classReflection->isInterface()) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->isFinal()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return $this->isInstanceOf(\ArrayAccess::class)->or( + $this->isExtraOffsetAccessibleClass() + ); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { + $acceptedOffsetType = RecursionGuard::run($this, function (): Type { + $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); + if (count($parameters) < 2) { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Method %s::%s() has less than 2 parameters.', + $this->className, + 'offsetSet' + )); + } + + $offsetParameter = $parameters[0]; + + return $offsetParameter->getType(); + }); + + if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + + return $this->isExtraOffsetAccessibleClass() + ->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if (!$this->isExtraOffsetAccessibleClass()->no()) { + return new MixedType(); + } + + if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { + return RecursionGuard::run($this, function (): Type { + return ParametersAcceptorSelector::selectSingle($this->getMethod('offsetGet', new OutOfClassScope())->getVariants())->getReturnType(); + }); + } + + return new ErrorType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + if ($this->isOffsetAccessible()->no()) { + return new ErrorType(); + } + + if ($this->isInstanceOf(\ArrayAccess::class)->yes()) { + $acceptedValueType = new NeverType(); + $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type { + $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); + if (count($parameters) < 2) { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Method %s::%s() has less than 2 parameters.', + $this->className, + 'offsetSet' + )); + } + + $offsetParameter = $parameters[0]; + $acceptedValueType = $parameters[1]->getType(); + + return $offsetParameter->getType(); + }); + + if ($offsetType === null) { + $offsetType = new NullType(); + } + + if ( + (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes()) + || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes()) + ) { + return new ErrorType(); + } + } + + // in the future we may return intersection of $this and OffsetAccessibleType() + return $this; + } + + public function isCallable(): TrinaryLogic + { + $parametersAcceptors = $this->findCallableParametersAcceptors(); + if ($parametersAcceptors === null) { + return TrinaryLogic::createNo(); + } + + if ( + count($parametersAcceptors) === 1 + && $parametersAcceptors[0] instanceof TrivialParametersAcceptor + ) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createYes(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + if ($this->className === \Closure::class) { + return [new TrivialParametersAcceptor()]; + } + $parametersAcceptors = $this->findCallableParametersAcceptors(); + if ($parametersAcceptors === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $parametersAcceptors; + } + + /** + * @return \PHPStan\Reflection\ParametersAcceptor[]|null + */ + private function findCallableParametersAcceptors(): ?array + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return [new TrivialParametersAcceptor()]; + } + + if ($classReflection->hasNativeMethod('__invoke')) { + return $this->getMethod('__invoke', new OutOfClassScope())->getVariants(); + } + + if (!$classReflection->getNativeReflection()->isFinal()) { + return [new TrivialParametersAcceptor()]; + } + + return null; + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self( + $properties['className'], + $properties['subtractedType'] ?? null + ); + } + + public function isInstanceOf(string $className): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->isSubclassOf($className) || $classReflection->getName() === $className) { + return TrinaryLogic::createYes(); + } + + if ($classReflection->isInterface()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + public function subtract(Type $type): Type + { + if ($this->subtractedType !== null) { + $type = TypeCombinator::union($this->subtractedType, $type); + } + + return $this->changeSubtractedType($type); + } + + public function getTypeWithoutSubtractedType(): Type + { + return $this->changeSubtractedType(null); + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + return new self($this->className, $subtractedType); + } + + public function getSubtractedType(): ?Type + { + return $this->subtractedType; + } + + public function traverse(callable $cb): Type + { + $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null; + + if ($subtractedType !== $this->subtractedType) { + return new self( + $this->className, + $subtractedType + ); + } + + return $this; + } + + public function getNakedClassReflection(): ?ClassReflection + { + if ($this->classReflection !== null) { + return $this->classReflection; + } + $broker = Broker::getInstance(); + if (!$broker->hasClass($this->className)) { + return null; + } + + $this->classReflection = $broker->getClass($this->className); + + return $this->classReflection; + } + + public function getClassReflection(): ?ClassReflection + { + if ($this->classReflection !== null) { + return $this->classReflection; + } + $broker = Broker::getInstance(); + if (!$broker->hasClass($this->className)) { + return null; + } + + $classReflection = $broker->getClass($this->className); + if ($classReflection->isGeneric()) { + return $this->classReflection = $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->resolveToBounds()->getTypes())); + } + + return $this->classReflection = $classReflection; + } + + /** + * @param string $className + * @return self|null + */ + public function getAncestorWithClassName(string $className): ?TypeWithClassName + { + if (isset($this->currentAncestors[$className])) { + return $this->currentAncestors[$className]; + } + + $thisReflection = $this->getClassReflection(); + if ($thisReflection === null) { + return null; + } + + $description = $this->describeCache() . '-' . $thisReflection->getCacheKey(); + if (isset(self::$ancestors[$description][$className])) { + return self::$ancestors[$description][$className]; + } + + $broker = Broker::getInstance(); + if (!$broker->hasClass($className)) { + return null; + } + $theirReflection = $broker->getClass($className); + + if ($theirReflection->getName() === $thisReflection->getName()) { + return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this; + } + + foreach ($this->getInterfaces() as $interface) { + $ancestor = $interface->getAncestorWithClassName($className); + if ($ancestor !== null) { + return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; + } + } + + $parent = $this->getParent(); + if ($parent !== null) { + $ancestor = $parent->getAncestorWithClassName($className); + if ($ancestor !== null) { + return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; + } + } + + return null; + } + + private function getParent(): ?ObjectType + { + if ($this->cachedParent !== null) { + return $this->cachedParent; + } + $thisReflection = $this->getClassReflection(); + if ($thisReflection === null) { + return null; + } + + $parentReflection = $thisReflection->getParentClass(); + if ($parentReflection === false) { + return null; + } + + return $this->cachedParent = self::createFromReflection($parentReflection); + } + + /** @return ObjectType[] */ + private function getInterfaces(): array + { + if ($this->cachedInterfaces !== null) { + return $this->cachedInterfaces; + } + $thisReflection = $this->getClassReflection(); + if ($thisReflection === null) { + return $this->cachedInterfaces = []; + } + + return $this->cachedInterfaces = array_map(static function (ClassReflection $interfaceReflection): self { + return self::createFromReflection($interfaceReflection); + }, $thisReflection->getInterfaces()); + } } diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index e9aaa46c02..2444e791aa 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -1,4 +1,6 @@ -subtractedType = $subtractedType; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return []; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return TrinaryLogic::createFromBoolean( - $type instanceof self || $type instanceof TypeWithClassName - ); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - if ($type instanceof self) { - if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); - } - if ($type->subtractedType !== null) { - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); - if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); - } - } - - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof TypeWithClassName) { - if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); - } - - return $this->subtractedType->isSuperTypeOf($type)->negate(); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - if (!$type instanceof self) { - return false; - } - - if ($this->subtractedType === null) { - if ($type->subtractedType === null) { - return true; - } - - return false; - } - - if ($type->subtractedType === null) { - return false; - } - - return $this->subtractedType->equals($type->subtractedType); - } - - public function describe(VerbosityLevel $level): string - { - return $level->handle( - static function (): string { - return 'object'; - }, - static function (): string { - return 'object'; - }, - function () use ($level): string { - $description = 'object'; - if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); - } - - return $description; - } - ); - } - - public function subtract(Type $type): Type - { - if ($type instanceof self) { - return new NeverType(); - } - if ($this->subtractedType !== null) { - $type = TypeCombinator::union($this->subtractedType, $type); - } - - return new self($type); - } - - public function getTypeWithoutSubtractedType(): Type - { - return new self(); - } - - public function changeSubtractedType(?Type $subtractedType): Type - { - return new self($subtractedType); - } - - public function getSubtractedType(): ?Type - { - return $this->subtractedType; - } - - - public function traverse(callable $cb): Type - { - $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null; - - if ($subtractedType !== $this->subtractedType) { - return new self($subtractedType); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['subtractedType'] ?? null); - } - + use ObjectTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonTypeTrait; + + private ?\PHPStan\Type\Type $subtractedType; + + public function __construct( + ?Type $subtractedType = null + ) { + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + $this->subtractedType = $subtractedType; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createFromBoolean( + $type instanceof self || $type instanceof TypeWithClassName + ); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + if ($type instanceof self) { + if ($this->subtractedType === null) { + return TrinaryLogic::createYes(); + } + if ($type->subtractedType !== null) { + $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + if ($isSuperType->yes()) { + return TrinaryLogic::createYes(); + } + } + + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof TypeWithClassName) { + if ($this->subtractedType === null) { + return TrinaryLogic::createYes(); + } + + return $this->subtractedType->isSuperTypeOf($type)->negate(); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if ($this->subtractedType === null) { + if ($type->subtractedType === null) { + return true; + } + + return false; + } + + if ($type->subtractedType === null) { + return false; + } + + return $this->subtractedType->equals($type->subtractedType); + } + + public function describe(VerbosityLevel $level): string + { + return $level->handle( + static function (): string { + return 'object'; + }, + static function (): string { + return 'object'; + }, + function () use ($level): string { + $description = 'object'; + if ($this->subtractedType !== null) { + $description .= sprintf('~%s', $this->subtractedType->describe($level)); + } + + return $description; + } + ); + } + + public function subtract(Type $type): Type + { + if ($type instanceof self) { + return new NeverType(); + } + if ($this->subtractedType !== null) { + $type = TypeCombinator::union($this->subtractedType, $type); + } + + return new self($type); + } + + public function getTypeWithoutSubtractedType(): Type + { + return new self(); + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + return new self($subtractedType); + } + + public function getSubtractedType(): ?Type + { + return $this->subtractedType; + } + + + public function traverse(callable $cb): Type + { + $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null; + + if ($subtractedType !== $this->subtractedType) { + return new self($subtractedType); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['subtractedType'] ?? null); + } } diff --git a/src/Type/OperatorTypeSpecifyingExtension.php b/src/Type/OperatorTypeSpecifyingExtension.php index 7766086e90..377ab6aca4 100644 --- a/src/Type/OperatorTypeSpecifyingExtension.php +++ b/src/Type/OperatorTypeSpecifyingExtension.php @@ -1,12 +1,12 @@ -setBroker($broker); - } - $this->extensions = $extensions; - } - - /** - * @return OperatorTypeSpecifyingExtension[] - */ - public function getOperatorTypeSpecifyingExtensions(string $operator, Type $leftType, Type $rightType): array - { - return array_values(array_filter($this->extensions, static function (OperatorTypeSpecifyingExtension $extension) use ($operator, $leftType, $rightType): bool { - return $extension->isOperatorSupported($operator, $leftType, $rightType); - })); - } - + /** @var OperatorTypeSpecifyingExtension[] */ + private array $extensions; + + /** + * @param \PHPStan\Type\OperatorTypeSpecifyingExtension[] $extensions + */ + public function __construct( + Broker $broker, + array $extensions + ) { + foreach ($extensions as $extension) { + if (!$extension instanceof BrokerAwareExtension) { + continue; + } + + $extension->setBroker($broker); + } + $this->extensions = $extensions; + } + + /** + * @return OperatorTypeSpecifyingExtension[] + */ + public function getOperatorTypeSpecifyingExtensions(string $operator, Type $leftType, Type $rightType): array + { + return array_values(array_filter($this->extensions, static function (OperatorTypeSpecifyingExtension $extension) use ($operator, $leftType, $rightType): bool { + return $extension->isOperatorSupported($operator, $leftType, $rightType); + })); + } } diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index a6910b65ae..e83e02d436 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -1,4 +1,6 @@ -type, $className)); - } elseif ($type instanceof \PhpParser\Node\UnionType) { - $types = []; - foreach ($type->types as $unionTypeType) { - $types[] = self::resolve($unionTypeType, $className); - } + if ($lowercasedClassName === 'static') { + return new StaticType($typeClassName); + } - return TypeCombinator::union(...$types); - } + return new ObjectType($typeClassName); + } elseif ($type instanceof NullableType) { + return TypeCombinator::addNull(self::resolve($type->type, $className)); + } elseif ($type instanceof \PhpParser\Node\UnionType) { + $types = []; + foreach ($type->types as $unionTypeType) { + $types[] = self::resolve($unionTypeType, $className); + } - $type = $type->name; - if ($type === 'string') { - return new StringType(); - } elseif ($type === 'int') { - return new IntegerType(); - } elseif ($type === 'bool') { - return new BooleanType(); - } elseif ($type === 'float') { - return new FloatType(); - } elseif ($type === 'callable') { - return new CallableType(); - } elseif ($type === 'array') { - return new ArrayType(new MixedType(), new MixedType()); - } elseif ($type === 'iterable') { - return new IterableType(new MixedType(), new MixedType()); - } elseif ($type === 'void') { - return new VoidType(); - } elseif ($type === 'object') { - return new ObjectWithoutClassType(); - } elseif ($type === 'false') { - return new ConstantBooleanType(false); - } elseif ($type === 'null') { - return new NullType(); - } elseif ($type === 'mixed') { - return new MixedType(true); - } + return TypeCombinator::union(...$types); + } - return new MixedType(); - } + $type = $type->name; + if ($type === 'string') { + return new StringType(); + } elseif ($type === 'int') { + return new IntegerType(); + } elseif ($type === 'bool') { + return new BooleanType(); + } elseif ($type === 'float') { + return new FloatType(); + } elseif ($type === 'callable') { + return new CallableType(); + } elseif ($type === 'array') { + return new ArrayType(new MixedType(), new MixedType()); + } elseif ($type === 'iterable') { + return new IterableType(new MixedType(), new MixedType()); + } elseif ($type === 'void') { + return new VoidType(); + } elseif ($type === 'object') { + return new ObjectWithoutClassType(); + } elseif ($type === 'false') { + return new ConstantBooleanType(false); + } elseif ($type === 'null') { + return new NullType(); + } elseif ($type === 'mixed') { + return new MixedType(true); + } + return new MixedType(); + } } diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index 29ede2987c..4065d2632d 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ - 0, + 'array_change_key_case' => 0, + 'array_diff_assoc' => 0, + 'array_diff_key' => 0, + 'array_diff_uassoc' => 0, + 'array_diff_ukey' => 0, + 'array_diff' => 0, + 'array_udiff_assoc' => 0, + 'array_udiff_uassoc' => 0, + 'array_udiff' => 0, + 'array_intersect_assoc' => 0, + 'array_intersect_key' => 0, + 'array_intersect_uassoc' => 0, + 'array_intersect_ukey' => 0, + 'array_intersect' => 0, + 'array_uintersect_assoc' => 0, + 'array_uintersect_uassoc' => 0, + 'array_uintersect' => 0, + 'iterator_to_array' => 0, + ]; - /** @var int[] */ - private array $functionNames = [ - 'array_unique' => 0, - 'array_change_key_case' => 0, - 'array_diff_assoc' => 0, - 'array_diff_key' => 0, - 'array_diff_uassoc' => 0, - 'array_diff_ukey' => 0, - 'array_diff' => 0, - 'array_udiff_assoc' => 0, - 'array_udiff_uassoc' => 0, - 'array_udiff' => 0, - 'array_intersect_assoc' => 0, - 'array_intersect_key' => 0, - 'array_intersect_uassoc' => 0, - 'array_intersect_ukey' => 0, - 'array_intersect' => 0, - 'array_uintersect_assoc' => 0, - 'array_uintersect_uassoc' => 0, - 'array_uintersect' => 0, - 'iterator_to_array' => 0, - ]; - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return isset($this->functionNames[$functionReflection->getName()]); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $argumentPosition = $this->functionNames[$functionReflection->getName()]; + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return isset($this->functionNames[$functionReflection->getName()]); + } - if (!isset($functionCall->args[$argumentPosition])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $argumentPosition = $this->functionNames[$functionReflection->getName()]; - $argument = $functionCall->args[$argumentPosition]; - $argumentType = $scope->getType($argument->value); - $argumentKeyType = $argumentType->getIterableKeyType(); - $argumentValueType = $argumentType->getIterableValueType(); - if ($argument->unpack) { - $argumentKeyType = TypeUtils::generalizeType($argumentKeyType); - $argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType()); - } + if (!isset($functionCall->args[$argumentPosition])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - return new ArrayType( - $argumentKeyType, - $argumentValueType - ); - } + $argument = $functionCall->args[$argumentPosition]; + $argumentType = $scope->getType($argument->value); + $argumentKeyType = $argumentType->getIterableKeyType(); + $argumentValueType = $argumentType->getIterableValueType(); + if ($argument->unpack) { + $argumentKeyType = TypeUtils::generalizeType($argumentKeyType); + $argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType()); + } + return new ArrayType( + $argumentKeyType, + $argumentValueType + ); + } } diff --git a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php index d1cdd2df95..950581f5d5 100644 --- a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_combine'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $firstArg = $functionCall->args[0]->value; - $secondArg = $functionCall->args[1]->value; - - $keysParamType = $scope->getType($firstArg); - $valuesParamType = $scope->getType($secondArg); - - if ( - $keysParamType instanceof ConstantArrayType - && $valuesParamType instanceof ConstantArrayType - ) { - $keyTypes = $keysParamType->getValueTypes(); - $valueTypes = $valuesParamType->getValueTypes(); - - if (count($keyTypes) !== count($valueTypes)) { - return new ConstantBooleanType(false); - } - - $keyTypes = $this->sanitizeConstantArrayKeyTypes($keyTypes); - if ($keyTypes !== null) { - return new ConstantArrayType( - $keyTypes, - $valueTypes - ); - } - } - - $arrayType = new ArrayType( - $keysParamType instanceof ArrayType ? $keysParamType->getItemType() : new MixedType(), - $valuesParamType instanceof ArrayType ? $valuesParamType->getItemType() : new MixedType() - ); - - if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { - return $arrayType; - } - - if ($firstArg instanceof Variable && $secondArg instanceof Variable && $firstArg->name === $secondArg->name) { - return $arrayType; - } - - return new UnionType([$arrayType, new ConstantBooleanType(false)]); - } - - /** - * @param array $types - * - * @return array|null - */ - private function sanitizeConstantArrayKeyTypes(array $types): ?array - { - $sanitizedTypes = []; - - foreach ($types as $type) { - if ( - !$type instanceof ConstantIntegerType - && !$type instanceof ConstantStringType - ) { - return null; - } - - $sanitizedTypes[] = $type; - } - - return $sanitizedTypes; - } - + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_combine'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $firstArg = $functionCall->args[0]->value; + $secondArg = $functionCall->args[1]->value; + + $keysParamType = $scope->getType($firstArg); + $valuesParamType = $scope->getType($secondArg); + + if ( + $keysParamType instanceof ConstantArrayType + && $valuesParamType instanceof ConstantArrayType + ) { + $keyTypes = $keysParamType->getValueTypes(); + $valueTypes = $valuesParamType->getValueTypes(); + + if (count($keyTypes) !== count($valueTypes)) { + return new ConstantBooleanType(false); + } + + $keyTypes = $this->sanitizeConstantArrayKeyTypes($keyTypes); + if ($keyTypes !== null) { + return new ConstantArrayType( + $keyTypes, + $valueTypes + ); + } + } + + $arrayType = new ArrayType( + $keysParamType instanceof ArrayType ? $keysParamType->getItemType() : new MixedType(), + $valuesParamType instanceof ArrayType ? $valuesParamType->getItemType() : new MixedType() + ); + + if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { + return $arrayType; + } + + if ($firstArg instanceof Variable && $secondArg instanceof Variable && $firstArg->name === $secondArg->name) { + return $arrayType; + } + + return new UnionType([$arrayType, new ConstantBooleanType(false)]); + } + + /** + * @param array $types + * + * @return array|null + */ + private function sanitizeConstantArrayKeyTypes(array $types): ?array + { + $sanitizedTypes = []; + + foreach ($types as $type) { + if ( + !$type instanceof ConstantIntegerType + && !$type instanceof ConstantStringType + ) { + return null; + } + + $sanitizedTypes[] = $type; + } + + return $sanitizedTypes; + } } diff --git a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php index a322e4de05..38a2f86e2c 100644 --- a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'current'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new ConstantBooleanType(false); - } - - $keyType = $argType->getIterableValueType(); - if ($iterableAtLeastOnce->yes()) { - return $keyType; - } - - return TypeCombinator::union($keyType, new ConstantBooleanType(false)); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'current'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new ConstantBooleanType(false); + } + + $keyType = $argType->getIterableValueType(); + if ($iterableAtLeastOnce->yes()) { + return $keyType; + } + + return TypeCombinator::union($keyType, new ConstantBooleanType(false)); + } } diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index 8b1819d57f..efbc56c5c5 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_fill'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 3) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_fill'; + } - $startIndexType = $scope->getType($functionCall->args[0]->value); - $numberType = $scope->getType($functionCall->args[1]->value); - $valueType = $scope->getType($functionCall->args[2]->value); + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 3) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - if ( - $startIndexType instanceof ConstantIntegerType - && $numberType instanceof ConstantIntegerType - && $numberType->getValue() <= static::MAX_SIZE_USE_CONSTANT_ARRAY - ) { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $nextIndex = $startIndexType->getValue(); - for ($i = 0; $i < $numberType->getValue(); $i++) { - $arrayBuilder->setOffsetValueType( - new ConstantIntegerType($nextIndex), - $valueType - ); - if ($nextIndex < 0) { - $nextIndex = 0; - } else { - $nextIndex++; - } - } + $startIndexType = $scope->getType($functionCall->args[0]->value); + $numberType = $scope->getType($functionCall->args[1]->value); + $valueType = $scope->getType($functionCall->args[2]->value); - return $arrayBuilder->getArray(); - } + if ( + $startIndexType instanceof ConstantIntegerType + && $numberType instanceof ConstantIntegerType + && $numberType->getValue() <= static::MAX_SIZE_USE_CONSTANT_ARRAY + ) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $nextIndex = $startIndexType->getValue(); + for ($i = 0; $i < $numberType->getValue(); $i++) { + $arrayBuilder->setOffsetValueType( + new ConstantIntegerType($nextIndex), + $valueType + ); + if ($nextIndex < 0) { + $nextIndex = 0; + } else { + $nextIndex++; + } + } - if ( - $numberType instanceof ConstantIntegerType - && $numberType->getValue() > 0 - ) { - return new IntersectionType([ - new ArrayType(new IntegerType(), $valueType), - new NonEmptyArrayType(), - ]); - } + return $arrayBuilder->getArray(); + } - return new ArrayType(new IntegerType(), $valueType); - } + if ( + $numberType instanceof ConstantIntegerType + && $numberType->getValue() > 0 + ) { + return new IntersectionType([ + new ArrayType(new IntegerType(), $valueType), + new NonEmptyArrayType(), + ]); + } + return new ArrayType(new IntegerType(), $valueType); + } } diff --git a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php index 28ffedb197..3e058109d5 100644 --- a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_fill_keys'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $valueType = $scope->getType($functionCall->args[1]->value); - $keysType = $scope->getType($functionCall->args[0]->value); - $constantArrays = TypeUtils::getConstantArrays($keysType); - if (count($constantArrays) === 0) { - return new ArrayType($keysType->getIterableValueType(), $valueType); - } - - $arrayTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($constantArray->getValueTypes() as $keyType) { - $arrayBuilder->setOffsetValueType($keyType, $valueType); - } - $arrayTypes[] = $arrayBuilder->getArray(); - } - - return TypeCombinator::union(...$arrayTypes); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_fill_keys'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $valueType = $scope->getType($functionCall->args[1]->value); + $keysType = $scope->getType($functionCall->args[0]->value); + $constantArrays = TypeUtils::getConstantArrays($keysType); + if (count($constantArrays) === 0) { + return new ArrayType($keysType->getIterableValueType(), $valueType); + } + + $arrayTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($constantArray->getValueTypes() as $keyType) { + $arrayBuilder->setOffsetValueType($keyType, $valueType); + } + $arrayTypes[] = $arrayBuilder->getArray(); + } + + return TypeCombinator::union(...$arrayTypes); + } } diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php index 0876a38dc9..0c31c5286f 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_filter'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $arrayArg = $functionCall->args[0]->value ?? null; - $callbackArg = $functionCall->args[1]->value ?? null; - $flagArg = $functionCall->args[2]->value ?? null; - - if ($arrayArg !== null) { - $arrayArgType = $scope->getType($arrayArg); - $keyType = $arrayArgType->getIterableKeyType(); - $itemType = $arrayArgType->getIterableValueType(); - - if ($arrayArgType instanceof MixedType) { - return new BenevolentUnionType([ - new ArrayType(new MixedType(), new MixedType()), - new NullType(), - ]); - } - - if ($callbackArg === null) { - return TypeCombinator::union( - ...array_map([$this, 'removeFalsey'], TypeUtils::getArrays($arrayArgType)) - ); - } - - if ($flagArg === null) { - $var = null; - $expr = null; - if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { - $statement = $callbackArg->stmts[0]; - if ($statement instanceof Return_ && $statement->expr !== null) { - $var = $callbackArg->params[0]->var; - $expr = $statement->expr; - } - } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { - $var = $callbackArg->params[0]->var; - $expr = $callbackArg->expr; - } - if ($var !== null && $expr !== null) { - if (!$var instanceof Variable || !is_string($var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - $itemVariableName = $var->name; - if (!$scope instanceof MutatingScope) { - throw new \PHPStan\ShouldNotHappenException(); - } - $scope = $scope->assignVariable($itemVariableName, $itemType); - $scope = $scope->filterByTruthyValue($expr); - $itemType = $scope->getVariableType($itemVariableName); - } - } - - } else { - $keyType = new MixedType(); - $itemType = new MixedType(); - } - - return new ArrayType($keyType, $itemType); - } - - public function removeFalsey(Type $type): Type - { - $falseyTypes = StaticTypeFactory::falsey(); - - if ($type instanceof ConstantArrayType) { - $keys = $type->getKeyTypes(); - $values = $type->getValueTypes(); - - $builder = ConstantArrayTypeBuilder::createEmpty(); - - foreach ($values as $offset => $value) { - $isFalsey = $falseyTypes->isSuperTypeOf($value); - - if ($isFalsey->maybe()) { - $builder->setOffsetValueType($keys[$offset], TypeCombinator::remove($value, $falseyTypes), true); - } elseif ($isFalsey->no()) { - $builder->setOffsetValueType($keys[$offset], $value); - } - } - - return $builder->getArray(); - } - - $keyType = $type->getIterableKeyType(); - $valueType = $type->getIterableValueType(); - - $valueType = TypeCombinator::remove($valueType, $falseyTypes); - - if ($valueType instanceof NeverType) { - return new ConstantArrayType([], []); - } - - return new ArrayType($keyType, $valueType); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_filter'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arrayArg = $functionCall->args[0]->value ?? null; + $callbackArg = $functionCall->args[1]->value ?? null; + $flagArg = $functionCall->args[2]->value ?? null; + + if ($arrayArg !== null) { + $arrayArgType = $scope->getType($arrayArg); + $keyType = $arrayArgType->getIterableKeyType(); + $itemType = $arrayArgType->getIterableValueType(); + + if ($arrayArgType instanceof MixedType) { + return new BenevolentUnionType([ + new ArrayType(new MixedType(), new MixedType()), + new NullType(), + ]); + } + + if ($callbackArg === null) { + return TypeCombinator::union( + ...array_map([$this, 'removeFalsey'], TypeUtils::getArrays($arrayArgType)) + ); + } + + if ($flagArg === null) { + $var = null; + $expr = null; + if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { + $statement = $callbackArg->stmts[0]; + if ($statement instanceof Return_ && $statement->expr !== null) { + $var = $callbackArg->params[0]->var; + $expr = $statement->expr; + } + } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { + $var = $callbackArg->params[0]->var; + $expr = $callbackArg->expr; + } + if ($var !== null && $expr !== null) { + if (!$var instanceof Variable || !is_string($var->name)) { + throw new \PHPStan\ShouldNotHappenException(); + } + $itemVariableName = $var->name; + if (!$scope instanceof MutatingScope) { + throw new \PHPStan\ShouldNotHappenException(); + } + $scope = $scope->assignVariable($itemVariableName, $itemType); + $scope = $scope->filterByTruthyValue($expr); + $itemType = $scope->getVariableType($itemVariableName); + } + } + } else { + $keyType = new MixedType(); + $itemType = new MixedType(); + } + + return new ArrayType($keyType, $itemType); + } + + public function removeFalsey(Type $type): Type + { + $falseyTypes = StaticTypeFactory::falsey(); + + if ($type instanceof ConstantArrayType) { + $keys = $type->getKeyTypes(); + $values = $type->getValueTypes(); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($values as $offset => $value) { + $isFalsey = $falseyTypes->isSuperTypeOf($value); + + if ($isFalsey->maybe()) { + $builder->setOffsetValueType($keys[$offset], TypeCombinator::remove($value, $falseyTypes), true); + } elseif ($isFalsey->no()) { + $builder->setOffsetValueType($keys[$offset], $value); + } + } + + return $builder->getArray(); + } + + $keyType = $type->getIterableKeyType(); + $valueType = $type->getIterableValueType(); + + $valueType = TypeCombinator::remove($valueType, $falseyTypes); + + if ($valueType instanceof NeverType) { + return new ConstantArrayType([], []); + } + + return new ArrayType($keyType, $valueType); + } } diff --git a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php index 08cb3013dc..56815b4b13 100644 --- a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'key'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $keyType = $argType->getIterableKeyType(); - if ($iterableAtLeastOnce->yes()) { - return $keyType; - } - - return TypeCombinator::union($keyType, new NullType()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'key'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $keyType = $argType->getIterableKeyType(); + if ($iterableAtLeastOnce->yes()) { + return $keyType; + } + + return TypeCombinator::union($keyType, new NullType()); + } } diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index 837f5e88a9..677c0d93cc 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection, - FuncCall $node, - TypeSpecifierContext $context - ): bool - { - return $functionReflection->getName() === 'array_key_exists' - && !$context->null(); - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - if (count($node->args) < 2) { - return new SpecifiedTypes(); - } - $keyType = $scope->getType($node->args[0]->value); + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return $functionReflection->getName() === 'array_key_exists' + && !$context->null(); + } - if ($context->truthy()) { - $type = TypeCombinator::intersect( - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType($keyType) - ); - } else { - $type = new HasOffsetType($keyType); - } + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + if (count($node->args) < 2) { + return new SpecifiedTypes(); + } + $keyType = $scope->getType($node->args[0]->value); - return $this->typeSpecifier->create( - $node->args[1]->value, - $type, - $context, - false, - $scope - ); - } + if ($context->truthy()) { + $type = TypeCombinator::intersect( + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType($keyType) + ); + } else { + $type = new HasOffsetType($keyType); + } + return $this->typeSpecifier->create( + $node->args[1]->value, + $type, + $context, + false, + $scope + ); + } } diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index c868e7a39e..5c50407d00 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_key_first'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $constantArrays = TypeUtils::getConstantArrays($argType); - if (count($constantArrays) > 0) { - $keyTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { - $keyTypes[] = new NullType(); - continue; - } - - $keyTypes[] = $arrayKeyTypes[0]; - } - - return TypeCombinator::union(...$keyTypes); - } - - $keyType = $argType->getIterableKeyType(); - if ($iterableAtLeastOnce->yes()) { - return $keyType; - } - - return TypeCombinator::union($keyType, new NullType()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_key_first'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $constantArrays = TypeUtils::getConstantArrays($argType); + if (count($constantArrays) > 0) { + $keyTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayKeyTypes = $constantArray->getKeyTypes(); + if (count($arrayKeyTypes) === 0) { + $keyTypes[] = new NullType(); + continue; + } + + $keyTypes[] = $arrayKeyTypes[0]; + } + + return TypeCombinator::union(...$keyTypes); + } + + $keyType = $argType->getIterableKeyType(); + if ($iterableAtLeastOnce->yes()) { + return $keyType; + } + + return TypeCombinator::union($keyType, new NullType()); + } } diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index 25bec78100..9369b34b75 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_key_last'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $constantArrays = TypeUtils::getConstantArrays($argType); - if (count($constantArrays) > 0) { - $keyTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { - $keyTypes[] = new NullType(); - continue; - } - - $keyTypes[] = $arrayKeyTypes[count($arrayKeyTypes) - 1]; - } - - return TypeCombinator::union(...$keyTypes); - } - - $keyType = $argType->getIterableKeyType(); - if ($iterableAtLeastOnce->yes()) { - return $keyType; - } - - return TypeCombinator::union($keyType, new NullType()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_key_last'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $constantArrays = TypeUtils::getConstantArrays($argType); + if (count($constantArrays) > 0) { + $keyTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayKeyTypes = $constantArray->getKeyTypes(); + if (count($arrayKeyTypes) === 0) { + $keyTypes[] = new NullType(); + continue; + } + + $keyTypes[] = $arrayKeyTypes[count($arrayKeyTypes) - 1]; + } + + return TypeCombinator::union(...$keyTypes); + } + + $keyType = $argType->getIterableKeyType(); + if ($iterableAtLeastOnce->yes()) { + return $keyType; + } + + return TypeCombinator::union($keyType, new NullType()); + } } diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 056294ffc9..0a99023cdf 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_keys'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_keys'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $arrayArg = $functionCall->args[0]->value ?? null; - if ($arrayArg !== null) { - $valueType = $scope->getType($arrayArg); - if ($valueType->isArray()->yes()) { - if ($valueType instanceof ConstantArrayType) { - return $valueType->getKeysArray(); - } - $keyType = $valueType->getIterableKeyType(); - return TypeCombinator::intersect(new ArrayType(new IntegerType(), $keyType), ...TypeUtils::getAccessoryTypes($valueType)); - } - } - - return new ArrayType( - new IntegerType(), - new UnionType([new StringType(), new IntegerType()]) - ); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arrayArg = $functionCall->args[0]->value ?? null; + if ($arrayArg !== null) { + $valueType = $scope->getType($arrayArg); + if ($valueType->isArray()->yes()) { + if ($valueType instanceof ConstantArrayType) { + return $valueType->getKeysArray(); + } + $keyType = $valueType->getIterableKeyType(); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $keyType), ...TypeUtils::getAccessoryTypes($valueType)); + } + } + return new ArrayType( + new IntegerType(), + new UnionType([new StringType(), new IntegerType()]) + ); + } } diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e21cdbffe0..171e2dd4c3 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_map'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_map'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $valueType = new MixedType(); - $callableType = $scope->getType($functionCall->args[0]->value); - if (!$callableType->isCallable()->no()) { - $valueType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $functionCall->args, - $callableType->getCallableParametersAcceptors($scope) - )->getReturnType(); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - $arrayType = $scope->getType($functionCall->args[1]->value); - $constantArrays = TypeUtils::getConstantArrays($arrayType); - if (count($constantArrays) > 0) { - $arrayTypes = []; - foreach ($constantArrays as $constantArray) { - $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($constantArray->getKeyTypes() as $keyType) { - $returnedArrayBuilder->setOffsetValueType( - $keyType, - $valueType - ); - } - $arrayTypes[] = $returnedArrayBuilder->getArray(); - } + $valueType = new MixedType(); + $callableType = $scope->getType($functionCall->args[0]->value); + if (!$callableType->isCallable()->no()) { + $valueType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->args, + $callableType->getCallableParametersAcceptors($scope) + )->getReturnType(); + } - return TypeCombinator::union(...$arrayTypes); - } elseif ($arrayType->isArray()->yes()) { - return TypeCombinator::intersect(new ArrayType( - $arrayType->getIterableKeyType(), - $valueType - ), ...TypeUtils::getAccessoryTypes($arrayType)); - } + $arrayType = $scope->getType($functionCall->args[1]->value); + $constantArrays = TypeUtils::getConstantArrays($arrayType); + if (count($constantArrays) > 0) { + $arrayTypes = []; + foreach ($constantArrays as $constantArray) { + $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($constantArray->getKeyTypes() as $keyType) { + $returnedArrayBuilder->setOffsetValueType( + $keyType, + $valueType + ); + } + $arrayTypes[] = $returnedArrayBuilder->getArray(); + } - return new ArrayType( - new MixedType(), - $valueType - ); - } + return TypeCombinator::union(...$arrayTypes); + } elseif ($arrayType->isArray()->yes()) { + return TypeCombinator::intersect(new ArrayType( + $arrayType->getIterableKeyType(), + $valueType + ), ...TypeUtils::getAccessoryTypes($arrayType)); + } + return new ArrayType( + new MixedType(), + $valueType + ); + } } diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 38bb6b3141..725ce04b00 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_merge'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $keyTypes = []; - $valueTypes = []; - foreach ($functionCall->args as $arg) { - $argType = $scope->getType($arg->value); - if ($arg->unpack) { - $argType = $argType->getIterableValueType(); - if ($argType instanceof UnionType) { - foreach ($argType->getTypes() as $innerType) { - $argType = $innerType; - } - } - } - - $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType()); - $valueTypes[] = $argType->getIterableValueType(); - } - - return new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_merge'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $keyTypes = []; + $valueTypes = []; + foreach ($functionCall->args as $arg) { + $argType = $scope->getType($arg->value); + if ($arg->unpack) { + $argType = $argType->getIterableValueType(); + if ($argType instanceof UnionType) { + foreach ($argType->getTypes() as $innerType) { + $argType = $innerType; + } + } + } + + $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType()); + $valueTypes[] = $argType->getIterableValueType(); + } + + return new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); + } } diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index b7e9557ded..6125b9bdf0 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), $this->functions, true); - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array($functionReflection->getName(), $this->functions, true); + } - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new ConstantBooleanType(false); - } + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - $constantArrays = TypeUtils::getConstantArrays($argType); - if (count($constantArrays) > 0) { - $keyTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { - $keyTypes[] = new ConstantBooleanType(false); - continue; - } + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new ConstantBooleanType(false); + } - $valueOffset = $functionReflection->getName() === 'reset' - ? $arrayKeyTypes[0] - : $arrayKeyTypes[count($arrayKeyTypes) - 1]; + $constantArrays = TypeUtils::getConstantArrays($argType); + if (count($constantArrays) > 0) { + $keyTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayKeyTypes = $constantArray->getKeyTypes(); + if (count($arrayKeyTypes) === 0) { + $keyTypes[] = new ConstantBooleanType(false); + continue; + } - $keyTypes[] = $constantArray->getOffsetValueType($valueOffset); - } + $valueOffset = $functionReflection->getName() === 'reset' + ? $arrayKeyTypes[0] + : $arrayKeyTypes[count($arrayKeyTypes) - 1]; - return TypeCombinator::union(...$keyTypes); - } + $keyTypes[] = $constantArray->getOffsetValueType($valueOffset); + } - $itemType = $argType->getIterableValueType(); - if ($iterableAtLeastOnce->yes()) { - return $itemType; - } + return TypeCombinator::union(...$keyTypes); + } - return TypeCombinator::union($itemType, new ConstantBooleanType(false)); - } + $itemType = $argType->getIterableValueType(); + if ($iterableAtLeastOnce->yes()) { + return $itemType; + } + return TypeCombinator::union($itemType, new ConstantBooleanType(false)); + } } diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 2e26f43a2b..5a7f70cc16 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_pop'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $constantArrays = TypeUtils::getConstantArrays($argType); - if (count($constantArrays) > 0) { - $valueTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { - $valueTypes[] = new NullType(); - continue; - } - - $valueTypes[] = $constantArray->getOffsetValueType($arrayKeyTypes[count($arrayKeyTypes) - 1]); - } - - return TypeCombinator::union(...$valueTypes); - } - - $itemType = $argType->getIterableValueType(); - if ($iterableAtLeastOnce->yes()) { - return $itemType; - } - - return TypeCombinator::union($itemType, new NullType()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_pop'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $constantArrays = TypeUtils::getConstantArrays($argType); + if (count($constantArrays) > 0) { + $valueTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayKeyTypes = $constantArray->getKeyTypes(); + if (count($arrayKeyTypes) === 0) { + $valueTypes[] = new NullType(); + continue; + } + + $valueTypes[] = $constantArray->getOffsetValueType($arrayKeyTypes[count($arrayKeyTypes) - 1]); + } + + return TypeCombinator::union(...$valueTypes); + } + + $itemType = $argType->getIterableValueType(); + if ($iterableAtLeastOnce->yes()) { + return $itemType; + } + + return TypeCombinator::union($itemType, new NullType()); + } } diff --git a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php index 00444e498f..918be39520 100644 --- a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_rand'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $argsCount = count($functionCall->args); - if (count($functionCall->args) < 1) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $firstArgType = $scope->getType($functionCall->args[0]->value); - $isInteger = (new IntegerType())->isSuperTypeOf($firstArgType->getIterableKeyType()); - $isString = (new StringType())->isSuperTypeOf($firstArgType->getIterableKeyType()); - - if ($isInteger->yes()) { - $valueType = new IntegerType(); - } elseif ($isString->yes()) { - $valueType = new StringType(); - } else { - $valueType = new UnionType([new IntegerType(), new StringType()]); - } - - if ($argsCount < 2) { - return $valueType; - } - - $secondArgType = $scope->getType($functionCall->args[1]->value); - - if ($secondArgType instanceof ConstantIntegerType) { - if ($secondArgType->getValue() === 1) { - return $valueType; - } - - if ($secondArgType->getValue() >= 2) { - return new ArrayType(new IntegerType(), $valueType); - } - } - - return TypeCombinator::union($valueType, new ArrayType(new IntegerType(), $valueType)); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_rand'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $argsCount = count($functionCall->args); + if (count($functionCall->args) < 1) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $firstArgType = $scope->getType($functionCall->args[0]->value); + $isInteger = (new IntegerType())->isSuperTypeOf($firstArgType->getIterableKeyType()); + $isString = (new StringType())->isSuperTypeOf($firstArgType->getIterableKeyType()); + + if ($isInteger->yes()) { + $valueType = new IntegerType(); + } elseif ($isString->yes()) { + $valueType = new StringType(); + } else { + $valueType = new UnionType([new IntegerType(), new StringType()]); + } + + if ($argsCount < 2) { + return $valueType; + } + + $secondArgType = $scope->getType($functionCall->args[1]->value); + + if ($secondArgType instanceof ConstantIntegerType) { + if ($secondArgType->getValue() === 1) { + return $valueType; + } + + if ($secondArgType->getValue() >= 2) { + return new ArrayType(new IntegerType(), $valueType); + } + } + + return TypeCombinator::union($valueType, new ArrayType(new IntegerType(), $valueType)); + } } diff --git a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php index ec876b45a7..5275ec5692 100644 --- a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_reduce'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_reduce'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[1])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $callbackType = $scope->getType($functionCall->args[1]->value); - if ($callbackType->isCallable()->no()) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[1])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - $callbackReturnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $functionCall->args, - $callbackType->getCallableParametersAcceptors($scope) - )->getReturnType(); + $callbackType = $scope->getType($functionCall->args[1]->value); + if ($callbackType->isCallable()->no()) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - if (isset($functionCall->args[2])) { - $initialType = $scope->getType($functionCall->args[2]->value); - } else { - $initialType = new NullType(); - } + $callbackReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->args, + $callbackType->getCallableParametersAcceptors($scope) + )->getReturnType(); - $arraysType = $scope->getType($functionCall->args[0]->value); - $constantArrays = TypeUtils::getConstantArrays($arraysType); - if (count($constantArrays) > 0) { - $onlyEmpty = true; - $onlyNonEmpty = true; - foreach ($constantArrays as $constantArray) { - $isEmpty = count($constantArray->getValueTypes()) === 0; - $onlyEmpty = $onlyEmpty && $isEmpty; - $onlyNonEmpty = $onlyNonEmpty && !$isEmpty; - } + if (isset($functionCall->args[2])) { + $initialType = $scope->getType($functionCall->args[2]->value); + } else { + $initialType = new NullType(); + } - if ($onlyEmpty) { - return $initialType; - } - if ($onlyNonEmpty) { - return $callbackReturnType; - } - } + $arraysType = $scope->getType($functionCall->args[0]->value); + $constantArrays = TypeUtils::getConstantArrays($arraysType); + if (count($constantArrays) > 0) { + $onlyEmpty = true; + $onlyNonEmpty = true; + foreach ($constantArrays as $constantArray) { + $isEmpty = count($constantArray->getValueTypes()) === 0; + $onlyEmpty = $onlyEmpty && $isEmpty; + $onlyNonEmpty = $onlyNonEmpty && !$isEmpty; + } - return TypeCombinator::union($callbackReturnType, $initialType); - } + if ($onlyEmpty) { + return $initialType; + } + if ($onlyNonEmpty) { + return $callbackReturnType; + } + } + return TypeCombinator::union($callbackReturnType, $initialType); + } } diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 4f893d81ca..bdfe217f26 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_reverse'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_reverse'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - return $scope->getType($functionCall->args[0]->value); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + return $scope->getType($functionCall->args[0]->value); + } } diff --git a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php index 2c56f6c47c..38303b589a 100644 --- a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_search'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $argsCount = count($functionCall->args); - if ($argsCount < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $haystackArgType = $scope->getType($functionCall->args[1]->value); - $haystackIsArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($haystackArgType); - if ($haystackIsArray->no()) { - return new NullType(); - } - - if ($argsCount < 3) { - return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); - } - - $strictArgType = $scope->getType($functionCall->args[2]->value); - if (!($strictArgType instanceof ConstantBooleanType)) { - return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false), new NullType()); - } elseif ($strictArgType->getValue() === false) { - return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); - } - - $needleArgType = $scope->getType($functionCall->args[0]->value); - if ($haystackArgType->getIterableValueType()->isSuperTypeOf($needleArgType)->no()) { - return new ConstantBooleanType(false); - } - - $typesFromConstantArrays = []; - if ($haystackIsArray->maybe()) { - $typesFromConstantArrays[] = new NullType(); - } - - $haystackArrays = $this->pickArrays($haystackArgType); - if (count($haystackArrays) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $arrays = []; - $typesFromConstantArraysCount = 0; - foreach ($haystackArrays as $haystackArray) { - if (!$haystackArray instanceof ConstantArrayType) { - $arrays[] = $haystackArray; - continue; - } - - $typesFromConstantArrays[] = $this->resolveTypeFromConstantHaystackAndNeedle($needleArgType, $haystackArray); - $typesFromConstantArraysCount++; - } - - if ( - $typesFromConstantArraysCount > 0 - && count($haystackArrays) === $typesFromConstantArraysCount - ) { - return TypeCombinator::union(...$typesFromConstantArrays); - } - - $iterableKeyType = TypeCombinator::union(...$arrays)->getIterableKeyType(); - - return TypeCombinator::union( - $iterableKeyType, - new ConstantBooleanType(false), - ...$typesFromConstantArrays - ); - } - - private function resolveTypeFromConstantHaystackAndNeedle(Type $needle, ConstantArrayType $haystack): Type - { - $matchesByType = []; - - foreach ($haystack->getValueTypes() as $index => $valueType) { - $isNeedleSuperType = $valueType->isSuperTypeOf($needle); - if ($isNeedleSuperType->no()) { - $matchesByType[] = new ConstantBooleanType(false); - continue; - } - - if ($needle instanceof ConstantScalarType && $valueType instanceof ConstantScalarType - && $needle->getValue() === $valueType->getValue() - ) { - return $haystack->getKeyTypes()[$index]; - } - - $matchesByType[] = $haystack->getKeyTypes()[$index]; - if (!$isNeedleSuperType->maybe()) { - continue; - } - - $matchesByType[] = new ConstantBooleanType(false); - } - - if (count($matchesByType) > 0) { - if ( - $haystack->getIterableValueType()->accepts($needle, true)->yes() - && $needle->isSuperTypeOf(new ObjectWithoutClassType())->no() - ) { - return TypeCombinator::union(...$matchesByType); - } - - return TypeCombinator::union(new ConstantBooleanType(false), ...$matchesByType); - } - - return new ConstantBooleanType(false); - } - - /** - * @param Type $type - * @return Type[] - */ - private function pickArrays(Type $type): array - { - if ($type instanceof ArrayType) { - return [$type]; - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - $arrayTypes = []; - - foreach ($type->getTypes() as $innerType) { - if (!($innerType instanceof ArrayType)) { - continue; - } - - $arrayTypes[] = $innerType; - } - - return $arrayTypes; - } - - return []; - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_search'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $argsCount = count($functionCall->args); + if ($argsCount < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $haystackArgType = $scope->getType($functionCall->args[1]->value); + $haystackIsArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($haystackArgType); + if ($haystackIsArray->no()) { + return new NullType(); + } + + if ($argsCount < 3) { + return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); + } + + $strictArgType = $scope->getType($functionCall->args[2]->value); + if (!($strictArgType instanceof ConstantBooleanType)) { + return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false), new NullType()); + } elseif ($strictArgType->getValue() === false) { + return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); + } + + $needleArgType = $scope->getType($functionCall->args[0]->value); + if ($haystackArgType->getIterableValueType()->isSuperTypeOf($needleArgType)->no()) { + return new ConstantBooleanType(false); + } + + $typesFromConstantArrays = []; + if ($haystackIsArray->maybe()) { + $typesFromConstantArrays[] = new NullType(); + } + + $haystackArrays = $this->pickArrays($haystackArgType); + if (count($haystackArrays) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $arrays = []; + $typesFromConstantArraysCount = 0; + foreach ($haystackArrays as $haystackArray) { + if (!$haystackArray instanceof ConstantArrayType) { + $arrays[] = $haystackArray; + continue; + } + + $typesFromConstantArrays[] = $this->resolveTypeFromConstantHaystackAndNeedle($needleArgType, $haystackArray); + $typesFromConstantArraysCount++; + } + + if ( + $typesFromConstantArraysCount > 0 + && count($haystackArrays) === $typesFromConstantArraysCount + ) { + return TypeCombinator::union(...$typesFromConstantArrays); + } + + $iterableKeyType = TypeCombinator::union(...$arrays)->getIterableKeyType(); + + return TypeCombinator::union( + $iterableKeyType, + new ConstantBooleanType(false), + ...$typesFromConstantArrays + ); + } + + private function resolveTypeFromConstantHaystackAndNeedle(Type $needle, ConstantArrayType $haystack): Type + { + $matchesByType = []; + + foreach ($haystack->getValueTypes() as $index => $valueType) { + $isNeedleSuperType = $valueType->isSuperTypeOf($needle); + if ($isNeedleSuperType->no()) { + $matchesByType[] = new ConstantBooleanType(false); + continue; + } + + if ($needle instanceof ConstantScalarType && $valueType instanceof ConstantScalarType + && $needle->getValue() === $valueType->getValue() + ) { + return $haystack->getKeyTypes()[$index]; + } + + $matchesByType[] = $haystack->getKeyTypes()[$index]; + if (!$isNeedleSuperType->maybe()) { + continue; + } + + $matchesByType[] = new ConstantBooleanType(false); + } + + if (count($matchesByType) > 0) { + if ( + $haystack->getIterableValueType()->accepts($needle, true)->yes() + && $needle->isSuperTypeOf(new ObjectWithoutClassType())->no() + ) { + return TypeCombinator::union(...$matchesByType); + } + + return TypeCombinator::union(new ConstantBooleanType(false), ...$matchesByType); + } + + return new ConstantBooleanType(false); + } + + /** + * @param Type $type + * @return Type[] + */ + private function pickArrays(Type $type): array + { + if ($type instanceof ArrayType) { + return [$type]; + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + $arrayTypes = []; + + foreach ($type->getTypes() as $innerType) { + if (!($innerType instanceof ArrayType)) { + continue; + } + + $arrayTypes[] = $innerType; + } + + return $arrayTypes; + } + + return []; + } } diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index b7feab7eca..7490bb9eaa 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_shift'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $constantArrays = TypeUtils::getConstantArrays($argType); - if (count($constantArrays) > 0) { - $valueTypes = []; - foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { - $valueTypes[] = new NullType(); - continue; - } - - $valueTypes[] = $constantArray->getOffsetValueType($arrayKeyTypes[0]); - } - - return TypeCombinator::union(...$valueTypes); - } - - $itemType = $argType->getIterableValueType(); - if ($iterableAtLeastOnce->yes()) { - return $itemType; - } - - return TypeCombinator::union($itemType, new NullType()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'array_shift'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $constantArrays = TypeUtils::getConstantArrays($argType); + if (count($constantArrays) > 0) { + $valueTypes = []; + foreach ($constantArrays as $constantArray) { + $arrayKeyTypes = $constantArray->getKeyTypes(); + if (count($arrayKeyTypes) === 0) { + $valueTypes[] = new NullType(); + continue; + } + + $valueTypes[] = $constantArray->getOffsetValueType($arrayKeyTypes[0]); + } + + return TypeCombinator::union(...$valueTypes); + } + + $itemType = $argType->getIterableValueType(); + if ($iterableAtLeastOnce->yes()) { + return $itemType; + } + + return TypeCombinator::union($itemType, new NullType()); + } } diff --git a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php index ac42cf83f9..20be11e204 100644 --- a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_slice'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_slice'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $arrayArg = $functionCall->args[0]->value ?? null; - - if ($arrayArg === null) { - return new ArrayType( - new IntegerType(), - new MixedType() - ); - } + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $arrayArg = $functionCall->args[0]->value ?? null; - $valueType = $scope->getType($arrayArg); + if ($arrayArg === null) { + return new ArrayType( + new IntegerType(), + new MixedType() + ); + } - if (isset($functionCall->args[1])) { - $offset = $scope->getType($functionCall->args[1]->value); - if (!$offset instanceof ConstantIntegerType) { - $offset = new ConstantIntegerType(0); - } - } else { - $offset = new ConstantIntegerType(0); - } + $valueType = $scope->getType($arrayArg); - if (isset($functionCall->args[2])) { - $limit = $scope->getType($functionCall->args[2]->value); - if (!$limit instanceof ConstantIntegerType) { - $limit = new NullType(); - } - } else { - $limit = new NullType(); - } + if (isset($functionCall->args[1])) { + $offset = $scope->getType($functionCall->args[1]->value); + if (!$offset instanceof ConstantIntegerType) { + $offset = new ConstantIntegerType(0); + } + } else { + $offset = new ConstantIntegerType(0); + } - $constantArrays = TypeUtils::getConstantArrays($valueType); - if (count($constantArrays) === 0) { - $arrays = TypeUtils::getArrays($valueType); - if (count($arrays) !== 0) { - return TypeCombinator::union(...$arrays); - } - return new ArrayType( - new MixedType(), - new MixedType() - ); - } + if (isset($functionCall->args[2])) { + $limit = $scope->getType($functionCall->args[2]->value); + if (!$limit instanceof ConstantIntegerType) { + $limit = new NullType(); + } + } else { + $limit = new NullType(); + } - if (isset($functionCall->args[3])) { - $preserveKeys = $scope->getType($functionCall->args[3]->value); - $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeys)->yes(); - } else { - $preserveKeys = false; - } + $constantArrays = TypeUtils::getConstantArrays($valueType); + if (count($constantArrays) === 0) { + $arrays = TypeUtils::getArrays($valueType); + if (count($arrays) !== 0) { + return TypeCombinator::union(...$arrays); + } + return new ArrayType( + new MixedType(), + new MixedType() + ); + } - $arrayTypes = array_map(static function (ConstantArrayType $constantArray) use ($offset, $limit, $preserveKeys): ConstantArrayType { - return $constantArray->slice($offset->getValue(), $limit->getValue(), $preserveKeys); - }, $constantArrays); + if (isset($functionCall->args[3])) { + $preserveKeys = $scope->getType($functionCall->args[3]->value); + $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeys)->yes(); + } else { + $preserveKeys = false; + } - return TypeCombinator::union(...$arrayTypes); - } + $arrayTypes = array_map(static function (ConstantArrayType $constantArray) use ($offset, $limit, $preserveKeys): ConstantArrayType { + return $constantArray->slice($offset->getValue(), $limit->getValue(), $preserveKeys); + }, $constantArrays); + return TypeCombinator::union(...$arrayTypes); + } } diff --git a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php index aa3cca722f..aea23c5efa 100644 --- a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_sum'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_sum'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $arrayType = $scope->getType($functionCall->args[0]->value); - $itemType = $arrayType->getIterableValueType(); + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - if ($arrayType->isIterableAtLeastOnce()->no()) { - return new ConstantIntegerType(0); - } + $arrayType = $scope->getType($functionCall->args[0]->value); + $itemType = $arrayType->getIterableValueType(); - $intUnionFloat = new UnionType([new IntegerType(), new FloatType()]); + if ($arrayType->isIterableAtLeastOnce()->no()) { + return new ConstantIntegerType(0); + } - if ($arrayType->isIterableAtLeastOnce()->yes()) { - if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) { - return $itemType; - } + $intUnionFloat = new UnionType([new IntegerType(), new FloatType()]); - return $intUnionFloat; - } + if ($arrayType->isIterableAtLeastOnce()->yes()) { + if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) { + return $itemType; + } - if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) { - return TypeCombinator::union(new ConstantIntegerType(0), $itemType); - } + return $intUnionFloat; + } - return TypeCombinator::union(new ConstantIntegerType(0), $intUnionFloat); - } + if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) { + return TypeCombinator::union(new ConstantIntegerType(0), $itemType); + } + return TypeCombinator::union(new ConstantIntegerType(0), $intUnionFloat); + } } diff --git a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php index 0c43caa801..33249ef412 100644 --- a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'array_values'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'array_values'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $arrayArg = $functionCall->args[0]->value ?? null; - if ($arrayArg !== null) { - $valueType = $scope->getType($arrayArg); - if ($valueType->isArray()->yes()) { - if ($valueType instanceof ConstantArrayType) { - return $valueType->getValuesArray(); - } - return TypeCombinator::intersect(new ArrayType(new IntegerType(), $valueType->getIterableValueType()), ...TypeUtils::getAccessoryTypes($valueType)); - } - } - - return new ArrayType( - new IntegerType(), - new MixedType() - ); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arrayArg = $functionCall->args[0]->value ?? null; + if ($arrayArg !== null) { + $valueType = $scope->getType($arrayArg); + if ($valueType->isArray()->yes()) { + if ($valueType instanceof ConstantArrayType) { + return $valueType->getValuesArray(); + } + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $valueType->getIterableValueType()), ...TypeUtils::getAccessoryTypes($valueType)); + } + } + return new ArrayType( + new IntegerType(), + new MixedType() + ); + } } diff --git a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php index c6412e9339..8d427566ff 100644 --- a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName() === 'assert' - && isset($node->args[0]); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition($scope, $node->args[0]->value, TypeSpecifierContext::createTruthy()); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return $functionReflection->getName() === 'assert' + && isset($node->args[0]); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + return $this->typeSpecifier->specifyTypesInCondition($scope, $node->args[0]->value, TypeSpecifierContext::createTruthy()); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php index dc36db80d4..0bb03053c8 100644 --- a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'base64_decode'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (!isset($functionCall->args[1])) { - return new StringType(); - } - - $argType = $scope->getType($functionCall->args[1]->value); - - if ($argType instanceof MixedType) { - return new BenevolentUnionType([new StringType(), new ConstantBooleanType(false)]); - } - - $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); - $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); - $compareTypes = $isTrueType->compareTo($isFalseType); - if ($compareTypes === $isTrueType) { - return new UnionType([new StringType(), new ConstantBooleanType(false)]); - } - if ($compareTypes === $isFalseType) { - return new StringType(); - } - - // second argument could be interpreted as true - if (!$isTrueType->no()) { - return new UnionType([new StringType(), new ConstantBooleanType(false)]); - } - - return new StringType(); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'base64_decode'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (!isset($functionCall->args[1])) { + return new StringType(); + } + + $argType = $scope->getType($functionCall->args[1]->value); + + if ($argType instanceof MixedType) { + return new BenevolentUnionType([new StringType(), new ConstantBooleanType(false)]); + } + + $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); + $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); + $compareTypes = $isTrueType->compareTo($isFalseType); + if ($compareTypes === $isTrueType) { + return new UnionType([new StringType(), new ConstantBooleanType(false)]); + } + if ($compareTypes === $isFalseType) { + return new StringType(); + } + + // second argument could be interpreted as true + if (!$isTrueType->no()) { + return new UnionType([new StringType(), new ConstantBooleanType(false)]); + } + + return new StringType(); + } } diff --git a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php index a3bc589d4f..6c507904f7 100644 --- a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php +++ b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), ['bcdiv', 'bcmod', 'bcpowmod', 'bcsqrt'], true); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if ($functionReflection->getName() === 'bcsqrt') { - return $this->getTypeForBcSqrt($functionCall, $scope); - } - - if ($functionReflection->getName() === 'bcpowmod') { - return $this->getTypeForBcPowMod($functionCall, $scope); - } - - $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); - - $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); - - if (isset($functionCall->args[1]) === false) { - return $stringAndNumericStringType; - } - - $secondArgument = $scope->getType($functionCall->args[1]->value); - $secondArgumentIsNumeric = ($secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue())) || $secondArgument instanceof IntegerType; - - if ($secondArgument instanceof ConstantScalarType && ($this->isZero($secondArgument->getValue()) || !$secondArgumentIsNumeric)) { - return new NullType(); - } - - if (isset($functionCall->args[2]) === false) { - if ($secondArgument instanceof ConstantScalarType || $secondArgumentIsNumeric) { - return $stringAndNumericStringType; - } - - return $defaultReturnType; - } - - $thirdArgument = $scope->getType($functionCall->args[2]->value); - $thirdArgumentIsNumeric = ($thirdArgument instanceof ConstantScalarType && is_numeric($thirdArgument->getValue())) || $thirdArgument instanceof IntegerType; - - if ($thirdArgument instanceof ConstantScalarType && !is_numeric($thirdArgument->getValue())) { - return new NullType(); - } - - if (($secondArgument instanceof ConstantScalarType || $secondArgumentIsNumeric) && $thirdArgumentIsNumeric) { - return $stringAndNumericStringType; - } - - return $defaultReturnType; - } - - /** - * bcsqrt - * https://www.php.net/manual/en/function.bcsqrt.php - * > Returns the square root as a string, or NULL if operand is negative. - * - * @param FuncCall $functionCall - * @param Scope $scope - * @return Type - */ - private function getTypeForBcSqrt(FuncCall $functionCall, Scope $scope): Type - { - $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); - $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); - - if (isset($functionCall->args[0]) === false) { - return $defaultReturnType; - } - - $firstArgument = $scope->getType($functionCall->args[0]->value); - - $firstArgumentIsPositive = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() >= 0; - $firstArgumentIsNegative = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() < 0; - - if ($firstArgument instanceof UnaryMinus || - ($firstArgumentIsNegative)) { - return new NullType(); - } - - if (isset($functionCall->args[1]) === false) { - if ($firstArgumentIsPositive) { - return $stringAndNumericStringType; - } - - return $defaultReturnType; - } - - $secondArgument = $scope->getType($functionCall->args[1]->value); - $secondArgumentIsValid = $secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue()) && !$this->isZero($secondArgument->getValue()); - $secondArgumentIsNonNumeric = $secondArgument instanceof ConstantScalarType && !is_numeric($secondArgument->getValue()); - - if ($secondArgumentIsNonNumeric) { - return new NullType(); - } - - if ($firstArgumentIsPositive && $secondArgumentIsValid) { - return $stringAndNumericStringType; - } - - return $defaultReturnType; - } - - /** - * bcpowmod() - * https://www.php.net/manual/en/function.bcpowmod.php - * > Returns the result as a string, or FALSE if modulus is 0 or exponent is negative. - * @param FuncCall $functionCall - * @param Scope $scope - * @return Type - */ - private function getTypeForBcPowMod(FuncCall $functionCall, Scope $scope): Type - { - $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); - - if (isset($functionCall->args[1]) === false) { - return new UnionType([$stringAndNumericStringType, new ConstantBooleanType(false)]); - } - - $exponent = $scope->getType($functionCall->args[1]->value); - $exponentIsNegative = IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($exponent)->yes(); - - if ($exponent instanceof ConstantScalarType) { - $exponentIsNegative = is_numeric($exponent->getValue()) && $exponent->getValue() < 0; - } - - if ($exponentIsNegative) { - return new ConstantBooleanType(false); - } - - if (isset($functionCall->args[2])) { - $modulus = $scope->getType($functionCall->args[2]->value); - $modulusIsZero = $modulus instanceof ConstantScalarType && $this->isZero($modulus->getValue()); - $modulusIsNonNumeric = $modulus instanceof ConstantScalarType && !is_numeric($modulus->getValue()); - - if ($modulusIsZero || $modulusIsNonNumeric) { - return new ConstantBooleanType(false); - } - - if ($modulus instanceof ConstantScalarType) { - return $stringAndNumericStringType; - } - } - - return new UnionType([$stringAndNumericStringType, new ConstantBooleanType(false)]); - } - - /** - * Utility to help us determine if value is zero. Handles cases where we pass "0.000" too. - * - * @param mixed $value - * @return bool - */ - private function isZero($value): bool - { - if (is_numeric($value) === false) { - return false; - } - - if ($value > 0 || $value < 0) { - return false; - } - - return true; - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array($functionReflection->getName(), ['bcdiv', 'bcmod', 'bcpowmod', 'bcsqrt'], true); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if ($functionReflection->getName() === 'bcsqrt') { + return $this->getTypeForBcSqrt($functionCall, $scope); + } + + if ($functionReflection->getName() === 'bcpowmod') { + return $this->getTypeForBcPowMod($functionCall, $scope); + } + + $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + + $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); + + if (isset($functionCall->args[1]) === false) { + return $stringAndNumericStringType; + } + + $secondArgument = $scope->getType($functionCall->args[1]->value); + $secondArgumentIsNumeric = ($secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue())) || $secondArgument instanceof IntegerType; + + if ($secondArgument instanceof ConstantScalarType && ($this->isZero($secondArgument->getValue()) || !$secondArgumentIsNumeric)) { + return new NullType(); + } + + if (isset($functionCall->args[2]) === false) { + if ($secondArgument instanceof ConstantScalarType || $secondArgumentIsNumeric) { + return $stringAndNumericStringType; + } + + return $defaultReturnType; + } + + $thirdArgument = $scope->getType($functionCall->args[2]->value); + $thirdArgumentIsNumeric = ($thirdArgument instanceof ConstantScalarType && is_numeric($thirdArgument->getValue())) || $thirdArgument instanceof IntegerType; + + if ($thirdArgument instanceof ConstantScalarType && !is_numeric($thirdArgument->getValue())) { + return new NullType(); + } + + if (($secondArgument instanceof ConstantScalarType || $secondArgumentIsNumeric) && $thirdArgumentIsNumeric) { + return $stringAndNumericStringType; + } + + return $defaultReturnType; + } + + /** + * bcsqrt + * https://www.php.net/manual/en/function.bcsqrt.php + * > Returns the square root as a string, or NULL if operand is negative. + * + * @param FuncCall $functionCall + * @param Scope $scope + * @return Type + */ + private function getTypeForBcSqrt(FuncCall $functionCall, Scope $scope): Type + { + $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); + + if (isset($functionCall->args[0]) === false) { + return $defaultReturnType; + } + + $firstArgument = $scope->getType($functionCall->args[0]->value); + + $firstArgumentIsPositive = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() >= 0; + $firstArgumentIsNegative = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() < 0; + + if ($firstArgument instanceof UnaryMinus || + ($firstArgumentIsNegative)) { + return new NullType(); + } + + if (isset($functionCall->args[1]) === false) { + if ($firstArgumentIsPositive) { + return $stringAndNumericStringType; + } + + return $defaultReturnType; + } + + $secondArgument = $scope->getType($functionCall->args[1]->value); + $secondArgumentIsValid = $secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue()) && !$this->isZero($secondArgument->getValue()); + $secondArgumentIsNonNumeric = $secondArgument instanceof ConstantScalarType && !is_numeric($secondArgument->getValue()); + + if ($secondArgumentIsNonNumeric) { + return new NullType(); + } + + if ($firstArgumentIsPositive && $secondArgumentIsValid) { + return $stringAndNumericStringType; + } + + return $defaultReturnType; + } + + /** + * bcpowmod() + * https://www.php.net/manual/en/function.bcpowmod.php + * > Returns the result as a string, or FALSE if modulus is 0 or exponent is negative. + * @param FuncCall $functionCall + * @param Scope $scope + * @return Type + */ + private function getTypeForBcPowMod(FuncCall $functionCall, Scope $scope): Type + { + $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + + if (isset($functionCall->args[1]) === false) { + return new UnionType([$stringAndNumericStringType, new ConstantBooleanType(false)]); + } + + $exponent = $scope->getType($functionCall->args[1]->value); + $exponentIsNegative = IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($exponent)->yes(); + + if ($exponent instanceof ConstantScalarType) { + $exponentIsNegative = is_numeric($exponent->getValue()) && $exponent->getValue() < 0; + } + + if ($exponentIsNegative) { + return new ConstantBooleanType(false); + } + + if (isset($functionCall->args[2])) { + $modulus = $scope->getType($functionCall->args[2]->value); + $modulusIsZero = $modulus instanceof ConstantScalarType && $this->isZero($modulus->getValue()); + $modulusIsNonNumeric = $modulus instanceof ConstantScalarType && !is_numeric($modulus->getValue()); + + if ($modulusIsZero || $modulusIsNonNumeric) { + return new ConstantBooleanType(false); + } + + if ($modulus instanceof ConstantScalarType) { + return $stringAndNumericStringType; + } + } + + return new UnionType([$stringAndNumericStringType, new ConstantBooleanType(false)]); + } + + /** + * Utility to help us determine if value is zero. Handles cases where we pass "0.000" too. + * + * @param mixed $value + * @return bool + */ + private function isZero($value): bool + { + if (is_numeric($value) === false) { + return false; + } + + if ($value > 0 || $value < 0) { + return false; + } + + return true; + } } diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index 1c78cb457b..82752db351 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName(), [ - 'class_exists', - 'interface_exists', - 'trait_exists', - ], true) && isset($node->args[0]) && $context->truthy(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - $argType = $scope->getType($node->args[0]->value); - $classStringType = new ClassStringType(); - if (TypeCombinator::intersect($argType, $classStringType) instanceof NeverType) { - if ($argType instanceof ConstantStringType) { - return $this->typeSpecifier->create( - new FuncCall(new FullyQualified('class_exists'), [ - new Arg(new String_(ltrim($argType->getValue(), '\\'))), - ]), - new ConstantBooleanType(true), - $context, - false, - $scope - ); - } + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return in_array($functionReflection->getName(), [ + 'class_exists', + 'interface_exists', + 'trait_exists', + ], true) && isset($node->args[0]) && $context->truthy(); + } - return new SpecifiedTypes(); - } + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $argType = $scope->getType($node->args[0]->value); + $classStringType = new ClassStringType(); + if (TypeCombinator::intersect($argType, $classStringType) instanceof NeverType) { + if ($argType instanceof ConstantStringType) { + return $this->typeSpecifier->create( + new FuncCall(new FullyQualified('class_exists'), [ + new Arg(new String_(ltrim($argType->getValue(), '\\'))), + ]), + new ConstantBooleanType(true), + $context, + false, + $scope + ); + } - return $this->typeSpecifier->create( - $node->args[0]->value, - $classStringType, - $context, - false, - $scope - ); - } + return new SpecifiedTypes(); + } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + return $this->typeSpecifier->create( + $node->args[0]->value, + $classStringType, + $context, + false, + $scope + ); + } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php index 9f64a13b7a..0545e68f3b 100644 --- a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'bind'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type - { - $closureType = $scope->getType($methodCall->args[0]->value); - if (!($closureType instanceof ClosureType)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return $closureType; - } - + public function getClass(): string + { + return \Closure::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'bind'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + $closureType = $scope->getType($methodCall->args[0]->value); + if (!($closureType instanceof ClosureType)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return $closureType; + } } diff --git a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php index 1ea706a5ec..6ca88557f8 100644 --- a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'bindTo'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $closureType = $scope->getType($methodCall->var); - if (!($closureType instanceof ClosureType)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return $closureType; - } - + public function getClass(): string + { + return \Closure::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'bindTo'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $closureType = $scope->getType($methodCall->var); + if (!($closureType instanceof ClosureType)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return $closureType; + } } diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index 8f3cf04574..046300b21b 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'fromCallable'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type - { - $callableType = $scope->getType($methodCall->args[0]->value); - if ($callableType->isCallable()->no()) { - return new ErrorType(); - } - - $closureTypes = []; - foreach ($callableType->getCallableParametersAcceptors($scope) as $variant) { - $parameters = $variant->getParameters(); - $closureTypes[] = new ClosureType( - $parameters, - $variant->getReturnType(), - $variant->isVariadic() - ); - } - - return TypeCombinator::union(...$closureTypes); - } - + public function getClass(): string + { + return \Closure::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'fromCallable'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + $callableType = $scope->getType($methodCall->args[0]->value); + if ($callableType->isCallable()->no()) { + return new ErrorType(); + } + + $closureTypes = []; + foreach ($callableType->getCallableParametersAcceptors($scope) as $variant) { + $parameters = $variant->getParameters(); + $closureTypes[] = new ClosureType( + $parameters, + $variant->getReturnType(), + $variant->isVariadic() + ); + } + + return TypeCombinator::union(...$closureTypes); + } } diff --git a/src/Type/Php/CompactFunctionReturnTypeExtension.php b/src/Type/Php/CompactFunctionReturnTypeExtension.php index ccd1cb8e47..8848500b8f 100644 --- a/src/Type/Php/CompactFunctionReturnTypeExtension.php +++ b/src/Type/Php/CompactFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'compact'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) === 0) { - return $defaultReturnType; - } - - if ($scope->canAnyVariableExist() && !$this->checkMaybeUndefinedVariables) { - return $defaultReturnType; - } - - $array = ConstantArrayTypeBuilder::createEmpty(); - foreach ($functionCall->args as $arg) { - $type = $scope->getType($arg->value); - $constantStrings = $this->findConstantStrings($type); - if ($constantStrings === null) { - return $defaultReturnType; - } - foreach ($constantStrings as $constantString) { - $has = $scope->hasVariableType($constantString->getValue()); - if ($has->no()) { - continue; - } - - $array->setOffsetValueType($constantString, $scope->getVariableType($constantString->getValue()), $has->maybe()); - } - } - - return $array->getArray(); - } - - /** - * @param Type $type - * @return array|null - */ - private function findConstantStrings(Type $type): ?array - { - if ($type instanceof ConstantStringType) { - return [$type]; - } - - if ($type instanceof ConstantArrayType) { - $result = []; - foreach ($type->getValueTypes() as $valueType) { - $constantStrings = $this->findConstantStrings($valueType); - if ($constantStrings === null) { - return null; - } - - $result = array_merge($result, $constantStrings); - } - - return $result; - } - - return null; - } - + private bool $checkMaybeUndefinedVariables; + + public function __construct(bool $checkMaybeUndefinedVariables) + { + $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'compact'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if (count($functionCall->args) === 0) { + return $defaultReturnType; + } + + if ($scope->canAnyVariableExist() && !$this->checkMaybeUndefinedVariables) { + return $defaultReturnType; + } + + $array = ConstantArrayTypeBuilder::createEmpty(); + foreach ($functionCall->args as $arg) { + $type = $scope->getType($arg->value); + $constantStrings = $this->findConstantStrings($type); + if ($constantStrings === null) { + return $defaultReturnType; + } + foreach ($constantStrings as $constantString) { + $has = $scope->hasVariableType($constantString->getValue()); + if ($has->no()) { + continue; + } + + $array->setOffsetValueType($constantString, $scope->getVariableType($constantString->getValue()), $has->maybe()); + } + } + + return $array->getArray(); + } + + /** + * @param Type $type + * @return array|null + */ + private function findConstantStrings(Type $type): ?array + { + if ($type instanceof ConstantStringType) { + return [$type]; + } + + if ($type instanceof ConstantArrayType) { + $result = []; + foreach ($type->getValueTypes() as $valueType) { + $constantStrings = $this->findConstantStrings($valueType); + if ($constantStrings === null) { + return null; + } + + $result = array_merge($result, $constantStrings); + } + + return $result; + } + + return null; + } } diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index 088717c349..48851563ae 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'count'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) < 1) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - if (count($functionCall->args) > 1) { - $mode = $scope->getType($functionCall->args[1]->value); - if ($mode->isSuperTypeOf(new ConstantIntegerType(\COUNT_RECURSIVE))->yes()) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - } - - $argType = $scope->getType($functionCall->args[0]->value); - $constantArrays = TypeUtils::getConstantArrays($scope->getType($functionCall->args[0]->value)); - if (count($constantArrays) === 0) { - if ($argType->isIterableAtLeastOnce()->yes()) { - return IntegerRangeType::fromInterval(1, null); - } - - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - $countTypes = []; - foreach ($constantArrays as $array) { - $countTypes[] = $array->count(); - } - - return TypeCombinator::union(...$countTypes); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'count'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) < 1) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + if (count($functionCall->args) > 1) { + $mode = $scope->getType($functionCall->args[1]->value); + if ($mode->isSuperTypeOf(new ConstantIntegerType(\COUNT_RECURSIVE))->yes()) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + } + + $argType = $scope->getType($functionCall->args[0]->value); + $constantArrays = TypeUtils::getConstantArrays($scope->getType($functionCall->args[0]->value)); + if (count($constantArrays) === 0) { + if ($argType->isIterableAtLeastOnce()->yes()) { + return IntegerRangeType::fromInterval(1, null); + } + + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + $countTypes = []; + foreach ($constantArrays as $array) { + $countTypes[] = $array->count(); + } + + return TypeCombinator::union(...$countTypes); + } } diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index 7fdde34e13..7a4ff6594d 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -null() - && count($node->args) >= 1 - && $functionReflection->getName() === 'count'; - } - - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - if (!(new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($node->args[0]->value))->yes()) { - return new SpecifiedTypes([], []); - } - - return $this->typeSpecifier->create($node->args[0]->value, new NonEmptyArrayType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return !$context->null() + && count($node->args) >= 1 + && $functionReflection->getName() === 'count'; + } + + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + if (!(new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($node->args[0]->value))->yes()) { + return new SpecifiedTypes([], []); + } + + return $this->typeSpecifier->create($node->args[0]->value, new NonEmptyArrayType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/CurlInitReturnTypeExtension.php b/src/Type/Php/CurlInitReturnTypeExtension.php index 6777c14666..39caa229b1 100644 --- a/src/Type/Php/CurlInitReturnTypeExtension.php +++ b/src/Type/Php/CurlInitReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'curl_init'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'curl_init'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - \PhpParser\Node\Expr\FuncCall $functionCall, - Scope $scope - ): Type - { - $argsCount = count($functionCall->args); - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if ($argsCount === 0) { - return TypeCombinator::remove($returnType, new ConstantBooleanType(false)); - } - - return $returnType; - } + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + \PhpParser\Node\Expr\FuncCall $functionCall, + Scope $scope + ): Type { + $argsCount = count($functionCall->args); + $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if ($argsCount === 0) { + return TypeCombinator::remove($returnType, new ConstantBooleanType(false)); + } + return $returnType; + } } diff --git a/src/Type/Php/DateFunctionReturnTypeExtension.php b/src/Type/Php/DateFunctionReturnTypeExtension.php index 7d4792c03f..02ada64540 100644 --- a/src/Type/Php/DateFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'date'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) === 0) { - return new StringType(); - } - $argType = $scope->getType($functionCall->args[0]->value); - $constantStrings = TypeUtils::getConstantStrings($argType); - if (count($constantStrings) === 0) { - return new StringType(); - } - - foreach ($constantStrings as $constantString) { - $formattedDate = date($constantString->getValue()); - if (!is_numeric($formattedDate)) { - return new StringType(); - } - } - - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'date'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) === 0) { + return new StringType(); + } + $argType = $scope->getType($functionCall->args[0]->value); + $constantStrings = TypeUtils::getConstantStrings($argType); + if (count($constantStrings) === 0) { + return new StringType(); + } + + foreach ($constantStrings as $constantString) { + $formattedDate = date($constantString->getValue()); + if (!is_numeric($formattedDate)) { + return new StringType(); + } + } + + return new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]); + } } diff --git a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php index e701ef80e5..5977c9862a 100644 --- a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php @@ -1,4 +1,6 @@ -getName() === '__construct' && in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true); - } - - public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type - { - if (count($methodCall->args) === 0) { - return null; - } - - $arg = $methodCall->args[0]->value; - $constantStrings = TypeUtils::getConstantStrings($scope->getType($arg)); - if (count($constantStrings) === 0) { - return $methodReflection->getThrowType(); - } - - foreach ($constantStrings as $constantString) { - try { - new \DateTime($constantString->getValue()); - } catch (\Exception $e) { // phpcs:ignore - return $methodReflection->getThrowType(); - } - } - - return null; - } - + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct' && in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true); + } + + public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->args) === 0) { + return null; + } + + $arg = $methodCall->args[0]->value; + $constantStrings = TypeUtils::getConstantStrings($scope->getType($arg)); + if (count($constantStrings) === 0) { + return $methodReflection->getThrowType(); + } + + foreach ($constantStrings as $constantString) { + try { + new \DateTime($constantString->getValue()); + } catch (\Exception $e) { // phpcs:ignore + return $methodReflection->getThrowType(); + } + } + + return null; + } } diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php index 48e3820856..c492eeb625 100644 --- a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), ['date_create_from_format', 'date_create_immutable_from_format'], true); + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return in_array($functionReflection->getName(), ['date_create_from_format', 'date_create_immutable_from_format'], true); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - - if (count($functionCall->args) < 2) { - return $defaultReturnType; - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - $format = $scope->getType($functionCall->args[0]->value); - $datetime = $scope->getType($functionCall->args[1]->value); + if (count($functionCall->args) < 2) { + return $defaultReturnType; + } - if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) { - return $defaultReturnType; - } + $format = $scope->getType($functionCall->args[0]->value); + $datetime = $scope->getType($functionCall->args[1]->value); - $isValid = (DateTime::createFromFormat($format->getValue(), $datetime->getValue()) !== false); + if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) { + return $defaultReturnType; + } - $className = $functionReflection->getName() === 'date_create_from_format' ? DateTime::class : DateTimeImmutable::class; - return $isValid ? new ObjectType($className) : new ConstantBooleanType(false); - } + $isValid = (DateTime::createFromFormat($format->getValue(), $datetime->getValue()) !== false); + $className = $functionReflection->getName() === 'date_create_from_format' ? DateTime::class : DateTimeImmutable::class; + return $isValid ? new ObjectType($className) : new ConstantBooleanType(false); + } } diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 0ac5590dca..74e9c5340a 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection, - FuncCall $node, - TypeSpecifierContext $context - ): bool - { - return $functionReflection->getName() === 'define' - && $context->null() - && count($node->args) >= 2; - } - - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - $constantName = $scope->getType($node->args[0]->value); - if ( - !$constantName instanceof ConstantStringType - || $constantName->getValue() === '' - ) { - return new SpecifiedTypes([], []); - } - - return $this->typeSpecifier->create( - new \PhpParser\Node\Expr\ConstFetch( - new \PhpParser\Node\Name\FullyQualified($constantName->getValue()) - ), - $scope->getType($node->args[1]->value), - TypeSpecifierContext::createTruthy(), - false, - $scope - ); - } - + private TypeSpecifier $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return $functionReflection->getName() === 'define' + && $context->null() + && count($node->args) >= 2; + } + + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $constantName = $scope->getType($node->args[0]->value); + if ( + !$constantName instanceof ConstantStringType + || $constantName->getValue() === '' + ) { + return new SpecifiedTypes([], []); + } + + return $this->typeSpecifier->create( + new \PhpParser\Node\Expr\ConstFetch( + new \PhpParser\Node\Name\FullyQualified($constantName->getValue()) + ), + $scope->getType($node->args[1]->value), + TypeSpecifierContext::createTruthy(), + false, + $scope + ); + } } diff --git a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php index 9ce72c1e1b..12c524d0e9 100644 --- a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection, - FuncCall $node, - TypeSpecifierContext $context - ): bool - { - return $functionReflection->getName() === 'defined' - && count($node->args) >= 1 - && !$context->null(); - } - - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - $constantName = $scope->getType($node->args[0]->value); - if ( - !$constantName instanceof ConstantStringType - || $constantName->getValue() === '' - ) { - return new SpecifiedTypes([], []); - } - - return $this->typeSpecifier->create( - new \PhpParser\Node\Expr\ConstFetch( - new \PhpParser\Node\Name\FullyQualified($constantName->getValue()) - ), - new MixedType(), - $context, - false, - $scope - ); - } - + private TypeSpecifier $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return $functionReflection->getName() === 'defined' + && count($node->args) >= 1 + && !$context->null(); + } + + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $constantName = $scope->getType($node->args[0]->value); + if ( + !$constantName instanceof ConstantStringType + || $constantName->getValue() === '' + ) { + return new SpecifiedTypes([], []); + } + + return $this->typeSpecifier->create( + new \PhpParser\Node\Expr\ConstFetch( + new \PhpParser\Node\Name\FullyQualified($constantName->getValue()) + ), + new MixedType(), + $context, + false, + $scope + ); + } } diff --git a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php index a134c6d4a2..b55d130ed7 100644 --- a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'dio_stat'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $valueType = new IntegerType(); - $builder = ConstantArrayTypeBuilder::createEmpty(); - $keys = [ - 'device', - 'inode', - 'mode', - 'nlink', - 'uid', - 'gid', - 'device_type', - 'size', - 'blocksize', - 'blocks', - 'atime', - 'mtime', - 'ctime', - ]; - - foreach ($keys as $key) { - $builder->setOffsetValueType(new ConstantStringType($key), $valueType); - } - - return TypeCombinator::addNull($builder->getArray()); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'dio_stat'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $valueType = new IntegerType(); + $builder = ConstantArrayTypeBuilder::createEmpty(); + $keys = [ + 'device', + 'inode', + 'mode', + 'nlink', + 'uid', + 'gid', + 'device_type', + 'size', + 'blocksize', + 'blocks', + 'atime', + 'mtime', + 'ctime', + ]; + + foreach ($keys as $key) { + $builder->setOffsetValueType(new ConstantStringType($key), $valueType); + } + + return TypeCombinator::addNull($builder->getArray()); + } } diff --git a/src/Type/Php/DsMapDynamicReturnTypeExtension.php b/src/Type/Php/DsMapDynamicReturnTypeExtension.php index faf1f5bbeb..3684ff48f0 100644 --- a/src/Type/Php/DsMapDynamicReturnTypeExtension.php +++ b/src/Type/Php/DsMapDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'get' || $methodReflection->getName() === 'remove'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $returnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $methodCall->args, - $methodReflection->getVariants() - )->getReturnType(); + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'get' || $methodReflection->getName() === 'remove'; + } - if (count($methodCall->args) > 1) { - return $returnType; - } + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $returnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->args, + $methodReflection->getVariants() + )->getReturnType(); - if ($returnType instanceof UnionType) { - $types = array_values( - array_filter( - $returnType->getTypes(), - static function (Type $type): bool { - if ( - $type instanceof TemplateType - && $type->getName() === 'TDefault' - && ( - $type->getScope()->equals(TemplateTypeScope::createWithMethod('Ds\Map', 'get')) - || $type->getScope()->equals(TemplateTypeScope::createWithMethod('Ds\Map', 'remove')) - ) - ) { - return false; - } + if (count($methodCall->args) > 1) { + return $returnType; + } - return true; - } - ) - ); + if ($returnType instanceof UnionType) { + $types = array_values( + array_filter( + $returnType->getTypes(), + static function (Type $type): bool { + if ( + $type instanceof TemplateType + && $type->getName() === 'TDefault' + && ( + $type->getScope()->equals(TemplateTypeScope::createWithMethod('Ds\Map', 'get')) + || $type->getScope()->equals(TemplateTypeScope::createWithMethod('Ds\Map', 'remove')) + ) + ) { + return false; + } - if (count($types) === 1) { - return $types[0]; - } + return true; + } + ) + ); - if (count($types) === 0) { - return $returnType; - } + if (count($types) === 1) { + return $types[0]; + } - return TypeCombinator::union(...$types); - } + if (count($types) === 0) { + return $returnType; + } - return $returnType; - } + return TypeCombinator::union(...$types); + } + return $returnType; + } } diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index 723ab81877..e6e3d45ece 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -phpVersion = $phpVersion; - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'explode'; - } + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'explode'; + } - $delimiterType = $scope->getType($functionCall->args[0]->value); - $isSuperset = (new ConstantStringType(''))->isSuperTypeOf($delimiterType); - if ($isSuperset->yes()) { - if ($this->phpVersion->getVersionId() >= 80000) { - return new NeverType(); - } - return new ConstantBooleanType(false); - } elseif ($isSuperset->no()) { - $arrayType = new ArrayType(new IntegerType(), new StringType()); - if ( - !isset($functionCall->args[2]) - || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->args[2]->value))->yes() - ) { - return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); - } + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - return $arrayType; - } + $delimiterType = $scope->getType($functionCall->args[0]->value); + $isSuperset = (new ConstantStringType(''))->isSuperTypeOf($delimiterType); + if ($isSuperset->yes()) { + if ($this->phpVersion->getVersionId() >= 80000) { + return new NeverType(); + } + return new ConstantBooleanType(false); + } elseif ($isSuperset->no()) { + $arrayType = new ArrayType(new IntegerType(), new StringType()); + if ( + !isset($functionCall->args[2]) + || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->args[2]->value))->yes() + ) { + return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if ($delimiterType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($returnType); - } + return $arrayType; + } - return $returnType; - } + $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if ($delimiterType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($returnType); + } + return $returnType; + } } diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index c88871a7fd..fb6eccc521 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -|null */ - private ?array $filterTypeMap = null; - - public function __construct(ReflectionProvider $reflectionProvider) - { - $this->reflectionProvider = $reflectionProvider; - - $this->flagsString = new ConstantStringType('flags'); - } - - /** - * @return array - */ - private function getFilterTypeMap(): array - { - if ($this->filterTypeMap !== null) { - return $this->filterTypeMap; - } - - $booleanType = new BooleanType(); - $floatType = new FloatType(); - $intType = new IntegerType(); - $stringType = new StringType(); - - $this->filterTypeMap = [ - $this->getConstant('FILTER_UNSAFE_RAW') => $stringType, - $this->getConstant('FILTER_SANITIZE_EMAIL') => $stringType, - $this->getConstant('FILTER_SANITIZE_ENCODED') => $stringType, - $this->getConstant('FILTER_SANITIZE_NUMBER_FLOAT') => $stringType, - $this->getConstant('FILTER_SANITIZE_NUMBER_INT') => $stringType, - $this->getConstant('FILTER_SANITIZE_SPECIAL_CHARS') => $stringType, - $this->getConstant('FILTER_SANITIZE_STRING') => $stringType, - $this->getConstant('FILTER_SANITIZE_URL') => $stringType, - $this->getConstant('FILTER_VALIDATE_BOOLEAN') => $booleanType, - $this->getConstant('FILTER_VALIDATE_EMAIL') => $stringType, - $this->getConstant('FILTER_VALIDATE_FLOAT') => $floatType, - $this->getConstant('FILTER_VALIDATE_INT') => $intType, - $this->getConstant('FILTER_VALIDATE_IP') => $stringType, - $this->getConstant('FILTER_VALIDATE_MAC') => $stringType, - $this->getConstant('FILTER_VALIDATE_REGEXP') => $stringType, - $this->getConstant('FILTER_VALIDATE_URL') => $stringType, - ]; - - if ($this->reflectionProvider->hasConstant(new Node\Name('FILTER_SANITIZE_MAGIC_QUOTES'), null)) { - $this->filterTypeMap[$this->getConstant('FILTER_SANITIZE_MAGIC_QUOTES')] = $stringType; - } - - if ($this->reflectionProvider->hasConstant(new Node\Name('FILTER_SANITIZE_ADD_SLASHES'), null)) { - $this->filterTypeMap[$this->getConstant('FILTER_SANITIZE_ADD_SLASHES')] = $stringType; - } - - return $this->filterTypeMap; - } - - private function getConstant(string $constantName): int - { - $constant = $this->reflectionProvider->getConstant(new Node\Name($constantName), null); - $valueType = $constant->getValueType(); - if (!$valueType instanceof ConstantIntegerType) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); - } - - return $valueType->getValue(); - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return strtolower($functionReflection->getName()) === 'filter_var'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $mixedType = new MixedType(); - - $filterArg = $functionCall->args[1] ?? null; - if ($filterArg === null) { - $filterValue = $this->getConstant('FILTER_DEFAULT'); - } else { - $filterType = $scope->getType($filterArg->value); - if (!$filterType instanceof ConstantIntegerType) { - return $mixedType; - } - $filterValue = $filterType->getValue(); - } - - $flagsArg = $functionCall->args[2] ?? null; - $inputType = $scope->getType($functionCall->args[0]->value); - $exactType = $this->determineExactType($inputType, $filterValue); - if ($exactType !== null) { - $type = $exactType; - } else { - $type = $this->getFilterTypeMap()[$filterValue] ?? $mixedType; - $otherType = $this->getOtherType($flagsArg, $scope); - - if ($otherType->isSuperTypeOf($type)->no()) { - $type = new UnionType([$type, $otherType]); - } - } - - if ($this->hasFlag($this->getConstant('FILTER_FORCE_ARRAY'), $flagsArg, $scope)) { - return new ArrayType(new MixedType(), $type); - } - - return $type; - } - - - private function determineExactType(Type $in, int $filterValue): ?Type - { - if (($filterValue === $this->getConstant('FILTER_VALIDATE_BOOLEAN') && $in instanceof BooleanType) - || ($filterValue === $this->getConstant('FILTER_VALIDATE_INT') && $in instanceof IntegerType) - || ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof FloatType)) { - return $in; - } - - if ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof IntegerType) { - return $in->toFloat(); - } - - return null; - } - - private function getOtherType(?Node\Arg $flagsArg, Scope $scope): Type - { - $falseType = new ConstantBooleanType(false); - if ($flagsArg === null) { - return $falseType; - } - - $defaultType = $this->getDefault($flagsArg, $scope); - if ($defaultType !== null) { - return $defaultType; - } - - if ($this->hasFlag($this->getConstant('FILTER_NULL_ON_FAILURE'), $flagsArg, $scope)) { - return new NullType(); - } - - return $falseType; - } - - private function getDefault(Node\Arg $expression, Scope $scope): ?Type - { - $exprType = $scope->getType($expression->value); - if (!$exprType instanceof ConstantArrayType) { - return null; - } - - $optionsType = $exprType->getOffsetValueType(new ConstantStringType('options')); - if (!$optionsType instanceof ConstantArrayType) { - return null; - } - - $defaultType = $optionsType->getOffsetValueType(new ConstantStringType('default')); - if (!$defaultType instanceof ErrorType) { - return $defaultType; - } - - return null; - } - - - private function hasFlag(int $flag, ?Node\Arg $expression, Scope $scope): bool - { - if ($expression === null) { - return false; - } - - $type = $this->getFlagsValue($scope->getType($expression->value)); - - return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag; - } - - private function getFlagsValue(Type $exprType): Type - { - if (!$exprType instanceof ConstantArrayType) { - return $exprType; - } - - return $exprType->getOffsetValueType($this->flagsString); - } - + private ReflectionProvider $reflectionProvider; + + private ConstantStringType $flagsString; + + /** @var array|null */ + private ?array $filterTypeMap = null; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + + $this->flagsString = new ConstantStringType('flags'); + } + + /** + * @return array + */ + private function getFilterTypeMap(): array + { + if ($this->filterTypeMap !== null) { + return $this->filterTypeMap; + } + + $booleanType = new BooleanType(); + $floatType = new FloatType(); + $intType = new IntegerType(); + $stringType = new StringType(); + + $this->filterTypeMap = [ + $this->getConstant('FILTER_UNSAFE_RAW') => $stringType, + $this->getConstant('FILTER_SANITIZE_EMAIL') => $stringType, + $this->getConstant('FILTER_SANITIZE_ENCODED') => $stringType, + $this->getConstant('FILTER_SANITIZE_NUMBER_FLOAT') => $stringType, + $this->getConstant('FILTER_SANITIZE_NUMBER_INT') => $stringType, + $this->getConstant('FILTER_SANITIZE_SPECIAL_CHARS') => $stringType, + $this->getConstant('FILTER_SANITIZE_STRING') => $stringType, + $this->getConstant('FILTER_SANITIZE_URL') => $stringType, + $this->getConstant('FILTER_VALIDATE_BOOLEAN') => $booleanType, + $this->getConstant('FILTER_VALIDATE_EMAIL') => $stringType, + $this->getConstant('FILTER_VALIDATE_FLOAT') => $floatType, + $this->getConstant('FILTER_VALIDATE_INT') => $intType, + $this->getConstant('FILTER_VALIDATE_IP') => $stringType, + $this->getConstant('FILTER_VALIDATE_MAC') => $stringType, + $this->getConstant('FILTER_VALIDATE_REGEXP') => $stringType, + $this->getConstant('FILTER_VALIDATE_URL') => $stringType, + ]; + + if ($this->reflectionProvider->hasConstant(new Node\Name('FILTER_SANITIZE_MAGIC_QUOTES'), null)) { + $this->filterTypeMap[$this->getConstant('FILTER_SANITIZE_MAGIC_QUOTES')] = $stringType; + } + + if ($this->reflectionProvider->hasConstant(new Node\Name('FILTER_SANITIZE_ADD_SLASHES'), null)) { + $this->filterTypeMap[$this->getConstant('FILTER_SANITIZE_ADD_SLASHES')] = $stringType; + } + + return $this->filterTypeMap; + } + + private function getConstant(string $constantName): int + { + $constant = $this->reflectionProvider->getConstant(new Node\Name($constantName), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); + } + + return $valueType->getValue(); + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return strtolower($functionReflection->getName()) === 'filter_var'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $mixedType = new MixedType(); + + $filterArg = $functionCall->args[1] ?? null; + if ($filterArg === null) { + $filterValue = $this->getConstant('FILTER_DEFAULT'); + } else { + $filterType = $scope->getType($filterArg->value); + if (!$filterType instanceof ConstantIntegerType) { + return $mixedType; + } + $filterValue = $filterType->getValue(); + } + + $flagsArg = $functionCall->args[2] ?? null; + $inputType = $scope->getType($functionCall->args[0]->value); + $exactType = $this->determineExactType($inputType, $filterValue); + if ($exactType !== null) { + $type = $exactType; + } else { + $type = $this->getFilterTypeMap()[$filterValue] ?? $mixedType; + $otherType = $this->getOtherType($flagsArg, $scope); + + if ($otherType->isSuperTypeOf($type)->no()) { + $type = new UnionType([$type, $otherType]); + } + } + + if ($this->hasFlag($this->getConstant('FILTER_FORCE_ARRAY'), $flagsArg, $scope)) { + return new ArrayType(new MixedType(), $type); + } + + return $type; + } + + + private function determineExactType(Type $in, int $filterValue): ?Type + { + if (($filterValue === $this->getConstant('FILTER_VALIDATE_BOOLEAN') && $in instanceof BooleanType) + || ($filterValue === $this->getConstant('FILTER_VALIDATE_INT') && $in instanceof IntegerType) + || ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof FloatType)) { + return $in; + } + + if ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof IntegerType) { + return $in->toFloat(); + } + + return null; + } + + private function getOtherType(?Node\Arg $flagsArg, Scope $scope): Type + { + $falseType = new ConstantBooleanType(false); + if ($flagsArg === null) { + return $falseType; + } + + $defaultType = $this->getDefault($flagsArg, $scope); + if ($defaultType !== null) { + return $defaultType; + } + + if ($this->hasFlag($this->getConstant('FILTER_NULL_ON_FAILURE'), $flagsArg, $scope)) { + return new NullType(); + } + + return $falseType; + } + + private function getDefault(Node\Arg $expression, Scope $scope): ?Type + { + $exprType = $scope->getType($expression->value); + if (!$exprType instanceof ConstantArrayType) { + return null; + } + + $optionsType = $exprType->getOffsetValueType(new ConstantStringType('options')); + if (!$optionsType instanceof ConstantArrayType) { + return null; + } + + $defaultType = $optionsType->getOffsetValueType(new ConstantStringType('default')); + if (!$defaultType instanceof ErrorType) { + return $defaultType; + } + + return null; + } + + + private function hasFlag(int $flag, ?Node\Arg $expression, Scope $scope): bool + { + if ($expression === null) { + return false; + } + + $type = $this->getFlagsValue($scope->getType($expression->value)); + + return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag; + } + + private function getFlagsValue(Type $exprType): Type + { + if (!$exprType instanceof ConstantArrayType) { + return $exprType; + } + + return $exprType->getOffsetValueType($this->flagsString); + } } diff --git a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php index 77381c798c..46b7b22489 100644 --- a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'get_called_class'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'get_called_class'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $classContext = $scope->getClassReflection(); - if ($classContext !== null) { - return new ConstantStringType($classContext->getName(), true); - } - return new ConstantBooleanType(false); - } - + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $classContext = $scope->getClassReflection(); + if ($classContext !== null) { + return new ConstantStringType($classContext->getName(), true); + } + return new ConstantBooleanType(false); + } } diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index 5a1bc77551..005303bde3 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'get_class'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'get_class'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $args = $functionCall->args; - if (count($args) === 0) { - if ($scope->isInClass()) { - return new ConstantStringType($scope->getClassReflection()->getName(), true); - } - - return new ConstantBooleanType(false); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $args = $functionCall->args; + if (count($args) === 0) { + if ($scope->isInClass()) { + return new ConstantStringType($scope->getClassReflection()->getName(), true); + } - $argType = $scope->getType($args[0]->value); + return new ConstantBooleanType(false); + } - return TypeTraverser::map( - $argType, - static function (Type $type, callable $traverse): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } + $argType = $scope->getType($args[0]->value); - if ($type instanceof TemplateType && !$type instanceof TypeWithClassName) { - return new GenericClassStringType($type); - } elseif ($type instanceof MixedType) { - return new ClassStringType(); - } elseif ($type instanceof StaticType) { - return new GenericClassStringType($type->getStaticObjectType()); - } elseif ($type instanceof TypeWithClassName) { - return new GenericClassStringType($type); - } elseif ($type instanceof ObjectWithoutClassType) { - return new ClassStringType(); - } + return TypeTraverser::map( + $argType, + static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } - return new ConstantBooleanType(false); - } - ); - } + if ($type instanceof TemplateType && !$type instanceof TypeWithClassName) { + return new GenericClassStringType($type); + } elseif ($type instanceof MixedType) { + return new ClassStringType(); + } elseif ($type instanceof StaticType) { + return new GenericClassStringType($type->getStaticObjectType()); + } elseif ($type instanceof TypeWithClassName) { + return new GenericClassStringType($type); + } elseif ($type instanceof ObjectWithoutClassType) { + return new ClassStringType(); + } + return new ConstantBooleanType(false); + } + ); + } } diff --git a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php index b834633536..7d78719fe9 100644 --- a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection - ): bool - { - return $functionReflection->getName() === 'get_parent_class'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle( - $functionReflection->getVariants() - )->getReturnType(); - if (count($functionCall->args) === 0) { - if ($scope->isInTrait()) { - return $defaultReturnType; - } - if ($scope->isInClass()) { - return $this->findParentClassType( - $scope->getClassReflection() - ); - } - - return new ConstantBooleanType(false); - } - - $argType = $scope->getType($functionCall->args[0]->value); - if ($scope->isInTrait() && TypeUtils::findThisType($argType) !== null) { - return $defaultReturnType; - } - - $constantStrings = TypeUtils::getConstantStrings($argType); - if (count($constantStrings) > 0) { - return \PHPStan\Type\TypeCombinator::union(...array_map(function (ConstantStringType $stringType): Type { - return $this->findParentClassNameType($stringType->getValue()); - }, $constantStrings)); - } - - $classNames = TypeUtils::getDirectClassNames($argType); - if (count($classNames) > 0) { - return \PHPStan\Type\TypeCombinator::union(...array_map(function (string $classNames): Type { - return $this->findParentClassNameType($classNames); - }, $classNames)); - } - - return $defaultReturnType; - } - - private function findParentClassNameType(string $className): Type - { - if (!$this->reflectionProvider->hasClass($className)) { - return new UnionType([ - new ClassStringType(), - new ConstantBooleanType(false), - ]); - } - - return $this->findParentClassType($this->reflectionProvider->getClass($className)); - } - - private function findParentClassType( - ClassReflection $classReflection - ): Type - { - $parentClass = $classReflection->getParentClass(); - if ($parentClass === false) { - return new ConstantBooleanType(false); - } - - return new ConstantStringType($parentClass->getName(), true); - } - + private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + + public function __construct(\PHPStan\Reflection\ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function isFunctionSupported( + FunctionReflection $functionReflection + ): bool { + return $functionReflection->getName() === 'get_parent_class'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $defaultReturnType = ParametersAcceptorSelector::selectSingle( + $functionReflection->getVariants() + )->getReturnType(); + if (count($functionCall->args) === 0) { + if ($scope->isInTrait()) { + return $defaultReturnType; + } + if ($scope->isInClass()) { + return $this->findParentClassType( + $scope->getClassReflection() + ); + } + + return new ConstantBooleanType(false); + } + + $argType = $scope->getType($functionCall->args[0]->value); + if ($scope->isInTrait() && TypeUtils::findThisType($argType) !== null) { + return $defaultReturnType; + } + + $constantStrings = TypeUtils::getConstantStrings($argType); + if (count($constantStrings) > 0) { + return \PHPStan\Type\TypeCombinator::union(...array_map(function (ConstantStringType $stringType): Type { + return $this->findParentClassNameType($stringType->getValue()); + }, $constantStrings)); + } + + $classNames = TypeUtils::getDirectClassNames($argType); + if (count($classNames) > 0) { + return \PHPStan\Type\TypeCombinator::union(...array_map(function (string $classNames): Type { + return $this->findParentClassNameType($classNames); + }, $classNames)); + } + + return $defaultReturnType; + } + + private function findParentClassNameType(string $className): Type + { + if (!$this->reflectionProvider->hasClass($className)) { + return new UnionType([ + new ClassStringType(), + new ConstantBooleanType(false), + ]); + } + + return $this->findParentClassType($this->reflectionProvider->getClass($className)); + } + + private function findParentClassType( + ClassReflection $classReflection + ): Type { + $parentClass = $classReflection->getParentClass(); + if ($parentClass === false) { + return new ConstantBooleanType(false); + } + + return new ConstantStringType($parentClass->getName(), true); + } } diff --git a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php index 8fdb910688..e0d2efcd7d 100644 --- a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'gettimeofday'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $arrayType = new ConstantArrayType([ - new ConstantStringType('sec'), - new ConstantStringType('usec'), - new ConstantStringType('minuteswest'), - new ConstantStringType('dsttime'), - ], [ - new IntegerType(), - new IntegerType(), - new IntegerType(), - new IntegerType(), - ]); - $floatType = new FloatType(); - - if (!isset($functionCall->args[0])) { - return $arrayType; - } - - $argType = $scope->getType($functionCall->args[0]->value); - $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); - $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); - $compareTypes = $isTrueType->compareTo($isFalseType); - if ($compareTypes === $isTrueType) { - return $floatType; - } - if ($compareTypes === $isFalseType) { - return $arrayType; - } - - if ($argType instanceof MixedType) { - return new BenevolentUnionType([$arrayType, $floatType]); - } - - return new UnionType([$arrayType, $floatType]); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'gettimeofday'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arrayType = new ConstantArrayType([ + new ConstantStringType('sec'), + new ConstantStringType('usec'), + new ConstantStringType('minuteswest'), + new ConstantStringType('dsttime'), + ], [ + new IntegerType(), + new IntegerType(), + new IntegerType(), + new IntegerType(), + ]); + $floatType = new FloatType(); + + if (!isset($functionCall->args[0])) { + return $arrayType; + } + + $argType = $scope->getType($functionCall->args[0]->value); + $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); + $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); + $compareTypes = $isTrueType->compareTo($isFalseType); + if ($compareTypes === $isTrueType) { + return $floatType; + } + if ($compareTypes === $isFalseType) { + return $arrayType; + } + + if ($argType instanceof MixedType) { + return new BenevolentUnionType([$arrayType, $floatType]); + } + + return new UnionType([$arrayType, $floatType]); + } } diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 402bc24196..284cbbbb02 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'hash'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - - if (!isset($functionCall->args[0])) { - return $defaultReturnType; - } - - $argType = $scope->getType($functionCall->args[0]->value); - if ($argType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } - - $values = TypeUtils::getConstantStrings($argType); - if (count($values) !== 1) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } - $string = $values[0]; - - return in_array($string->getValue(), hash_algos(), true) ? new StringType() : new ConstantBooleanType(false); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'hash'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + + if (!isset($functionCall->args[0])) { + return $defaultReturnType; + } + + $argType = $scope->getType($functionCall->args[0]->value); + if ($argType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + + $values = TypeUtils::getConstantStrings($argType); + if (count($values) !== 1) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + $string = $values[0]; + + return in_array($string->getValue(), hash_algos(), true) ? new StringType() : new ConstantBooleanType(false); + } } diff --git a/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php b/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php index 208b55ed24..13b026b1bc 100644 --- a/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), ['hash_hmac', 'hash_hmac_file'], true); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if ($functionReflection->getName() === 'hash_hmac') { - $defaultReturnType = new StringType(); - } else { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array($functionReflection->getName(), ['hash_hmac', 'hash_hmac_file'], true); + } - if (!isset($functionCall->args[0])) { - return $defaultReturnType; - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if ($functionReflection->getName() === 'hash_hmac') { + $defaultReturnType = new StringType(); + } else { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - $argType = $scope->getType($functionCall->args[0]->value); - if ($argType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } + if (!isset($functionCall->args[0])) { + return $defaultReturnType; + } - $values = TypeUtils::getConstantStrings($argType); - if (count($values) !== 1) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } - $string = $values[0]; + $argType = $scope->getType($functionCall->args[0]->value); + if ($argType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } - return in_array($string->getValue(), self::HMAC_ALGORITHMS, true) ? $defaultReturnType : new ConstantBooleanType(false); - } + $values = TypeUtils::getConstantStrings($argType); + if (count($values) !== 1) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + $string = $values[0]; + return in_array($string->getValue(), self::HMAC_ALGORITHMS, true) ? $defaultReturnType : new ConstantBooleanType(false); + } } diff --git a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php index 44e8581750..7720ff43ec 100644 --- a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'hrtime'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $arrayType = new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new IntegerType(), new IntegerType()], 2); - $numberType = TypeUtils::toBenevolentUnion(TypeCombinator::union(new IntegerType(), new FloatType())); - - if (count($functionCall->args) < 1) { - return $arrayType; - } - - $argType = $scope->getType($functionCall->args[0]->value); - $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); - $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); - $compareTypes = $isTrueType->compareTo($isFalseType); - if ($compareTypes === $isTrueType) { - return $numberType; - } - if ($compareTypes === $isFalseType) { - return $arrayType; - } - - return TypeCombinator::union($arrayType, $numberType); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'hrtime'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arrayType = new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new IntegerType(), new IntegerType()], 2); + $numberType = TypeUtils::toBenevolentUnion(TypeCombinator::union(new IntegerType(), new FloatType())); + + if (count($functionCall->args) < 1) { + return $arrayType; + } + + $argType = $scope->getType($functionCall->args[0]->value); + $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); + $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); + $compareTypes = $isTrueType->compareTo($isFalseType); + if ($compareTypes === $isTrueType) { + return $numberType; + } + if ($compareTypes === $isFalseType) { + return $arrayType; + } + + return TypeCombinator::union($arrayType, $numberType); + } } diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 8310a0207f..cd6592ee6a 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool - { - return strtolower($functionReflection->getName()) === 'in_array' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (count($node->args) < 3) { - return new SpecifiedTypes(); - } - $strictNodeType = $scope->getType($node->args[2]->value); - if (!(new ConstantBooleanType(true))->isSuperTypeOf($strictNodeType)->yes()) { - return new SpecifiedTypes([], []); - } - - $arrayValueType = $scope->getType($node->args[1]->value)->getIterableValueType(); - - if ( - $context->truthy() - || count(TypeUtils::getConstantScalars($arrayValueType)) > 0 - ) { - return $this->typeSpecifier->create( - $node->args[0]->value, - $arrayValueType, - $context, - false, - $scope - ); - } - - return new SpecifiedTypes([], []); - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'in_array' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (count($node->args) < 3) { + return new SpecifiedTypes(); + } + $strictNodeType = $scope->getType($node->args[2]->value); + if (!(new ConstantBooleanType(true))->isSuperTypeOf($strictNodeType)->yes()) { + return new SpecifiedTypes([], []); + } + + $arrayValueType = $scope->getType($node->args[1]->value)->getIterableValueType(); + + if ( + $context->truthy() + || count(TypeUtils::getConstantScalars($arrayValueType)) > 0 + ) { + return $this->typeSpecifier->create( + $node->args[0]->value, + $arrayValueType, + $context, + false, + $scope + ); + } + + return new SpecifiedTypes([], []); + } } diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index 4ee1670161..5c882e0ca4 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_a' - && isset($node->args[0]) - && isset($node->args[1]) - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_a' + && isset($node->args[0]) + && isset($node->args[1]) + && !$context->null(); + } - $classNameArgExpr = $node->args[1]->value; - $classNameArgExprType = $scope->getType($classNameArgExpr); - if ( - $classNameArgExpr instanceof ClassConstFetch - && $classNameArgExpr->class instanceof Name - && $classNameArgExpr->name instanceof \PhpParser\Node\Identifier - && strtolower($classNameArgExpr->name->name) === 'class' - ) { - $objectType = $scope->resolveTypeByName($classNameArgExpr->class); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); - } elseif ($classNameArgExprType instanceof ConstantStringType) { - $objectType = new ObjectType($classNameArgExprType->getValue()); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); - } elseif ($classNameArgExprType instanceof GenericClassStringType) { - $objectType = $classNameArgExprType->getGenericType(); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); - } elseif ($context->true()) { - $objectType = new ObjectWithoutClassType(); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); - } else { - $types = new SpecifiedTypes(); - } + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } - if (isset($node->args[2]) && $context->true()) { - if (!$scope->getType($node->args[2]->value)->isSuperTypeOf(new ConstantBooleanType(true))->no()) { - $types = $types->intersectWith($this->typeSpecifier->create( - $node->args[0]->value, - isset($objectType) ? new GenericClassStringType($objectType) : new ClassStringType(), - $context, - false, - $scope - )); - } - } + $classNameArgExpr = $node->args[1]->value; + $classNameArgExprType = $scope->getType($classNameArgExpr); + if ( + $classNameArgExpr instanceof ClassConstFetch + && $classNameArgExpr->class instanceof Name + && $classNameArgExpr->name instanceof \PhpParser\Node\Identifier + && strtolower($classNameArgExpr->name->name) === 'class' + ) { + $objectType = $scope->resolveTypeByName($classNameArgExpr->class); + $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + } elseif ($classNameArgExprType instanceof ConstantStringType) { + $objectType = new ObjectType($classNameArgExprType->getValue()); + $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + } elseif ($classNameArgExprType instanceof GenericClassStringType) { + $objectType = $classNameArgExprType->getGenericType(); + $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + } elseif ($context->true()) { + $objectType = new ObjectWithoutClassType(); + $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + } else { + $types = new SpecifiedTypes(); + } - return $types; - } + if (isset($node->args[2]) && $context->true()) { + if (!$scope->getType($node->args[2]->value)->isSuperTypeOf(new ConstantBooleanType(true))->no()) { + $types = $types->intersectWith($this->typeSpecifier->create( + $node->args[0]->value, + isset($objectType) ? new GenericClassStringType($objectType) : new ClassStringType(), + $context, + false, + $scope + )); + } + } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + return $types; + } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index a0b82542a0..795ede5aab 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_array' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new ArrayType(new MixedType(), new MixedType()), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_array' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new ArrayType(new MixedType(), new MixedType()), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php index 3753d2304a..21bf9a6de1 100644 --- a/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_bool' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new BooleanType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_bool' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new BooleanType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index eb051391d4..2ba1f883af 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -methodExistsExtension = $methodExistsExtension; - } + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; - public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool - { - return strtolower($functionReflection->getName()) === 'is_callable' - && !$context->null(); - } + public function __construct(MethodExistsTypeSpecifyingExtension $methodExistsExtension) + { + $this->methodExistsExtension = $methodExistsExtension; + } - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_callable' + && !$context->null(); + } - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } - $value = $node->args[0]->value; - $valueType = $scope->getType($value); - if ( - $value instanceof Array_ - && count($value->items) === 2 - && $valueType instanceof ConstantArrayType - && !$valueType->isCallable()->no() - ) { - if ($value->items[0] === null || $value->items[1] === null) { - throw new \PHPStan\ShouldNotHappenException(); - } + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } - $functionCall = new FuncCall(new Name('method_exists'), [ - new Arg($value->items[0]->value), - new Arg($value->items[1]->value), - ]); - return $this->methodExistsExtension->specifyTypes($functionReflection, $functionCall, $scope, $context); - } + $value = $node->args[0]->value; + $valueType = $scope->getType($value); + if ( + $value instanceof Array_ + && count($value->items) === 2 + && $valueType instanceof ConstantArrayType + && !$valueType->isCallable()->no() + ) { + if ($value->items[0] === null || $value->items[1] === null) { + throw new \PHPStan\ShouldNotHappenException(); + } - return $this->typeSpecifier->create($value, new CallableType(), $context, false, $scope); - } + $functionCall = new FuncCall(new Name('method_exists'), [ + new Arg($value->items[0]->value), + new Arg($value->items[1]->value), + ]); + return $this->methodExistsExtension->specifyTypes($functionReflection, $functionCall, $scope, $context); + } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + return $this->typeSpecifier->create($value, new CallableType(), $context, false, $scope); + } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php index 50814695d3..5ca9549ddd 100644 --- a/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_countable' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create( - $node->args[0]->value, - new UnionType([ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(\Countable::class), - ]), - $context, - false, - $scope - ); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_countable' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create( + $node->args[0]->value, + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new ObjectType(\Countable::class), + ]), + $context, + false, + $scope + ); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php index 33b021251e..e8fef7bb26 100644 --- a/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()), [ - 'is_float', - 'is_double', - 'is_real', - ], true) - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new FloatType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return in_array(strtolower($functionReflection->getName()), [ + 'is_float', + 'is_double', + 'is_real', + ], true) + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new FloatType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php index b8d70648ae..9fb9573e39 100644 --- a/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()), [ - 'is_int', - 'is_integer', - 'is_long', - ], true) - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new IntegerType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return in_array(strtolower($functionReflection->getName()), [ + 'is_int', + 'is_integer', + 'is_long', + ], true) + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new IntegerType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php index 78d5337610..868671124e 100644 --- a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_iterable' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new IterableType(new MixedType(), new MixedType()), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_iterable' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new IterableType(new MixedType(), new MixedType()), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php index 85d26bb99b..facd5f1e6a 100644 --- a/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_null' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new NullType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_null' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new NullType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php index 9cf67e8359..fed78bacad 100644 --- a/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName() === 'is_numeric' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $numericTypes = [ - new IntegerType(), - new FloatType(), - ]; - - if ($context->truthy()) { - $numericTypes[] = new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - - return $this->typeSpecifier->create($node->args[0]->value, new UnionType($numericTypes), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return $functionReflection->getName() === 'is_numeric' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $numericTypes = [ + new IntegerType(), + new FloatType(), + ]; + + if ($context->truthy()) { + $numericTypes[] = new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]); + } + + return $this->typeSpecifier->create($node->args[0]->value, new UnionType($numericTypes), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php index 9d60ab9b3b..dd15c5ef42 100644 --- a/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_object' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new ObjectWithoutClassType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_object' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new ObjectWithoutClassType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php index efaec426d8..07900510b5 100644 --- a/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_resource' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new ResourceType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_resource' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new ResourceType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php index 4bcdd7c962..51bc87c935 100644 --- a/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName() === 'is_scalar' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new UnionType([ - new StringType(), - new IntegerType(), - new FloatType(), - new BooleanType(), - ]), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return $functionReflection->getName() === 'is_scalar' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new UnionType([ + new StringType(), + new IntegerType(), + new FloatType(), + new BooleanType(), + ]), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php index f589ec523c..bf8d659725 100644 --- a/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_string' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if (!isset($node->args[0])) { - return new SpecifiedTypes(); - } - - return $this->typeSpecifier->create($node->args[0]->value, new StringType(), $context, false, $scope); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_string' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!isset($node->args[0])) { + return new SpecifiedTypes(); + } + + return $this->typeSpecifier->create($node->args[0]->value, new StringType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index e692d24ed1..65b7230e59 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -getName()) === 'is_subclass_of' - && !$context->null(); - } - - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if (count($node->args) < 2) { - return new SpecifiedTypes(); - } - $objectType = $scope->getType($node->args[0]->value); - $classType = $scope->getType($node->args[1]->value); - $allowStringType = isset($node->args[2]) ? $scope->getType($node->args[2]->value) : new ConstantBooleanType(true); - $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName()) === 'is_subclass_of' + && !$context->null(); + } - if (!$classType instanceof ConstantStringType) { - if ($context->truthy()) { - if ($allowString) { - $type = TypeCombinator::union( - new ObjectWithoutClassType(), - new ClassStringType() - ); - } else { - $type = new ObjectWithoutClassType(); - } + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (count($node->args) < 2) { + return new SpecifiedTypes(); + } + $objectType = $scope->getType($node->args[0]->value); + $classType = $scope->getType($node->args[1]->value); + $allowStringType = isset($node->args[2]) ? $scope->getType($node->args[2]->value) : new ConstantBooleanType(true); + $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); - return $this->typeSpecifier->create( - $node->args[0]->value, - $type, - $context, - false, - $scope - ); - } + if (!$classType instanceof ConstantStringType) { + if ($context->truthy()) { + if ($allowString) { + $type = TypeCombinator::union( + new ObjectWithoutClassType(), + new ClassStringType() + ); + } else { + $type = new ObjectWithoutClassType(); + } - return new SpecifiedTypes(); - } + return $this->typeSpecifier->create( + $node->args[0]->value, + $type, + $context, + false, + $scope + ); + } - $type = TypeTraverser::map($objectType, static function (Type $type, callable $traverse) use ($classType, $allowString): Type { - if ($type instanceof UnionType) { - return $traverse($type); - } - if ($type instanceof IntersectionType) { - return $traverse($type); - } - if ($allowString) { - if ($type instanceof StringType) { - return new GenericClassStringType(new ObjectType($classType->getValue())); - } - } - if ($type instanceof ObjectWithoutClassType || $type instanceof TypeWithClassName) { - return new ObjectType($classType->getValue()); - } - if ($type instanceof MixedType) { - $objectType = new ObjectType($classType->getValue()); - if ($allowString) { - return TypeCombinator::union( - new GenericClassStringType($objectType), - $objectType - ); - } + return new SpecifiedTypes(); + } - return $objectType; - } - return new NeverType(); - }); + $type = TypeTraverser::map($objectType, static function (Type $type, callable $traverse) use ($classType, $allowString): Type { + if ($type instanceof UnionType) { + return $traverse($type); + } + if ($type instanceof IntersectionType) { + return $traverse($type); + } + if ($allowString) { + if ($type instanceof StringType) { + return new GenericClassStringType(new ObjectType($classType->getValue())); + } + } + if ($type instanceof ObjectWithoutClassType || $type instanceof TypeWithClassName) { + return new ObjectType($classType->getValue()); + } + if ($type instanceof MixedType) { + $objectType = new ObjectType($classType->getValue()); + if ($allowString) { + return TypeCombinator::union( + new GenericClassStringType($objectType), + $objectType + ); + } - return $this->typeSpecifier->create( - $node->args[0]->value, - $type, - $context, - false, - $scope - ); - } + return $objectType; + } + return new NeverType(); + }); - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + return $this->typeSpecifier->create( + $node->args[0]->value, + $type, + $context, + false, + $scope + ); + } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } } diff --git a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php index e2697da0a0..d891a15b29 100644 --- a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ - */ - private array $argumentPositions = [ - 'json_encode' => 1, - 'json_decode' => 3, - ]; - - private ReflectionProvider $reflectionProvider; - - public function __construct(ReflectionProvider $reflectionProvider) - { - $this->reflectionProvider = $reflectionProvider; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection - ): bool - { - return $this->reflectionProvider->hasConstant(new FullyQualified('JSON_THROW_ON_ERROR'), null) && in_array( - $functionReflection->getName(), - [ - 'json_encode', - 'json_decode', - ], - true - ); - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!isset($functionCall->args[$argumentPosition])) { - return $defaultReturnType; - } - - $optionsExpr = $functionCall->args[$argumentPosition]->value; - $constrictedReturnType = TypeCombinator::remove($defaultReturnType, new ConstantBooleanType(false)); - if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { - return $constrictedReturnType; - } - - $valueType = $scope->getType($optionsExpr); - if (!$valueType instanceof ConstantIntegerType) { - return $defaultReturnType; - } - - $value = $valueType->getValue(); - $throwOnErrorType = $this->reflectionProvider->getConstant(new FullyQualified('JSON_THROW_ON_ERROR'), null)->getValueType(); - if (!$throwOnErrorType instanceof ConstantIntegerType) { - return $defaultReturnType; - } - - $throwOnErrorValue = $throwOnErrorType->getValue(); - if (($value & $throwOnErrorValue) !== $throwOnErrorValue) { - return $defaultReturnType; - } - - return $constrictedReturnType; - } - - private function isBitwiseOrWithJsonThrowOnError(Expr $expr, Scope $scope): bool - { - if ($expr instanceof ConstFetch) { - $constant = $this->reflectionProvider->resolveConstantName($expr->name, $scope); - if ($constant === 'JSON_THROW_ON_ERROR') { - return true; - } - } - - if (!$expr instanceof BitwiseOr) { - return false; - } - - return $this->isBitwiseOrWithJsonThrowOnError($expr->left, $scope) || - $this->isBitwiseOrWithJsonThrowOnError($expr->right, $scope); - } - + /** @var array */ + private array $argumentPositions = [ + 'json_encode' => 1, + 'json_decode' => 3, + ]; + + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function isFunctionSupported( + FunctionReflection $functionReflection + ): bool { + return $this->reflectionProvider->hasConstant(new FullyQualified('JSON_THROW_ON_ERROR'), null) && in_array( + $functionReflection->getName(), + [ + 'json_encode', + 'json_decode', + ], + true + ); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if (!isset($functionCall->args[$argumentPosition])) { + return $defaultReturnType; + } + + $optionsExpr = $functionCall->args[$argumentPosition]->value; + $constrictedReturnType = TypeCombinator::remove($defaultReturnType, new ConstantBooleanType(false)); + if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { + return $constrictedReturnType; + } + + $valueType = $scope->getType($optionsExpr); + if (!$valueType instanceof ConstantIntegerType) { + return $defaultReturnType; + } + + $value = $valueType->getValue(); + $throwOnErrorType = $this->reflectionProvider->getConstant(new FullyQualified('JSON_THROW_ON_ERROR'), null)->getValueType(); + if (!$throwOnErrorType instanceof ConstantIntegerType) { + return $defaultReturnType; + } + + $throwOnErrorValue = $throwOnErrorType->getValue(); + if (($value & $throwOnErrorValue) !== $throwOnErrorValue) { + return $defaultReturnType; + } + + return $constrictedReturnType; + } + + private function isBitwiseOrWithJsonThrowOnError(Expr $expr, Scope $scope): bool + { + if ($expr instanceof ConstFetch) { + $constant = $this->reflectionProvider->resolveConstantName($expr->name, $scope); + if ($constant === 'JSON_THROW_ON_ERROR') { + return true; + } + } + + if (!$expr instanceof BitwiseOr) { + return false; + } + + return $this->isBitwiseOrWithJsonThrowOnError($expr->left, $scope) || + $this->isBitwiseOrWithJsonThrowOnError($expr->right, $scope); + } } diff --git a/src/Type/Php/JsonThrowTypeExtension.php b/src/Type/Php/JsonThrowTypeExtension.php index bcb7bd4ee3..1ca09f1a9a 100644 --- a/src/Type/Php/JsonThrowTypeExtension.php +++ b/src/Type/Php/JsonThrowTypeExtension.php @@ -1,4 +1,6 @@ - */ - private array $argumentPositions = [ - 'json_encode' => 1, - 'json_decode' => 3, - ]; - - private ReflectionProvider $reflectionProvider; - - public function __construct(ReflectionProvider $reflectionProvider) - { - $this->reflectionProvider = $reflectionProvider; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection - ): bool - { - return $this->reflectionProvider->hasConstant(new Name\FullyQualified('JSON_THROW_ON_ERROR'), null) && in_array( - $functionReflection->getName(), - [ - 'json_encode', - 'json_decode', - ], - true - ); - } - - public function getThrowTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): ?Type - { - $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; - if (!isset($functionCall->args[$argumentPosition])) { - return null; - } - - $optionsExpr = $functionCall->args[$argumentPosition]->value; - if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { - return new ObjectType('JsonException'); - } - - $valueType = $scope->getType($optionsExpr); - if (!$valueType instanceof ConstantIntegerType) { - return null; - } - - $value = $valueType->getValue(); - $throwOnErrorType = $this->reflectionProvider->getConstant(new Name\FullyQualified('JSON_THROW_ON_ERROR'), null)->getValueType(); - if (!$throwOnErrorType instanceof ConstantIntegerType) { - return null; - } - - $throwOnErrorValue = $throwOnErrorType->getValue(); - if (($value & $throwOnErrorValue) !== $throwOnErrorValue) { - return null; - } - - return new ObjectType('JsonException'); - } - - private function isBitwiseOrWithJsonThrowOnError(Expr $expr, Scope $scope): bool - { - if ($expr instanceof ConstFetch) { - $constant = $this->reflectionProvider->resolveConstantName($expr->name, $scope); - if ($constant === 'JSON_THROW_ON_ERROR') { - return true; - } - } - - if (!$expr instanceof BitwiseOr) { - return false; - } - - return $this->isBitwiseOrWithJsonThrowOnError($expr->left, $scope) || - $this->isBitwiseOrWithJsonThrowOnError($expr->right, $scope); - } - + /** @var array */ + private array $argumentPositions = [ + 'json_encode' => 1, + 'json_decode' => 3, + ]; + + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function isFunctionSupported( + FunctionReflection $functionReflection + ): bool { + return $this->reflectionProvider->hasConstant(new Name\FullyQualified('JSON_THROW_ON_ERROR'), null) && in_array( + $functionReflection->getName(), + [ + 'json_encode', + 'json_decode', + ], + true + ); + } + + public function getThrowTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): ?Type { + $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; + if (!isset($functionCall->args[$argumentPosition])) { + return null; + } + + $optionsExpr = $functionCall->args[$argumentPosition]->value; + if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { + return new ObjectType('JsonException'); + } + + $valueType = $scope->getType($optionsExpr); + if (!$valueType instanceof ConstantIntegerType) { + return null; + } + + $value = $valueType->getValue(); + $throwOnErrorType = $this->reflectionProvider->getConstant(new Name\FullyQualified('JSON_THROW_ON_ERROR'), null)->getValueType(); + if (!$throwOnErrorType instanceof ConstantIntegerType) { + return null; + } + + $throwOnErrorValue = $throwOnErrorType->getValue(); + if (($value & $throwOnErrorValue) !== $throwOnErrorValue) { + return null; + } + + return new ObjectType('JsonException'); + } + + private function isBitwiseOrWithJsonThrowOnError(Expr $expr, Scope $scope): bool + { + if ($expr instanceof ConstFetch) { + $constant = $this->reflectionProvider->resolveConstantName($expr->name, $scope); + if ($constant === 'JSON_THROW_ON_ERROR') { + return true; + } + } + + if (!$expr instanceof BitwiseOr) { + return false; + } + + return $this->isBitwiseOrWithJsonThrowOnError($expr->left, $scope) || + $this->isBitwiseOrWithJsonThrowOnError($expr->right, $scope); + } } diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index 1369e545d6..44606a845f 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'mb_convert_encoding'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!isset($functionCall->args[0])) { - return $defaultReturnType; - } - - $argType = $scope->getType($functionCall->args[0]->value); - $isString = (new StringType())->isSuperTypeOf($argType); - $isArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($argType); - $compare = $isString->compareTo($isArray); - if ($compare === $isString) { - return new StringType(); - } elseif ($compare === $isArray) { - return new ArrayType(new IntegerType(), new StringType()); - } - - return $defaultReturnType; - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'mb_convert_encoding'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if (!isset($functionCall->args[0])) { + return $defaultReturnType; + } + + $argType = $scope->getType($functionCall->args[0]->value); + $isString = (new StringType())->isSuperTypeOf($argType); + $isArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($argType); + $compare = $isString->compareTo($isArray); + if ($compare === $isString) { + return new StringType(); + } elseif ($compare === $isArray) { + return new ArrayType(new IntegerType(), new StringType()); + } + + return $defaultReturnType; + } } diff --git a/src/Type/Php/MbFunctionsReturnTypeExtension.php b/src/Type/Php/MbFunctionsReturnTypeExtension.php index 0b98c19e43..47778e8cc8 100644 --- a/src/Type/Php/MbFunctionsReturnTypeExtension.php +++ b/src/Type/Php/MbFunctionsReturnTypeExtension.php @@ -1,4 +1,6 @@ - 1, - 'mb_regex_encoding' => 1, - 'mb_internal_encoding' => 1, - 'mb_encoding_aliases' => 1, - 'mb_strlen' => 2, - 'mb_chr' => 2, - 'mb_ord' => 2, - ]; - - public function __construct() - { - $supportedEncodings = []; - if (function_exists('mb_list_encodings')) { - foreach (mb_list_encodings() as $encoding) { - $aliases = mb_encoding_aliases($encoding); - if ($aliases === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $supportedEncodings = array_merge($supportedEncodings, $aliases, [$encoding]); - } - } - $this->supportedEncodings = array_map('strtoupper', $supportedEncodings); - } + /** @var int[] */ + private array $encodingPositionMap = [ + 'mb_http_output' => 1, + 'mb_regex_encoding' => 1, + 'mb_internal_encoding' => 1, + 'mb_encoding_aliases' => 1, + 'mb_strlen' => 2, + 'mb_chr' => 2, + 'mb_ord' => 2, + ]; - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return array_key_exists($functionReflection->getName(), $this->encodingPositionMap); - } + public function __construct() + { + $supportedEncodings = []; + if (function_exists('mb_list_encodings')) { + foreach (mb_list_encodings() as $encoding) { + $aliases = mb_encoding_aliases($encoding); + if ($aliases === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $supportedEncodings = array_merge($supportedEncodings, $aliases, [$encoding]); + } + } + $this->supportedEncodings = array_map('strtoupper', $supportedEncodings); + } - private function isSupportedEncoding(string $encoding): bool - { - return in_array(strtoupper($encoding), $this->supportedEncodings, true); - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return array_key_exists($functionReflection->getName(), $this->encodingPositionMap); + } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - $positionEncodingParam = $this->encodingPositionMap[$functionReflection->getName()]; + private function isSupportedEncoding(string $encoding): bool + { + return in_array(strtoupper($encoding), $this->supportedEncodings, true); + } - if (count($functionCall->args) < $positionEncodingParam) { - return TypeCombinator::remove($returnType, new BooleanType()); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $positionEncodingParam = $this->encodingPositionMap[$functionReflection->getName()]; - $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[$positionEncodingParam - 1]->value)); - $results = array_unique(array_map(function (ConstantStringType $encoding): bool { - return $this->isSupportedEncoding($encoding->getValue()); - }, $strings)); + if (count($functionCall->args) < $positionEncodingParam) { + return TypeCombinator::remove($returnType, new BooleanType()); + } - if ($returnType->equals(new UnionType([new StringType(), new BooleanType()]))) { - return count($results) === 1 ? new ConstantBooleanType($results[0]) : new BooleanType(); - } + $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[$positionEncodingParam - 1]->value)); + $results = array_unique(array_map(function (ConstantStringType $encoding): bool { + return $this->isSupportedEncoding($encoding->getValue()); + }, $strings)); - if (count($results) === 1) { - return $results[0] - ? TypeCombinator::remove($returnType, new ConstantBooleanType(false)) - : new ConstantBooleanType(false); - } + if ($returnType->equals(new UnionType([new StringType(), new BooleanType()]))) { + return count($results) === 1 ? new ConstantBooleanType($results[0]) : new BooleanType(); + } - return $returnType; - } + if (count($results) === 1) { + return $results[0] + ? TypeCombinator::remove($returnType, new ConstantBooleanType(false)) + : new ConstantBooleanType(false); + } + return $returnType; + } } diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 2a5228d7d9..a97d52af0f 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported( - FunctionReflection $functionReflection, - FuncCall $node, - TypeSpecifierContext $context - ): bool - { - return $functionReflection->getName() === 'method_exists' - && $context->truthy() - && count($node->args) >= 2; - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - $objectType = $scope->getType($node->args[0]->value); - if (!$objectType instanceof ObjectType) { - if ((new StringType())->isSuperTypeOf($objectType)->yes()) { - return new SpecifiedTypes([], []); - } - } + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return $functionReflection->getName() === 'method_exists' + && $context->truthy() + && count($node->args) >= 2; + } - $methodNameType = $scope->getType($node->args[1]->value); - if (!$methodNameType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); - } + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $objectType = $scope->getType($node->args[0]->value); + if (!$objectType instanceof ObjectType) { + if ((new StringType())->isSuperTypeOf($objectType)->yes()) { + return new SpecifiedTypes([], []); + } + } - return $this->typeSpecifier->create( - $node->args[0]->value, - new UnionType([ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasMethodType($methodNameType->getValue()), - ]), - new ClassStringType(), - ]), - $context, - false, - $scope - ); - } + $methodNameType = $scope->getType($node->args[1]->value); + if (!$methodNameType instanceof ConstantStringType) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( + $node->args[0]->value, + new UnionType([ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasMethodType($methodNameType->getValue()), + ]), + new ClassStringType(), + ]), + $context, + false, + $scope + ); + } } diff --git a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php index 15ef4772dd..82941694c9 100644 --- a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'microtime'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 1) { - return new StringType(); - } - - $argType = $scope->getType($functionCall->args[0]->value); - $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); - $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); - $compareTypes = $isTrueType->compareTo($isFalseType); - if ($compareTypes === $isTrueType) { - return new FloatType(); - } - if ($compareTypes === $isFalseType) { - return new StringType(); - } - - if ($argType instanceof MixedType) { - return new BenevolentUnionType([new StringType(), new FloatType()]); - } - - return new UnionType([new StringType(), new FloatType()]); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'microtime'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 1) { + return new StringType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); + $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); + $compareTypes = $isTrueType->compareTo($isFalseType); + if ($compareTypes === $isTrueType) { + return new FloatType(); + } + if ($compareTypes === $isFalseType) { + return new StringType(); + } + + if ($argType instanceof MixedType) { + return new BenevolentUnionType([new StringType(), new FloatType()]); + } + + return new UnionType([new StringType(), new FloatType()]); + } } diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index e710b9a8e3..bc5400151f 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ - '', - 'max' => '', - ]; - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return isset($this->functionNames[$functionReflection->getName()]); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - if (count($functionCall->args) === 1) { - $argType = $scope->getType($functionCall->args[0]->value); - if ($argType->isArray()->yes()) { - $isIterable = $argType->isIterableAtLeastOnce(); - if ($isIterable->no()) { - return new ConstantBooleanType(false); - } - $iterableValueType = $argType->getIterableValueType(); - $argumentTypes = []; - if (!$isIterable->yes()) { - $argumentTypes[] = new ConstantBooleanType(false); - } - if ($iterableValueType instanceof UnionType) { - foreach ($iterableValueType->getTypes() as $innerType) { - $argumentTypes[] = $innerType; - } - } else { - $argumentTypes[] = $iterableValueType; - } - - return $this->processType( - $functionReflection->getName(), - $argumentTypes - ); - } - - return new ErrorType(); - } - - $argumentTypes = []; - foreach ($functionCall->args as $arg) { - $argType = $scope->getType($arg->value); - if ($arg->unpack) { - $iterableValueType = $argType->getIterableValueType(); - if ($iterableValueType instanceof UnionType) { - foreach ($iterableValueType->getTypes() as $innerType) { - $argumentTypes[] = $innerType; - } - } else { - $argumentTypes[] = $iterableValueType; - } - continue; - } - - $argumentTypes[] = $argType; - } - - return $this->processType( - $functionReflection->getName(), - $argumentTypes - ); - } - - /** - * @param string $functionName - * @param \PHPStan\Type\Type[] $types - * @return Type - */ - private function processType( - string $functionName, - array $types - ): Type - { - $resultType = null; - foreach ($types as $type) { - if (!$type instanceof ConstantType) { - return TypeCombinator::union(...$types); - } - - if ($resultType === null) { - $resultType = $type; - continue; - } - - $compareResult = $this->compareTypes($resultType, $type); - if ($functionName === 'min') { - if ($compareResult === $type) { - $resultType = $type; - } - } elseif ($functionName === 'max') { - if ($compareResult === $resultType) { - $resultType = $type; - } - } - } - - if ($resultType === null) { - return new ErrorType(); - } - - return $resultType; - } - - private function compareTypes( - Type $firstType, - Type $secondType - ): ?Type - { - if ( - $firstType instanceof ConstantArrayType - && $secondType instanceof ConstantScalarType - ) { - return $secondType; - } - - if ( - $firstType instanceof ConstantScalarType - && $secondType instanceof ConstantArrayType - ) { - return $firstType; - } - - if ( - $firstType instanceof ConstantArrayType - && $secondType instanceof ConstantArrayType - ) { - if ($secondType->count() < $firstType->count()) { - return $secondType; - } elseif ($firstType->count() < $secondType->count()) { - return $firstType; - } - - foreach ($firstType->getValueTypes() as $i => $firstValueType) { - $secondValueType = $secondType->getValueTypes()[$i]; - $compareResult = $this->compareTypes($firstValueType, $secondValueType); - if ($compareResult === $firstValueType) { - return $firstType; - } - - if ($compareResult === $secondValueType) { - return $secondType; - } - } - - return null; - } - - if ( - $firstType instanceof ConstantScalarType - && $secondType instanceof ConstantScalarType - ) { - if ($secondType->getValue() < $firstType->getValue()) { - return $secondType; - } - - if ($firstType->getValue() < $secondType->getValue()) { - return $firstType; - } - } - - return null; - } - + /** @var string[] */ + private array $functionNames = [ + 'min' => '', + 'max' => '', + ]; + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return isset($this->functionNames[$functionReflection->getName()]); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + if (count($functionCall->args) === 1) { + $argType = $scope->getType($functionCall->args[0]->value); + if ($argType->isArray()->yes()) { + $isIterable = $argType->isIterableAtLeastOnce(); + if ($isIterable->no()) { + return new ConstantBooleanType(false); + } + $iterableValueType = $argType->getIterableValueType(); + $argumentTypes = []; + if (!$isIterable->yes()) { + $argumentTypes[] = new ConstantBooleanType(false); + } + if ($iterableValueType instanceof UnionType) { + foreach ($iterableValueType->getTypes() as $innerType) { + $argumentTypes[] = $innerType; + } + } else { + $argumentTypes[] = $iterableValueType; + } + + return $this->processType( + $functionReflection->getName(), + $argumentTypes + ); + } + + return new ErrorType(); + } + + $argumentTypes = []; + foreach ($functionCall->args as $arg) { + $argType = $scope->getType($arg->value); + if ($arg->unpack) { + $iterableValueType = $argType->getIterableValueType(); + if ($iterableValueType instanceof UnionType) { + foreach ($iterableValueType->getTypes() as $innerType) { + $argumentTypes[] = $innerType; + } + } else { + $argumentTypes[] = $iterableValueType; + } + continue; + } + + $argumentTypes[] = $argType; + } + + return $this->processType( + $functionReflection->getName(), + $argumentTypes + ); + } + + /** + * @param string $functionName + * @param \PHPStan\Type\Type[] $types + * @return Type + */ + private function processType( + string $functionName, + array $types + ): Type { + $resultType = null; + foreach ($types as $type) { + if (!$type instanceof ConstantType) { + return TypeCombinator::union(...$types); + } + + if ($resultType === null) { + $resultType = $type; + continue; + } + + $compareResult = $this->compareTypes($resultType, $type); + if ($functionName === 'min') { + if ($compareResult === $type) { + $resultType = $type; + } + } elseif ($functionName === 'max') { + if ($compareResult === $resultType) { + $resultType = $type; + } + } + } + + if ($resultType === null) { + return new ErrorType(); + } + + return $resultType; + } + + private function compareTypes( + Type $firstType, + Type $secondType + ): ?Type { + if ( + $firstType instanceof ConstantArrayType + && $secondType instanceof ConstantScalarType + ) { + return $secondType; + } + + if ( + $firstType instanceof ConstantScalarType + && $secondType instanceof ConstantArrayType + ) { + return $firstType; + } + + if ( + $firstType instanceof ConstantArrayType + && $secondType instanceof ConstantArrayType + ) { + if ($secondType->count() < $firstType->count()) { + return $secondType; + } elseif ($firstType->count() < $secondType->count()) { + return $firstType; + } + + foreach ($firstType->getValueTypes() as $i => $firstValueType) { + $secondValueType = $secondType->getValueTypes()[$i]; + $compareResult = $this->compareTypes($firstValueType, $secondValueType); + if ($compareResult === $firstValueType) { + return $firstType; + } + + if ($compareResult === $secondValueType) { + return $secondType; + } + } + + return null; + } + + if ( + $firstType instanceof ConstantScalarType + && $secondType instanceof ConstantScalarType + ) { + if ($secondType->getValue() < $firstType->getValue()) { + return $secondType; + } + + if ($firstType->getValue() < $secondType->getValue()) { + return $firstType; + } + } + + return null; + } } diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index 724ed2dca8..321d983511 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -|null */ + private ?array $componentTypesPairedConstants = null; - /** @var array|null */ - private ?array $componentTypesPairedConstants = null; - - /** @var array|null */ - private ?array $componentTypesPairedStrings = null; - - private ?Type $allComponentsTogetherType = null; - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'parse_url'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle( - $functionReflection->getVariants() - )->getReturnType(); - - if (count($functionCall->args) < 1) { - return $defaultReturnType; - } - - $this->cacheReturnTypes(); - - $urlType = $scope->getType($functionCall->args[0]->value); - if (count($functionCall->args) > 1) { - $componentType = $scope->getType($functionCall->args[1]->value); - - if (!$componentType instanceof ConstantType) { - return $this->createAllComponentsReturnType(); - } - - $componentType = $componentType->toInteger(); - - if (!$componentType instanceof ConstantIntegerType) { - throw new \PHPStan\ShouldNotHappenException(); - } - } else { - $componentType = new ConstantIntegerType(-1); - } - - if ($urlType instanceof ConstantStringType) { - try { - $result = @parse_url($urlType->getValue(), $componentType->getValue()); - } catch (\ValueError $e) { - return new ConstantBooleanType(false); - } - - return $scope->getTypeFromValue($result); - } - - if ($componentType->getValue() === -1) { - return $this->createAllComponentsReturnType(); - } - - return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false); - } - - private function createAllComponentsReturnType(): Type - { - if ($this->allComponentsTogetherType === null) { - $returnTypes = [ - new ConstantBooleanType(false), - ]; - - $builder = ConstantArrayTypeBuilder::createEmpty(); - - if ($this->componentTypesPairedStrings === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { - $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); - } - - $returnTypes[] = $builder->getArray(); - - $this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes); - } - - return $this->allComponentsTogetherType; - } - - private function cacheReturnTypes(): void - { - if ($this->componentTypesPairedConstants !== null) { - return; - } - - $string = new StringType(); - $integer = new IntegerType(); - $false = new ConstantBooleanType(false); - $null = new NullType(); - - $stringOrFalseOrNull = TypeCombinator::union($string, $false, $null); - $integerOrFalseOrNull = TypeCombinator::union($integer, $false, $null); - - $this->componentTypesPairedConstants = [ - PHP_URL_SCHEME => $stringOrFalseOrNull, - PHP_URL_HOST => $stringOrFalseOrNull, - PHP_URL_PORT => $integerOrFalseOrNull, - PHP_URL_USER => $stringOrFalseOrNull, - PHP_URL_PASS => $stringOrFalseOrNull, - PHP_URL_PATH => $stringOrFalseOrNull, - PHP_URL_QUERY => $stringOrFalseOrNull, - PHP_URL_FRAGMENT => $stringOrFalseOrNull, - ]; - - $this->componentTypesPairedStrings = [ - 'scheme' => $string, - 'host' => $string, - 'port' => $integer, - 'user' => $string, - 'pass' => $string, - 'path' => $string, - 'query' => $string, - 'fragment' => $string, - ]; - } - + /** @var array|null */ + private ?array $componentTypesPairedStrings = null; + + private ?Type $allComponentsTogetherType = null; + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'parse_url'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle( + $functionReflection->getVariants() + )->getReturnType(); + + if (count($functionCall->args) < 1) { + return $defaultReturnType; + } + + $this->cacheReturnTypes(); + + $urlType = $scope->getType($functionCall->args[0]->value); + if (count($functionCall->args) > 1) { + $componentType = $scope->getType($functionCall->args[1]->value); + + if (!$componentType instanceof ConstantType) { + return $this->createAllComponentsReturnType(); + } + + $componentType = $componentType->toInteger(); + + if (!$componentType instanceof ConstantIntegerType) { + throw new \PHPStan\ShouldNotHappenException(); + } + } else { + $componentType = new ConstantIntegerType(-1); + } + + if ($urlType instanceof ConstantStringType) { + try { + $result = @parse_url($urlType->getValue(), $componentType->getValue()); + } catch (\ValueError $e) { + return new ConstantBooleanType(false); + } + + return $scope->getTypeFromValue($result); + } + + if ($componentType->getValue() === -1) { + return $this->createAllComponentsReturnType(); + } + + return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false); + } + + private function createAllComponentsReturnType(): Type + { + if ($this->allComponentsTogetherType === null) { + $returnTypes = [ + new ConstantBooleanType(false), + ]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + + if ($this->componentTypesPairedStrings === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { + $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); + } + + $returnTypes[] = $builder->getArray(); + + $this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes); + } + + return $this->allComponentsTogetherType; + } + + private function cacheReturnTypes(): void + { + if ($this->componentTypesPairedConstants !== null) { + return; + } + + $string = new StringType(); + $integer = new IntegerType(); + $false = new ConstantBooleanType(false); + $null = new NullType(); + + $stringOrFalseOrNull = TypeCombinator::union($string, $false, $null); + $integerOrFalseOrNull = TypeCombinator::union($integer, $false, $null); + + $this->componentTypesPairedConstants = [ + PHP_URL_SCHEME => $stringOrFalseOrNull, + PHP_URL_HOST => $stringOrFalseOrNull, + PHP_URL_PORT => $integerOrFalseOrNull, + PHP_URL_USER => $stringOrFalseOrNull, + PHP_URL_PASS => $stringOrFalseOrNull, + PHP_URL_PATH => $stringOrFalseOrNull, + PHP_URL_QUERY => $stringOrFalseOrNull, + PHP_URL_FRAGMENT => $stringOrFalseOrNull, + ]; + + $this->componentTypesPairedStrings = [ + 'scheme' => $string, + 'host' => $string, + 'port' => $integer, + 'user' => $string, + 'pass' => $string, + 'path' => $string, + 'query' => $string, + 'fragment' => $string, + ]; + } } diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index dbb09f1a8a..86485b0a50 100644 --- a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'pathinfo'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - \PhpParser\Node\Expr\FuncCall $functionCall, - Scope $scope - ): Type - { - $argsCount = count($functionCall->args); - if ($argsCount === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } elseif ($argsCount === 1) { - $stringType = new StringType(); - - $builder = ConstantArrayTypeBuilder::createFromConstantArray( - new ConstantArrayType( - [new ConstantStringType('dirname'), new ConstantStringType('basename'), new ConstantStringType('filename')], - [$stringType, $stringType, $stringType] - ) - ); - $builder->setOffsetValueType(new ConstantStringType('extension'), $stringType, true); - - return $builder->getArray(); - } - - return new StringType(); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'pathinfo'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + \PhpParser\Node\Expr\FuncCall $functionCall, + Scope $scope + ): Type { + $argsCount = count($functionCall->args); + if ($argsCount === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } elseif ($argsCount === 1) { + $stringType = new StringType(); + + $builder = ConstantArrayTypeBuilder::createFromConstantArray( + new ConstantArrayType( + [new ConstantStringType('dirname'), new ConstantStringType('basename'), new ConstantStringType('filename')], + [$stringType, $stringType, $stringType] + ) + ); + $builder->setOffsetValueType(new ConstantStringType('extension'), $stringType, true); + + return $builder->getArray(); + } + + return new StringType(); + } } diff --git a/src/Type/Php/PowFunctionReturnTypeExtension.php b/src/Type/Php/PowFunctionReturnTypeExtension.php index 9e84218270..b854afd165 100644 --- a/src/Type/Php/PowFunctionReturnTypeExtension.php +++ b/src/Type/Php/PowFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'pow'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - ]); - if (count($functionCall->args) < 2) { - return $defaultReturnType; - } - - $firstArgType = $scope->getType($functionCall->args[0]->value); - $secondArgType = $scope->getType($functionCall->args[1]->value); - if ($firstArgType instanceof MixedType || $secondArgType instanceof MixedType) { - return $defaultReturnType; - } - - $object = new ObjectWithoutClassType(); - if ( - !$object->isSuperTypeOf($firstArgType)->no() - || !$object->isSuperTypeOf($secondArgType)->no() - ) { - return TypeCombinator::union($firstArgType, $secondArgType); - } - - return $defaultReturnType; - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'pow'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + if (count($functionCall->args) < 2) { + return $defaultReturnType; + } + + $firstArgType = $scope->getType($functionCall->args[0]->value); + $secondArgType = $scope->getType($functionCall->args[1]->value); + if ($firstArgType instanceof MixedType || $secondArgType instanceof MixedType) { + return $defaultReturnType; + } + + $object = new ObjectWithoutClassType(); + if ( + !$object->isSuperTypeOf($firstArgType)->no() + || !$object->isSuperTypeOf($secondArgType)->no() + ) { + return TypeCombinator::union($firstArgType, $secondArgType); + } + + return $defaultReturnType; + } } diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index 912363f117..a274f7fe29 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return strtolower($functionReflection->getName()) === 'preg_split'; - } + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return strtolower($functionReflection->getName()) === 'preg_split'; + } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $flagsArg = $functionCall->args[3] ?? null; - if ($this->hasFlag($this->getConstant('PREG_SPLIT_OFFSET_CAPTURE'), $flagsArg, $scope)) { - $type = new ArrayType( - new IntegerType(), - new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), new IntegerType()]) - ); - return TypeCombinator::union($type, new ConstantBooleanType(false)); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $flagsArg = $functionCall->args[3] ?? null; - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + if ($this->hasFlag($this->getConstant('PREG_SPLIT_OFFSET_CAPTURE'), $flagsArg, $scope)) { + $type = new ArrayType( + new IntegerType(), + new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), new IntegerType()]) + ); + return TypeCombinator::union($type, new ConstantBooleanType(false)); + } + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - private function hasFlag(int $flag, ?Arg $expression, Scope $scope): bool - { - if ($expression === null) { - return false; - } - $type = $scope->getType($expression->value); - return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag; - } + private function hasFlag(int $flag, ?Arg $expression, Scope $scope): bool + { + if ($expression === null) { + return false; + } + $type = $scope->getType($expression->value); + return $type instanceof ConstantIntegerType && ($type->getValue() & $flag) === $flag; + } - private function getConstant(string $constantName): int - { - $constant = $this->reflectionProvider->getConstant(new Name($constantName), null); - $valueType = $constant->getValueType(); - if (!$valueType instanceof ConstantIntegerType) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); - } - return $valueType->getValue(); - } + private function getConstant(string $constantName): int + { + $constant = $this->reflectionProvider->getConstant(new Name($constantName), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); + } + return $valueType->getValue(); + } } diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 7ff323cfa2..dedd6f8e63 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -propertyReflectionFinder = $propertyReflectionFinder; - } + private TypeSpecifier $typeSpecifier; - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + public function __construct(PropertyReflectionFinder $propertyReflectionFinder) + { + $this->propertyReflectionFinder = $propertyReflectionFinder; + } - public function isFunctionSupported( - FunctionReflection $functionReflection, - FuncCall $node, - TypeSpecifierContext $context - ): bool - { - return $functionReflection->getName() === 'property_exists' - && $context->truthy() - && count($node->args) >= 2; - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function specifyTypes( - FunctionReflection $functionReflection, - FuncCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - $propertyNameType = $scope->getType($node->args[1]->value); - if (!$propertyNameType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); - } + public function isFunctionSupported( + FunctionReflection $functionReflection, + FuncCall $node, + TypeSpecifierContext $context + ): bool { + return $functionReflection->getName() === 'property_exists' + && $context->truthy() + && count($node->args) >= 2; + } - $objectType = $scope->getType($node->args[0]->value); - if ($objectType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); - } elseif ((new ObjectWithoutClassType())->isSuperTypeOf($objectType)->yes()) { - $propertyNode = new PropertyFetch( - $node->args[0]->value, - new Identifier($propertyNameType->getValue()) - ); - } else { - return new SpecifiedTypes([], []); - } + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + $propertyNameType = $scope->getType($node->args[1]->value); + if (!$propertyNameType instanceof ConstantStringType) { + return new SpecifiedTypes([], []); + } - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope); - if ($propertyReflection !== null) { - if (!$propertyReflection->isNative()) { - return new SpecifiedTypes([], []); - } - } + $objectType = $scope->getType($node->args[0]->value); + if ($objectType instanceof ConstantStringType) { + return new SpecifiedTypes([], []); + } elseif ((new ObjectWithoutClassType())->isSuperTypeOf($objectType)->yes()) { + $propertyNode = new PropertyFetch( + $node->args[0]->value, + new Identifier($propertyNameType->getValue()) + ); + } else { + return new SpecifiedTypes([], []); + } - return $this->typeSpecifier->create( - $node->args[0]->value, - new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType($propertyNameType->getValue()), - ]), - $context, - false, - $scope - ); - } + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope); + if ($propertyReflection !== null) { + if (!$propertyReflection->isNative()) { + return new SpecifiedTypes([], []); + } + } + return $this->typeSpecifier->create( + $node->args[0]->value, + new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType($propertyNameType->getValue()), + ]), + $context, + false, + $scope + ); + } } diff --git a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php index 21faac5e72..a95c666fc3 100644 --- a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php +++ b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'random_int'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'random_int'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $minType = $scope->getType($functionCall->args[0]->value)->toInteger(); - $maxType = $scope->getType($functionCall->args[1]->value)->toInteger(); + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - return $this->createRange($minType, $maxType); - } + $minType = $scope->getType($functionCall->args[0]->value)->toInteger(); + $maxType = $scope->getType($functionCall->args[1]->value)->toInteger(); - private function createRange(Type $minType, Type $maxType): Type - { - $minValues = array_map( - static function (Type $type): ?int { - if ($type instanceof IntegerRangeType) { - return $type->getMin(); - } - if ($type instanceof ConstantIntegerType) { - return $type->getValue(); - } - return null; - }, - $minType instanceof UnionType ? $minType->getTypes() : [$minType] - ); + return $this->createRange($minType, $maxType); + } - $maxValues = array_map( - static function (Type $type): ?int { - if ($type instanceof IntegerRangeType) { - return $type->getMax(); - } - if ($type instanceof ConstantIntegerType) { - return $type->getValue(); - } - return null; - }, - $maxType instanceof UnionType ? $maxType->getTypes() : [$maxType] - ); + private function createRange(Type $minType, Type $maxType): Type + { + $minValues = array_map( + static function (Type $type): ?int { + if ($type instanceof IntegerRangeType) { + return $type->getMin(); + } + if ($type instanceof ConstantIntegerType) { + return $type->getValue(); + } + return null; + }, + $minType instanceof UnionType ? $minType->getTypes() : [$minType] + ); - assert(count($minValues) > 0); - assert(count($maxValues) > 0); + $maxValues = array_map( + static function (Type $type): ?int { + if ($type instanceof IntegerRangeType) { + return $type->getMax(); + } + if ($type instanceof ConstantIntegerType) { + return $type->getValue(); + } + return null; + }, + $maxType instanceof UnionType ? $maxType->getTypes() : [$maxType] + ); - return IntegerRangeType::fromInterval( - in_array(null, $minValues, true) ? null : min($minValues), - in_array(null, $maxValues, true) ? null : max($maxValues) - ); - } + assert(count($minValues) > 0); + assert(count($maxValues) > 0); + return IntegerRangeType::fromInterval( + in_array(null, $minValues, true) ? null : min($minValues), + in_array(null, $maxValues, true) ? null : max($maxValues) + ); + } } diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 1fadd6b2df..7742b9e545 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'range'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $startType = $scope->getType($functionCall->args[0]->value); - $endType = $scope->getType($functionCall->args[1]->value); - $stepType = count($functionCall->args) >= 3 ? $scope->getType($functionCall->args[2]->value) : new ConstantIntegerType(1); - - $constantReturnTypes = []; - - $startConstants = TypeUtils::getConstantScalars($startType); - foreach ($startConstants as $startConstant) { - if (!$startConstant instanceof ConstantIntegerType && !$startConstant instanceof ConstantFloatType && !$startConstant instanceof ConstantStringType) { - continue; - } - - $endConstants = TypeUtils::getConstantScalars($endType); - foreach ($endConstants as $endConstant) { - if (!$endConstant instanceof ConstantIntegerType && !$endConstant instanceof ConstantFloatType && !$endConstant instanceof ConstantStringType) { - continue; - } - - $stepConstants = TypeUtils::getConstantScalars($stepType); - foreach ($stepConstants as $stepConstant) { - if (!$stepConstant instanceof ConstantIntegerType && !$stepConstant instanceof ConstantFloatType) { - continue; - } - - $rangeValues = range($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue()); - if (count($rangeValues) > self::RANGE_LENGTH_THRESHOLD) { - return new IntersectionType([ - new ArrayType( - new IntegerType(), - TypeCombinator::union( - $startConstant->generalize(), - $endConstant->generalize() - ) - ), - new NonEmptyArrayType(), - ]); - } - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($rangeValues as $value) { - $arrayBuilder->setOffsetValueType(null, $scope->getTypeFromValue($value)); - } - - $constantReturnTypes[] = $arrayBuilder->getArray(); - } - } - } - - if (count($constantReturnTypes) > 0) { - return TypeCombinator::union(...$constantReturnTypes); - } - - $argType = TypeCombinator::union($startType, $endType); - $isInteger = (new IntegerType())->isSuperTypeOf($argType)->yes(); - $isStepInteger = (new IntegerType())->isSuperTypeOf($stepType)->yes(); - - if ($isInteger && $isStepInteger) { - return new ArrayType(new IntegerType(), new IntegerType()); - } - - $isFloat = (new FloatType())->isSuperTypeOf($argType)->yes(); - if ($isFloat) { - return new ArrayType(new IntegerType(), new FloatType()); - } - - $numberType = new UnionType([new IntegerType(), new FloatType()]); - $isNumber = $numberType->isSuperTypeOf($argType)->yes(); - if ($isNumber) { - return new ArrayType(new IntegerType(), $numberType); - } - - $isString = (new StringType())->isSuperTypeOf($argType)->yes(); - if ($isString) { - return new ArrayType(new IntegerType(), new StringType()); - } - - return new ArrayType(new IntegerType(), new BenevolentUnionType([new IntegerType(), new FloatType(), new StringType()])); - } - + private const RANGE_LENGTH_THRESHOLD = 50; + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'range'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $startType = $scope->getType($functionCall->args[0]->value); + $endType = $scope->getType($functionCall->args[1]->value); + $stepType = count($functionCall->args) >= 3 ? $scope->getType($functionCall->args[2]->value) : new ConstantIntegerType(1); + + $constantReturnTypes = []; + + $startConstants = TypeUtils::getConstantScalars($startType); + foreach ($startConstants as $startConstant) { + if (!$startConstant instanceof ConstantIntegerType && !$startConstant instanceof ConstantFloatType && !$startConstant instanceof ConstantStringType) { + continue; + } + + $endConstants = TypeUtils::getConstantScalars($endType); + foreach ($endConstants as $endConstant) { + if (!$endConstant instanceof ConstantIntegerType && !$endConstant instanceof ConstantFloatType && !$endConstant instanceof ConstantStringType) { + continue; + } + + $stepConstants = TypeUtils::getConstantScalars($stepType); + foreach ($stepConstants as $stepConstant) { + if (!$stepConstant instanceof ConstantIntegerType && !$stepConstant instanceof ConstantFloatType) { + continue; + } + + $rangeValues = range($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue()); + if (count($rangeValues) > self::RANGE_LENGTH_THRESHOLD) { + return new IntersectionType([ + new ArrayType( + new IntegerType(), + TypeCombinator::union( + $startConstant->generalize(), + $endConstant->generalize() + ) + ), + new NonEmptyArrayType(), + ]); + } + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($rangeValues as $value) { + $arrayBuilder->setOffsetValueType(null, $scope->getTypeFromValue($value)); + } + + $constantReturnTypes[] = $arrayBuilder->getArray(); + } + } + } + + if (count($constantReturnTypes) > 0) { + return TypeCombinator::union(...$constantReturnTypes); + } + + $argType = TypeCombinator::union($startType, $endType); + $isInteger = (new IntegerType())->isSuperTypeOf($argType)->yes(); + $isStepInteger = (new IntegerType())->isSuperTypeOf($stepType)->yes(); + + if ($isInteger && $isStepInteger) { + return new ArrayType(new IntegerType(), new IntegerType()); + } + + $isFloat = (new FloatType())->isSuperTypeOf($argType)->yes(); + if ($isFloat) { + return new ArrayType(new IntegerType(), new FloatType()); + } + + $numberType = new UnionType([new IntegerType(), new FloatType()]); + $isNumber = $numberType->isSuperTypeOf($argType)->yes(); + if ($isNumber) { + return new ArrayType(new IntegerType(), $numberType); + } + + $isString = (new StringType())->isSuperTypeOf($argType)->yes(); + if ($isString) { + return new ArrayType(new IntegerType(), new StringType()); + } + + return new ArrayType(new IntegerType(), new BenevolentUnionType([new IntegerType(), new FloatType(), new StringType()])); + } } diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index 54f9eacab4..47488cb2b5 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -typeSpecifier = $typeSpecifier; - } - - public function getClass(): string - { - return \ReflectionClass::class; - } - - public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool - { - return $methodReflection->getName() === 'isSubclassOf' - && isset($node->args[0]) - && $context->true(); - } - - public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - $valueType = $scope->getType($node->args[0]->value); - if (!$valueType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); - } - - return $this->typeSpecifier->create( - $node->var, - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType($valueType->getValue()), - ]), - $context, - false, - $scope - ); - } - + private TypeSpecifier $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return \ReflectionClass::class; + } + + public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool + { + return $methodReflection->getName() === 'isSubclassOf' + && isset($node->args[0]) + && $context->true(); + } + + public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $valueType = $scope->getType($node->args[0]->value); + if (!$valueType instanceof ConstantStringType) { + return new SpecifiedTypes([], []); + } + + return $this->typeSpecifier->create( + $node->var, + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType($valueType->getValue()), + ]), + $context, + false, + $scope + ); + } } diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 752aa8560f..6f02daa290 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ - */ + private array $functions = [ + 'preg_replace' => 2, + 'preg_replace_callback' => 2, + 'preg_replace_callback_array' => 1, + 'str_replace' => 2, + 'str_ireplace' => 2, + 'substr_replace' => 0, + ]; - /** @var array */ - private array $functions = [ - 'preg_replace' => 2, - 'preg_replace_callback' => 2, - 'preg_replace_callback_array' => 1, - 'str_replace' => 2, - 'str_ireplace' => 2, - 'substr_replace' => 0, - ]; - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return array_key_exists($functionReflection->getName(), $this->functions); - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $type = $this->getPreliminarilyResolvedTypeFromFunctionCall($functionReflection, $functionCall, $scope); + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return array_key_exists($functionReflection->getName(), $this->functions); + } - $possibleTypes = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $type = $this->getPreliminarilyResolvedTypeFromFunctionCall($functionReflection, $functionCall, $scope); - if (TypeCombinator::containsNull($possibleTypes)) { - $type = TypeCombinator::addNull($type); - } + $possibleTypes = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - return $type; - } + if (TypeCombinator::containsNull($possibleTypes)) { + $type = TypeCombinator::addNull($type); + } - private function getPreliminarilyResolvedTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $argumentPosition = $this->functions[$functionReflection->getName()]; - if (count($functionCall->args) <= $argumentPosition) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + return $type; + } - $subjectArgumentType = $scope->getType($functionCall->args[$argumentPosition]->value); - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if ($subjectArgumentType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } - $stringType = new StringType(); - $arrayType = new ArrayType(new MixedType(), new MixedType()); + private function getPreliminarilyResolvedTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $argumentPosition = $this->functions[$functionReflection->getName()]; + if (count($functionCall->args) <= $argumentPosition) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } - $isStringSuperType = $stringType->isSuperTypeOf($subjectArgumentType); - $isArraySuperType = $arrayType->isSuperTypeOf($subjectArgumentType); - $compareSuperTypes = $isStringSuperType->compareTo($isArraySuperType); - if ($compareSuperTypes === $isStringSuperType) { - return $stringType; - } elseif ($compareSuperTypes === $isArraySuperType) { - if ($subjectArgumentType instanceof ArrayType) { - return $subjectArgumentType->generalizeValues(); - } - return $subjectArgumentType; - } + $subjectArgumentType = $scope->getType($functionCall->args[$argumentPosition]->value); + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if ($subjectArgumentType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + $stringType = new StringType(); + $arrayType = new ArrayType(new MixedType(), new MixedType()); - return $defaultReturnType; - } + $isStringSuperType = $stringType->isSuperTypeOf($subjectArgumentType); + $isArraySuperType = $arrayType->isSuperTypeOf($subjectArgumentType); + $compareSuperTypes = $isStringSuperType->compareTo($isArraySuperType); + if ($compareSuperTypes === $isStringSuperType) { + return $stringType; + } elseif ($compareSuperTypes === $isArraySuperType) { + if ($subjectArgumentType instanceof ArrayType) { + return $subjectArgumentType->generalizeValues(); + } + return $subjectArgumentType; + } + return $defaultReturnType; + } } diff --git a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php index 562ec373be..2183a0f3f7 100644 --- a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'asXML'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - if (count($methodCall->args) === 1) { - return new BooleanType(); - } - return new UnionType([new StringType(), new ConstantBooleanType(false)]); - } + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'asXML'; + } + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if (count($methodCall->args) === 1) { + return new BooleanType(); + } + return new UnionType([new StringType(), new ConstantBooleanType(false)]); + } } diff --git a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php index 9646898c00..b0a861cb1c 100644 --- a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php +++ b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php @@ -1,4 +1,6 @@ -getName() === 'SimpleXMLElement' || $classReflection->isSubclassOf('SimpleXMLElement'); + } - public function hasProperty(ClassReflection $classReflection, string $propertyName): bool - { - return $classReflection->getName() === 'SimpleXMLElement' || $classReflection->isSubclassOf('SimpleXMLElement'); - } - - - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection - { - return new SimpleXMLElementProperty($classReflection, new ObjectType($classReflection->getName())); - } + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + return new SimpleXMLElementProperty($classReflection, new ObjectType($classReflection->getName())); + } } diff --git a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php index c98f5ab7f3..5d47a5592c 100644 --- a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'xpath'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - if (!isset($methodCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($methodCall->args[0]->value); - - $xmlElement = new \SimpleXMLElement(''); // @phpstan-ignore-line - - foreach (TypeUtils::getConstantStrings($argType) as $constantString) { - $result = @$xmlElement->xpath($constantString->getValue()); - if ($result === false) { - // We can't be sure since it's maybe a namespaced xpath - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $argType = TypeCombinator::remove($argType, $constantString); - } - - if (!$argType instanceof NeverType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ArrayType(new MixedType(), $scope->getType($methodCall->var)); - } - + public function getClass(): string + { + return \SimpleXMLElement::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'xpath'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if (!isset($methodCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($methodCall->args[0]->value); + + $xmlElement = new \SimpleXMLElement(''); // @phpstan-ignore-line + + foreach (TypeUtils::getConstantStrings($argType) as $constantString) { + $result = @$xmlElement->xpath($constantString->getValue()); + if ($result === false) { + // We can't be sure since it's maybe a namespaced xpath + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $argType = TypeCombinator::remove($argType, $constantString); + } + + if (!$argType instanceof NeverType) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ArrayType(new MixedType(), $scope->getType($methodCall->var)); + } } diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 62016da18f..6a70339d11 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'sprintf'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - $values = []; - $returnType = new StringType(); - foreach ($functionCall->args as $arg) { - $argType = $scope->getType($arg->value); - if (!$argType instanceof ConstantScalarType) { - return $returnType; - } - - $values[] = $argType->getValue(); - } - - if (count($values) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $format = array_shift($values); - if (!is_string($format)) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - try { - $value = @sprintf($format, ...$values); - } catch (\Throwable $e) { - return $returnType; - } - - return $scope->getTypeFromValue($value); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'sprintf'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + $values = []; + $returnType = new StringType(); + foreach ($functionCall->args as $arg) { + $argType = $scope->getType($arg->value); + if (!$argType instanceof ConstantScalarType) { + return $returnType; + } + + $values[] = $argType->getValue(); + } + + if (count($values) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $format = array_shift($values); + if (!is_string($format)) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + try { + $value = @sprintf($format, ...$values); + } catch (\Throwable $e) { + return $returnType; + } + + return $scope->getTypeFromValue($value); + } } diff --git a/src/Type/Php/StatDynamicReturnTypeExtension.php b/src/Type/Php/StatDynamicReturnTypeExtension.php index ccc97ff5c9..ce50a5d7bf 100644 --- a/src/Type/Php/StatDynamicReturnTypeExtension.php +++ b/src/Type/Php/StatDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), ['stat', 'lstat', 'fstat', 'ssh2_sftp_stat'], true); + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return in_array($functionReflection->getName(), ['stat', 'lstat', 'fstat', 'ssh2_sftp_stat'], true); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - return $this->getReturnType(); - } - - public function getClass(): string - { - return \SplFileObject::class; - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + return $this->getReturnType(); + } - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'fstat'; - } + public function getClass(): string + { + return \SplFileObject::class; + } - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return $this->getReturnType(); - } + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'fstat'; + } - private function getReturnType(): Type - { - $valueType = new IntegerType(); - $builder = ConstantArrayTypeBuilder::createEmpty(); - $keys = [ - 'dev', - 'ino', - 'mode', - 'nlink', - 'uid', - 'gid', - 'rdev', - 'size', - 'atime', - 'mtime', - 'ctime', - 'blksize', - 'blocks', - ]; + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return $this->getReturnType(); + } - foreach ($keys as $key) { - $builder->setOffsetValueType(null, $valueType); - } + private function getReturnType(): Type + { + $valueType = new IntegerType(); + $builder = ConstantArrayTypeBuilder::createEmpty(); + $keys = [ + 'dev', + 'ino', + 'mode', + 'nlink', + 'uid', + 'gid', + 'rdev', + 'size', + 'atime', + 'mtime', + 'ctime', + 'blksize', + 'blocks', + ]; - foreach ($keys as $key) { - $builder->setOffsetValueType(new ConstantStringType($key), $valueType); - } + foreach ($keys as $key) { + $builder->setOffsetValueType(null, $valueType); + } - return TypeCombinator::union($builder->getArray(), new ConstantBooleanType(false)); - } + foreach ($keys as $key) { + $builder->setOffsetValueType(new ConstantStringType($key), $valueType); + } + return TypeCombinator::union($builder->getArray(), new ConstantBooleanType(false)); + } } diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 0157f68db2..9e5e22b468 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -supportedEncodings = array_map('strtoupper', $supportedEncodings); - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return in_array($functionReflection->getName(), ['str_split', 'mb_str_split'], true); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - - if (count($functionCall->args) < 1) { - return $defaultReturnType; - } - - if (count($functionCall->args) >= 2) { - $splitLengthType = $scope->getType($functionCall->args[1]->value); - if ($splitLengthType instanceof ConstantIntegerType) { - $splitLength = $splitLengthType->getValue(); - if ($splitLength < 1) { - return new ConstantBooleanType(false); - } - } - } else { - $splitLength = 1; - } - - if ($functionReflection->getName() === 'mb_str_split') { - if (count($functionCall->args) >= 3) { - $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); - $values = array_unique(array_map(static function (ConstantStringType $encoding): string { - return $encoding->getValue(); - }, $strings)); - - if (count($values) !== 1) { - return $defaultReturnType; - } - - $encoding = $values[0]; - if (!$this->isSupportedEncoding($encoding)) { - return new ConstantBooleanType(false); - } - } else { - $encoding = mb_internal_encoding(); - } - } - - if (!isset($splitLength)) { - return $defaultReturnType; - } - - $stringType = $scope->getType($functionCall->args[0]->value); - if (!$stringType instanceof ConstantStringType) { - return new ArrayType(new IntegerType(), new StringType()); - } - $stringValue = $stringType->getValue(); - - $items = isset($encoding) - ? mb_str_split($stringValue, $splitLength, $encoding) - : str_split($stringValue, $splitLength); - if (!is_array($items)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return self::createConstantArrayFrom($items, $scope); - } - - private function isSupportedEncoding(string $encoding): bool - { - return in_array(strtoupper($encoding), $this->supportedEncodings, true); - } - - /** - * @param string[] $constantArray - * @param \PHPStan\Analyser\Scope $scope - * @return \PHPStan\Type\Constant\ConstantArrayType - */ - private static function createConstantArrayFrom(array $constantArray, Scope $scope): ConstantArrayType - { - $keyTypes = []; - $valueTypes = []; - $isList = true; - $i = 0; - - foreach ($constantArray as $key => $value) { - $keyType = $scope->getTypeFromValue($key); - if (!$keyType instanceof ConstantIntegerType) { - throw new \PHPStan\ShouldNotHappenException(); - } - $keyTypes[] = $keyType; - - $valueTypes[] = $scope->getTypeFromValue($value); - - $isList = $isList && $key === $i; - $i++; - } - - return new ConstantArrayType($keyTypes, $valueTypes, $isList ? $i : 0); - } - + /** @var string[] */ + private array $supportedEncodings; + + public function __construct() + { + $supportedEncodings = []; + if (function_exists('mb_list_encodings')) { + foreach (mb_list_encodings() as $encoding) { + $aliases = mb_encoding_aliases($encoding); + if ($aliases === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $supportedEncodings = array_merge($supportedEncodings, $aliases, [$encoding]); + } + } + $this->supportedEncodings = array_map('strtoupper', $supportedEncodings); + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array($functionReflection->getName(), ['str_split', 'mb_str_split'], true); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + + if (count($functionCall->args) < 1) { + return $defaultReturnType; + } + + if (count($functionCall->args) >= 2) { + $splitLengthType = $scope->getType($functionCall->args[1]->value); + if ($splitLengthType instanceof ConstantIntegerType) { + $splitLength = $splitLengthType->getValue(); + if ($splitLength < 1) { + return new ConstantBooleanType(false); + } + } + } else { + $splitLength = 1; + } + + if ($functionReflection->getName() === 'mb_str_split') { + if (count($functionCall->args) >= 3) { + $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); + $values = array_unique(array_map(static function (ConstantStringType $encoding): string { + return $encoding->getValue(); + }, $strings)); + + if (count($values) !== 1) { + return $defaultReturnType; + } + + $encoding = $values[0]; + if (!$this->isSupportedEncoding($encoding)) { + return new ConstantBooleanType(false); + } + } else { + $encoding = mb_internal_encoding(); + } + } + + if (!isset($splitLength)) { + return $defaultReturnType; + } + + $stringType = $scope->getType($functionCall->args[0]->value); + if (!$stringType instanceof ConstantStringType) { + return new ArrayType(new IntegerType(), new StringType()); + } + $stringValue = $stringType->getValue(); + + $items = isset($encoding) + ? mb_str_split($stringValue, $splitLength, $encoding) + : str_split($stringValue, $splitLength); + if (!is_array($items)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return self::createConstantArrayFrom($items, $scope); + } + + private function isSupportedEncoding(string $encoding): bool + { + return in_array(strtoupper($encoding), $this->supportedEncodings, true); + } + + /** + * @param string[] $constantArray + * @param \PHPStan\Analyser\Scope $scope + * @return \PHPStan\Type\Constant\ConstantArrayType + */ + private static function createConstantArrayFrom(array $constantArray, Scope $scope): ConstantArrayType + { + $keyTypes = []; + $valueTypes = []; + $isList = true; + $i = 0; + + foreach ($constantArray as $key => $value) { + $keyType = $scope->getTypeFromValue($key); + if (!$keyType instanceof ConstantIntegerType) { + throw new \PHPStan\ShouldNotHappenException(); + } + $keyTypes[] = $keyType; + + $valueTypes[] = $scope->getTypeFromValue($value); + + $isList = $isList && $key === $i; + $i++; + } + + return new ConstantArrayType($keyTypes, $valueTypes, $isList ? $i : 0); + } } diff --git a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php index 6e53059859..03e5167340 100644 --- a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'str_word_count'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - \PhpParser\Node\Expr\FuncCall $functionCall, - Scope $scope - ): Type - { - $argsCount = count($functionCall->args); - if ($argsCount === 1) { - return new IntegerType(); - } elseif ($argsCount === 2 || $argsCount === 3) { - $formatType = $scope->getType($functionCall->args[1]->value); - if ($formatType instanceof ConstantIntegerType) { - $val = $formatType->getValue(); - if ($val === 0) { - // return word count - return new IntegerType(); - } elseif ($val === 1 || $val === 2) { - // return [word] or [offset => word] - return new ArrayType(new IntegerType(), new StringType()); - } - - // return false, invalid format value specified - return new ConstantBooleanType(false); - } - - // Could be invalid format type as well, but parameter type checks will catch that. - - return new UnionType([ - new IntegerType(), - new ArrayType(new IntegerType(), new StringType()), - new ConstantBooleanType(false), - ]); - } - - // else fatal error; too many or too few arguments - return new ErrorType(); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'str_word_count'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + \PhpParser\Node\Expr\FuncCall $functionCall, + Scope $scope + ): Type { + $argsCount = count($functionCall->args); + if ($argsCount === 1) { + return new IntegerType(); + } elseif ($argsCount === 2 || $argsCount === 3) { + $formatType = $scope->getType($functionCall->args[1]->value); + if ($formatType instanceof ConstantIntegerType) { + $val = $formatType->getValue(); + if ($val === 0) { + // return word count + return new IntegerType(); + } elseif ($val === 1 || $val === 2) { + // return [word] or [offset => word] + return new ArrayType(new IntegerType(), new StringType()); + } + + // return false, invalid format value specified + return new ConstantBooleanType(false); + } + + // Could be invalid format type as well, but parameter type checks will catch that. + + return new UnionType([ + new IntegerType(), + new ArrayType(new IntegerType(), new StringType()), + new ConstantBooleanType(false), + ]); + } + + // else fatal error; too many or too few arguments + return new ErrorType(); + } } diff --git a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php index 767c141307..91af716df4 100644 --- a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'strtotime'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) === 0) { - return $defaultReturnType; - } - $argType = $scope->getType($functionCall->args[0]->value); - if ($argType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($defaultReturnType); - } - $result = array_unique(array_map(static function (ConstantStringType $string): bool { - return is_int(strtotime($string->getValue())); - }, TypeUtils::getConstantStrings($argType))); - - if (count($result) !== 1) { - return $defaultReturnType; - } - - return $result[0] ? new IntegerType() : new ConstantBooleanType(false); - } - + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'strtotime'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + if (count($functionCall->args) === 0) { + return $defaultReturnType; + } + $argType = $scope->getType($functionCall->args[0]->value); + if ($argType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + $result = array_unique(array_map(static function (ConstantStringType $string): bool { + return is_int(strtotime($string->getValue())); + }, TypeUtils::getConstantStrings($argType))); + + if (count($result) !== 1) { + return $defaultReturnType; + } + + return $result[0] ? new IntegerType() : new ConstantBooleanType(false); + } } diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index 9dc7d71a13..48bac10626 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return in_array($functionReflection->getName(), [ - 'array_key_exists', - 'in_array', - 'is_numeric', - 'is_int', - 'is_array', - 'is_bool', - 'is_callable', - 'is_float', - 'is_double', - 'is_real', - 'is_iterable', - 'is_null', - 'is_object', - 'is_resource', - 'is_scalar', - 'is_string', - 'is_subclass_of', - 'is_countable', - ], true); - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $isAlways = $this->getHelper()->findSpecifiedType( - $scope, - $functionCall - ); - if ($isAlways === null) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - return new ConstantBooleanType($isAlways); - } - - private function getHelper(): ImpossibleCheckTypeHelper - { - if ($this->helper === null) { - $this->helper = new ImpossibleCheckTypeHelper($this->broker, $this->typeSpecifier, $this->broker->getUniversalObjectCratesClasses(), $this->treatPhpDocTypesAsCertain); - } - - return $this->helper; - } - + private bool $treatPhpDocTypesAsCertain; + + private \PHPStan\Broker\Broker $broker; + + private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; + + private ?\PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $helper = null; + + public function __construct(bool $treatPhpDocTypesAsCertain) + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array($functionReflection->getName(), [ + 'array_key_exists', + 'in_array', + 'is_numeric', + 'is_int', + 'is_array', + 'is_bool', + 'is_callable', + 'is_float', + 'is_double', + 'is_real', + 'is_iterable', + 'is_null', + 'is_object', + 'is_resource', + 'is_scalar', + 'is_string', + 'is_subclass_of', + 'is_countable', + ], true); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $isAlways = $this->getHelper()->findSpecifiedType( + $scope, + $functionCall + ); + if ($isAlways === null) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + return new ConstantBooleanType($isAlways); + } + + private function getHelper(): ImpossibleCheckTypeHelper + { + if ($this->helper === null) { + $this->helper = new ImpossibleCheckTypeHelper($this->broker, $this->typeSpecifier, $this->broker->getUniversalObjectCratesClasses(), $this->treatPhpDocTypesAsCertain); + } + + return $this->helper; + } } diff --git a/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php index b90055cd8d..a7997fba35 100644 --- a/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName(), - [ - 'var_export', - 'highlight_file', - 'highlight_string', - 'print_r', - ], - true - ); - } - - public function getTypeFromFunctionCall(\PHPStan\Reflection\FunctionReflection $functionReflection, \PhpParser\Node\Expr\FuncCall $functionCall, \PHPStan\Analyser\Scope $scope): \PHPStan\Type\Type - { - if ($functionReflection->getName() === 'var_export') { - $fallbackReturnType = new NullType(); - } elseif ($functionReflection->getName() === 'print_r') { - $fallbackReturnType = new ConstantBooleanType(true); - } else { - $fallbackReturnType = new BooleanType(); - } - - if (count($functionCall->args) < 1) { - return TypeCombinator::union( - new StringType(), - $fallbackReturnType - ); - } - - if (count($functionCall->args) < 2) { - return $fallbackReturnType; - } - - $returnArgumentType = $scope->getType($functionCall->args[1]->value); - if ((new ConstantBooleanType(true))->isSuperTypeOf($returnArgumentType)->yes()) { - return new StringType(); - } - - return $fallbackReturnType; - } - + public function isFunctionSupported(\PHPStan\Reflection\FunctionReflection $functionReflection): bool + { + return in_array( + $functionReflection->getName(), + [ + 'var_export', + 'highlight_file', + 'highlight_string', + 'print_r', + ], + true + ); + } + + public function getTypeFromFunctionCall(\PHPStan\Reflection\FunctionReflection $functionReflection, \PhpParser\Node\Expr\FuncCall $functionCall, \PHPStan\Analyser\Scope $scope): \PHPStan\Type\Type + { + if ($functionReflection->getName() === 'var_export') { + $fallbackReturnType = new NullType(); + } elseif ($functionReflection->getName() === 'print_r') { + $fallbackReturnType = new ConstantBooleanType(true); + } else { + $fallbackReturnType = new BooleanType(); + } + + if (count($functionCall->args) < 1) { + return TypeCombinator::union( + new StringType(), + $fallbackReturnType + ); + } + + if (count($functionCall->args) < 2) { + return $fallbackReturnType; + } + + $returnArgumentType = $scope->getType($functionCall->args[1]->value); + if ((new ConstantBooleanType(true))->isSuperTypeOf($returnArgumentType)->yes()) { + return new StringType(); + } + + return $fallbackReturnType; + } } diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index d0a6c37763..7c8dd45b7c 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'version_compare'; + } - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'version_compare'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type - { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->args, $functionReflection->getVariants())->getReturnType(); - } - - $version1Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[0]->value)); - $version2Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[1]->value)); - $counts = [ - count($version1Strings), - count($version2Strings), - ]; + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type { + if (count($functionCall->args) < 2) { + return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->args, $functionReflection->getVariants())->getReturnType(); + } - if (isset($functionCall->args[2])) { - $operatorStrings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); - $counts[] = count($operatorStrings); - $returnType = new BooleanType(); - } else { - $returnType = TypeCombinator::union( - new ConstantIntegerType(-1), - new ConstantIntegerType(0), - new ConstantIntegerType(1) - ); - } + $version1Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[0]->value)); + $version2Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[1]->value)); + $counts = [ + count($version1Strings), + count($version2Strings), + ]; - if (count(array_filter($counts, static function (int $count): bool { - return $count === 0; - })) > 0) { - return $returnType; // one of the arguments is not a constant string - } + if (isset($functionCall->args[2])) { + $operatorStrings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); + $counts[] = count($operatorStrings); + $returnType = new BooleanType(); + } else { + $returnType = TypeCombinator::union( + new ConstantIntegerType(-1), + new ConstantIntegerType(0), + new ConstantIntegerType(1) + ); + } - if (count(array_filter($counts, static function (int $count): bool { - return $count > 1; - })) > 1) { - return $returnType; // more than one argument can have multiple possibilities, avoid combinatorial explosion - } + if (count(array_filter($counts, static function (int $count): bool { + return $count === 0; + })) > 0) { + return $returnType; // one of the arguments is not a constant string + } - $types = []; - foreach ($version1Strings as $version1String) { - foreach ($version2Strings as $version2String) { - if (isset($operatorStrings)) { - foreach ($operatorStrings as $operatorString) { - $value = version_compare($version1String->getValue(), $version2String->getValue(), $operatorString->getValue()); - $types[$value] = new ConstantBooleanType($value); - } - } else { - $value = version_compare($version1String->getValue(), $version2String->getValue()); - $types[$value] = new ConstantIntegerType($value); - } - } - } - return TypeCombinator::union(...$types); - } + if (count(array_filter($counts, static function (int $count): bool { + return $count > 1; + })) > 1) { + return $returnType; // more than one argument can have multiple possibilities, avoid combinatorial explosion + } + $types = []; + foreach ($version1Strings as $version1String) { + foreach ($version2Strings as $version2String) { + if (isset($operatorStrings)) { + foreach ($operatorStrings as $operatorString) { + $value = version_compare($version1String->getValue(), $version2String->getValue(), $operatorString->getValue()); + $types[$value] = new ConstantBooleanType($value); + } + } else { + $value = version_compare($version1String->getValue(), $version2String->getValue()); + $types[$value] = new ConstantIntegerType($value); + } + } + } + return TypeCombinator::union(...$types); + } } diff --git a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php index 62e820b703..2e7ed15be3 100644 --- a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php +++ b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php @@ -1,4 +1,6 @@ -getName() === 'open'; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $this->isMethodSupported($methodReflection); - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new BooleanType(); - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type - { - return new UnionType([new ObjectType(self::XML_READER_CLASS), new ConstantBooleanType(false)]); - } - + private const XML_READER_CLASS = 'XMLReader'; + + public function getClass(): string + { + return self::XML_READER_CLASS; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'open'; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $this->isMethodSupported($methodReflection); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new BooleanType(); + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + return new UnionType([new ObjectType(self::XML_READER_CLASS), new ConstantBooleanType(false)]); + } } diff --git a/src/Type/RecursionGuard.php b/src/Type/RecursionGuard.php index 6e1cf7191a..ce98a8e66c 100644 --- a/src/Type/RecursionGuard.php +++ b/src/Type/RecursionGuard.php @@ -1,32 +1,32 @@ -describe(VerbosityLevel::value()); - if (isset(self::$context[$key])) { - return new ErrorType(); - } - - try { - self::$context[$key] = true; - return $callback(); - } finally { - unset(self::$context[$key]); - } - } + /** + * @param Type $type + * @param callable(): Type $callback + * + * @return Type + */ + public static function run(Type $type, callable $callback): Type + { + $key = $type->describe(VerbosityLevel::value()); + if (isset(self::$context[$key])) { + return new ErrorType(); + } + try { + self::$context[$key] = true; + return $callback(); + } finally { + unset(self::$context[$key]); + } + } } diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index e6758d0823..2ee59dc6a9 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -1,4 +1,6 @@ -hasClass($classReflection)) { - $classReflection = $broker->getClass($classReflection); - $this->classReflection = $classReflection; - $this->baseClass = $classReflection->getName(); - return; - } - - $this->classReflection = null; - $this->baseClass = $classReflection; - return; - } - - $this->classReflection = $classReflection; - $this->baseClass = $classReflection->getName(); - } - - public function getClassName(): string - { - return $this->baseClass; - } - - public function getClassReflection(): ?ClassReflection - { - return $this->classReflection; - } - - public function getAncestorWithClassName(string $className): ?TypeWithClassName - { - $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className); - if ($ancestor === null) { - return null; - } - - return $this->changeBaseClass($ancestor->getClassReflection() ?? $ancestor->getClassName()); - } - - public function getStaticObjectType(): ObjectType - { - if ($this->staticObjectType === null) { - if ($this->classReflection !== null && $this->classReflection->isGeneric()) { - $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static function (string $name, Type $type): Type { - return TemplateTypeHelper::toArgument($type); - }); - return $this->staticObjectType = new GenericObjectType( - $this->classReflection->getName(), - $this->classReflection->typeMapToList($typeMap) - ); - } - - return $this->staticObjectType = new ObjectType($this->baseClass, null, $this->classReflection); - } - - return $this->staticObjectType; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return $this->getStaticObjectType()->getReferencedClasses(); - } - - public function getBaseClass(): string - { - return $this->baseClass; - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - if (!$type instanceof static) { - return TrinaryLogic::createNo(); - } - - return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOf($type); - } - - if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof ObjectType) { - $result = $this->getStaticObjectType()->isSuperTypeOf($type); - $classReflection = $type->getClassReflection(); - if ($result->yes() && $classReflection !== null && $classReflection->isFinal()) { - return $result; - } - - return TrinaryLogic::createMaybe()->and($result); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - if (get_class($type) !== static::class) { - return false; - } - - /** @var StaticType $type */ - $type = $type; - return $this->getStaticObjectType()->equals($type->getStaticObjectType()); - } - - public function describe(VerbosityLevel $level): string - { - return sprintf('static(%s)', $this->getClassName()); - } - - public function canAccessProperties(): TrinaryLogic - { - return $this->getStaticObjectType()->canAccessProperties(); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return $this->getStaticObjectType()->hasProperty($propertyName); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $staticObject = $this->getStaticObjectType(); - $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty(); - - $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName()); - $classReflection = null; - if ($ancestor !== null) { - $classReflection = $ancestor->getClassReflection(); - } - if ($classReflection === null) { - $classReflection = $nakedProperty->getDeclaringClass(); - } - - return new CallbackUnresolvedPropertyPrototypeReflection( - $nakedProperty, - $classReflection, - false, - function (Type $type) use ($scope): Type { - return $this->transformStaticType($type, $scope); - } - ); - } - - public function canCallMethods(): TrinaryLogic - { - return $this->getStaticObjectType()->canCallMethods(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return $this->getStaticObjectType()->hasMethod($methodName); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $staticObject = $this->getStaticObjectType(); - $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod(); - - $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName()); - $classReflection = null; - if ($ancestor !== null) { - $classReflection = $ancestor->getClassReflection(); - } - if ($classReflection === null) { - $classReflection = $nakedMethod->getDeclaringClass(); - } - - return new CallbackUnresolvedMethodPrototypeReflection( - $nakedMethod, - $classReflection, - false, - function (Type $type) use ($scope): Type { - return $this->transformStaticType($type, $scope); - } - ); - } - - private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type - { - return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type { - if ($type instanceof StaticType) { - $classReflection = $this->classReflection; - $isFinal = false; - if ($classReflection === null) { - $classReflection = $this->baseClass; - } elseif ($scope->isInClass()) { - $classReflection = $scope->getClassReflection(); - $isFinal = $classReflection->isFinal(); - } - $type = $type->changeBaseClass($classReflection); - if (!$isFinal) { - return $type; - } - - return $type->getStaticObjectType(); - } - - return $traverse($type); - }); - } - - public function canAccessConstants(): TrinaryLogic - { - return $this->getStaticObjectType()->canAccessConstants(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return $this->getStaticObjectType()->hasConstant($constantName); - } - - public function getConstant(string $constantName): ConstantReflection - { - return $this->getStaticObjectType()->getConstant($constantName); - } - - /** - * @param ClassReflection|string $classReflection - * @return self - */ - public function changeBaseClass($classReflection): self - { - return new self($classReflection); - } - - public function isIterable(): TrinaryLogic - { - return $this->getStaticObjectType()->isIterable(); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return $this->getStaticObjectType()->isIterableAtLeastOnce(); - } - - public function getIterableKeyType(): Type - { - return $this->getStaticObjectType()->getIterableKeyType(); - } - - public function getIterableValueType(): Type - { - return $this->getStaticObjectType()->getIterableValueType(); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return $this->getStaticObjectType()->isOffsetAccessible(); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return $this->getStaticObjectType()->hasOffsetValueType($offsetType); - } - - public function getOffsetValueType(Type $offsetType): Type - { - return $this->getStaticObjectType()->getOffsetValueType($offsetType); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType); - } - - public function isCallable(): TrinaryLogic - { - return $this->getStaticObjectType()->isCallable(); - } - - public function isArray(): TrinaryLogic - { - return $this->getStaticObjectType()->isArray(); - } - - public function isNumericString(): TrinaryLogic - { - return $this->getStaticObjectType()->isNumericString(); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - return $this->getStaticObjectType()->getCallableParametersAcceptors($scope); - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return $this->getStaticObjectType()->toString(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return $this->getStaticObjectType()->toArray(); - } - - public function toBoolean(): BooleanType - { - return $this->getStaticObjectType()->toBoolean(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['baseClass']); - } - + use NonGenericTypeTrait; + use UndecidedComparisonTypeTrait; + + private ?ClassReflection $classReflection; + + private ?\PHPStan\Type\ObjectType $staticObjectType = null; + + private string $baseClass; + + /** + * @param string|ClassReflection $classReflection + */ + public function __construct($classReflection) + { + if (is_string($classReflection)) { + $broker = Broker::getInstance(); + if ($broker->hasClass($classReflection)) { + $classReflection = $broker->getClass($classReflection); + $this->classReflection = $classReflection; + $this->baseClass = $classReflection->getName(); + return; + } + + $this->classReflection = null; + $this->baseClass = $classReflection; + return; + } + + $this->classReflection = $classReflection; + $this->baseClass = $classReflection->getName(); + } + + public function getClassName(): string + { + return $this->baseClass; + } + + public function getClassReflection(): ?ClassReflection + { + return $this->classReflection; + } + + public function getAncestorWithClassName(string $className): ?TypeWithClassName + { + $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className); + if ($ancestor === null) { + return null; + } + + return $this->changeBaseClass($ancestor->getClassReflection() ?? $ancestor->getClassName()); + } + + public function getStaticObjectType(): ObjectType + { + if ($this->staticObjectType === null) { + if ($this->classReflection !== null && $this->classReflection->isGeneric()) { + $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static function (string $name, Type $type): Type { + return TemplateTypeHelper::toArgument($type); + }); + return $this->staticObjectType = new GenericObjectType( + $this->classReflection->getName(), + $this->classReflection->typeMapToList($typeMap) + ); + } + + return $this->staticObjectType = new ObjectType($this->baseClass, null, $this->classReflection); + } + + return $this->staticObjectType; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return $this->getStaticObjectType()->getReferencedClasses(); + } + + public function getBaseClass(): string + { + return $this->baseClass; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + if (!$type instanceof static) { + return TrinaryLogic::createNo(); + } + + return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return $this->getStaticObjectType()->isSuperTypeOf($type); + } + + if ($type instanceof ObjectWithoutClassType) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof ObjectType) { + $result = $this->getStaticObjectType()->isSuperTypeOf($type); + $classReflection = $type->getClassReflection(); + if ($result->yes() && $classReflection !== null && $classReflection->isFinal()) { + return $result; + } + + return TrinaryLogic::createMaybe()->and($result); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + if (get_class($type) !== static::class) { + return false; + } + + /** @var StaticType $type */ + $type = $type; + return $this->getStaticObjectType()->equals($type->getStaticObjectType()); + } + + public function describe(VerbosityLevel $level): string + { + return sprintf('static(%s)', $this->getClassName()); + } + + public function canAccessProperties(): TrinaryLogic + { + return $this->getStaticObjectType()->canAccessProperties(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return $this->getStaticObjectType()->hasProperty($propertyName); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $staticObject = $this->getStaticObjectType(); + $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty(); + + $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName()); + $classReflection = null; + if ($ancestor !== null) { + $classReflection = $ancestor->getClassReflection(); + } + if ($classReflection === null) { + $classReflection = $nakedProperty->getDeclaringClass(); + } + + return new CallbackUnresolvedPropertyPrototypeReflection( + $nakedProperty, + $classReflection, + false, + function (Type $type) use ($scope): Type { + return $this->transformStaticType($type, $scope); + } + ); + } + + public function canCallMethods(): TrinaryLogic + { + return $this->getStaticObjectType()->canCallMethods(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return $this->getStaticObjectType()->hasMethod($methodName); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $staticObject = $this->getStaticObjectType(); + $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod(); + + $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName()); + $classReflection = null; + if ($ancestor !== null) { + $classReflection = $ancestor->getClassReflection(); + } + if ($classReflection === null) { + $classReflection = $nakedMethod->getDeclaringClass(); + } + + return new CallbackUnresolvedMethodPrototypeReflection( + $nakedMethod, + $classReflection, + false, + function (Type $type) use ($scope): Type { + return $this->transformStaticType($type, $scope); + } + ); + } + + private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type + { + return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type { + if ($type instanceof StaticType) { + $classReflection = $this->classReflection; + $isFinal = false; + if ($classReflection === null) { + $classReflection = $this->baseClass; + } elseif ($scope->isInClass()) { + $classReflection = $scope->getClassReflection(); + $isFinal = $classReflection->isFinal(); + } + $type = $type->changeBaseClass($classReflection); + if (!$isFinal) { + return $type; + } + + return $type->getStaticObjectType(); + } + + return $traverse($type); + }); + } + + public function canAccessConstants(): TrinaryLogic + { + return $this->getStaticObjectType()->canAccessConstants(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return $this->getStaticObjectType()->hasConstant($constantName); + } + + public function getConstant(string $constantName): ConstantReflection + { + return $this->getStaticObjectType()->getConstant($constantName); + } + + /** + * @param ClassReflection|string $classReflection + * @return self + */ + public function changeBaseClass($classReflection): self + { + return new self($classReflection); + } + + public function isIterable(): TrinaryLogic + { + return $this->getStaticObjectType()->isIterable(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return $this->getStaticObjectType()->isIterableAtLeastOnce(); + } + + public function getIterableKeyType(): Type + { + return $this->getStaticObjectType()->getIterableKeyType(); + } + + public function getIterableValueType(): Type + { + return $this->getStaticObjectType()->getIterableValueType(); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return $this->getStaticObjectType()->isOffsetAccessible(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return $this->getStaticObjectType()->hasOffsetValueType($offsetType); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return $this->getStaticObjectType()->getOffsetValueType($offsetType); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType); + } + + public function isCallable(): TrinaryLogic + { + return $this->getStaticObjectType()->isCallable(); + } + + public function isArray(): TrinaryLogic + { + return $this->getStaticObjectType()->isArray(); + } + + public function isNumericString(): TrinaryLogic + { + return $this->getStaticObjectType()->isNumericString(); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + return $this->getStaticObjectType()->getCallableParametersAcceptors($scope); + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return $this->getStaticObjectType()->toString(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return $this->getStaticObjectType()->toArray(); + } + + public function toBoolean(): BooleanType + { + return $this->getStaticObjectType()->toBoolean(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['baseClass']); + } } diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index ba99a36130..e070a546ad 100644 --- a/src/Type/StaticTypeFactory.php +++ b/src/Type/StaticTypeFactory.php @@ -1,4 +1,6 @@ -hasClass($type->getClassName())) { + return TrinaryLogic::createNo(); + } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof TypeWithClassName) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getClassName())) { - return TrinaryLogic::createNo(); - } - - $typeClass = $broker->getClass($type->getClassName()); - return TrinaryLogic::createFromBoolean( - $typeClass->hasNativeMethod('__toString') - ); - } - - return parent::accepts($type, $strictTypes); - } + $typeClass = $broker->getClass($type->getClassName()); + return TrinaryLogic::createFromBoolean( + $typeClass->hasNativeMethod('__toString') + ); + } + return parent::accepts($type, $strictTypes); + } } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index da8873069e..12c80b05cd 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -1,4 +1,6 @@ -isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); - } - - public function getOffsetValueType(Type $offsetType): Type - { - if ($this->hasOffsetValueType($offsetType)->no()) { - return new ErrorType(); - } - - return new StringType(); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - if ($offsetType === null) { - return new ErrorType(); - } - - $valueStringType = $valueType->toString(); - if ($valueStringType instanceof ErrorType) { - return new ErrorType(); - } - - if ((new IntegerType())->isSuperTypeOf($offsetType)->yes() || $offsetType instanceof MixedType) { - return new StringType(); - } - - return new ErrorType(); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - if ($type instanceof TypeWithClassName && !$strictTypes) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getClassName())) { - return TrinaryLogic::createNo(); - } - - $typeClass = $broker->getClass($type->getClassName()); - return TrinaryLogic::createFromBoolean( - $typeClass->hasNativeMethod('__toString') - ); - } - - return TrinaryLogic::createNo(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new IntegerType(); - } - - public function toFloat(): Type - { - return new FloatType(); - } - - public function toString(): Type - { - return $this; - } - - public function toArray(): Type - { - return new ConstantArrayType( - [new ConstantIntegerType(0)], - [$this], - 1 - ); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + use JustNullableTypeTrait; + use MaybeCallableTypeTrait; + use NonIterableTypeTrait; + use NonObjectTypeTrait; + use UndecidedBooleanTypeTrait; + use UndecidedComparisonTypeTrait; + use NonGenericTypeTrait; + + public function describe(VerbosityLevel $level): string + { + return 'string'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + return new StringType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + if ($offsetType === null) { + return new ErrorType(); + } + + $valueStringType = $valueType->toString(); + if ($valueStringType instanceof ErrorType) { + return new ErrorType(); + } + + if ((new IntegerType())->isSuperTypeOf($offsetType)->yes() || $offsetType instanceof MixedType) { + return new StringType(); + } + + return new ErrorType(); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + if ($type instanceof TypeWithClassName && !$strictTypes) { + $broker = Broker::getInstance(); + if (!$broker->hasClass($type->getClassName())) { + return TrinaryLogic::createNo(); + } + + $typeClass = $broker->getClass($type->getClassName()); + return TrinaryLogic::createFromBoolean( + $typeClass->hasNativeMethod('__toString') + ); + } + + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/Type/SubtractableType.php b/src/Type/SubtractableType.php index c40188af2d..e2acf11372 100644 --- a/src/Type/SubtractableType.php +++ b/src/Type/SubtractableType.php @@ -1,16 +1,16 @@ -getClassName()); - } - + public function describe(VerbosityLevel $level): string + { + return sprintf('$this(%s)', $this->getClassName()); + } } diff --git a/src/Type/Traits/ConstantNumericComparisonTypeTrait.php b/src/Type/Traits/ConstantNumericComparisonTypeTrait.php index 44cac2d98e..366de3665b 100644 --- a/src/Type/Traits/ConstantNumericComparisonTypeTrait.php +++ b/src/Type/Traits/ConstantNumericComparisonTypeTrait.php @@ -1,4 +1,6 @@ -value), - ]; - - if (!(bool) $this->value) { - $subtractedTypes[] = new NullType(); - $subtractedTypes[] = new ConstantBooleanType(false); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getSmallerOrEqualType(): Type - { - $subtractedTypes = [ - IntegerRangeType::createAllGreaterThan($this->value), - ]; - - if (!(bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(true); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterType(): Type - { - $subtractedTypes = [ - new NullType(), - new ConstantBooleanType(false), - IntegerRangeType::createAllSmallerThanOrEqualTo($this->value), - ]; - - if ((bool) $this->value) { - $subtractedTypes[] = new ConstantBooleanType(true); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - - public function getGreaterOrEqualType(): Type - { - $subtractedTypes = [ - IntegerRangeType::createAllSmallerThan($this->value), - ]; - - if ((bool) $this->value) { - $subtractedTypes[] = new NullType(); - $subtractedTypes[] = new ConstantBooleanType(false); - } - - return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); - } - + public function getSmallerType(): Type + { + $subtractedTypes = [ + new ConstantBooleanType(true), + IntegerRangeType::createAllGreaterThanOrEqualTo($this->value), + ]; + + if (!(bool) $this->value) { + $subtractedTypes[] = new NullType(); + $subtractedTypes[] = new ConstantBooleanType(false); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getSmallerOrEqualType(): Type + { + $subtractedTypes = [ + IntegerRangeType::createAllGreaterThan($this->value), + ]; + + if (!(bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(true); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterType(): Type + { + $subtractedTypes = [ + new NullType(), + new ConstantBooleanType(false), + IntegerRangeType::createAllSmallerThanOrEqualTo($this->value), + ]; + + if ((bool) $this->value) { + $subtractedTypes[] = new ConstantBooleanType(true); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } + + public function getGreaterOrEqualType(): Type + { + $subtractedTypes = [ + IntegerRangeType::createAllSmallerThan($this->value), + ]; + + if ((bool) $this->value) { + $subtractedTypes[] = new NullType(); + $subtractedTypes[] = new ConstantBooleanType(false); + } + + return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); + } } diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index fc24cc9abc..99d7ba06ba 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -1,4 +1,6 @@ -value === $type->value); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return TrinaryLogic::createNo(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); - } - - if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return $type instanceof self && $this->value === $type->value; - } - - public function isSmallerThan(Type $otherType): TrinaryLogic - { - if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean($this->value < $otherType->getValue()); - } - - if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThan($this); - } - - return TrinaryLogic::createMaybe(); - } - - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic - { - if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean($this->value <= $otherType->getValue()); - } - - if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThanOrEqual($this); - } - - return TrinaryLogic::createMaybe(); - } - - public function generalize(): Type - { - return new parent(); - } - + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createFromBoolean($this->value === $type->value); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + return TrinaryLogic::createNo(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + } + + if ($type instanceof parent) { + return TrinaryLogic::createMaybe(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return $type instanceof self && $this->value === $type->value; + } + + public function isSmallerThan(Type $otherType): TrinaryLogic + { + if ($otherType instanceof ConstantScalarType) { + return TrinaryLogic::createFromBoolean($this->value < $otherType->getValue()); + } + + if ($otherType instanceof CompoundType) { + return $otherType->isGreaterThan($this); + } + + return TrinaryLogic::createMaybe(); + } + + public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + { + if ($otherType instanceof ConstantScalarType) { + return TrinaryLogic::createFromBoolean($this->value <= $otherType->getValue()); + } + + if ($otherType instanceof CompoundType) { + return $otherType->isGreaterThanOrEqual($this); + } + + return TrinaryLogic::createMaybe(); + } + + public function generalize(): Type + { + return new parent(); + } } diff --git a/src/Type/Traits/FalseyBooleanTypeTrait.php b/src/Type/Traits/FalseyBooleanTypeTrait.php index ca1e75efbd..38313115b7 100644 --- a/src/Type/Traits/FalseyBooleanTypeTrait.php +++ b/src/Type/Traits/FalseyBooleanTypeTrait.php @@ -1,4 +1,6 @@ -getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $property = new DummyPropertyReflection(); - return new CallbackUnresolvedPropertyPrototypeReflection( - $property, - $property->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canCallMethods(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $method = new DummyMethodReflection($methodName); - return new CallbackUnresolvedMethodPrototypeReflection( - $method, - $method->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canAccessConstants(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getConstant(string $constantName): ConstantReflection - { - return new DummyConstantReflection($constantName); - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - + public function canAccessProperties(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canCallMethods(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $method = new DummyMethodReflection($methodName); + return new CallbackUnresolvedMethodPrototypeReflection( + $method, + $method->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canAccessConstants(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getConstant(string $constantName): ConstantReflection + { + return new DummyConstantReflection($constantName); + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } } diff --git a/src/Type/Traits/MaybeOffsetAccessibleTypeTrait.php b/src/Type/Traits/MaybeOffsetAccessibleTypeTrait.php index 6859652d88..59e1e0e4eb 100644 --- a/src/Type/Traits/MaybeOffsetAccessibleTypeTrait.php +++ b/src/Type/Traits/MaybeOffsetAccessibleTypeTrait.php @@ -1,4 +1,6 @@ -getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $property = new DummyPropertyReflection(); - return new CallbackUnresolvedPropertyPrototypeReflection( - $property, - $property->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canCallMethods(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $method = new DummyMethodReflection($methodName); - return new CallbackUnresolvedMethodPrototypeReflection( - $method, - $method->getDeclaringClass(), - false, - static function (Type $type): Type { - return $type; - } - ); - } - - public function canAccessConstants(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - - public function getConstant(string $constantName): ConstantReflection - { - return new DummyConstantReflection($constantName); - } - - public function isCloneable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new StringType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new ArrayType(new MixedType(), new MixedType()); - } - + use MaybeCallableTypeTrait; + use MaybeIterableTypeTrait; + use MaybeOffsetAccessibleTypeTrait; + use TruthyBooleanTypeTrait; + + public function canAccessProperties(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canCallMethods(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $method = new DummyMethodReflection($methodName); + return new CallbackUnresolvedMethodPrototypeReflection( + $method, + $method->getDeclaringClass(), + false, + static function (Type $type): Type { + return $type; + } + ); + } + + public function canAccessConstants(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getConstant(string $constantName): ConstantReflection + { + return new DummyConstantReflection($constantName); + } + + public function isCloneable(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new StringType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new ArrayType(new MixedType(), new MixedType()); + } } diff --git a/src/Type/Traits/TruthyBooleanTypeTrait.php b/src/Type/Traits/TruthyBooleanTypeTrait.php index 21a6ba918a..cba3b206eb 100644 --- a/src/Type/Traits/TruthyBooleanTypeTrait.php +++ b/src/Type/Traits/TruthyBooleanTypeTrait.php @@ -1,4 +1,6 @@ -,Bar> (with T a template type) - * will return one TemplateTypeReference for the type T. - * - * @param TemplateTypeVariance $positionVariance The variance position in - * which the receiver type was - * found. - * - * @return TemplateTypeReference[] - */ - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array; + /** + * Infers template types + * + * Infers the real Type of the TemplateTypes found in $this, based on + * the received Type. + */ + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; - /** - * Traverses inner types - * - * Returns a new instance with all inner types mapped through $cb. Might - * return the same instance if inner types did not change. - * - * @param callable(Type):Type $cb - */ - public function traverse(callable $cb): Type; + /** + * Returns the template types referenced by this Type, recursively + * + * The return value is a list of TemplateTypeReferences, who contain the + * referenced template type as well as the variance position in which it was + * found. + * + * For example, calling this on array,Bar> (with T a template type) + * will return one TemplateTypeReference for the type T. + * + * @param TemplateTypeVariance $positionVariance The variance position in + * which the receiver type was + * found. + * + * @return TemplateTypeReference[] + */ + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array; - /** - * @param mixed[] $properties - * @return self - */ - public static function __set_state(array $properties): self; + /** + * Traverses inner types + * + * Returns a new instance with all inner types mapped through $cb. Might + * return the same instance if inner types did not change. + * + * @param callable(Type):Type $cb + */ + public function traverse(callable $cb): Type; + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): self; } diff --git a/src/Type/TypeAlias.php b/src/Type/TypeAlias.php index 48a59ec40e..125ca37415 100644 --- a/src/Type/TypeAlias.php +++ b/src/Type/TypeAlias.php @@ -1,4 +1,6 @@ -typeNode = $typeNode; - $this->nameScope = $nameScope; - } - - public static function invalid(): self - { - $self = new self(new IdentifierTypeNode('*ERROR*'), new NameScope(null, [])); - $self->resolvedType = new ErrorType(); - return $self; - } - - public function resolve(TypeNodeResolver $typeNodeResolver): Type - { - if ($this->resolvedType === null) { - $this->resolvedType = $typeNodeResolver->resolve( - $this->typeNode, - $this->nameScope - ); - } - - return $this->resolvedType; - } - + private TypeNode $typeNode; + + private NameScope $nameScope; + + private ?Type $resolvedType = null; + + public function __construct( + TypeNode $typeNode, + NameScope $nameScope + ) { + $this->typeNode = $typeNode; + $this->nameScope = $nameScope; + } + + public static function invalid(): self + { + $self = new self(new IdentifierTypeNode('*ERROR*'), new NameScope(null, [])); + $self->resolvedType = new ErrorType(); + return $self; + } + + public function resolve(TypeNodeResolver $typeNodeResolver): Type + { + if ($this->resolvedType === null) { + $this->resolvedType = $typeNodeResolver->resolve( + $this->typeNode, + $this->nameScope + ); + } + + return $this->resolvedType; + } } diff --git a/src/Type/TypeAliasResolver.php b/src/Type/TypeAliasResolver.php index 1ce2e8f215..a714a63e41 100644 --- a/src/Type/TypeAliasResolver.php +++ b/src/Type/TypeAliasResolver.php @@ -1,4 +1,6 @@ - */ + private array $globalTypeAliases; + + private TypeStringResolver $typeStringResolver; + + private TypeNodeResolver $typeNodeResolver; - /** @var array */ - private array $globalTypeAliases; - - private TypeStringResolver $typeStringResolver; - - private TypeNodeResolver $typeNodeResolver; - - private ReflectionProvider $reflectionProvider; - - /** @var array */ - private array $resolvedGlobalTypeAliases = []; - - /** @var array */ - private array $resolvedLocalTypeAliases = []; - - /** @var array */ - private array $resolvingClassTypeAliases = []; - - /** @var array */ - private array $inProcess = []; - - /** - * @param array $globalTypeAliases - */ - public function __construct( - array $globalTypeAliases, - TypeStringResolver $typeStringResolver, - TypeNodeResolver $typeNodeResolver, - ReflectionProvider $reflectionProvider - ) - { - $this->globalTypeAliases = $globalTypeAliases; - $this->typeStringResolver = $typeStringResolver; - $this->typeNodeResolver = $typeNodeResolver; - $this->reflectionProvider = $reflectionProvider; - } - - public function hasTypeAlias(string $aliasName, ?string $classNameScope): bool - { - $hasGlobalTypeAlias = array_key_exists($aliasName, $this->globalTypeAliases); - if ($hasGlobalTypeAlias) { - return true; - } - - if ($classNameScope === null || !$this->reflectionProvider->hasClass($classNameScope)) { - return false; - } - - $classReflection = $this->reflectionProvider->getClass($classNameScope); - $localTypeAliases = $classReflection->getTypeAliases(); - return array_key_exists($aliasName, $localTypeAliases); - } - - public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type - { - return $this->resolveLocalTypeAlias($aliasName, $nameScope) - ?? $this->resolveGlobalTypeAlias($aliasName, $nameScope); - } - - private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope): ?Type - { - if (array_key_exists($aliasName, $this->globalTypeAliases)) { - return null; - } - - if (!$nameScope->hasTypeAlias($aliasName)) { - return null; - } - - $className = $nameScope->getClassName(); - if ($className === null) { - return null; - } - - $aliasNameInClassScope = $className . '::' . $aliasName; - - if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) { - return $this->resolvedLocalTypeAliases[$aliasNameInClassScope]; - } - - // prevent infinite recursion - if (array_key_exists($className, $this->resolvingClassTypeAliases)) { - return null; - } - - $this->resolvingClassTypeAliases[$className] = true; - - if (!$this->reflectionProvider->hasClass($className)) { - unset($this->resolvingClassTypeAliases[$className]); - return null; - } - - $classReflection = $this->reflectionProvider->getClass($className); - $localTypeAliases = $classReflection->getTypeAliases(); - - unset($this->resolvingClassTypeAliases[$className]); - - if (!array_key_exists($aliasName, $localTypeAliases)) { - return null; - } - - if (array_key_exists($aliasNameInClassScope, $this->inProcess)) { - // resolve circular reference as ErrorType to make it easier to detect - throw new \PHPStan\Type\CircularTypeAliasDefinitionException(); - } - - $this->inProcess[$aliasNameInClassScope] = true; - - try { - $unresolvedAlias = $localTypeAliases[$aliasName]; - $resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver); - } catch (\PHPStan\Type\CircularTypeAliasDefinitionException $e) { - $resolvedAliasType = new ErrorType(); - } - - $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType; - unset($this->inProcess[$aliasNameInClassScope]); - - return $resolvedAliasType; - } - - private function resolveGlobalTypeAlias(string $aliasName, NameScope $nameScope): ?Type - { - if (!array_key_exists($aliasName, $this->globalTypeAliases)) { - return null; - } - - if (array_key_exists($aliasName, $this->resolvedGlobalTypeAliases)) { - return $this->resolvedGlobalTypeAliases[$aliasName]; - } - - if ($this->reflectionProvider->hasClass($nameScope->resolveStringName($aliasName))) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Type alias %s already exists as a class.', $aliasName)); - } + private ReflectionProvider $reflectionProvider; + + /** @var array */ + private array $resolvedGlobalTypeAliases = []; + + /** @var array */ + private array $resolvedLocalTypeAliases = []; + + /** @var array */ + private array $resolvingClassTypeAliases = []; + + /** @var array */ + private array $inProcess = []; + + /** + * @param array $globalTypeAliases + */ + public function __construct( + array $globalTypeAliases, + TypeStringResolver $typeStringResolver, + TypeNodeResolver $typeNodeResolver, + ReflectionProvider $reflectionProvider + ) { + $this->globalTypeAliases = $globalTypeAliases; + $this->typeStringResolver = $typeStringResolver; + $this->typeNodeResolver = $typeNodeResolver; + $this->reflectionProvider = $reflectionProvider; + } + + public function hasTypeAlias(string $aliasName, ?string $classNameScope): bool + { + $hasGlobalTypeAlias = array_key_exists($aliasName, $this->globalTypeAliases); + if ($hasGlobalTypeAlias) { + return true; + } + + if ($classNameScope === null || !$this->reflectionProvider->hasClass($classNameScope)) { + return false; + } - if (array_key_exists($aliasName, $this->inProcess)) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Circular definition for type alias %s.', $aliasName)); - } + $classReflection = $this->reflectionProvider->getClass($classNameScope); + $localTypeAliases = $classReflection->getTypeAliases(); + return array_key_exists($aliasName, $localTypeAliases); + } + + public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type + { + return $this->resolveLocalTypeAlias($aliasName, $nameScope) + ?? $this->resolveGlobalTypeAlias($aliasName, $nameScope); + } + + private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope): ?Type + { + if (array_key_exists($aliasName, $this->globalTypeAliases)) { + return null; + } + + if (!$nameScope->hasTypeAlias($aliasName)) { + return null; + } + + $className = $nameScope->getClassName(); + if ($className === null) { + return null; + } + + $aliasNameInClassScope = $className . '::' . $aliasName; + + if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) { + return $this->resolvedLocalTypeAliases[$aliasNameInClassScope]; + } + + // prevent infinite recursion + if (array_key_exists($className, $this->resolvingClassTypeAliases)) { + return null; + } + + $this->resolvingClassTypeAliases[$className] = true; + + if (!$this->reflectionProvider->hasClass($className)) { + unset($this->resolvingClassTypeAliases[$className]); + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $localTypeAliases = $classReflection->getTypeAliases(); + + unset($this->resolvingClassTypeAliases[$className]); + + if (!array_key_exists($aliasName, $localTypeAliases)) { + return null; + } + + if (array_key_exists($aliasNameInClassScope, $this->inProcess)) { + // resolve circular reference as ErrorType to make it easier to detect + throw new \PHPStan\Type\CircularTypeAliasDefinitionException(); + } + + $this->inProcess[$aliasNameInClassScope] = true; + + try { + $unresolvedAlias = $localTypeAliases[$aliasName]; + $resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver); + } catch (\PHPStan\Type\CircularTypeAliasDefinitionException $e) { + $resolvedAliasType = new ErrorType(); + } + + $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType; + unset($this->inProcess[$aliasNameInClassScope]); + + return $resolvedAliasType; + } + + private function resolveGlobalTypeAlias(string $aliasName, NameScope $nameScope): ?Type + { + if (!array_key_exists($aliasName, $this->globalTypeAliases)) { + return null; + } + + if (array_key_exists($aliasName, $this->resolvedGlobalTypeAliases)) { + return $this->resolvedGlobalTypeAliases[$aliasName]; + } + + if ($this->reflectionProvider->hasClass($nameScope->resolveStringName($aliasName))) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Type alias %s already exists as a class.', $aliasName)); + } - $this->inProcess[$aliasName] = true; + if (array_key_exists($aliasName, $this->inProcess)) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Circular definition for type alias %s.', $aliasName)); + } - $aliasTypeString = $this->globalTypeAliases[$aliasName]; - $aliasType = $this->typeStringResolver->resolve($aliasTypeString); - $this->resolvedGlobalTypeAliases[$aliasName] = $aliasType; + $this->inProcess[$aliasName] = true; - unset($this->inProcess[$aliasName]); + $aliasTypeString = $this->globalTypeAliases[$aliasName]; + $aliasType = $this->typeStringResolver->resolve($aliasTypeString); + $this->resolvedGlobalTypeAliases[$aliasName] = $aliasType; - return $aliasType; - } + unset($this->inProcess[$aliasName]); + return $aliasType; + } } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 475a1d9a5e..4fca5d6804 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -1,4 +1,6 @@ -getTypes() as $unionTypeToRemove) { - $fromType = self::remove($fromType, $unionTypeToRemove); - } - return $fromType; - } - - if ($fromType instanceof UnionType) { - $innerTypes = []; - foreach ($fromType->getTypes() as $innerType) { - $innerTypes[] = self::remove($innerType, $typeToRemove); - } - - return self::union(...$innerTypes); - } - - $isSuperType = $typeToRemove->isSuperTypeOf($fromType); - if ($isSuperType->yes()) { - return new NeverType(); - } - if ($isSuperType->no()) { - return $fromType; - } - - if ($typeToRemove instanceof MixedType) { - $typeToRemoveSubtractedType = $typeToRemove->getSubtractedType(); - if ($typeToRemoveSubtractedType !== null) { - return self::intersect($fromType, $typeToRemoveSubtractedType); - } - } - - if ($fromType instanceof BooleanType) { - if ($typeToRemove instanceof ConstantBooleanType) { - return new ConstantBooleanType(!$typeToRemove->getValue()); - } - } elseif ($fromType instanceof IterableType) { - $arrayType = new ArrayType(new MixedType(), new MixedType()); - if ($typeToRemove->isSuperTypeOf($arrayType)->yes()) { - return new GenericObjectType(\Traversable::class, [ - $fromType->getIterableKeyType(), - $fromType->getIterableValueType(), - ]); - } - - $traversableType = new ObjectType(\Traversable::class); - if ($typeToRemove->isSuperTypeOf($traversableType)->yes()) { - return new ArrayType($fromType->getIterableKeyType(), $fromType->getIterableValueType()); - } - } elseif ($fromType instanceof IntegerRangeType) { - $type = $fromType->tryRemove($typeToRemove); - if ($type !== null) { - return $type; - } - } elseif ($fromType instanceof IntegerType) { - if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { - if ($typeToRemove instanceof IntegerRangeType) { - $removeValueMin = $typeToRemove->getMin(); - $removeValueMax = $typeToRemove->getMax(); - } else { - $removeValueMin = $typeToRemove->getValue(); - $removeValueMax = $typeToRemove->getValue(); - } - $lowerPart = $removeValueMin !== null ? IntegerRangeType::fromInterval(null, $removeValueMin, -1) : null; - $upperPart = $removeValueMax !== null ? IntegerRangeType::fromInterval($removeValueMax, null, +1) : null; - if ($lowerPart !== null && $upperPart !== null) { - return self::union($lowerPart, $upperPart); - } - return $lowerPart ?? $upperPart ?? new NeverType(); - } - } elseif ($fromType->isArray()->yes()) { - if ($typeToRemove instanceof ConstantArrayType && $typeToRemove->isIterableAtLeastOnce()->no()) { - return self::intersect($fromType, new NonEmptyArrayType()); - } - - if ($typeToRemove instanceof NonEmptyArrayType) { - return new ConstantArrayType([], []); - } - - if ($fromType instanceof ConstantArrayType && $typeToRemove instanceof HasOffsetType) { - return $fromType->unsetOffset($typeToRemove->getOffsetType()); - } - } elseif ($fromType instanceof SubtractableType) { - $typeToSubtractFrom = $fromType; - if ($fromType instanceof TemplateType) { - $typeToSubtractFrom = $fromType->getBound(); - } - - if ($typeToSubtractFrom->isSuperTypeOf($typeToRemove)->yes()) { - return $fromType->subtract($typeToRemove); - } - } - - return $fromType; - } - - public static function removeNull(Type $type): Type - { - if (self::containsNull($type)) { - return self::remove($type, new NullType()); - } - - return $type; - } - - public static function containsNull(Type $type): bool - { - if ($type instanceof UnionType) { - foreach ($type->getTypes() as $innerType) { - if ($innerType instanceof NullType) { - return true; - } - } - - return false; - } - - return $type instanceof NullType; - } - - public static function union(Type ...$types): Type - { - $benevolentTypes = []; - // transform A | (B | C) to A | B | C - for ($i = 0; $i < count($types); $i++) { - if ($types[$i] instanceof BenevolentUnionType) { - foreach ($types[$i]->getTypes() as $benevolentInnerType) { - $benevolentTypes[$benevolentInnerType->describe(VerbosityLevel::value())] = $benevolentInnerType; - } - array_splice($types, $i, 1, $types[$i]->getTypes()); - continue; - } - if (!($types[$i] instanceof UnionType)) { - continue; - } - - array_splice($types, $i, 1, $types[$i]->getTypes()); - } - - $typesCount = count($types); - $arrayTypes = []; - $arrayAccessoryTypes = []; - $scalarTypes = []; - $hasGenericScalarTypes = []; - for ($i = 0; $i < $typesCount; $i++) { - if ($types[$i] instanceof NeverType) { - unset($types[$i]); - continue; - } - if ($types[$i] instanceof ConstantScalarType) { - $type = $types[$i]; - $scalarTypes[get_class($type)][md5($type->describe(VerbosityLevel::cache()))] = $type; - unset($types[$i]); - continue; - } - if ($types[$i] instanceof BooleanType) { - $hasGenericScalarTypes[ConstantBooleanType::class] = true; - } - if ($types[$i] instanceof FloatType) { - $hasGenericScalarTypes[ConstantFloatType::class] = true; - } - if ($types[$i] instanceof IntegerType && !$types[$i] instanceof IntegerRangeType) { - $hasGenericScalarTypes[ConstantIntegerType::class] = true; - } - if ($types[$i] instanceof StringType && !$types[$i] instanceof ClassStringType) { - $hasGenericScalarTypes[ConstantStringType::class] = true; - } - if ($types[$i] instanceof IntersectionType) { - $intermediateArrayType = null; - $intermediateAccessoryTypes = []; - foreach ($types[$i]->getTypes() as $innerType) { - if ($innerType instanceof ArrayType) { - $intermediateArrayType = $innerType; - continue; - } - if ($innerType instanceof AccessoryType || $innerType instanceof CallableType) { - $intermediateAccessoryTypes[$innerType->describe(VerbosityLevel::cache())] = $innerType; - continue; - } - } - - if ($intermediateArrayType !== null) { - $arrayTypes[] = $intermediateArrayType; - $arrayAccessoryTypes[] = $intermediateAccessoryTypes; - unset($types[$i]); - continue; - } - } - if (!$types[$i] instanceof ArrayType) { - continue; - } - - $arrayTypes[] = $types[$i]; - $arrayAccessoryTypes[] = []; - unset($types[$i]); - } - - /** @var ArrayType[] $arrayTypes */ - $arrayTypes = $arrayTypes; - - $arrayAccessoryTypesToProcess = []; - if (count($arrayAccessoryTypes) > 1) { - $arrayAccessoryTypesToProcess = array_values(array_intersect_key(...$arrayAccessoryTypes)); - } elseif (count($arrayAccessoryTypes) > 0) { - $arrayAccessoryTypesToProcess = array_values($arrayAccessoryTypes[0]); - } - - $types = array_values( - array_merge( - $types, - self::processArrayTypes($arrayTypes, $arrayAccessoryTypesToProcess) - ) - ); - - // simplify string[] | int[] to (string|int)[] - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { - if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { - $types[$i] = new IterableType( - self::union($types[$i]->getIterableKeyType(), $types[$j]->getIterableKeyType()), - self::union($types[$i]->getIterableValueType(), $types[$j]->getIterableValueType()) - ); - array_splice($types, $j, 1); - continue 2; - } - } - } - - foreach ($scalarTypes as $classType => $scalarTypeItems) { - if (isset($hasGenericScalarTypes[$classType])) { - continue; - } - if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) { - $types[] = new BooleanType(); - continue; - } - foreach ($scalarTypeItems as $type) { - if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) { - $types[] = $type->generalize(); - - if ($type instanceof ConstantStringType) { - continue; - } - - break; - } - $types[] = $type; - } - } - - // transform A | A to A - // transform A | never to A - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { - if ($types[$i] instanceof IntegerRangeType) { - $type = $types[$i]->tryUnion($types[$j]); - if ($type !== null) { - $types[$i] = $type; - $i--; - array_splice($types, $j, 1); - continue 2; - } - } - - if ($types[$i] instanceof SubtractableType) { - $typeWithoutSubtractedTypeA = $types[$i]->getTypeWithoutSubtractedType(); - if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$j] instanceof MixedType) { - $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$j]); - } else { - $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$j]); - } - if ($isSuperType->yes()) { - $subtractedType = null; - if ($types[$j] instanceof SubtractableType) { - $subtractedType = $types[$j]->getSubtractedType(); - } - $types[$i] = self::intersectWithSubtractedType($types[$i], $subtractedType); - array_splice($types, $j--, 1); - continue 1; - } - } - - if ($types[$j] instanceof SubtractableType) { - $typeWithoutSubtractedTypeB = $types[$j]->getTypeWithoutSubtractedType(); - if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$i] instanceof MixedType) { - $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$i]); - } else { - $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$i]); - } - if ($isSuperType->yes()) { - $subtractedType = null; - if ($types[$i] instanceof SubtractableType) { - $subtractedType = $types[$i]->getSubtractedType(); - } - $types[$j] = self::intersectWithSubtractedType($types[$j], $subtractedType); - array_splice($types, $i--, 1); - continue 2; - } - } - - if ( - !$types[$j] instanceof ConstantArrayType - && $types[$j]->isSuperTypeOf($types[$i])->yes() - ) { - array_splice($types, $i--, 1); - continue 2; - } - - if ( - !$types[$i] instanceof ConstantArrayType - && $types[$i]->isSuperTypeOf($types[$j])->yes() - ) { - array_splice($types, $j--, 1); - continue 1; - } - } - } - - if (count($types) === 0) { - return new NeverType(); - - } elseif (count($types) === 1) { - return $types[0]; - } - - if (count($benevolentTypes) > 0) { - $tempTypes = $types; - foreach ($tempTypes as $i => $type) { - if (!isset($benevolentTypes[$type->describe(VerbosityLevel::value())])) { - break; - } - - unset($tempTypes[$i]); - } - - if (count($tempTypes) === 0) { - return new BenevolentUnionType($types); - } - } - - return new UnionType($types); - } - - private static function unionWithSubtractedType( - Type $type, - ?Type $subtractedType - ): Type - { - if ($subtractedType === null) { - return $type; - } - - if ($type instanceof SubtractableType) { - if ($type->getSubtractedType() === null) { - return $type; - } - - $subtractedType = self::union( - $type->getSubtractedType(), - $subtractedType - ); - if ($subtractedType instanceof NeverType) { - $subtractedType = null; - } - - return $type->changeSubtractedType($subtractedType); - } - - if ($subtractedType->isSuperTypeOf($type)->yes()) { - return new NeverType(); - } - - return self::remove($type, $subtractedType); - } - - private static function intersectWithSubtractedType( - SubtractableType $subtractableType, - ?Type $subtractedType - ): Type - { - if ($subtractableType->getSubtractedType() === null) { - return $subtractableType; - } - - if ($subtractedType === null) { - return $subtractableType->getTypeWithoutSubtractedType(); - } - - $subtractedType = self::intersect( - $subtractableType->getSubtractedType(), - $subtractedType - ); - if ($subtractedType instanceof NeverType) { - $subtractedType = null; - } - - return $subtractableType->changeSubtractedType($subtractedType); - } - - /** - * @param ArrayType[] $arrayTypes - * @param Type[] $accessoryTypes - * @return Type[] - */ - private static function processArrayTypes(array $arrayTypes, array $accessoryTypes): array - { - foreach ($arrayTypes as $arrayType) { - if (!$arrayType instanceof ConstantArrayType) { - continue; - } - if (count($arrayType->getKeyTypes()) > 0) { - continue; - } - - foreach ($accessoryTypes as $i => $accessoryType) { - if (!$accessoryType instanceof NonEmptyArrayType) { - continue; - } - - unset($accessoryTypes[$i]); - break 2; - } - } - if (count($arrayTypes) === 0) { - return []; - } elseif (count($arrayTypes) === 1) { - return [ - self::intersect($arrayTypes[0], ...$accessoryTypes), - ]; - } - - $keyTypesForGeneralArray = []; - $valueTypesForGeneralArray = []; - $generalArrayOccurred = false; - $constantKeyTypesNumbered = []; - - /** @var int|float $nextConstantKeyTypeIndex */ - $nextConstantKeyTypeIndex = 1; - - foreach ($arrayTypes as $arrayType) { - if (!$arrayType instanceof ConstantArrayType || $generalArrayOccurred) { - $keyTypesForGeneralArray[] = $arrayType->getKeyType(); - $valueTypesForGeneralArray[] = $arrayType->getItemType(); - $generalArrayOccurred = true; - continue; - } - - foreach ($arrayType->getKeyTypes() as $i => $keyType) { - $keyTypesForGeneralArray[] = $keyType; - $valueTypesForGeneralArray[] = $arrayType->getValueTypes()[$i]; - - $keyTypeValue = $keyType->getValue(); - if (array_key_exists($keyTypeValue, $constantKeyTypesNumbered)) { - continue; - } - - $constantKeyTypesNumbered[$keyTypeValue] = $nextConstantKeyTypeIndex; - $nextConstantKeyTypeIndex *= 2; - if (!is_int($nextConstantKeyTypeIndex)) { - $generalArrayOccurred = true; - continue; - } - } - } - - if ($generalArrayOccurred) { - return [ - self::intersect(new ArrayType( - self::union(...$keyTypesForGeneralArray), - self::union(...$valueTypesForGeneralArray) - ), ...$accessoryTypes), - ]; - } - - /** @var ConstantArrayType[] $arrayTypes */ - $arrayTypes = $arrayTypes; - - /** @var int[] $constantKeyTypesNumbered */ - $constantKeyTypesNumbered = $constantKeyTypesNumbered; - - $constantArraysBuckets = []; - foreach ($arrayTypes as $arrayTypeAgain) { - $arrayIndex = 0; - foreach ($arrayTypeAgain->getKeyTypes() as $keyType) { - $arrayIndex += $constantKeyTypesNumbered[$keyType->getValue()]; - } - - if (!array_key_exists($arrayIndex, $constantArraysBuckets)) { - $bucket = []; - foreach ($arrayTypeAgain->getKeyTypes() as $i => $keyType) { - $bucket[$keyType->getValue()] = [ - 'keyType' => $keyType, - 'valueType' => $arrayTypeAgain->getValueTypes()[$i], - 'optional' => $arrayTypeAgain->isOptionalKey($i), - ]; - } - $constantArraysBuckets[$arrayIndex] = $bucket; - continue; - } - - $bucket = $constantArraysBuckets[$arrayIndex]; - foreach ($arrayTypeAgain->getKeyTypes() as $i => $keyType) { - $bucket[$keyType->getValue()]['valueType'] = self::union( - $bucket[$keyType->getValue()]['valueType'], - $arrayTypeAgain->getValueTypes()[$i] - ); - $bucket[$keyType->getValue()]['optional'] = $bucket[$keyType->getValue()]['optional'] || $arrayTypeAgain->isOptionalKey($i); - } - - $constantArraysBuckets[$arrayIndex] = $bucket; - } - - $resultArrays = []; - foreach ($constantArraysBuckets as $bucket) { - $builder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($bucket as $data) { - $builder->setOffsetValueType($data['keyType'], $data['valueType'], $data['optional']); - } - - $resultArrays[] = self::intersect($builder->getArray(), ...$accessoryTypes); - } - - return self::reduceArrays($resultArrays); - } - - /** - * @param Type[] $constantArrays - * @return Type[] - */ - private static function reduceArrays(array $constantArrays): array - { - $newArrays = []; - $arraysToProcess = []; - foreach ($constantArrays as $constantArray) { - if (!$constantArray instanceof ConstantArrayType) { - $newArrays[] = $constantArray; - continue; - } - - $arraysToProcess[] = $constantArray; - } - - for ($i = 0; $i < count($arraysToProcess); $i++) { - for ($j = $i + 1; $j < count($arraysToProcess); $j++) { - if ($arraysToProcess[$j]->isKeysSupersetOf($arraysToProcess[$i])) { - $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]); - array_splice($arraysToProcess, $i--, 1); - continue 2; - - } elseif ($arraysToProcess[$i]->isKeysSupersetOf($arraysToProcess[$j])) { - $arraysToProcess[$i] = $arraysToProcess[$i]->mergeWith($arraysToProcess[$j]); - array_splice($arraysToProcess, $j--, 1); - continue 1; - } - } - } - - return array_merge($newArrays, $arraysToProcess); - } - - public static function intersect(Type ...$types): Type - { - usort($types, static function (Type $a, Type $b): int { - if (!$a instanceof UnionType || !$b instanceof UnionType) { - return 0; - } - - if ($a instanceof BenevolentUnionType) { - return 1; - } - if ($b instanceof BenevolentUnionType) { - return -1; - } - - return 0; - }); - // transform A & (B | C) to (A & B) | (A & C) - foreach ($types as $i => $type) { - if ($type instanceof UnionType) { - $topLevelUnionSubTypes = []; - foreach ($type->getTypes() as $innerUnionSubType) { - $topLevelUnionSubTypes[] = self::intersect( - $innerUnionSubType, - ...array_slice($types, 0, $i), - ...array_slice($types, $i + 1) - ); - } - - $union = self::union(...$topLevelUnionSubTypes); - if ($type instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union); - } - - return $union; - } - } - - // transform A & (B & C) to A & B & C - for ($i = 0; $i < count($types); $i++) { - $type = $types[$i]; - if (!($type instanceof IntersectionType)) { - continue; - } - - array_splice($types, $i--, 1, $type->getTypes()); - } - - // transform IntegerType & ConstantIntegerType to ConstantIntegerType - // transform Child & Parent to Child - // transform Object & ~null to Object - // transform A & A to A - // transform int[] & string to never - // transform callable & int to never - // transform A & ~A to never - // transform int & string to never - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { - if ($types[$j] instanceof SubtractableType) { - $typeWithoutSubtractedTypeA = $types[$j]->getTypeWithoutSubtractedType(); - - if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$i] instanceof MixedType) { - $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$i]); - } else { - $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$i]); - } - if ($isSuperTypeSubtractableA->yes()) { - $types[$i] = self::unionWithSubtractedType($types[$i], $types[$j]->getSubtractedType()); - array_splice($types, $j--, 1); - continue 1; - } - } - - if ($types[$i] instanceof SubtractableType) { - $typeWithoutSubtractedTypeB = $types[$i]->getTypeWithoutSubtractedType(); - - if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$j] instanceof MixedType) { - $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$j]); - } else { - $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$j]); - } - if ($isSuperTypeSubtractableB->yes()) { - $types[$j] = self::unionWithSubtractedType($types[$j], $types[$i]->getSubtractedType()); - array_splice($types, $i--, 1); - continue 2; - } - } - - if ($types[$i] instanceof IntegerRangeType) { - $intersectionType = $types[$i]->tryIntersect($types[$j]); - if ($intersectionType !== null) { - $types[$j] = $intersectionType; - array_splice($types, $i--, 1); - continue 2; - } - } - - if ($types[$j] instanceof IterableType) { - $isSuperTypeA = $types[$j]->isSuperTypeOfMixed($types[$i]); - } else { - $isSuperTypeA = $types[$j]->isSuperTypeOf($types[$i]); - } - - if ($isSuperTypeA->yes()) { - array_splice($types, $j--, 1); - continue; - } - - if ($types[$i] instanceof IterableType) { - $isSuperTypeB = $types[$i]->isSuperTypeOfMixed($types[$j]); - } else { - $isSuperTypeB = $types[$i]->isSuperTypeOf($types[$j]); - } - - if ($isSuperTypeB->maybe()) { - if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetType) { - $types[$i] = $types[$i]->makeOffsetRequired($types[$j]->getOffsetType()); - array_splice($types, $j--, 1); - continue; - } - - if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetType) { - $types[$j] = $types[$j]->makeOffsetRequired($types[$i]->getOffsetType()); - array_splice($types, $i--, 1); - continue 2; - } - - if ( - ($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) && - ($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType) - ) { - $keyType = self::intersect($types[$i]->getKeyType(), $types[$j]->getKeyType()); - $itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType()); - if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { - $types[$j] = new IterableType($keyType, $itemType); - } else { - $types[$j] = new ArrayType($keyType, $itemType); - } - array_splice($types, $i--, 1); - continue 2; - } - - continue; - } - - if ($isSuperTypeB->yes()) { - array_splice($types, $i--, 1); - continue 2; - } - - if ($isSuperTypeA->no()) { - return new NeverType(); - } - } - } - - if (count($types) === 1) { - return $types[0]; - - } - - return new IntersectionType($types); - } - + private const CONSTANT_SCALAR_UNION_THRESHOLD = 8; + + public static function addNull(Type $type): Type + { + return self::union($type, new NullType()); + } + + public static function remove(Type $fromType, Type $typeToRemove): Type + { + if ($typeToRemove instanceof UnionType) { + foreach ($typeToRemove->getTypes() as $unionTypeToRemove) { + $fromType = self::remove($fromType, $unionTypeToRemove); + } + return $fromType; + } + + if ($fromType instanceof UnionType) { + $innerTypes = []; + foreach ($fromType->getTypes() as $innerType) { + $innerTypes[] = self::remove($innerType, $typeToRemove); + } + + return self::union(...$innerTypes); + } + + $isSuperType = $typeToRemove->isSuperTypeOf($fromType); + if ($isSuperType->yes()) { + return new NeverType(); + } + if ($isSuperType->no()) { + return $fromType; + } + + if ($typeToRemove instanceof MixedType) { + $typeToRemoveSubtractedType = $typeToRemove->getSubtractedType(); + if ($typeToRemoveSubtractedType !== null) { + return self::intersect($fromType, $typeToRemoveSubtractedType); + } + } + + if ($fromType instanceof BooleanType) { + if ($typeToRemove instanceof ConstantBooleanType) { + return new ConstantBooleanType(!$typeToRemove->getValue()); + } + } elseif ($fromType instanceof IterableType) { + $arrayType = new ArrayType(new MixedType(), new MixedType()); + if ($typeToRemove->isSuperTypeOf($arrayType)->yes()) { + return new GenericObjectType(\Traversable::class, [ + $fromType->getIterableKeyType(), + $fromType->getIterableValueType(), + ]); + } + + $traversableType = new ObjectType(\Traversable::class); + if ($typeToRemove->isSuperTypeOf($traversableType)->yes()) { + return new ArrayType($fromType->getIterableKeyType(), $fromType->getIterableValueType()); + } + } elseif ($fromType instanceof IntegerRangeType) { + $type = $fromType->tryRemove($typeToRemove); + if ($type !== null) { + return $type; + } + } elseif ($fromType instanceof IntegerType) { + if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { + if ($typeToRemove instanceof IntegerRangeType) { + $removeValueMin = $typeToRemove->getMin(); + $removeValueMax = $typeToRemove->getMax(); + } else { + $removeValueMin = $typeToRemove->getValue(); + $removeValueMax = $typeToRemove->getValue(); + } + $lowerPart = $removeValueMin !== null ? IntegerRangeType::fromInterval(null, $removeValueMin, -1) : null; + $upperPart = $removeValueMax !== null ? IntegerRangeType::fromInterval($removeValueMax, null, +1) : null; + if ($lowerPart !== null && $upperPart !== null) { + return self::union($lowerPart, $upperPart); + } + return $lowerPart ?? $upperPart ?? new NeverType(); + } + } elseif ($fromType->isArray()->yes()) { + if ($typeToRemove instanceof ConstantArrayType && $typeToRemove->isIterableAtLeastOnce()->no()) { + return self::intersect($fromType, new NonEmptyArrayType()); + } + + if ($typeToRemove instanceof NonEmptyArrayType) { + return new ConstantArrayType([], []); + } + + if ($fromType instanceof ConstantArrayType && $typeToRemove instanceof HasOffsetType) { + return $fromType->unsetOffset($typeToRemove->getOffsetType()); + } + } elseif ($fromType instanceof SubtractableType) { + $typeToSubtractFrom = $fromType; + if ($fromType instanceof TemplateType) { + $typeToSubtractFrom = $fromType->getBound(); + } + + if ($typeToSubtractFrom->isSuperTypeOf($typeToRemove)->yes()) { + return $fromType->subtract($typeToRemove); + } + } + + return $fromType; + } + + public static function removeNull(Type $type): Type + { + if (self::containsNull($type)) { + return self::remove($type, new NullType()); + } + + return $type; + } + + public static function containsNull(Type $type): bool + { + if ($type instanceof UnionType) { + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof NullType) { + return true; + } + } + + return false; + } + + return $type instanceof NullType; + } + + public static function union(Type ...$types): Type + { + $benevolentTypes = []; + // transform A | (B | C) to A | B | C + for ($i = 0; $i < count($types); $i++) { + if ($types[$i] instanceof BenevolentUnionType) { + foreach ($types[$i]->getTypes() as $benevolentInnerType) { + $benevolentTypes[$benevolentInnerType->describe(VerbosityLevel::value())] = $benevolentInnerType; + } + array_splice($types, $i, 1, $types[$i]->getTypes()); + continue; + } + if (!($types[$i] instanceof UnionType)) { + continue; + } + + array_splice($types, $i, 1, $types[$i]->getTypes()); + } + + $typesCount = count($types); + $arrayTypes = []; + $arrayAccessoryTypes = []; + $scalarTypes = []; + $hasGenericScalarTypes = []; + for ($i = 0; $i < $typesCount; $i++) { + if ($types[$i] instanceof NeverType) { + unset($types[$i]); + continue; + } + if ($types[$i] instanceof ConstantScalarType) { + $type = $types[$i]; + $scalarTypes[get_class($type)][md5($type->describe(VerbosityLevel::cache()))] = $type; + unset($types[$i]); + continue; + } + if ($types[$i] instanceof BooleanType) { + $hasGenericScalarTypes[ConstantBooleanType::class] = true; + } + if ($types[$i] instanceof FloatType) { + $hasGenericScalarTypes[ConstantFloatType::class] = true; + } + if ($types[$i] instanceof IntegerType && !$types[$i] instanceof IntegerRangeType) { + $hasGenericScalarTypes[ConstantIntegerType::class] = true; + } + if ($types[$i] instanceof StringType && !$types[$i] instanceof ClassStringType) { + $hasGenericScalarTypes[ConstantStringType::class] = true; + } + if ($types[$i] instanceof IntersectionType) { + $intermediateArrayType = null; + $intermediateAccessoryTypes = []; + foreach ($types[$i]->getTypes() as $innerType) { + if ($innerType instanceof ArrayType) { + $intermediateArrayType = $innerType; + continue; + } + if ($innerType instanceof AccessoryType || $innerType instanceof CallableType) { + $intermediateAccessoryTypes[$innerType->describe(VerbosityLevel::cache())] = $innerType; + continue; + } + } + + if ($intermediateArrayType !== null) { + $arrayTypes[] = $intermediateArrayType; + $arrayAccessoryTypes[] = $intermediateAccessoryTypes; + unset($types[$i]); + continue; + } + } + if (!$types[$i] instanceof ArrayType) { + continue; + } + + $arrayTypes[] = $types[$i]; + $arrayAccessoryTypes[] = []; + unset($types[$i]); + } + + /** @var ArrayType[] $arrayTypes */ + $arrayTypes = $arrayTypes; + + $arrayAccessoryTypesToProcess = []; + if (count($arrayAccessoryTypes) > 1) { + $arrayAccessoryTypesToProcess = array_values(array_intersect_key(...$arrayAccessoryTypes)); + } elseif (count($arrayAccessoryTypes) > 0) { + $arrayAccessoryTypesToProcess = array_values($arrayAccessoryTypes[0]); + } + + $types = array_values( + array_merge( + $types, + self::processArrayTypes($arrayTypes, $arrayAccessoryTypesToProcess) + ) + ); + + // simplify string[] | int[] to (string|int)[] + for ($i = 0; $i < count($types); $i++) { + for ($j = $i + 1; $j < count($types); $j++) { + if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { + $types[$i] = new IterableType( + self::union($types[$i]->getIterableKeyType(), $types[$j]->getIterableKeyType()), + self::union($types[$i]->getIterableValueType(), $types[$j]->getIterableValueType()) + ); + array_splice($types, $j, 1); + continue 2; + } + } + } + + foreach ($scalarTypes as $classType => $scalarTypeItems) { + if (isset($hasGenericScalarTypes[$classType])) { + continue; + } + if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) { + $types[] = new BooleanType(); + continue; + } + foreach ($scalarTypeItems as $type) { + if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) { + $types[] = $type->generalize(); + + if ($type instanceof ConstantStringType) { + continue; + } + + break; + } + $types[] = $type; + } + } + + // transform A | A to A + // transform A | never to A + for ($i = 0; $i < count($types); $i++) { + for ($j = $i + 1; $j < count($types); $j++) { + if ($types[$i] instanceof IntegerRangeType) { + $type = $types[$i]->tryUnion($types[$j]); + if ($type !== null) { + $types[$i] = $type; + $i--; + array_splice($types, $j, 1); + continue 2; + } + } + + if ($types[$i] instanceof SubtractableType) { + $typeWithoutSubtractedTypeA = $types[$i]->getTypeWithoutSubtractedType(); + if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$j] instanceof MixedType) { + $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$j]); + } else { + $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$j]); + } + if ($isSuperType->yes()) { + $subtractedType = null; + if ($types[$j] instanceof SubtractableType) { + $subtractedType = $types[$j]->getSubtractedType(); + } + $types[$i] = self::intersectWithSubtractedType($types[$i], $subtractedType); + array_splice($types, $j--, 1); + continue 1; + } + } + + if ($types[$j] instanceof SubtractableType) { + $typeWithoutSubtractedTypeB = $types[$j]->getTypeWithoutSubtractedType(); + if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$i] instanceof MixedType) { + $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$i]); + } else { + $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$i]); + } + if ($isSuperType->yes()) { + $subtractedType = null; + if ($types[$i] instanceof SubtractableType) { + $subtractedType = $types[$i]->getSubtractedType(); + } + $types[$j] = self::intersectWithSubtractedType($types[$j], $subtractedType); + array_splice($types, $i--, 1); + continue 2; + } + } + + if ( + !$types[$j] instanceof ConstantArrayType + && $types[$j]->isSuperTypeOf($types[$i])->yes() + ) { + array_splice($types, $i--, 1); + continue 2; + } + + if ( + !$types[$i] instanceof ConstantArrayType + && $types[$i]->isSuperTypeOf($types[$j])->yes() + ) { + array_splice($types, $j--, 1); + continue 1; + } + } + } + + if (count($types) === 0) { + return new NeverType(); + } elseif (count($types) === 1) { + return $types[0]; + } + + if (count($benevolentTypes) > 0) { + $tempTypes = $types; + foreach ($tempTypes as $i => $type) { + if (!isset($benevolentTypes[$type->describe(VerbosityLevel::value())])) { + break; + } + + unset($tempTypes[$i]); + } + + if (count($tempTypes) === 0) { + return new BenevolentUnionType($types); + } + } + + return new UnionType($types); + } + + private static function unionWithSubtractedType( + Type $type, + ?Type $subtractedType + ): Type { + if ($subtractedType === null) { + return $type; + } + + if ($type instanceof SubtractableType) { + if ($type->getSubtractedType() === null) { + return $type; + } + + $subtractedType = self::union( + $type->getSubtractedType(), + $subtractedType + ); + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + return $type->changeSubtractedType($subtractedType); + } + + if ($subtractedType->isSuperTypeOf($type)->yes()) { + return new NeverType(); + } + + return self::remove($type, $subtractedType); + } + + private static function intersectWithSubtractedType( + SubtractableType $subtractableType, + ?Type $subtractedType + ): Type { + if ($subtractableType->getSubtractedType() === null) { + return $subtractableType; + } + + if ($subtractedType === null) { + return $subtractableType->getTypeWithoutSubtractedType(); + } + + $subtractedType = self::intersect( + $subtractableType->getSubtractedType(), + $subtractedType + ); + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + return $subtractableType->changeSubtractedType($subtractedType); + } + + /** + * @param ArrayType[] $arrayTypes + * @param Type[] $accessoryTypes + * @return Type[] + */ + private static function processArrayTypes(array $arrayTypes, array $accessoryTypes): array + { + foreach ($arrayTypes as $arrayType) { + if (!$arrayType instanceof ConstantArrayType) { + continue; + } + if (count($arrayType->getKeyTypes()) > 0) { + continue; + } + + foreach ($accessoryTypes as $i => $accessoryType) { + if (!$accessoryType instanceof NonEmptyArrayType) { + continue; + } + + unset($accessoryTypes[$i]); + break 2; + } + } + if (count($arrayTypes) === 0) { + return []; + } elseif (count($arrayTypes) === 1) { + return [ + self::intersect($arrayTypes[0], ...$accessoryTypes), + ]; + } + + $keyTypesForGeneralArray = []; + $valueTypesForGeneralArray = []; + $generalArrayOccurred = false; + $constantKeyTypesNumbered = []; + + /** @var int|float $nextConstantKeyTypeIndex */ + $nextConstantKeyTypeIndex = 1; + + foreach ($arrayTypes as $arrayType) { + if (!$arrayType instanceof ConstantArrayType || $generalArrayOccurred) { + $keyTypesForGeneralArray[] = $arrayType->getKeyType(); + $valueTypesForGeneralArray[] = $arrayType->getItemType(); + $generalArrayOccurred = true; + continue; + } + + foreach ($arrayType->getKeyTypes() as $i => $keyType) { + $keyTypesForGeneralArray[] = $keyType; + $valueTypesForGeneralArray[] = $arrayType->getValueTypes()[$i]; + + $keyTypeValue = $keyType->getValue(); + if (array_key_exists($keyTypeValue, $constantKeyTypesNumbered)) { + continue; + } + + $constantKeyTypesNumbered[$keyTypeValue] = $nextConstantKeyTypeIndex; + $nextConstantKeyTypeIndex *= 2; + if (!is_int($nextConstantKeyTypeIndex)) { + $generalArrayOccurred = true; + continue; + } + } + } + + if ($generalArrayOccurred) { + return [ + self::intersect(new ArrayType( + self::union(...$keyTypesForGeneralArray), + self::union(...$valueTypesForGeneralArray) + ), ...$accessoryTypes), + ]; + } + + /** @var ConstantArrayType[] $arrayTypes */ + $arrayTypes = $arrayTypes; + + /** @var int[] $constantKeyTypesNumbered */ + $constantKeyTypesNumbered = $constantKeyTypesNumbered; + + $constantArraysBuckets = []; + foreach ($arrayTypes as $arrayTypeAgain) { + $arrayIndex = 0; + foreach ($arrayTypeAgain->getKeyTypes() as $keyType) { + $arrayIndex += $constantKeyTypesNumbered[$keyType->getValue()]; + } + + if (!array_key_exists($arrayIndex, $constantArraysBuckets)) { + $bucket = []; + foreach ($arrayTypeAgain->getKeyTypes() as $i => $keyType) { + $bucket[$keyType->getValue()] = [ + 'keyType' => $keyType, + 'valueType' => $arrayTypeAgain->getValueTypes()[$i], + 'optional' => $arrayTypeAgain->isOptionalKey($i), + ]; + } + $constantArraysBuckets[$arrayIndex] = $bucket; + continue; + } + + $bucket = $constantArraysBuckets[$arrayIndex]; + foreach ($arrayTypeAgain->getKeyTypes() as $i => $keyType) { + $bucket[$keyType->getValue()]['valueType'] = self::union( + $bucket[$keyType->getValue()]['valueType'], + $arrayTypeAgain->getValueTypes()[$i] + ); + $bucket[$keyType->getValue()]['optional'] = $bucket[$keyType->getValue()]['optional'] || $arrayTypeAgain->isOptionalKey($i); + } + + $constantArraysBuckets[$arrayIndex] = $bucket; + } + + $resultArrays = []; + foreach ($constantArraysBuckets as $bucket) { + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($bucket as $data) { + $builder->setOffsetValueType($data['keyType'], $data['valueType'], $data['optional']); + } + + $resultArrays[] = self::intersect($builder->getArray(), ...$accessoryTypes); + } + + return self::reduceArrays($resultArrays); + } + + /** + * @param Type[] $constantArrays + * @return Type[] + */ + private static function reduceArrays(array $constantArrays): array + { + $newArrays = []; + $arraysToProcess = []; + foreach ($constantArrays as $constantArray) { + if (!$constantArray instanceof ConstantArrayType) { + $newArrays[] = $constantArray; + continue; + } + + $arraysToProcess[] = $constantArray; + } + + for ($i = 0; $i < count($arraysToProcess); $i++) { + for ($j = $i + 1; $j < count($arraysToProcess); $j++) { + if ($arraysToProcess[$j]->isKeysSupersetOf($arraysToProcess[$i])) { + $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]); + array_splice($arraysToProcess, $i--, 1); + continue 2; + } elseif ($arraysToProcess[$i]->isKeysSupersetOf($arraysToProcess[$j])) { + $arraysToProcess[$i] = $arraysToProcess[$i]->mergeWith($arraysToProcess[$j]); + array_splice($arraysToProcess, $j--, 1); + continue 1; + } + } + } + + return array_merge($newArrays, $arraysToProcess); + } + + public static function intersect(Type ...$types): Type + { + usort($types, static function (Type $a, Type $b): int { + if (!$a instanceof UnionType || !$b instanceof UnionType) { + return 0; + } + + if ($a instanceof BenevolentUnionType) { + return 1; + } + if ($b instanceof BenevolentUnionType) { + return -1; + } + + return 0; + }); + // transform A & (B | C) to (A & B) | (A & C) + foreach ($types as $i => $type) { + if ($type instanceof UnionType) { + $topLevelUnionSubTypes = []; + foreach ($type->getTypes() as $innerUnionSubType) { + $topLevelUnionSubTypes[] = self::intersect( + $innerUnionSubType, + ...array_slice($types, 0, $i), + ...array_slice($types, $i + 1) + ); + } + + $union = self::union(...$topLevelUnionSubTypes); + if ($type instanceof BenevolentUnionType) { + return TypeUtils::toBenevolentUnion($union); + } + + return $union; + } + } + + // transform A & (B & C) to A & B & C + for ($i = 0; $i < count($types); $i++) { + $type = $types[$i]; + if (!($type instanceof IntersectionType)) { + continue; + } + + array_splice($types, $i--, 1, $type->getTypes()); + } + + // transform IntegerType & ConstantIntegerType to ConstantIntegerType + // transform Child & Parent to Child + // transform Object & ~null to Object + // transform A & A to A + // transform int[] & string to never + // transform callable & int to never + // transform A & ~A to never + // transform int & string to never + for ($i = 0; $i < count($types); $i++) { + for ($j = $i + 1; $j < count($types); $j++) { + if ($types[$j] instanceof SubtractableType) { + $typeWithoutSubtractedTypeA = $types[$j]->getTypeWithoutSubtractedType(); + + if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$i] instanceof MixedType) { + $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$i]); + } else { + $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$i]); + } + if ($isSuperTypeSubtractableA->yes()) { + $types[$i] = self::unionWithSubtractedType($types[$i], $types[$j]->getSubtractedType()); + array_splice($types, $j--, 1); + continue 1; + } + } + + if ($types[$i] instanceof SubtractableType) { + $typeWithoutSubtractedTypeB = $types[$i]->getTypeWithoutSubtractedType(); + + if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$j] instanceof MixedType) { + $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$j]); + } else { + $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$j]); + } + if ($isSuperTypeSubtractableB->yes()) { + $types[$j] = self::unionWithSubtractedType($types[$j], $types[$i]->getSubtractedType()); + array_splice($types, $i--, 1); + continue 2; + } + } + + if ($types[$i] instanceof IntegerRangeType) { + $intersectionType = $types[$i]->tryIntersect($types[$j]); + if ($intersectionType !== null) { + $types[$j] = $intersectionType; + array_splice($types, $i--, 1); + continue 2; + } + } + + if ($types[$j] instanceof IterableType) { + $isSuperTypeA = $types[$j]->isSuperTypeOfMixed($types[$i]); + } else { + $isSuperTypeA = $types[$j]->isSuperTypeOf($types[$i]); + } + + if ($isSuperTypeA->yes()) { + array_splice($types, $j--, 1); + continue; + } + + if ($types[$i] instanceof IterableType) { + $isSuperTypeB = $types[$i]->isSuperTypeOfMixed($types[$j]); + } else { + $isSuperTypeB = $types[$i]->isSuperTypeOf($types[$j]); + } + + if ($isSuperTypeB->maybe()) { + if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetType) { + $types[$i] = $types[$i]->makeOffsetRequired($types[$j]->getOffsetType()); + array_splice($types, $j--, 1); + continue; + } + + if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetType) { + $types[$j] = $types[$j]->makeOffsetRequired($types[$i]->getOffsetType()); + array_splice($types, $i--, 1); + continue 2; + } + + if ( + ($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) && + ($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType) + ) { + $keyType = self::intersect($types[$i]->getKeyType(), $types[$j]->getKeyType()); + $itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType()); + if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { + $types[$j] = new IterableType($keyType, $itemType); + } else { + $types[$j] = new ArrayType($keyType, $itemType); + } + array_splice($types, $i--, 1); + continue 2; + } + + continue; + } + + if ($isSuperTypeB->yes()) { + array_splice($types, $i--, 1); + continue 2; + } + + if ($isSuperTypeA->no()) { + return new NeverType(); + } + } + } + + if (count($types) === 1) { + return $types[0]; + } + + return new IntersectionType($types); + } } diff --git a/src/Type/TypeTraverser.php b/src/Type/TypeTraverser.php index f1c6855fc4..5ec816f701 100644 --- a/src/Type/TypeTraverser.php +++ b/src/Type/TypeTraverser.php @@ -1,60 +1,60 @@ -getValue()); - * } - * // Replaces the current type, and don't traverse - * return new MixedType(); - * }); - * - * @param callable(Type $type, callable(Type): Type $traverse): Type $cb - */ - public static function map(Type $type, callable $cb): Type - { - $self = new self($cb); - - return $self->mapInternal($type); - } - - /** @param callable(Type $type, callable(Type): Type $traverse): Type $cb */ - private function __construct(callable $cb) - { - $this->cb = $cb; - } - - /** @internal */ - public function mapInternal(Type $type): Type - { - return ($this->cb)($type, [$this, 'traverseInternal']); - } - - /** @internal */ - public function traverseInternal(Type $type): Type - { - return $type->traverse([$this, 'mapInternal']); - } - + /** @var callable(Type $type, callable(Type): Type $traverse): Type */ + private $cb; + + /** + * Map a Type recursively + * + * For every Type instance, the callback can return a new Type, and/or + * decide to traverse inner types or to ignore them. + * + * The following example converts constant strings to objects, while + * preserving unions and intersections: + * + * TypeTraverser::map($type, function (Type $type, callable $traverse): Type { + * if ($type instanceof UnionType || $type instanceof IntersectionType) { + * // Traverse inner types + * return $traverse($type); + * } + * if ($type instanceof ConstantStringType) { + * // Replaces the current type, and don't traverse + * return new ObjectType($type->getValue()); + * } + * // Replaces the current type, and don't traverse + * return new MixedType(); + * }); + * + * @param callable(Type $type, callable(Type): Type $traverse): Type $cb + */ + public static function map(Type $type, callable $cb): Type + { + $self = new self($cb); + + return $self->mapInternal($type); + } + + /** @param callable(Type $type, callable(Type): Type $traverse): Type $cb */ + private function __construct(callable $cb) + { + $this->cb = $cb; + } + + /** @internal */ + public function mapInternal(Type $type): Type + { + return ($this->cb)($type, [$this, 'traverseInternal']); + } + + /** @internal */ + public function traverseInternal(Type $type): Type + { + return $type->traverse([$this, 'mapInternal']); + } } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 3dc1327425..0a2d4554dc 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -1,4 +1,6 @@ -getAllArrays(); - } - - if ($type instanceof ArrayType) { - return [$type]; - } - - if ($type instanceof UnionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ArrayType) { - return []; - } - foreach (self::getArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - if ($type instanceof IntersectionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ArrayType) { - continue; - } - foreach (self::getArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - return []; - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\Constant\ConstantArrayType[] - */ - public static function getConstantArrays(Type $type): array - { - if ($type instanceof ConstantArrayType) { - return $type->getAllArrays(); - } - - if ($type instanceof UnionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ConstantArrayType) { - return []; - } - foreach (self::getConstantArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - return []; - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\Constant\ConstantStringType[] - */ - public static function getConstantStrings(Type $type): array - { - return self::map(ConstantStringType::class, $type, false); - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\ConstantType[] - */ - public static function getConstantTypes(Type $type): array - { - return self::map(ConstantType::class, $type, false); - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\ConstantType[] - */ - public static function getAnyConstantTypes(Type $type): array - { - return self::map(ConstantType::class, $type, false, false); - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\ArrayType[] - */ - public static function getAnyArrays(Type $type): array - { - return self::map(ArrayType::class, $type, true, false); - } - - public static function generalizeType(Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { - if ($type instanceof ConstantType) { - return $type->generalize(); - } - - return $traverse($type); - }); - } - - /** - * @param Type $type - * @return string[] - */ - public static function getDirectClassNames(Type $type): array - { - if ($type instanceof TypeWithClassName) { - return [$type->getClassName()]; - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - $classNames = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof TypeWithClassName) { - continue; - } - - $classNames[] = $innerType->getClassName(); - } - - return $classNames; - } - - return []; - } - - /** - * @param Type $type - * @return \PHPStan\Type\ConstantScalarType[] - */ - public static function getConstantScalars(Type $type): array - { - return self::map(ConstantScalarType::class, $type, false); - } - - /** - * @internal - * @param Type $type - * @return ConstantArrayType[] - */ - public static function getOldConstantArrays(Type $type): array - { - return self::map(ConstantArrayType::class, $type, false); - } - - /** - * @param string $typeClass - * @param Type $type - * @param bool $inspectIntersections - * @param bool $stopOnUnmatched - * @return mixed[] - */ - private static function map( - string $typeClass, - Type $type, - bool $inspectIntersections, - bool $stopOnUnmatched = true - ): array - { - if ($type instanceof $typeClass) { - return [$type]; - } - - if ($type instanceof UnionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof $typeClass) { - if ($stopOnUnmatched) { - return []; - } - - continue; - } - - $matchingTypes[] = $innerType; - } - - return $matchingTypes; - } - - if ($inspectIntersections && $type instanceof IntersectionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof $typeClass) { - if ($stopOnUnmatched) { - return []; - } - - continue; - } - - $matchingTypes[] = $innerType; - } - - return $matchingTypes; - } - - return []; - } - - public static function toBenevolentUnion(Type $type): Type - { - if ($type instanceof BenevolentUnionType) { - return $type; - } - - if ($type instanceof UnionType) { - return new BenevolentUnionType($type->getTypes()); - } - - return $type; - } - - /** - * @param Type $type - * @return Type[] - */ - public static function flattenTypes(Type $type): array - { - if ($type instanceof ConstantArrayType) { - return $type->getAllArrays(); - } - - if ($type instanceof UnionType) { - $types = []; - foreach ($type->getTypes() as $innerType) { - if ($innerType instanceof ConstantArrayType) { - foreach ($innerType->getAllArrays() as $array) { - $types[] = $array; - } - continue; - } - - $types[] = $innerType; - } - - return $types; - } - - return [$type]; - } - - public static function findThisType(Type $type): ?ThisType - { - if ($type instanceof ThisType) { - return $type; - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - foreach ($type->getTypes() as $innerType) { - $thisType = self::findThisType($innerType); - if ($thisType !== null) { - return $thisType; - } - } - } - - return null; - } - - /** - * @param Type $type - * @return HasPropertyType[] - */ - public static function getHasPropertyTypes(Type $type): array - { - if ($type instanceof HasPropertyType) { - return [$type]; - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - $hasPropertyTypes = [[]]; - foreach ($type->getTypes() as $innerType) { - $hasPropertyTypes[] = self::getHasPropertyTypes($innerType); - } - - return array_merge(...$hasPropertyTypes); - } - - return []; - } - - /** - * @param \PHPStan\Type\Type $type - * @return \PHPStan\Type\Accessory\AccessoryType[] - */ - public static function getAccessoryTypes(Type $type): array - { - return self::map(AccessoryType::class, $type, true, false); - } - - public static function containsCallable(Type $type): bool - { - if ($type->isCallable()->yes()) { - return true; - } - - if ($type instanceof UnionType) { - foreach ($type->getTypes() as $innerType) { - if ($innerType->isCallable()->yes()) { - return true; - } - } - } - - return false; - } - + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\ArrayType[] + */ + public static function getArrays(Type $type): array + { + if ($type instanceof ConstantArrayType) { + return $type->getAllArrays(); + } + + if ($type instanceof ArrayType) { + return [$type]; + } + + if ($type instanceof UnionType) { + $matchingTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof ArrayType) { + return []; + } + foreach (self::getArrays($innerType) as $innerInnerType) { + $matchingTypes[] = $innerInnerType; + } + } + + return $matchingTypes; + } + + if ($type instanceof IntersectionType) { + $matchingTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof ArrayType) { + continue; + } + foreach (self::getArrays($innerType) as $innerInnerType) { + $matchingTypes[] = $innerInnerType; + } + } + + return $matchingTypes; + } + + return []; + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\Constant\ConstantArrayType[] + */ + public static function getConstantArrays(Type $type): array + { + if ($type instanceof ConstantArrayType) { + return $type->getAllArrays(); + } + + if ($type instanceof UnionType) { + $matchingTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof ConstantArrayType) { + return []; + } + foreach (self::getConstantArrays($innerType) as $innerInnerType) { + $matchingTypes[] = $innerInnerType; + } + } + + return $matchingTypes; + } + + return []; + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\Constant\ConstantStringType[] + */ + public static function getConstantStrings(Type $type): array + { + return self::map(ConstantStringType::class, $type, false); + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\ConstantType[] + */ + public static function getConstantTypes(Type $type): array + { + return self::map(ConstantType::class, $type, false); + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\ConstantType[] + */ + public static function getAnyConstantTypes(Type $type): array + { + return self::map(ConstantType::class, $type, false, false); + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\ArrayType[] + */ + public static function getAnyArrays(Type $type): array + { + return self::map(ArrayType::class, $type, true, false); + } + + public static function generalizeType(Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof ConstantType) { + return $type->generalize(); + } + + return $traverse($type); + }); + } + + /** + * @param Type $type + * @return string[] + */ + public static function getDirectClassNames(Type $type): array + { + if ($type instanceof TypeWithClassName) { + return [$type->getClassName()]; + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + $classNames = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof TypeWithClassName) { + continue; + } + + $classNames[] = $innerType->getClassName(); + } + + return $classNames; + } + + return []; + } + + /** + * @param Type $type + * @return \PHPStan\Type\ConstantScalarType[] + */ + public static function getConstantScalars(Type $type): array + { + return self::map(ConstantScalarType::class, $type, false); + } + + /** + * @internal + * @param Type $type + * @return ConstantArrayType[] + */ + public static function getOldConstantArrays(Type $type): array + { + return self::map(ConstantArrayType::class, $type, false); + } + + /** + * @param string $typeClass + * @param Type $type + * @param bool $inspectIntersections + * @param bool $stopOnUnmatched + * @return mixed[] + */ + private static function map( + string $typeClass, + Type $type, + bool $inspectIntersections, + bool $stopOnUnmatched = true + ): array { + if ($type instanceof $typeClass) { + return [$type]; + } + + if ($type instanceof UnionType) { + $matchingTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof $typeClass) { + if ($stopOnUnmatched) { + return []; + } + + continue; + } + + $matchingTypes[] = $innerType; + } + + return $matchingTypes; + } + + if ($inspectIntersections && $type instanceof IntersectionType) { + $matchingTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType instanceof $typeClass) { + if ($stopOnUnmatched) { + return []; + } + + continue; + } + + $matchingTypes[] = $innerType; + } + + return $matchingTypes; + } + + return []; + } + + public static function toBenevolentUnion(Type $type): Type + { + if ($type instanceof BenevolentUnionType) { + return $type; + } + + if ($type instanceof UnionType) { + return new BenevolentUnionType($type->getTypes()); + } + + return $type; + } + + /** + * @param Type $type + * @return Type[] + */ + public static function flattenTypes(Type $type): array + { + if ($type instanceof ConstantArrayType) { + return $type->getAllArrays(); + } + + if ($type instanceof UnionType) { + $types = []; + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof ConstantArrayType) { + foreach ($innerType->getAllArrays() as $array) { + $types[] = $array; + } + continue; + } + + $types[] = $innerType; + } + + return $types; + } + + return [$type]; + } + + public static function findThisType(Type $type): ?ThisType + { + if ($type instanceof ThisType) { + return $type; + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + foreach ($type->getTypes() as $innerType) { + $thisType = self::findThisType($innerType); + if ($thisType !== null) { + return $thisType; + } + } + } + + return null; + } + + /** + * @param Type $type + * @return HasPropertyType[] + */ + public static function getHasPropertyTypes(Type $type): array + { + if ($type instanceof HasPropertyType) { + return [$type]; + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + $hasPropertyTypes = [[]]; + foreach ($type->getTypes() as $innerType) { + $hasPropertyTypes[] = self::getHasPropertyTypes($innerType); + } + + return array_merge(...$hasPropertyTypes); + } + + return []; + } + + /** + * @param \PHPStan\Type\Type $type + * @return \PHPStan\Type\Accessory\AccessoryType[] + */ + public static function getAccessoryTypes(Type $type): array + { + return self::map(AccessoryType::class, $type, true, false); + } + + public static function containsCallable(Type $type): bool + { + if ($type->isCallable()->yes()) { + return true; + } + + if ($type instanceof UnionType) { + foreach ($type->getTypes() as $innerType) { + if ($innerType->isCallable()->yes()) { + return true; + } + } + } + + return false; + } } diff --git a/src/Type/TypeWithClassName.php b/src/Type/TypeWithClassName.php index d54fec9e22..6d35c41ae0 100644 --- a/src/Type/TypeWithClassName.php +++ b/src/Type/TypeWithClassName.php @@ -1,4 +1,6 @@ -hasClass($selfClass)) { - $classReflection = $broker->getClass($selfClass); - if ($classReflection->getParentClass() !== false) { - return new ObjectType($classReflection->getParentClass()->getName()); - } - } - return new NonexistentParentClassType(); - case 'static': - return $selfClass !== null ? new StaticType($selfClass) : new ErrorType(); - case 'null': - return new NullType(); - default: - return new ObjectType($typeString); - } - } - - public static function decideTypeFromReflection( - ?\ReflectionType $reflectionType, - ?Type $phpDocType = null, - ?string $selfClass = null, - bool $isVariadic = false - ): Type - { - if ($reflectionType === null) { - if ($isVariadic && $phpDocType instanceof ArrayType) { - $phpDocType = $phpDocType->getItemType(); - } - return $phpDocType ?? new MixedType(); - } - - if ($reflectionType instanceof ReflectionUnionType) { - $type = TypeCombinator::union(...array_map(static function (ReflectionType $type) use ($selfClass): Type { - return self::decideTypeFromReflection($type, null, $selfClass, false); - }, $reflectionType->getTypes())); - - return self::decideType($type, $phpDocType); - } - - if (!$reflectionType instanceof ReflectionNamedType) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType))); - } - - $reflectionTypeString = $reflectionType->getName(); - if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\object')) { - $reflectionTypeString = 'object'; - } - if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\mixed')) { - $reflectionTypeString = 'mixed'; - } - if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\false')) { - $reflectionTypeString = 'false'; - } - if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\null')) { - $reflectionTypeString = 'null'; - } - - $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass); - if ($reflectionType->allowsNull()) { - $type = TypeCombinator::addNull($type); - } elseif ($phpDocType !== null) { - $phpDocType = TypeCombinator::removeNull($phpDocType); - } - - return self::decideType($type, $phpDocType); - } - - public static function decideType( - Type $type, - ?Type $phpDocType = null - ): Type - { - if ($phpDocType !== null && !$phpDocType instanceof ErrorType) { - if ($type instanceof VoidType) { - if ($phpDocType instanceof NeverType && $phpDocType->isExplicit()) { - return $phpDocType; - } - - return new VoidType(); - } - if ( - $type instanceof MixedType - && !$type->isExplicitMixed() - && $phpDocType instanceof VoidType - ) { - return $phpDocType; - } - - if (TypeCombinator::removeNull($type) instanceof IterableType) { - if ($phpDocType instanceof UnionType) { - $innerTypes = []; - foreach ($phpDocType->getTypes() as $innerType) { - if ($innerType instanceof ArrayType) { - $innerTypes[] = new IterableType( - $innerType->getKeyType(), - $innerType->getItemType() - ); - } else { - $innerTypes[] = $innerType; - } - } - $phpDocType = new UnionType($innerTypes); - } elseif ($phpDocType instanceof ArrayType) { - $phpDocType = new IterableType( - $phpDocType->getKeyType(), - $phpDocType->getItemType() - ); - } - } - - if ( - (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) - && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() - ) { - $resultType = $phpDocType; - } else { - $resultType = $type; - } - - if ($type instanceof UnionType) { - $addToUnionTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType->isSuperTypeOf($resultType)->no()) { - continue; - } - - $addToUnionTypes[] = $innerType; - } - - if (count($addToUnionTypes) > 0) { - $type = TypeCombinator::union($resultType, ...$addToUnionTypes); - } else { - $type = $resultType; - } - } elseif (TypeCombinator::containsNull($type)) { - $type = TypeCombinator::addNull($resultType); - } else { - $type = $resultType; - } - } - - return $type; - } - + private static function getTypeObjectFromTypehint(string $typeString, ?string $selfClass): Type + { + switch (strtolower($typeString)) { + case 'int': + return new IntegerType(); + case 'bool': + return new BooleanType(); + case 'false': + return new ConstantBooleanType(false); + case 'string': + return new StringType(); + case 'float': + return new FloatType(); + case 'array': + return new ArrayType(new MixedType(), new MixedType()); + case 'iterable': + return new IterableType(new MixedType(), new MixedType()); + case 'callable': + return new CallableType(); + case 'void': + return new VoidType(); + case 'object': + return new ObjectWithoutClassType(); + case 'mixed': + return new MixedType(true); + case 'self': + return $selfClass !== null ? new ObjectType($selfClass) : new ErrorType(); + case 'parent': + $broker = Broker::getInstance(); + if ($selfClass !== null && $broker->hasClass($selfClass)) { + $classReflection = $broker->getClass($selfClass); + if ($classReflection->getParentClass() !== false) { + return new ObjectType($classReflection->getParentClass()->getName()); + } + } + return new NonexistentParentClassType(); + case 'static': + return $selfClass !== null ? new StaticType($selfClass) : new ErrorType(); + case 'null': + return new NullType(); + default: + return new ObjectType($typeString); + } + } + + public static function decideTypeFromReflection( + ?\ReflectionType $reflectionType, + ?Type $phpDocType = null, + ?string $selfClass = null, + bool $isVariadic = false + ): Type { + if ($reflectionType === null) { + if ($isVariadic && $phpDocType instanceof ArrayType) { + $phpDocType = $phpDocType->getItemType(); + } + return $phpDocType ?? new MixedType(); + } + + if ($reflectionType instanceof ReflectionUnionType) { + $type = TypeCombinator::union(...array_map(static function (ReflectionType $type) use ($selfClass): Type { + return self::decideTypeFromReflection($type, null, $selfClass, false); + }, $reflectionType->getTypes())); + + return self::decideType($type, $phpDocType); + } + + if (!$reflectionType instanceof ReflectionNamedType) { + throw new \PHPStan\ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType))); + } + + $reflectionTypeString = $reflectionType->getName(); + if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\object')) { + $reflectionTypeString = 'object'; + } + if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\mixed')) { + $reflectionTypeString = 'mixed'; + } + if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\false')) { + $reflectionTypeString = 'false'; + } + if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\null')) { + $reflectionTypeString = 'null'; + } + + $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass); + if ($reflectionType->allowsNull()) { + $type = TypeCombinator::addNull($type); + } elseif ($phpDocType !== null) { + $phpDocType = TypeCombinator::removeNull($phpDocType); + } + + return self::decideType($type, $phpDocType); + } + + public static function decideType( + Type $type, + ?Type $phpDocType = null + ): Type { + if ($phpDocType !== null && !$phpDocType instanceof ErrorType) { + if ($type instanceof VoidType) { + if ($phpDocType instanceof NeverType && $phpDocType->isExplicit()) { + return $phpDocType; + } + + return new VoidType(); + } + if ( + $type instanceof MixedType + && !$type->isExplicitMixed() + && $phpDocType instanceof VoidType + ) { + return $phpDocType; + } + + if (TypeCombinator::removeNull($type) instanceof IterableType) { + if ($phpDocType instanceof UnionType) { + $innerTypes = []; + foreach ($phpDocType->getTypes() as $innerType) { + if ($innerType instanceof ArrayType) { + $innerTypes[] = new IterableType( + $innerType->getKeyType(), + $innerType->getItemType() + ); + } else { + $innerTypes[] = $innerType; + } + } + $phpDocType = new UnionType($innerTypes); + } elseif ($phpDocType instanceof ArrayType) { + $phpDocType = new IterableType( + $phpDocType->getKeyType(), + $phpDocType->getItemType() + ); + } + } + + if ( + (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) + && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + ) { + $resultType = $phpDocType; + } else { + $resultType = $type; + } + + if ($type instanceof UnionType) { + $addToUnionTypes = []; + foreach ($type->getTypes() as $innerType) { + if (!$innerType->isSuperTypeOf($resultType)->no()) { + continue; + } + + $addToUnionTypes[] = $innerType; + } + + if (count($addToUnionTypes) > 0) { + $type = TypeCombinator::union($resultType, ...$addToUnionTypes); + } else { + $type = $resultType; + } + } elseif (TypeCombinator::containsNull($type)) { + $type = TypeCombinator::addNull($resultType); + } else { + $type = $resultType; + } + } + + return $type; + } } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index b9608611b6..e74441ca9f 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -1,4 +1,6 @@ -describe(VerbosityLevel::value()); - }, $types)) - )); - }; - if (count($types) < 2) { - $throwException(); - } - foreach ($types as $type) { - if (!($type instanceof UnionType)) { - continue; - } - - $throwException(); - } - $this->types = UnionTypeHelper::sortTypes($types); - } - - /** - * @return \PHPStan\Type\Type[] - */ - public function getTypes(): array - { - return $this->types; - } - - /** - * @return string[] - */ - public function getReferencedClasses(): array - { - return UnionTypeHelper::getReferencedClasses($this->getTypes()); - } - - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - if ($type instanceof CompoundType && !$type instanceof CallableType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $innerType->accepts($type, $strictTypes); - } - - return TrinaryLogic::createNo()->or(...$results); - } - - public function isSuperTypeOf(Type $otherType): TrinaryLogic - { - if ($otherType instanceof self || $otherType instanceof IterableType) { - return $otherType->isSubTypeOf($this); - } - - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $innerType->isSuperTypeOf($otherType); - } - - return TrinaryLogic::createNo()->or(...$results); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $otherType->isSuperTypeOf($innerType); - } - - return TrinaryLogic::extremeIdentity(...$results); - } - - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - $results = []; - foreach ($this->getTypes() as $innerType) { - $results[] = $acceptingType->accepts($innerType, $strictTypes); - } - - return TrinaryLogic::extremeIdentity(...$results); - } - - public function equals(Type $type): bool - { - if (!$type instanceof static) { - return false; - } - - if (count($this->types) !== count($type->types)) { - return false; - } - - foreach ($this->types as $i => $innerType) { - if (!$innerType->equals($type->types[$i])) { - return false; - } - } - - return true; - } - - public function describe(VerbosityLevel $level): string - { - $joinTypes = static function (array $types) use ($level): string { - $typeNames = []; - foreach ($types as $type) { - if ($type instanceof ClosureType || $type instanceof CallableType) { - $typeNames[] = sprintf('(%s)', $type->describe($level)); - } elseif ($type instanceof IntersectionType) { - $intersectionDescription = $type->describe($level); - if (strpos($intersectionDescription, '&') !== false) { - $typeNames[] = sprintf('(%s)', $type->describe($level)); - } else { - $typeNames[] = $intersectionDescription; - } - } else { - $typeNames[] = $type->describe($level); - } - } - - return implode('|', $typeNames); - }; - - return $level->handle( - function () use ($joinTypes): string { - $types = TypeCombinator::union(...array_map(static function (Type $type): Type { - if ( - $type instanceof ConstantType - && !$type instanceof ConstantBooleanType - ) { - return $type->generalize(); - } - - return $type; - }, $this->types)); - - if ($types instanceof UnionType) { - return $joinTypes($types->getTypes()); - } - - return $joinTypes([$types]); - }, - function () use ($joinTypes): string { - return $joinTypes($this->types); - } - ); - } - - /** - * @param callable(Type $type): TrinaryLogic $canCallback - * @param callable(Type $type): TrinaryLogic $hasCallback - * @return TrinaryLogic - */ - private function hasInternal( - callable $canCallback, - callable $hasCallback - ): TrinaryLogic - { - $results = []; - foreach ($this->types as $type) { - if ($canCallback($type)->no()) { - $results[] = TrinaryLogic::createNo(); - continue; - } - $results[] = $hasCallback($type); - } - - return TrinaryLogic::extremeIdentity(...$results); - } - - /** - * @param callable(Type $type): TrinaryLogic $hasCallback - * @param callable(Type $type): object $getCallback - * @return object - */ - private function getInternal( - callable $hasCallback, - callable $getCallback - ) - { - /** @var TrinaryLogic|null $result */ - $result = null; - - /** @var object|null $object */ - $object = null; - foreach ($this->types as $type) { - $has = $hasCallback($type); - if (!$has->yes()) { - continue; - } - if ($result !== null && $result->compareTo($has) !== $has) { - continue; - } - - $get = $getCallback($type); - $result = $has; - $object = $get; - } - - if ($object === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - return $object; - } - - public function canAccessProperties(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->canAccessProperties(); - }); - } - - public function hasProperty(string $propertyName): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($propertyName): TrinaryLogic { - return $type->hasProperty($propertyName); - }); - } - - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - $propertyPrototypes = []; - foreach ($this->types as $type) { - if (!$type->hasProperty($propertyName)->yes()) { - continue; - } - - $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this); - } - - $propertiesCount = count($propertyPrototypes); - if ($propertiesCount === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($propertiesCount === 1) { - return $propertyPrototypes[0]; - } - - return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); - } - - public function canCallMethods(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->canCallMethods(); - }); - } - - public function hasMethod(string $methodName): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($methodName): TrinaryLogic { - return $type->hasMethod($methodName); - }); - } - - public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection - { - return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); - } - - public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection - { - $methodPrototypes = []; - foreach ($this->types as $type) { - if (!$type->hasMethod($methodName)->yes()) { - continue; - } - - $methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this); - } - - $methodsCount = count($methodPrototypes); - if ($methodsCount === 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - - if ($methodsCount === 1) { - return $methodPrototypes[0]; - } - - return new UnionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes); - } - - public function canAccessConstants(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->canAccessConstants(); - }); - } - - public function hasConstant(string $constantName): TrinaryLogic - { - return $this->hasInternal( - static function (Type $type): TrinaryLogic { - return $type->canAccessConstants(); - }, - static function (Type $type) use ($constantName): TrinaryLogic { - return $type->hasConstant($constantName); - } - ); - } - - public function getConstant(string $constantName): ConstantReflection - { - return $this->getInternal( - static function (Type $type) use ($constantName): TrinaryLogic { - return $type->hasConstant($constantName); - }, - static function (Type $type) use ($constantName): ConstantReflection { - return $type->getConstant($constantName); - } - ); - } - - public function isIterable(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isIterable(); - }); - } - - public function isIterableAtLeastOnce(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isIterableAtLeastOnce(); - }); - } - - public function getIterableKeyType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getIterableKeyType(); - }); - } - - public function getIterableValueType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getIterableValueType(); - }); - } - - public function isArray(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isArray(); - }); - } - - public function isNumericString(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isNumericString(); - }); - } - - public function isOffsetAccessible(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isOffsetAccessible(); - }); - } - - public function hasOffsetValueType(Type $offsetType): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($offsetType): TrinaryLogic { - return $type->hasOffsetValueType($offsetType); - }); - } - - public function getOffsetValueType(Type $offsetType): Type - { - $types = []; - foreach ($this->types as $innerType) { - $valueType = $innerType->getOffsetValueType($offsetType); - if ($valueType instanceof ErrorType) { - continue; - } - - $types[] = $valueType; - } - - if (count($types) === 0) { - return new ErrorType(); - } - - return TypeCombinator::union(...$types); - } - - public function setOffsetValueType(?Type $offsetType, Type $valueType): Type - { - return $this->unionTypes(static function (Type $type) use ($offsetType, $valueType): Type { - return $type->setOffsetValueType($offsetType, $valueType); - }); - } - - public function isCallable(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isCallable(); - }); - } - - /** - * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope - * @return \PHPStan\Reflection\ParametersAcceptor[] - */ - public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array - { - foreach ($this->types as $type) { - if ($type->isCallable()->no()) { - continue; - } - - return $type->getCallableParametersAcceptors($scope); - } - - throw new \PHPStan\ShouldNotHappenException(); - } - - public function isCloneable(): TrinaryLogic - { - return $this->unionResults(static function (Type $type): TrinaryLogic { - return $type->isCloneable(); - }); - } - - public function isSmallerThan(Type $otherType): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $type->isSmallerThan($otherType); - }); - } - - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $type->isSmallerThanOrEqual($otherType); - }); - } - - public function getSmallerType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getSmallerType(); - }); - } - - public function getSmallerOrEqualType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getSmallerOrEqualType(); - }); - } - - public function getGreaterType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getGreaterType(); - }); - } - - public function getGreaterOrEqualType(): Type - { - return $this->unionTypes(static function (Type $type): Type { - return $type->getGreaterOrEqualType(); - }); - } - - public function isGreaterThan(Type $otherType): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $otherType->isSmallerThan($type); - }); - } - - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic - { - return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { - return $otherType->isSmallerThanOrEqual($type); - }); - } - - public function toBoolean(): BooleanType - { - /** @var BooleanType $type */ - $type = $this->unionTypes(static function (Type $type): BooleanType { - return $type->toBoolean(); - }); - - return $type; - } - - public function toNumber(): Type - { - $type = $this->unionTypes(static function (Type $type): Type { - return $type->toNumber(); - }); - - return $type; - } - - public function toString(): Type - { - $type = $this->unionTypes(static function (Type $type): Type { - return $type->toString(); - }); - - return $type; - } - - public function toInteger(): Type - { - $type = $this->unionTypes(static function (Type $type): Type { - return $type->toInteger(); - }); - - return $type; - } - - public function toFloat(): Type - { - $type = $this->unionTypes(static function (Type $type): Type { - return $type->toFloat(); - }); - - return $type; - } - - public function toArray(): Type - { - $type = $this->unionTypes(static function (Type $type): Type { - return $type->toArray(); - }); - - return $type; - } - - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - if ($receivedType instanceof UnionType) { - $myTypes = []; - $remainingReceivedTypes = []; - foreach ($receivedType->getTypes() as $receivedInnerType) { - foreach ($this->types as $type) { - if ($type->isSuperTypeOf($receivedInnerType)->yes()) { - $types = $types->union($type->inferTemplateTypes($receivedInnerType)); - continue 2; - } - $myTypes[] = $type; - } - $remainingReceivedTypes[] = $receivedInnerType; - } - if (count($remainingReceivedTypes) === 0) { - return $types; - } - $receivedType = TypeCombinator::union(...$remainingReceivedTypes); - } else { - $myTypes = $this->types; - } - - $myTemplateTypes = []; - foreach ($myTypes as $type) { - if ($type instanceof TemplateType || ($type instanceof GenericClassStringType && $type->getGenericType() instanceof TemplateType)) { - $myTemplateTypes[] = $type; - continue; - } - $types = $types->union($type->inferTemplateTypes($receivedType)); - } - - if (!$types->isEmpty()) { - return $types; - } - - foreach ($myTypes as $type) { - $types = $types->union($type->inferTemplateTypes($receivedType)); - } - - return $types; - } - - public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap - { - $types = TemplateTypeMap::createEmpty(); - - foreach ($this->types as $type) { - $types = $types->union($templateType->inferTemplateTypes($type)); - } - - return $types; - } - - public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array - { - $references = []; - - foreach ($this->types as $type) { - foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { - $references[] = $reference; - } - } - - return $references; - } - - public function traverse(callable $cb): Type - { - $types = []; - $changed = false; - - foreach ($this->types as $type) { - $newType = $cb($type); - if ($type !== $newType) { - $changed = true; - } - $types[] = $newType; - } - - if ($changed) { - return TypeCombinator::union(...$types); - } - - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types']); - } - - /** - * @param callable(Type $type): TrinaryLogic $getResult - * @return TrinaryLogic - */ - private function unionResults(callable $getResult): TrinaryLogic - { - return TrinaryLogic::extremeIdentity(...array_map($getResult, $this->types)); - } - - /** - * @param callable(Type $type): Type $getType - * @return Type - */ - protected function unionTypes(callable $getType): Type - { - return TypeCombinator::union(...array_map($getType, $this->types)); - } - + /** @var \PHPStan\Type\Type[] */ + private array $types; + + /** + * @param Type[] $types + */ + public function __construct(array $types) + { + $throwException = static function () use ($types): void { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Cannot create %s with: %s', + self::class, + implode(', ', array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::value()); + }, $types)) + )); + }; + if (count($types) < 2) { + $throwException(); + } + foreach ($types as $type) { + if (!($type instanceof UnionType)) { + continue; + } + + $throwException(); + } + $this->types = UnionTypeHelper::sortTypes($types); + } + + /** + * @return \PHPStan\Type\Type[] + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return UnionTypeHelper::getReferencedClasses($this->getTypes()); + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType && !$type instanceof CallableType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $innerType->accepts($type, $strictTypes); + } + + return TrinaryLogic::createNo()->or(...$results); + } + + public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof self || $otherType instanceof IterableType) { + return $otherType->isSubTypeOf($this); + } + + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $innerType->isSuperTypeOf($otherType); + } + + return TrinaryLogic::createNo()->or(...$results); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $otherType->isSuperTypeOf($innerType); + } + + return TrinaryLogic::extremeIdentity(...$results); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + $results = []; + foreach ($this->getTypes() as $innerType) { + $results[] = $acceptingType->accepts($innerType, $strictTypes); + } + + return TrinaryLogic::extremeIdentity(...$results); + } + + public function equals(Type $type): bool + { + if (!$type instanceof static) { + return false; + } + + if (count($this->types) !== count($type->types)) { + return false; + } + + foreach ($this->types as $i => $innerType) { + if (!$innerType->equals($type->types[$i])) { + return false; + } + } + + return true; + } + + public function describe(VerbosityLevel $level): string + { + $joinTypes = static function (array $types) use ($level): string { + $typeNames = []; + foreach ($types as $type) { + if ($type instanceof ClosureType || $type instanceof CallableType) { + $typeNames[] = sprintf('(%s)', $type->describe($level)); + } elseif ($type instanceof IntersectionType) { + $intersectionDescription = $type->describe($level); + if (strpos($intersectionDescription, '&') !== false) { + $typeNames[] = sprintf('(%s)', $type->describe($level)); + } else { + $typeNames[] = $intersectionDescription; + } + } else { + $typeNames[] = $type->describe($level); + } + } + + return implode('|', $typeNames); + }; + + return $level->handle( + function () use ($joinTypes): string { + $types = TypeCombinator::union(...array_map(static function (Type $type): Type { + if ( + $type instanceof ConstantType + && !$type instanceof ConstantBooleanType + ) { + return $type->generalize(); + } + + return $type; + }, $this->types)); + + if ($types instanceof UnionType) { + return $joinTypes($types->getTypes()); + } + + return $joinTypes([$types]); + }, + function () use ($joinTypes): string { + return $joinTypes($this->types); + } + ); + } + + /** + * @param callable(Type $type): TrinaryLogic $canCallback + * @param callable(Type $type): TrinaryLogic $hasCallback + * @return TrinaryLogic + */ + private function hasInternal( + callable $canCallback, + callable $hasCallback + ): TrinaryLogic { + $results = []; + foreach ($this->types as $type) { + if ($canCallback($type)->no()) { + $results[] = TrinaryLogic::createNo(); + continue; + } + $results[] = $hasCallback($type); + } + + return TrinaryLogic::extremeIdentity(...$results); + } + + /** + * @param callable(Type $type): TrinaryLogic $hasCallback + * @param callable(Type $type): object $getCallback + * @return object + */ + private function getInternal( + callable $hasCallback, + callable $getCallback + ) { + /** @var TrinaryLogic|null $result */ + $result = null; + + /** @var object|null $object */ + $object = null; + foreach ($this->types as $type) { + $has = $hasCallback($type); + if (!$has->yes()) { + continue; + } + if ($result !== null && $result->compareTo($has) !== $has) { + continue; + } + + $get = $getCallback($type); + $result = $has; + $object = $get; + } + + if ($object === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $object; + } + + public function canAccessProperties(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->canAccessProperties(); + }); + } + + public function hasProperty(string $propertyName): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($propertyName): TrinaryLogic { + return $type->hasProperty($propertyName); + }); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + + public function canCallMethods(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->canCallMethods(); + }); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($methodName): TrinaryLogic { + return $type->hasMethod($methodName); + }); + } + + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection + { + return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); + } + + public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection + { + $methodPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasMethod($methodName)->yes()) { + continue; + } + + $methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this); + } + + $methodsCount = count($methodPrototypes); + if ($methodsCount === 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if ($methodsCount === 1) { + return $methodPrototypes[0]; + } + + return new UnionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes); + } + + public function canAccessConstants(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->canAccessConstants(); + }); + } + + public function hasConstant(string $constantName): TrinaryLogic + { + return $this->hasInternal( + static function (Type $type): TrinaryLogic { + return $type->canAccessConstants(); + }, + static function (Type $type) use ($constantName): TrinaryLogic { + return $type->hasConstant($constantName); + } + ); + } + + public function getConstant(string $constantName): ConstantReflection + { + return $this->getInternal( + static function (Type $type) use ($constantName): TrinaryLogic { + return $type->hasConstant($constantName); + }, + static function (Type $type) use ($constantName): ConstantReflection { + return $type->getConstant($constantName); + } + ); + } + + public function isIterable(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isIterable(); + }); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isIterableAtLeastOnce(); + }); + } + + public function getIterableKeyType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getIterableKeyType(); + }); + } + + public function getIterableValueType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getIterableValueType(); + }); + } + + public function isArray(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isArray(); + }); + } + + public function isNumericString(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isNumericString(); + }); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isOffsetAccessible(); + }); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($offsetType): TrinaryLogic { + return $type->hasOffsetValueType($offsetType); + }); + } + + public function getOffsetValueType(Type $offsetType): Type + { + $types = []; + foreach ($this->types as $innerType) { + $valueType = $innerType->getOffsetValueType($offsetType); + if ($valueType instanceof ErrorType) { + continue; + } + + $types[] = $valueType; + } + + if (count($types) === 0) { + return new ErrorType(); + } + + return TypeCombinator::union(...$types); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType): Type + { + return $this->unionTypes(static function (Type $type) use ($offsetType, $valueType): Type { + return $type->setOffsetValueType($offsetType, $valueType); + }); + } + + public function isCallable(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isCallable(); + }); + } + + /** + * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope + * @return \PHPStan\Reflection\ParametersAcceptor[] + */ + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array + { + foreach ($this->types as $type) { + if ($type->isCallable()->no()) { + continue; + } + + return $type->getCallableParametersAcceptors($scope); + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function isCloneable(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isCloneable(); + }); + } + + public function isSmallerThan(Type $otherType): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $type->isSmallerThan($otherType); + }); + } + + public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $type->isSmallerThanOrEqual($otherType); + }); + } + + public function getSmallerType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getSmallerType(); + }); + } + + public function getSmallerOrEqualType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getSmallerOrEqualType(); + }); + } + + public function getGreaterType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getGreaterType(); + }); + } + + public function getGreaterOrEqualType(): Type + { + return $this->unionTypes(static function (Type $type): Type { + return $type->getGreaterOrEqualType(); + }); + } + + public function isGreaterThan(Type $otherType): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $otherType->isSmallerThan($type); + }); + } + + public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + { + return $this->unionResults(static function (Type $type) use ($otherType): TrinaryLogic { + return $otherType->isSmallerThanOrEqual($type); + }); + } + + public function toBoolean(): BooleanType + { + /** @var BooleanType $type */ + $type = $this->unionTypes(static function (Type $type): BooleanType { + return $type->toBoolean(); + }); + + return $type; + } + + public function toNumber(): Type + { + $type = $this->unionTypes(static function (Type $type): Type { + return $type->toNumber(); + }); + + return $type; + } + + public function toString(): Type + { + $type = $this->unionTypes(static function (Type $type): Type { + return $type->toString(); + }); + + return $type; + } + + public function toInteger(): Type + { + $type = $this->unionTypes(static function (Type $type): Type { + return $type->toInteger(); + }); + + return $type; + } + + public function toFloat(): Type + { + $type = $this->unionTypes(static function (Type $type): Type { + return $type->toFloat(); + }); + + return $type; + } + + public function toArray(): Type + { + $type = $this->unionTypes(static function (Type $type): Type { + return $type->toArray(); + }); + + return $type; + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + if ($receivedType instanceof UnionType) { + $myTypes = []; + $remainingReceivedTypes = []; + foreach ($receivedType->getTypes() as $receivedInnerType) { + foreach ($this->types as $type) { + if ($type->isSuperTypeOf($receivedInnerType)->yes()) { + $types = $types->union($type->inferTemplateTypes($receivedInnerType)); + continue 2; + } + $myTypes[] = $type; + } + $remainingReceivedTypes[] = $receivedInnerType; + } + if (count($remainingReceivedTypes) === 0) { + return $types; + } + $receivedType = TypeCombinator::union(...$remainingReceivedTypes); + } else { + $myTypes = $this->types; + } + + $myTemplateTypes = []; + foreach ($myTypes as $type) { + if ($type instanceof TemplateType || ($type instanceof GenericClassStringType && $type->getGenericType() instanceof TemplateType)) { + $myTemplateTypes[] = $type; + continue; + } + $types = $types->union($type->inferTemplateTypes($receivedType)); + } + + if (!$types->isEmpty()) { + return $types; + } + + foreach ($myTypes as $type) { + $types = $types->union($type->inferTemplateTypes($receivedType)); + } + + return $types; + } + + public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap + { + $types = TemplateTypeMap::createEmpty(); + + foreach ($this->types as $type) { + $types = $types->union($templateType->inferTemplateTypes($type)); + } + + return $types; + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $references = []; + + foreach ($this->types as $type) { + foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + + public function traverse(callable $cb): Type + { + $types = []; + $changed = false; + + foreach ($this->types as $type) { + $newType = $cb($type); + if ($type !== $newType) { + $changed = true; + } + $types[] = $newType; + } + + if ($changed) { + return TypeCombinator::union(...$types); + } + + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self($properties['types']); + } + + /** + * @param callable(Type $type): TrinaryLogic $getResult + * @return TrinaryLogic + */ + private function unionResults(callable $getResult): TrinaryLogic + { + return TrinaryLogic::extremeIdentity(...array_map($getResult, $this->types)); + } + + /** + * @param callable(Type $type): Type $getType + * @return Type + */ + protected function unionTypes(callable $getType): Type + { + return TypeCombinator::union(...array_map($getType, $this->types)); + } } diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index c0c99bbd18..4fb908561b 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -1,4 +1,6 @@ -getReferencedClasses(); - } - - return array_merge(...$subTypeClasses); - } - - /** - * @param \PHPStan\Type\Type[] $types - * @return \PHPStan\Type\Type[] - */ - public static function sortTypes(array $types): array - { - usort($types, static function (Type $a, Type $b): int { - if ($a instanceof NullType) { - return 1; - } elseif ($b instanceof NullType) { - return -1; - } - - if ($a instanceof AccessoryType) { - if ($b instanceof AccessoryType) { - return strcasecmp($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); - } - - return 1; - } - if ($b instanceof AccessoryType) { - return -1; - } - - $aIsBool = $a instanceof ConstantBooleanType; - $bIsBool = $b instanceof ConstantBooleanType; - if ($aIsBool && !$bIsBool) { - return 1; - } elseif ($bIsBool && !$aIsBool) { - return -1; - } - if ($a instanceof ConstantScalarType && !$b instanceof ConstantScalarType) { - return -1; - } elseif (!$a instanceof ConstantScalarType && $b instanceof ConstantScalarType) { - return 1; - } - - if ( - ( - $a instanceof ConstantIntegerType - || $a instanceof ConstantFloatType - ) - && ( - $b instanceof ConstantIntegerType - || $b instanceof ConstantFloatType - ) - ) { - $cmp = $a->getValue() <=> $b->getValue(); - if ($cmp !== 0) { - return $cmp; - } - if ($a instanceof ConstantIntegerType && $b instanceof ConstantFloatType) { - return -1; - } - if ($b instanceof ConstantIntegerType && $a instanceof ConstantFloatType) { - return 1; - } - return 0; - } - - if ($a instanceof IntegerRangeType && $b instanceof IntegerRangeType) { - return ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN); - } - - if ($a instanceof ConstantStringType && $b instanceof ConstantStringType) { - return strcasecmp($a->getValue(), $b->getValue()); - } - - if ($a instanceof ConstantArrayType && $b instanceof ConstantArrayType) { - if ($a->isEmpty()) { - if ($b->isEmpty()) { - return 0; - } - - return -1; - } elseif ($b->isEmpty()) { - return 1; - } - - return strcasecmp($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); - } - - return strcasecmp($a->describe(VerbosityLevel::typeOnly()), $b->describe(VerbosityLevel::typeOnly())); - }); - return $types; - } - + /** + * @param \PHPStan\Type\Type[] $types + * @return string[] + */ + public static function getReferencedClasses(array $types): array + { + $subTypeClasses = []; + foreach ($types as $type) { + $subTypeClasses[] = $type->getReferencedClasses(); + } + + return array_merge(...$subTypeClasses); + } + + /** + * @param \PHPStan\Type\Type[] $types + * @return \PHPStan\Type\Type[] + */ + public static function sortTypes(array $types): array + { + usort($types, static function (Type $a, Type $b): int { + if ($a instanceof NullType) { + return 1; + } elseif ($b instanceof NullType) { + return -1; + } + + if ($a instanceof AccessoryType) { + if ($b instanceof AccessoryType) { + return strcasecmp($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); + } + + return 1; + } + if ($b instanceof AccessoryType) { + return -1; + } + + $aIsBool = $a instanceof ConstantBooleanType; + $bIsBool = $b instanceof ConstantBooleanType; + if ($aIsBool && !$bIsBool) { + return 1; + } elseif ($bIsBool && !$aIsBool) { + return -1; + } + if ($a instanceof ConstantScalarType && !$b instanceof ConstantScalarType) { + return -1; + } elseif (!$a instanceof ConstantScalarType && $b instanceof ConstantScalarType) { + return 1; + } + + if ( + ( + $a instanceof ConstantIntegerType + || $a instanceof ConstantFloatType + ) + && ( + $b instanceof ConstantIntegerType + || $b instanceof ConstantFloatType + ) + ) { + $cmp = $a->getValue() <=> $b->getValue(); + if ($cmp !== 0) { + return $cmp; + } + if ($a instanceof ConstantIntegerType && $b instanceof ConstantFloatType) { + return -1; + } + if ($b instanceof ConstantIntegerType && $a instanceof ConstantFloatType) { + return 1; + } + return 0; + } + + if ($a instanceof IntegerRangeType && $b instanceof IntegerRangeType) { + return ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN); + } + + if ($a instanceof ConstantStringType && $b instanceof ConstantStringType) { + return strcasecmp($a->getValue(), $b->getValue()); + } + + if ($a instanceof ConstantArrayType && $b instanceof ConstantArrayType) { + if ($a->isEmpty()) { + if ($b->isEmpty()) { + return 0; + } + + return -1; + } elseif ($b->isEmpty()) { + return 1; + } + + return strcasecmp($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); + } + + return strcasecmp($a->describe(VerbosityLevel::typeOnly()), $b->describe(VerbosityLevel::typeOnly())); + }); + return $types; + } } diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 8cd82fdd28..08ea1d4927 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -1,4 +1,6 @@ -value = $value; - } - - private static function create(int $value): self - { - self::$registry[$value] = self::$registry[$value] ?? new self($value); - return self::$registry[$value]; - } - - public static function typeOnly(): self - { - return self::create(self::TYPE_ONLY); - } - - public static function value(): self - { - return self::create(self::VALUE); - } - - public static function precise(): self - { - return self::create(self::PRECISE); - } - - public static function cache(): self - { - return self::create(self::CACHE); - } - - public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self - { - $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose): Type { - if ($type->isCallable()->yes()) { - $moreVerbose = true; - return $type; - } - if ($type instanceof ConstantType && !$type instanceof NullType) { - $moreVerbose = true; - return $type; - } - if ($type instanceof AccessoryNumericStringType) { - $moreVerbose = true; - return $type; - } - if ($type instanceof NonEmptyArrayType) { - $moreVerbose = true; - return $type; - } - return $traverse($type); - }; - - /** @var bool $moreVerbose */ - $moreVerbose = false; - TypeTraverser::map($acceptingType, $moreVerboseCallback); - - if ($moreVerbose) { - return self::value(); - } - - if ($acceptedType === null) { - return self::typeOnly(); - } - - $containsInvariantTemplateType = false; - TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type { - if ($type instanceof GenericObjectType) { - $reflection = $type->getClassReflection(); - if ($reflection !== null) { - $templateTypeMap = $reflection->getTemplateTypeMap(); - foreach ($templateTypeMap->getTypes() as $templateType) { - if (!$templateType instanceof TemplateType) { - continue; - } - - if (!$templateType->getVariance()->invariant()) { - continue; - } - - $containsInvariantTemplateType = true; - return $type; - } - } - } - - return $traverse($type); - }); - - if (!$containsInvariantTemplateType) { - return self::typeOnly(); - } - - /** @var bool $moreVerbose */ - $moreVerbose = false; - TypeTraverser::map($acceptedType, $moreVerboseCallback); - - return $moreVerbose ? self::value() : self::typeOnly(); - } - - /** - * @param callable(): string $typeOnlyCallback - * @param callable(): string $valueCallback - * @param callable(): string|null $preciseCallback - * @param callable(): string|null $cacheCallback - * @return string - */ - public function handle( - callable $typeOnlyCallback, - callable $valueCallback, - ?callable $preciseCallback = null, - ?callable $cacheCallback = null - ): string - { - if ($this->value === self::TYPE_ONLY) { - return $typeOnlyCallback(); - } - - if ($this->value === self::VALUE) { - return $valueCallback(); - } - - if ($this->value === self::PRECISE) { - if ($preciseCallback !== null) { - return $preciseCallback(); - } - - return $valueCallback(); - } - - if ($this->value === self::CACHE) { - if ($cacheCallback !== null) { - return $cacheCallback(); - } - - if ($preciseCallback !== null) { - return $preciseCallback(); - } - - return $valueCallback(); - } - - throw new \PHPStan\ShouldNotHappenException(); - } - + private const TYPE_ONLY = 1; + private const VALUE = 2; + private const PRECISE = 3; + private const CACHE = 4; + + /** @var self[] */ + private static array $registry; + + private int $value; + + private function __construct(int $value) + { + $this->value = $value; + } + + private static function create(int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + public static function typeOnly(): self + { + return self::create(self::TYPE_ONLY); + } + + public static function value(): self + { + return self::create(self::VALUE); + } + + public static function precise(): self + { + return self::create(self::PRECISE); + } + + public static function cache(): self + { + return self::create(self::CACHE); + } + + public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self + { + $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose): Type { + if ($type->isCallable()->yes()) { + $moreVerbose = true; + return $type; + } + if ($type instanceof ConstantType && !$type instanceof NullType) { + $moreVerbose = true; + return $type; + } + if ($type instanceof AccessoryNumericStringType) { + $moreVerbose = true; + return $type; + } + if ($type instanceof NonEmptyArrayType) { + $moreVerbose = true; + return $type; + } + return $traverse($type); + }; + + /** @var bool $moreVerbose */ + $moreVerbose = false; + TypeTraverser::map($acceptingType, $moreVerboseCallback); + + if ($moreVerbose) { + return self::value(); + } + + if ($acceptedType === null) { + return self::typeOnly(); + } + + $containsInvariantTemplateType = false; + TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type { + if ($type instanceof GenericObjectType) { + $reflection = $type->getClassReflection(); + if ($reflection !== null) { + $templateTypeMap = $reflection->getTemplateTypeMap(); + foreach ($templateTypeMap->getTypes() as $templateType) { + if (!$templateType instanceof TemplateType) { + continue; + } + + if (!$templateType->getVariance()->invariant()) { + continue; + } + + $containsInvariantTemplateType = true; + return $type; + } + } + } + + return $traverse($type); + }); + + if (!$containsInvariantTemplateType) { + return self::typeOnly(); + } + + /** @var bool $moreVerbose */ + $moreVerbose = false; + TypeTraverser::map($acceptedType, $moreVerboseCallback); + + return $moreVerbose ? self::value() : self::typeOnly(); + } + + /** + * @param callable(): string $typeOnlyCallback + * @param callable(): string $valueCallback + * @param callable(): string|null $preciseCallback + * @param callable(): string|null $cacheCallback + * @return string + */ + public function handle( + callable $typeOnlyCallback, + callable $valueCallback, + ?callable $preciseCallback = null, + ?callable $cacheCallback = null + ): string { + if ($this->value === self::TYPE_ONLY) { + return $typeOnlyCallback(); + } + + if ($this->value === self::VALUE) { + return $valueCallback(); + } + + if ($this->value === self::PRECISE) { + if ($preciseCallback !== null) { + return $preciseCallback(); + } + + return $valueCallback(); + } + + if ($this->value === self::CACHE) { + if ($cacheCallback !== null) { + return $cacheCallback(); + } + + if ($preciseCallback !== null) { + return $preciseCallback(); + } + + return $valueCallback(); + } + + throw new \PHPStan\ShouldNotHappenException(); + } } diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 3f0e284be6..1066b1a93d 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -1,4 +1,6 @@ -isAcceptedBy($this, $strictTypes); - } - - return TrinaryLogic::createFromBoolean($type instanceof self); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof self) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); - } - - return TrinaryLogic::createNo(); - } - - public function equals(Type $type): bool - { - return $type instanceof self; - } - - public function describe(VerbosityLevel $level): string - { - return 'void'; - } - - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - - public function toInteger(): Type - { - return new ErrorType(); - } - - public function toFloat(): Type - { - return new ErrorType(); - } - - public function toArray(): Type - { - return new ErrorType(); - } - - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function traverse(callable $cb): Type - { - return $this; - } - - /** - * @param mixed[] $properties - * @return Type - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - + use NonCallableTypeTrait; + use NonIterableTypeTrait; + use NonObjectTypeTrait; + use NonOffsetAccessibleTypeTrait; + use FalseyBooleanTypeTrait; + use NonGenericTypeTrait; + use UndecidedComparisonTypeTrait; + + /** + * @return string[] + */ + public function getReferencedClasses(): array + { + return []; + } + + public function accepts(Type $type, bool $strictTypes): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + return TrinaryLogic::createFromBoolean($type instanceof self); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof self) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + return TrinaryLogic::createNo(); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(VerbosityLevel $level): string + { + return 'void'; + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toString(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new ErrorType(); + } + + public function toFloat(): Type + { + return new ErrorType(); + } + + public function toArray(): Type + { + return new ErrorType(); + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + return new self(); + } } diff --git a/src/dumpType.php b/src/dumpType.php index 2fbd4421b4..e6ea74fc74 100644 --- a/src/dumpType.php +++ b/src/dumpType.php @@ -1,4 +1,6 @@ -flags = $flags; - } - - } + /** + * @param int $flags A value in the form of a bitmask indicating the places + * where attributes can be defined. + */ + public function __construct($flags = self::TARGET_ALL) + { + $this->flags = $flags; + } + } } diff --git a/stubs/runtime/ReflectionUnionType.php b/stubs/runtime/ReflectionUnionType.php index 5479241efc..f100f48d18 100644 --- a/stubs/runtime/ReflectionUnionType.php +++ b/stubs/runtime/ReflectionUnionType.php @@ -1,18 +1,16 @@ runAnalyse(__DIR__ . '/data/undefined-variable-assign.php'); - $this->assertCount(2, $errors); - $error = $errors[0]; - $this->assertSame('Undefined variable: $bar', $error->getMessage()); - $this->assertSame(3, $error->getLine()); - - $error = $errors[1]; - $this->assertSame('Variable $foo might not be defined.', $error->getMessage()); - $this->assertSame(6, $error->getLine()); - } - - public function testMissingPropertyAndMethod(): void - { - $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/Foo.php'); - $this->assertCount(0, $errors); - } - - public function testMissingClassErrorAboutMisconfiguredAutoloader(): void - { - $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/Bar.php'); - $this->assertCount(0, $errors); - } - - public function testMissingFunctionErrorAboutMisconfiguredAutoloader(): void - { - $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/functionFoo.php'); - $this->assertCount(1, $errors); - $this->assertSame('Function doSomething not found.', $errors[0]->getMessage()); - $this->assertSame(7, $errors[0]->getLine()); - } - - public function testAnonymousClassWithInheritedConstructor(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/anonymous-class-with-inherited-constructor.php'); - $this->assertCount(0, $errors); - } - - public function testNestedFunctionCallsDoNotCauseExcessiveFunctionNesting(): void - { - if (extension_loaded('xdebug')) { - $this->markTestSkipped('This test takes too long with XDebug enabled.'); - } - $errors = $this->runAnalyse(__DIR__ . '/data/nested-functions.php'); - $this->assertCount(0, $errors); - } - - public function testExtendingUnknownClass(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/extending-unknown-class.php'); - $this->assertCount(1, $errors); - - if (self::$useStaticReflectionProvider) { - $this->assertSame(5, $errors[0]->getLine()); - $this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage()); - } else { - $this->assertNull($errors[0]->getLine()); - $this->assertSame('Class ExtendingUnknownClass\Bar not found.', $errors[0]->getMessage()); - } - } - - public function testExtendingKnownClassWithCheck(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/extending-known-class-with-check.php'); - $this->assertCount(0, $errors); - - $broker = self::getContainer()->getByType(Broker::class); - $this->assertTrue($broker->hasClass(\ExtendingKnownClassWithCheck\Foo::class)); - } - - public function testInfiniteRecursionWithCallable(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/Foo-callable.php'); - $this->assertCount(0, $errors); - } - - public function testClassThatExtendsUnknownClassIn3rdPartyPropertyTypeShouldNotCauseAutoloading(): void - { - // no error about PHPStan\Tests\Baz not being able to be autoloaded - $errors = $this->runAnalyse(__DIR__ . '/data/ExtendsClassWithUnknownPropertyType.php'); - $this->assertCount(1, $errors); - //$this->assertSame(11, $errors[0]->getLine()); - $this->assertSame('Call to an undefined method ExtendsClassWithUnknownPropertyType::foo().', $errors[0]->getMessage()); - } - - public function testAnonymousClassesWithComments(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/AnonymousClassesWithComments.php'); - $this->assertCount(3, $errors); - foreach ($errors as $error) { - $this->assertStringContainsString('Call to an undefined method', $error->getMessage()); - } - } - - public function testUniversalObjectCrateIssue(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/universal-object-crate.php'); - $this->assertCount(1, $errors); - $this->assertSame('Parameter #1 $i of method UniversalObjectCrate\Foo::doBaz() expects int, string given.', $errors[0]->getMessage()); - $this->assertSame(19, $errors[0]->getLine()); - } - - public function testCustomFunctionWithNameEquivalentInSignatureMap(): void - { - $signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class); - if (!$signatureMapProvider->hasFunctionSignature('bcompiler_write_file')) { - $this->fail(); - } - require_once __DIR__ . '/data/custom-function-in-signature-map.php'; - $errors = $this->runAnalyse(__DIR__ . '/data/custom-function-in-signature-map.php'); - $this->assertCount(0, $errors); - } - - public function testAnonymousClassWithWrongFilename(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/anonymous-class-wrong-filename-regression.php'); - $this->assertCount(5, $errors); - $this->assertStringContainsString('Return typehint of method', $errors[0]->getMessage()); - $this->assertSame(16, $errors[0]->getLine()); - $this->assertStringContainsString('Return typehint of method', $errors[1]->getMessage()); - $this->assertSame(16, $errors[1]->getLine()); - $this->assertSame('Instantiated class AnonymousClassWrongFilename\Bar not found.', $errors[2]->getMessage()); - $this->assertSame(18, $errors[2]->getLine()); - $this->assertStringContainsString('Parameter #1 $test of method', $errors[3]->getMessage()); - $this->assertStringContainsString('$this(AnonymousClassWrongFilename\Foo) given', $errors[3]->getMessage()); - $this->assertSame(23, $errors[3]->getLine()); - $this->assertSame('Call to method test() on an unknown class AnonymousClassWrongFilename\Bar.', $errors[4]->getMessage()); - $this->assertSame(24, $errors[4]->getLine()); - } - - public function testExtendsPdoStatementCrash(): void - { - if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped(); - } - $errors = $this->runAnalyse(__DIR__ . '/data/extends-pdo-statement.php'); - $this->assertCount(0, $errors); - } - - public function testArrayDestructuringArrayDimFetch(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/array-destructuring-array-dim-fetch.php'); - $this->assertCount(0, $errors); - } - - public function testNestedNamespaces(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/nested-namespaces.php'); - $this->assertCount(2, $errors); - $this->assertSame('Property y\x::$baz has unknown class x\baz as its type.', $errors[0]->getMessage()); - $this->assertSame(15, $errors[0]->getLine()); - $this->assertSame('Parameter $baz of method y\x::__construct() has invalid typehint type x\baz.', $errors[1]->getMessage()); - $this->assertSame(16, $errors[1]->getLine()); - } - - public function testClassExistsAutoloadingError(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/class-exists.php'); - $this->assertCount(0, $errors); - } - - public function testCollectWarnings(): void - { - if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Fatal error in PHP 8.0'); - } - restore_error_handler(); - $errors = $this->runAnalyse(__DIR__ . '/data/declaration-warning.php'); - if (self::$useStaticReflectionProvider) { - $this->assertCount(1, $errors); - $this->assertSame('Parameter #1 $i of method DeclarationWarning\Bar::doFoo() is not optional.', $errors[0]->getMessage()); - $this->assertSame(22, $errors[0]->getLine()); - return; - } - $this->assertCount(2, $errors); - $messages = [ - 'Declaration of DeclarationWarning\Bar::doFoo(int $i): void should be compatible with DeclarationWarning\Foo::doFoo(): void', - 'Parameter #1 $i of method DeclarationWarning\Bar::doFoo() is not optional.', - ]; - if (PHP_VERSION_ID < 70400) { - $messages = array_reverse($messages); - } - foreach ($messages as $i => $message) { - $this->assertSame($message, $errors[$i]->getMessage()); - } - } - - public function testPropertyAssignIntersectionStaticTypeBug(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/property-assign-intersection-static-type-bug.php'); - $this->assertCount(0, $errors); - } - - public function testBug2823(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-2823.php'); - $this->assertCount(0, $errors); - } - - public function testTwoSameClassesInSingleFile(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/two-same-classes.php'); - $this->assertCount(4, $errors); - $error = $errors[0]; - $this->assertSame('Property TwoSame\Foo::$prop (string) does not accept default value of type int.', $error->getMessage()); - $this->assertSame(9, $error->getLine()); - - $error = $errors[1]; - $this->assertSame('Access to undefined constant TwoSame\Foo::FOO_CONST.', $error->getMessage()); - $this->assertSame(13, $error->getLine()); - - $error = $errors[2]; - $this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage()); - $this->assertSame(25, $error->getLine()); - - $error = $errors[3]; - $this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage()); - $this->assertSame(28, $error->getLine()); - } - - public function testBug3405(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3405.php'); - $this->assertCount(0, $errors); - } - - public function testBug3415(): void - { - $errors = $this->runAnalyse(__DIR__ . '/../Rules/Methods/data/bug-3415.php'); - $this->assertCount(0, $errors); - } - - public function testBug3415Two(): void - { - $errors = $this->runAnalyse(__DIR__ . '/../Rules/Methods/data/bug-3415-2.php'); - $this->assertCount(0, $errors); - } - - public function testBug3468(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3468.php'); - $this->assertCount(0, $errors); - } - - public function testBug3686(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3686.php'); - $this->assertCount(0, $errors); - } - - public function testBug3379(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection'); - } - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3379.php'); - $this->assertCount(2, $errors); - $this->assertSame('Constant SOME_UNKNOWN_CONST not found.', $errors[0]->getMessage()); - $this->assertSame('Reflection error: Could not locate constant "SOME_UNKNOWN_CONST" while evaluating expression in Bug3379\Foo at line 8', $errors[1]->getMessage()); - } - - public function testBug3798(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3798.php'); - $this->assertCount(0, $errors); - } - - public function testBug3909(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3909.php'); - $this->assertCount(0, $errors); - } - - public function testBug4097(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4097.php'); - $this->assertCount(0, $errors); - } - - public function testBug4300(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4300.php'); - $this->assertCount(1, $errors); - $this->assertSame('Comparison operation ">" between 0 and 0 is always false.', $errors[0]->getMessage()); - $this->assertSame(13, $errors[0]->getLine()); - } - - public function testBug4513(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4513.php'); - $this->assertCount(0, $errors); - } - - public function testBug1871(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-1871.php'); - $this->assertCount(0, $errors); - } - - public function testBug3309(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3309.php'); - $this->assertCount(0, $errors); - } - - public function testBug3769(): void - { - require_once __DIR__ . '/../Rules/Generics/data/bug-3769.php'; - $errors = $this->runAnalyse(__DIR__ . '/../Rules/Generics/data/bug-3769.php'); - $this->assertCount(0, $errors); - } - - public function testBug3922(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-3922-integration.php'); - $this->assertCount(0, $errors); - } - - public function testBug1843(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-1843.php'); - $this->assertCount(0, $errors); - } - - public function testBug4713(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4713.php'); - $this->assertCount(0, $errors); - - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass(Service::class); - $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('createInstance')->getVariants())->getParameters()[0]; - $defaultValue = $parameter->getDefaultValue(); - $this->assertInstanceOf(ConstantStringType::class, $defaultValue); - $this->assertSame(Service::class, $defaultValue->getValue()); - } - - public function testBug4288(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4288.php'); - $this->assertCount(0, $errors); - - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass(MyClass::class); - $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('paginate')->getVariants())->getParameters()[0]; - $defaultValue = $parameter->getDefaultValue(); - $this->assertInstanceOf(ConstantIntegerType::class, $defaultValue); - $this->assertSame(10, $defaultValue->getValue()); - - $nativeProperty = $class->getNativeReflection()->getProperty('test'); - if (!method_exists($nativeProperty, 'getDefaultValue')) { - return; - } - - $this->assertSame(10, $nativeProperty->getDefaultValue()); - } - - public function testBug4702(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4702.php'); - $this->assertCount(0, $errors); - } - - public function testFunctionThatExistsOn72AndLater(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/ldap-exop-passwd.php'); - if (PHP_VERSION_ID >= 70200) { - $this->assertCount(0, $errors); - return; - } - - $this->assertCount(1, $errors); - $this->assertSame('Function ldap_exop_passwd not found.', $errors[0]->getMessage()); - } - - public function testBug4715(): void - { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4715.php'); - $this->assertCount(0, $errors); - } - - public function testBug4734(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); - $this->assertCount(2, $errors); - - $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[0]->getMessage()); - $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[1]->getMessage()); - } - - /** - * @param string $file - * @return \PHPStan\Analyser\Error[] - */ - private function runAnalyse(string $file): array - { - $file = $this->getFileHelper()->normalizePath($file); - /** @var \PHPStan\Analyser\Analyser $analyser */ - $analyser = self::getContainer()->getByType(Analyser::class); - /** @var \PHPStan\File\FileHelper $fileHelper */ - $fileHelper = self::getContainer()->getByType(FileHelper::class); - /** @var \PHPStan\Analyser\Error[] $errors */ - $errors = $analyser->analyse([$file])->getErrors(); - foreach ($errors as $error) { - $this->assertSame($fileHelper->normalizePath($file), $error->getFilePath()); - } - - return $errors; - } - + public function testUndefinedVariableFromAssignErrorHasLine(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/undefined-variable-assign.php'); + $this->assertCount(2, $errors); + $error = $errors[0]; + $this->assertSame('Undefined variable: $bar', $error->getMessage()); + $this->assertSame(3, $error->getLine()); + + $error = $errors[1]; + $this->assertSame('Variable $foo might not be defined.', $error->getMessage()); + $this->assertSame(6, $error->getLine()); + } + + public function testMissingPropertyAndMethod(): void + { + $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/Foo.php'); + $this->assertCount(0, $errors); + } + + public function testMissingClassErrorAboutMisconfiguredAutoloader(): void + { + $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/Bar.php'); + $this->assertCount(0, $errors); + } + + public function testMissingFunctionErrorAboutMisconfiguredAutoloader(): void + { + $errors = $this->runAnalyse(__DIR__ . '/../../notAutoloaded/functionFoo.php'); + $this->assertCount(1, $errors); + $this->assertSame('Function doSomething not found.', $errors[0]->getMessage()); + $this->assertSame(7, $errors[0]->getLine()); + } + + public function testAnonymousClassWithInheritedConstructor(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/anonymous-class-with-inherited-constructor.php'); + $this->assertCount(0, $errors); + } + + public function testNestedFunctionCallsDoNotCauseExcessiveFunctionNesting(): void + { + if (extension_loaded('xdebug')) { + $this->markTestSkipped('This test takes too long with XDebug enabled.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/nested-functions.php'); + $this->assertCount(0, $errors); + } + + public function testExtendingUnknownClass(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/extending-unknown-class.php'); + $this->assertCount(1, $errors); + + if (self::$useStaticReflectionProvider) { + $this->assertSame(5, $errors[0]->getLine()); + $this->assertSame('Class ExtendingUnknownClass\Foo extends unknown class ExtendingUnknownClass\Bar.', $errors[0]->getMessage()); + } else { + $this->assertNull($errors[0]->getLine()); + $this->assertSame('Class ExtendingUnknownClass\Bar not found.', $errors[0]->getMessage()); + } + } + + public function testExtendingKnownClassWithCheck(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/extending-known-class-with-check.php'); + $this->assertCount(0, $errors); + + $broker = self::getContainer()->getByType(Broker::class); + $this->assertTrue($broker->hasClass(\ExtendingKnownClassWithCheck\Foo::class)); + } + + public function testInfiniteRecursionWithCallable(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/Foo-callable.php'); + $this->assertCount(0, $errors); + } + + public function testClassThatExtendsUnknownClassIn3rdPartyPropertyTypeShouldNotCauseAutoloading(): void + { + // no error about PHPStan\Tests\Baz not being able to be autoloaded + $errors = $this->runAnalyse(__DIR__ . '/data/ExtendsClassWithUnknownPropertyType.php'); + $this->assertCount(1, $errors); + //$this->assertSame(11, $errors[0]->getLine()); + $this->assertSame('Call to an undefined method ExtendsClassWithUnknownPropertyType::foo().', $errors[0]->getMessage()); + } + + public function testAnonymousClassesWithComments(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/AnonymousClassesWithComments.php'); + $this->assertCount(3, $errors); + foreach ($errors as $error) { + $this->assertStringContainsString('Call to an undefined method', $error->getMessage()); + } + } + + public function testUniversalObjectCrateIssue(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/universal-object-crate.php'); + $this->assertCount(1, $errors); + $this->assertSame('Parameter #1 $i of method UniversalObjectCrate\Foo::doBaz() expects int, string given.', $errors[0]->getMessage()); + $this->assertSame(19, $errors[0]->getLine()); + } + + public function testCustomFunctionWithNameEquivalentInSignatureMap(): void + { + $signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class); + if (!$signatureMapProvider->hasFunctionSignature('bcompiler_write_file')) { + $this->fail(); + } + require_once __DIR__ . '/data/custom-function-in-signature-map.php'; + $errors = $this->runAnalyse(__DIR__ . '/data/custom-function-in-signature-map.php'); + $this->assertCount(0, $errors); + } + + public function testAnonymousClassWithWrongFilename(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/anonymous-class-wrong-filename-regression.php'); + $this->assertCount(5, $errors); + $this->assertStringContainsString('Return typehint of method', $errors[0]->getMessage()); + $this->assertSame(16, $errors[0]->getLine()); + $this->assertStringContainsString('Return typehint of method', $errors[1]->getMessage()); + $this->assertSame(16, $errors[1]->getLine()); + $this->assertSame('Instantiated class AnonymousClassWrongFilename\Bar not found.', $errors[2]->getMessage()); + $this->assertSame(18, $errors[2]->getLine()); + $this->assertStringContainsString('Parameter #1 $test of method', $errors[3]->getMessage()); + $this->assertStringContainsString('$this(AnonymousClassWrongFilename\Foo) given', $errors[3]->getMessage()); + $this->assertSame(23, $errors[3]->getLine()); + $this->assertSame('Call to method test() on an unknown class AnonymousClassWrongFilename\Bar.', $errors[4]->getMessage()); + $this->assertSame(24, $errors[4]->getLine()); + } + + public function testExtendsPdoStatementCrash(): void + { + if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped(); + } + $errors = $this->runAnalyse(__DIR__ . '/data/extends-pdo-statement.php'); + $this->assertCount(0, $errors); + } + + public function testArrayDestructuringArrayDimFetch(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/array-destructuring-array-dim-fetch.php'); + $this->assertCount(0, $errors); + } + + public function testNestedNamespaces(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/nested-namespaces.php'); + $this->assertCount(2, $errors); + $this->assertSame('Property y\x::$baz has unknown class x\baz as its type.', $errors[0]->getMessage()); + $this->assertSame(15, $errors[0]->getLine()); + $this->assertSame('Parameter $baz of method y\x::__construct() has invalid typehint type x\baz.', $errors[1]->getMessage()); + $this->assertSame(16, $errors[1]->getLine()); + } + + public function testClassExistsAutoloadingError(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/class-exists.php'); + $this->assertCount(0, $errors); + } + + public function testCollectWarnings(): void + { + if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Fatal error in PHP 8.0'); + } + restore_error_handler(); + $errors = $this->runAnalyse(__DIR__ . '/data/declaration-warning.php'); + if (self::$useStaticReflectionProvider) { + $this->assertCount(1, $errors); + $this->assertSame('Parameter #1 $i of method DeclarationWarning\Bar::doFoo() is not optional.', $errors[0]->getMessage()); + $this->assertSame(22, $errors[0]->getLine()); + return; + } + $this->assertCount(2, $errors); + $messages = [ + 'Declaration of DeclarationWarning\Bar::doFoo(int $i): void should be compatible with DeclarationWarning\Foo::doFoo(): void', + 'Parameter #1 $i of method DeclarationWarning\Bar::doFoo() is not optional.', + ]; + if (PHP_VERSION_ID < 70400) { + $messages = array_reverse($messages); + } + foreach ($messages as $i => $message) { + $this->assertSame($message, $errors[$i]->getMessage()); + } + } + + public function testPropertyAssignIntersectionStaticTypeBug(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/property-assign-intersection-static-type-bug.php'); + $this->assertCount(0, $errors); + } + + public function testBug2823(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-2823.php'); + $this->assertCount(0, $errors); + } + + public function testTwoSameClassesInSingleFile(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/two-same-classes.php'); + $this->assertCount(4, $errors); + $error = $errors[0]; + $this->assertSame('Property TwoSame\Foo::$prop (string) does not accept default value of type int.', $error->getMessage()); + $this->assertSame(9, $error->getLine()); + + $error = $errors[1]; + $this->assertSame('Access to undefined constant TwoSame\Foo::FOO_CONST.', $error->getMessage()); + $this->assertSame(13, $error->getLine()); + + $error = $errors[2]; + $this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage()); + $this->assertSame(25, $error->getLine()); + + $error = $errors[3]; + $this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage()); + $this->assertSame(28, $error->getLine()); + } + + public function testBug3405(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3405.php'); + $this->assertCount(0, $errors); + } + + public function testBug3415(): void + { + $errors = $this->runAnalyse(__DIR__ . '/../Rules/Methods/data/bug-3415.php'); + $this->assertCount(0, $errors); + } + + public function testBug3415Two(): void + { + $errors = $this->runAnalyse(__DIR__ . '/../Rules/Methods/data/bug-3415-2.php'); + $this->assertCount(0, $errors); + } + + public function testBug3468(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3468.php'); + $this->assertCount(0, $errors); + } + + public function testBug3686(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3686.php'); + $this->assertCount(0, $errors); + } + + public function testBug3379(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3379.php'); + $this->assertCount(2, $errors); + $this->assertSame('Constant SOME_UNKNOWN_CONST not found.', $errors[0]->getMessage()); + $this->assertSame('Reflection error: Could not locate constant "SOME_UNKNOWN_CONST" while evaluating expression in Bug3379\Foo at line 8', $errors[1]->getMessage()); + } + + public function testBug3798(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3798.php'); + $this->assertCount(0, $errors); + } + + public function testBug3909(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3909.php'); + $this->assertCount(0, $errors); + } + + public function testBug4097(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4097.php'); + $this->assertCount(0, $errors); + } + + public function testBug4300(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4300.php'); + $this->assertCount(1, $errors); + $this->assertSame('Comparison operation ">" between 0 and 0 is always false.', $errors[0]->getMessage()); + $this->assertSame(13, $errors[0]->getLine()); + } + + public function testBug4513(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4513.php'); + $this->assertCount(0, $errors); + } + + public function testBug1871(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-1871.php'); + $this->assertCount(0, $errors); + } + + public function testBug3309(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3309.php'); + $this->assertCount(0, $errors); + } + + public function testBug3769(): void + { + require_once __DIR__ . '/../Rules/Generics/data/bug-3769.php'; + $errors = $this->runAnalyse(__DIR__ . '/../Rules/Generics/data/bug-3769.php'); + $this->assertCount(0, $errors); + } + + public function testBug3922(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-3922-integration.php'); + $this->assertCount(0, $errors); + } + + public function testBug1843(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-1843.php'); + $this->assertCount(0, $errors); + } + + public function testBug4713(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4713.php'); + $this->assertCount(0, $errors); + + $reflectionProvider = $this->createBroker(); + $class = $reflectionProvider->getClass(Service::class); + $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('createInstance')->getVariants())->getParameters()[0]; + $defaultValue = $parameter->getDefaultValue(); + $this->assertInstanceOf(ConstantStringType::class, $defaultValue); + $this->assertSame(Service::class, $defaultValue->getValue()); + } + + public function testBug4288(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4288.php'); + $this->assertCount(0, $errors); + + $reflectionProvider = $this->createBroker(); + $class = $reflectionProvider->getClass(MyClass::class); + $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('paginate')->getVariants())->getParameters()[0]; + $defaultValue = $parameter->getDefaultValue(); + $this->assertInstanceOf(ConstantIntegerType::class, $defaultValue); + $this->assertSame(10, $defaultValue->getValue()); + + $nativeProperty = $class->getNativeReflection()->getProperty('test'); + if (!method_exists($nativeProperty, 'getDefaultValue')) { + return; + } + + $this->assertSame(10, $nativeProperty->getDefaultValue()); + } + + public function testBug4702(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4702.php'); + $this->assertCount(0, $errors); + } + + public function testFunctionThatExistsOn72AndLater(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/ldap-exop-passwd.php'); + if (PHP_VERSION_ID >= 70200) { + $this->assertCount(0, $errors); + return; + } + + $this->assertCount(1, $errors); + $this->assertSame('Function ldap_exop_passwd not found.', $errors[0]->getMessage()); + } + + public function testBug4715(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4715.php'); + $this->assertCount(0, $errors); + } + + public function testBug4734(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); + $this->assertCount(2, $errors); + + $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[0]->getMessage()); + $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[1]->getMessage()); + } + + /** + * @param string $file + * @return \PHPStan\Analyser\Error[] + */ + private function runAnalyse(string $file): array + { + $file = $this->getFileHelper()->normalizePath($file); + /** @var \PHPStan\Analyser\Analyser $analyser */ + $analyser = self::getContainer()->getByType(Analyser::class); + /** @var \PHPStan\File\FileHelper $fileHelper */ + $fileHelper = self::getContainer()->getByType(FileHelper::class); + /** @var \PHPStan\Analyser\Error[] $errors */ + $errors = $analyser->analyse([$file])->getErrors(); + foreach ($errors as $error) { + $this->assertSame($fileHelper->normalizePath($file), $error->getFilePath()); + } + + return $errors; + } } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 2547c6985e..c02050afd7 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -1,4 +1,6 @@ -runAnalyser(['#Unknown error#'], true, __DIR__ . '/data/empty/empty.php', false); - $this->assertSame([ - 'Ignored error pattern #Unknown error# was not matched in reported errors.', - ], $result); - } - - public function testDoNotReturnErrorIfIgnoredMessagesDoesNotOccurWithReportUnmatchedIgnoredErrorsOff(): void - { - $result = $this->runAnalyser(['#Unknown error#'], false, __DIR__ . '/data/empty/empty.php', false); - $this->assertEmpty($result); - } - - public function testDoNotReturnErrorIfIgnoredMessagesDoNotOccurWhileAnalysingIndividualFiles(): void - { - $result = $this->runAnalyser(['#Unknown error#'], true, __DIR__ . '/data/empty/empty.php', true); - $this->assertEmpty($result); - } - - public function testReportInvalidIgnorePatternEarly(): void - { - $result = $this->runAnalyser(['#Regexp syntax error'], true, __DIR__ . '/data/parse-error.php', false); - $this->assertSame([ - "No ending delimiter '#' found in pattern: #Regexp syntax error", - ], $result); - } - - public function testFileWithAnIgnoredError(): void - { - $result = $this->runAnalyser(['#Fail\.#'], true, __DIR__ . '/data/bootstrap-error.php', false); - $this->assertEmpty($result); - } - - public function testIgnoringBrokenConfigurationDoesNotWork(): void - { - $this->markTestIncomplete(); - $result = $this->runAnalyser(['#was not found while trying to analyse it#'], true, __DIR__ . '/../../notAutoloaded/Baz.php', false); - $this->assertCount(2, $result); - assert($result[0] instanceof Error); - $this->assertSame('Class PHPStan\Tests\Baz was not found while trying to analyse it - autoloading is probably not configured properly.', $result[0]->getMessage()); - $this->assertSame('Error message "Class PHPStan\Tests\Baz was not found while trying to analyse it - autoloading is probably not configured properly." cannot be ignored, use excludePaths instead.', $result[1]); - } - - public function testIgnoreErrorByPath(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/bootstrap-error.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); - $this->assertCount(0, $result); - } - - public function testIgnoreErrorByPathAndCount(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'count' => 3, - 'path' => __DIR__ . '/data/two-fails.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', false); - $this->assertCount(0, $result); - } - - public function dataTrueAndFalse(): array - { - return [ - [true], - [false], - ]; - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $onlyFiles - */ - public function testIgnoreErrorByPathAndCountMoreThanExpected(bool $onlyFiles): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'count' => 1, - 'path' => __DIR__ . '/data/two-fails.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', $onlyFiles); - $this->assertCount(3, $result); - $this->assertInstanceOf(Error::class, $result[0]); - $this->assertSame('Fail.', $result[0]->getMessage()); - $this->assertSame(6, $result[0]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); - - $this->assertInstanceOf(Error::class, $result[1]); - $this->assertSame('Fail.', $result[1]->getMessage()); - $this->assertSame(7, $result[1]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[1]->getFile()); - - $this->assertInstanceOf(Error::class, $result[2]); - $this->assertStringContainsString('Ignored error pattern #Fail\.#', $result[2]->getMessage()); - $this->assertStringContainsString('is expected to occur 1 time, but occurred 3 times.', $result[2]->getMessage()); - $this->assertSame(5, $result[2]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $onlyFiles - */ - public function testIgnoreErrorByPathAndCountLessThanExpected(bool $onlyFiles): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'count' => 4, - 'path' => __DIR__ . '/data/two-fails.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', $onlyFiles); - $this->assertCount(1, $result); - $this->assertInstanceOf(Error::class, $result[0]); - $this->assertStringContainsString('Ignored error pattern #Fail\.#', $result[0]->getMessage()); - $this->assertStringContainsString('is expected to occur 4 times, but occurred only 3 times.', $result[0]->getMessage()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); - $this->assertSame(5, $result[0]->getLine()); - } - - public function testIgnoreErrorByPathAndCountMissing(): void - { - $ignoreErrors = [ - [ - 'message' => '#Some custom error\.#', - 'count' => 2, - 'path' => __DIR__ . '/data/two-fails.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', false); - $this->assertCount(4, $result); - $this->assertInstanceOf(Error::class, $result[0]); - $this->assertSame('Fail.', $result[0]->getMessage()); - $this->assertSame(5, $result[0]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); - - $this->assertInstanceOf(Error::class, $result[1]); - $this->assertSame('Fail.', $result[1]->getMessage()); - $this->assertSame(6, $result[1]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[1]->getFile()); - - $this->assertInstanceOf(Error::class, $result[2]); - $this->assertSame('Fail.', $result[2]->getMessage()); - $this->assertSame(7, $result[2]->getLine()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); - - $this->assertInstanceOf(Error::class, $result[3]); - $this->assertStringContainsString('Ignored error pattern #Some custom error\.# in path', $result[3]->getMessage()); - $this->assertStringContainsString('was not matched in reported errors.', $result[3]->getMessage()); - $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); - } - - public function testIgnoreErrorByPaths(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'paths' => [__DIR__ . '/data/bootstrap-error.php'], - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); - $this->assertCount(0, $result); - } - - public function testIgnoreErrorByPathsMultipleUnmatched(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'paths' => [__DIR__ . '/data/bootstrap-error.php', __DIR__ . '/data/another-path.php', '/data/yet-another-path.php'], - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); - $this->assertCount(1, $result); - $this->assertIsString($result[0]); - $this->assertStringContainsString('Ignored error pattern #Fail\.# in paths: ', $result[0]); - $this->assertStringContainsString('was not matched in reported errors', $result[0]); - } - - public function testIgnoreErrorByPathsUnmatched(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'paths' => [__DIR__ . '/data/bootstrap-error.php', __DIR__ . '/data/another-path.php'], - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); - $this->assertCount(1, $result); - $this->assertIsString($result[0]); - $this->assertStringContainsString('Ignored error pattern #Fail\.# in path ', $result[0]); - $this->assertStringContainsString('was not matched in reported errors', $result[0]); - } - - public function testIgnoreErrorNotFoundInPath(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/not-existent-path.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); - $this->assertCount(1, $result); - $this->assertSame('Ignored error pattern #Fail\.# in path ' . __DIR__ . '/data/not-existent-path.php was not matched in reported errors.', $result[0]); - } - - public function dataIgnoreErrorInTraitUsingClassFilePath(): array - { - return [ - [ - __DIR__ . '/data/traits-ignore/Foo.php', - ], - [ - __DIR__ . '/data/traits-ignore/FooTrait.php', - ], - ]; - } - - /** - * @dataProvider dataIgnoreErrorInTraitUsingClassFilePath - * @param string $pathToIgnore - */ - public function testIgnoreErrorInTraitUsingClassFilePath(string $pathToIgnore): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'path' => $pathToIgnore, - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, [ - __DIR__ . '/data/traits-ignore/Foo.php', - __DIR__ . '/data/traits-ignore/FooTrait.php', - ], true); - $this->assertCount(0, $result); - } - - public function testIgnoredErrorMissingMessage(): void - { - $ignoreErrors = [ - [ - 'path' => __DIR__ . '/data/empty/empty.php', - ], - ]; - - $expectedPath = __DIR__; - - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $expectedPath = str_replace('\\', '\\\\', $expectedPath); - } - - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); - $this->assertCount(1, $result); - $this->assertSame('Ignored error {"path":"' . $expectedPath . '/data/empty/empty.php"} is missing a message.', $result[0]); - } - - public function testIgnoredErrorMissingPath(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); - $this->assertCount(1, $result); - $this->assertSame('Ignored error {"message":"#Fail\\\\.#"} is missing a path.', $result[0]); - } - - public function testIgnoredErrorMessageStillValidatedIfMissingAPath(): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); - $this->assertCount(2, $result); - $this->assertSame('Ignored error {"message":"#Fail\\\\."} is missing a path.', $result[0]); - $this->assertSame('No ending delimiter \'#\' found in pattern: #Fail\.', $result[1]); - } - - public function testReportMultipleParserErrorsAtOnce(): void - { - $result = $this->runAnalyser([], false, __DIR__ . '/data/multipleParseErrors.php', false); - $this->assertCount(2, $result); - - /** @var Error $errorOne */ - $errorOne = $result[0]; - $this->assertSame('Syntax error, unexpected T_IS_EQUAL, expecting T_VARIABLE on line 3', $errorOne->getMessage()); - $this->assertSame(3, $errorOne->getLine()); - - /** @var Error $errorTwo */ - $errorTwo = $result[1]; - $this->assertSame('Syntax error, unexpected EOF on line 10', $errorTwo->getMessage()); - $this->assertSame(10, $errorTwo->getLine()); - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $onlyFiles - */ - public function testDoNotReportUnmatchedIgnoredErrorsFromPathIfPathWasNotAnalysed(bool $onlyFiles): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/bootstrap-error.php', - ], - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/two-fails.php', - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, [ - __DIR__ . '/data/two-fails.php', - ], $onlyFiles); - $this->assertCount(0, $result); - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $onlyFiles - */ - public function testDoNotReportUnmatchedIgnoredErrorsFromPathWithCountIfPathWasNotAnalysed(bool $onlyFiles): void - { - $ignoreErrors = [ - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/bootstrap-error.php', - 'count' => 2, - ], - [ - 'message' => '#Fail\.#', - 'path' => __DIR__ . '/data/two-fails.php', - 'count' => 3, - ], - ]; - $result = $this->runAnalyser($ignoreErrors, true, [ - __DIR__ . '/data/two-fails.php', - ], $onlyFiles); - $this->assertCount(0, $result); - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $reportUnmatchedIgnoredErrors - */ - public function testIgnoreNextLine(bool $reportUnmatchedIgnoredErrors): void - { - $result = $this->runAnalyser([], $reportUnmatchedIgnoredErrors, [ - __DIR__ . '/data/ignore-next-line.php', - ], true); - $this->assertCount($reportUnmatchedIgnoredErrors ? 4 : 3, $result); - foreach ([10, 30, 34] as $i => $line) { - $this->assertArrayHasKey($i, $result); - $this->assertInstanceOf(Error::class, $result[$i]); - $this->assertSame('Fail.', $result[$i]->getMessage()); - $this->assertSame($line, $result[$i]->getLine()); - } - - if (!$reportUnmatchedIgnoredErrors) { - return; - } - - $this->assertArrayHasKey(3, $result); - $this->assertInstanceOf(Error::class, $result[3]); - $this->assertSame('No error to ignore is reported on line 38.', $result[3]->getMessage()); - $this->assertSame(38, $result[3]->getLine()); - } - - /** - * @dataProvider dataTrueAndFalse - * @param bool $reportUnmatchedIgnoredErrors - */ - public function testIgnoreLine(bool $reportUnmatchedIgnoredErrors): void - { - $result = $this->runAnalyser([], $reportUnmatchedIgnoredErrors, [ - __DIR__ . '/data/ignore-line.php', - ], true); - $this->assertCount($reportUnmatchedIgnoredErrors ? 4 : 3, $result); - foreach ([10, 19, 22] as $i => $line) { - $this->assertArrayHasKey($i, $result); - $this->assertInstanceOf(Error::class, $result[$i]); - $this->assertSame('Fail.', $result[$i]->getMessage()); - $this->assertSame($line, $result[$i]->getLine()); - } - - if (!$reportUnmatchedIgnoredErrors) { - return; - } - - $this->assertArrayHasKey(3, $result); - $this->assertInstanceOf(Error::class, $result[3]); - $this->assertSame('No error to ignore is reported on line 26.', $result[3]->getMessage()); - $this->assertSame(26, $result[3]->getLine()); - } - - /** - * @param mixed[] $ignoreErrors - * @param bool $reportUnmatchedIgnoredErrors - * @param string|string[] $filePaths - * @param bool $onlyFiles - * @return string[]|\PHPStan\Analyser\Error[] - */ - private function runAnalyser( - array $ignoreErrors, - bool $reportUnmatchedIgnoredErrors, - $filePaths, - bool $onlyFiles - ): array - { - $analyser = $this->createAnalyser($reportUnmatchedIgnoredErrors); - - if (is_string($filePaths)) { - $filePaths = [$filePaths]; - } - - $ignoredErrorHelper = new IgnoredErrorHelper( - self::getContainer()->getByType(IgnoredRegexValidator::class), - $this->getFileHelper(), - $ignoreErrors, - $reportUnmatchedIgnoredErrors - ); - $ignoredErrorHelperResult = $ignoredErrorHelper->initialize(); - if (count($ignoredErrorHelperResult->getErrors()) > 0) { - return $ignoredErrorHelperResult->getErrors(); - } - - $normalizedFilePaths = array_map(function (string $path): string { - return $this->getFileHelper()->normalizePath($path); - }, $filePaths); - - $analyserResult = $analyser->analyse($normalizedFilePaths); - - $errors = $ignoredErrorHelperResult->process($analyserResult->getErrors(), $onlyFiles, $normalizedFilePaths, $analyserResult->hasReachedInternalErrorsCountLimit()); - if ($analyserResult->hasReachedInternalErrorsCountLimit()) { - $errors[] = sprintf('Reached internal errors count limit of %d, exiting...', 50); - } - - return array_merge( - $errors, - $ignoredErrorHelperResult->getWarnings(), - $analyserResult->getInternalErrors() - ); - } - - private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\Analyser\Analyser - { - $registry = new Registry([ - new AlwaysFailRule(), - ]); - - $broker = $this->createBroker(); - $printer = new \PhpParser\PrettyPrinter\Standard(); - $fileHelper = $this->getFileHelper(); - - /** @var RelativePathHelper $relativePathHelper */ - $relativePathHelper = self::getContainer()->getService('simpleRelativePathHelper'); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $typeSpecifier = $this->createTypeSpecifier($printer, $broker); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, $relativePathHelper)); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); - - $nodeScopeResolver = new NodeScopeResolver( - $broker, - self::getReflectors()[0], - $this->getClassReflectionExtensionRegistryProvider(), - $this->getParser(), - $fileTypeMapper, - self::getContainer()->getByType(PhpVersion::class), - $phpDocInheritanceResolver, - $fileHelper, - $typeSpecifier, - self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), - false, - false, - true, - [], - [], - true, - true - ); - $lexer = new \PhpParser\Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); - $fileAnalyser = new FileAnalyser( - $this->createScopeFactory($broker, $typeSpecifier), - $nodeScopeResolver, - new RichParser( - new \PhpParser\Parser\Php7($lexer), - new \PhpParser\NodeVisitor\NameResolver(), - new NodeConnectingVisitor(), - new StatementOrderVisitor() - ), - new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), - $reportUnmatchedIgnoredErrors - ); - - return new Analyser( - $fileAnalyser, - $registry, - $nodeScopeResolver, - 50 - ); - } - + public function testReturnErrorIfIgnoredMessagesDoesNotOccur(): void + { + $result = $this->runAnalyser(['#Unknown error#'], true, __DIR__ . '/data/empty/empty.php', false); + $this->assertSame([ + 'Ignored error pattern #Unknown error# was not matched in reported errors.', + ], $result); + } + + public function testDoNotReturnErrorIfIgnoredMessagesDoesNotOccurWithReportUnmatchedIgnoredErrorsOff(): void + { + $result = $this->runAnalyser(['#Unknown error#'], false, __DIR__ . '/data/empty/empty.php', false); + $this->assertEmpty($result); + } + + public function testDoNotReturnErrorIfIgnoredMessagesDoNotOccurWhileAnalysingIndividualFiles(): void + { + $result = $this->runAnalyser(['#Unknown error#'], true, __DIR__ . '/data/empty/empty.php', true); + $this->assertEmpty($result); + } + + public function testReportInvalidIgnorePatternEarly(): void + { + $result = $this->runAnalyser(['#Regexp syntax error'], true, __DIR__ . '/data/parse-error.php', false); + $this->assertSame([ + "No ending delimiter '#' found in pattern: #Regexp syntax error", + ], $result); + } + + public function testFileWithAnIgnoredError(): void + { + $result = $this->runAnalyser(['#Fail\.#'], true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertEmpty($result); + } + + public function testIgnoringBrokenConfigurationDoesNotWork(): void + { + $this->markTestIncomplete(); + $result = $this->runAnalyser(['#was not found while trying to analyse it#'], true, __DIR__ . '/../../notAutoloaded/Baz.php', false); + $this->assertCount(2, $result); + assert($result[0] instanceof Error); + $this->assertSame('Class PHPStan\Tests\Baz was not found while trying to analyse it - autoloading is probably not configured properly.', $result[0]->getMessage()); + $this->assertSame('Error message "Class PHPStan\Tests\Baz was not found while trying to analyse it - autoloading is probably not configured properly." cannot be ignored, use excludePaths instead.', $result[1]); + } + + public function testIgnoreErrorByPath(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/bootstrap-error.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertCount(0, $result); + } + + public function testIgnoreErrorByPathAndCount(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'count' => 3, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', false); + $this->assertCount(0, $result); + } + + public function dataTrueAndFalse(): array + { + return [ + [true], + [false], + ]; + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $onlyFiles + */ + public function testIgnoreErrorByPathAndCountMoreThanExpected(bool $onlyFiles): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'count' => 1, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', $onlyFiles); + $this->assertCount(3, $result); + $this->assertInstanceOf(Error::class, $result[0]); + $this->assertSame('Fail.', $result[0]->getMessage()); + $this->assertSame(6, $result[0]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); + + $this->assertInstanceOf(Error::class, $result[1]); + $this->assertSame('Fail.', $result[1]->getMessage()); + $this->assertSame(7, $result[1]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[1]->getFile()); + + $this->assertInstanceOf(Error::class, $result[2]); + $this->assertStringContainsString('Ignored error pattern #Fail\.#', $result[2]->getMessage()); + $this->assertStringContainsString('is expected to occur 1 time, but occurred 3 times.', $result[2]->getMessage()); + $this->assertSame(5, $result[2]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $onlyFiles + */ + public function testIgnoreErrorByPathAndCountLessThanExpected(bool $onlyFiles): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'count' => 4, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', $onlyFiles); + $this->assertCount(1, $result); + $this->assertInstanceOf(Error::class, $result[0]); + $this->assertStringContainsString('Ignored error pattern #Fail\.#', $result[0]->getMessage()); + $this->assertStringContainsString('is expected to occur 4 times, but occurred only 3 times.', $result[0]->getMessage()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); + $this->assertSame(5, $result[0]->getLine()); + } + + public function testIgnoreErrorByPathAndCountMissing(): void + { + $ignoreErrors = [ + [ + 'message' => '#Some custom error\.#', + 'count' => 2, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-fails.php', false); + $this->assertCount(4, $result); + $this->assertInstanceOf(Error::class, $result[0]); + $this->assertSame('Fail.', $result[0]->getMessage()); + $this->assertSame(5, $result[0]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[0]->getFile()); + + $this->assertInstanceOf(Error::class, $result[1]); + $this->assertSame('Fail.', $result[1]->getMessage()); + $this->assertSame(6, $result[1]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[1]->getFile()); + + $this->assertInstanceOf(Error::class, $result[2]); + $this->assertSame('Fail.', $result[2]->getMessage()); + $this->assertSame(7, $result[2]->getLine()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); + + $this->assertInstanceOf(Error::class, $result[3]); + $this->assertStringContainsString('Ignored error pattern #Some custom error\.# in path', $result[3]->getMessage()); + $this->assertStringContainsString('was not matched in reported errors.', $result[3]->getMessage()); + $this->assertSamePaths(__DIR__ . '/data/two-fails.php', $result[2]->getFile()); + } + + public function testIgnoreErrorByPaths(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'paths' => [__DIR__ . '/data/bootstrap-error.php'], + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertCount(0, $result); + } + + public function testIgnoreErrorByPathsMultipleUnmatched(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'paths' => [__DIR__ . '/data/bootstrap-error.php', __DIR__ . '/data/another-path.php', '/data/yet-another-path.php'], + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertCount(1, $result); + $this->assertIsString($result[0]); + $this->assertStringContainsString('Ignored error pattern #Fail\.# in paths: ', $result[0]); + $this->assertStringContainsString('was not matched in reported errors', $result[0]); + } + + public function testIgnoreErrorByPathsUnmatched(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'paths' => [__DIR__ . '/data/bootstrap-error.php', __DIR__ . '/data/another-path.php'], + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertCount(1, $result); + $this->assertIsString($result[0]); + $this->assertStringContainsString('Ignored error pattern #Fail\.# in path ', $result[0]); + $this->assertStringContainsString('was not matched in reported errors', $result[0]); + } + + public function testIgnoreErrorNotFoundInPath(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/not-existent-path.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); + $this->assertCount(1, $result); + $this->assertSame('Ignored error pattern #Fail\.# in path ' . __DIR__ . '/data/not-existent-path.php was not matched in reported errors.', $result[0]); + } + + public function dataIgnoreErrorInTraitUsingClassFilePath(): array + { + return [ + [ + __DIR__ . '/data/traits-ignore/Foo.php', + ], + [ + __DIR__ . '/data/traits-ignore/FooTrait.php', + ], + ]; + } + + /** + * @dataProvider dataIgnoreErrorInTraitUsingClassFilePath + * @param string $pathToIgnore + */ + public function testIgnoreErrorInTraitUsingClassFilePath(string $pathToIgnore): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'path' => $pathToIgnore, + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, [ + __DIR__ . '/data/traits-ignore/Foo.php', + __DIR__ . '/data/traits-ignore/FooTrait.php', + ], true); + $this->assertCount(0, $result); + } + + public function testIgnoredErrorMissingMessage(): void + { + $ignoreErrors = [ + [ + 'path' => __DIR__ . '/data/empty/empty.php', + ], + ]; + + $expectedPath = __DIR__; + + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $expectedPath = str_replace('\\', '\\\\', $expectedPath); + } + + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); + $this->assertCount(1, $result); + $this->assertSame('Ignored error {"path":"' . $expectedPath . '/data/empty/empty.php"} is missing a message.', $result[0]); + } + + public function testIgnoredErrorMissingPath(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); + $this->assertCount(1, $result); + $this->assertSame('Ignored error {"message":"#Fail\\\\.#"} is missing a path.', $result[0]); + } + + public function testIgnoredErrorMessageStillValidatedIfMissingAPath(): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false); + $this->assertCount(2, $result); + $this->assertSame('Ignored error {"message":"#Fail\\\\."} is missing a path.', $result[0]); + $this->assertSame('No ending delimiter \'#\' found in pattern: #Fail\.', $result[1]); + } + + public function testReportMultipleParserErrorsAtOnce(): void + { + $result = $this->runAnalyser([], false, __DIR__ . '/data/multipleParseErrors.php', false); + $this->assertCount(2, $result); + + /** @var Error $errorOne */ + $errorOne = $result[0]; + $this->assertSame('Syntax error, unexpected T_IS_EQUAL, expecting T_VARIABLE on line 3', $errorOne->getMessage()); + $this->assertSame(3, $errorOne->getLine()); + + /** @var Error $errorTwo */ + $errorTwo = $result[1]; + $this->assertSame('Syntax error, unexpected EOF on line 10', $errorTwo->getMessage()); + $this->assertSame(10, $errorTwo->getLine()); + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $onlyFiles + */ + public function testDoNotReportUnmatchedIgnoredErrorsFromPathIfPathWasNotAnalysed(bool $onlyFiles): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/bootstrap-error.php', + ], + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/two-fails.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, [ + __DIR__ . '/data/two-fails.php', + ], $onlyFiles); + $this->assertCount(0, $result); + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $onlyFiles + */ + public function testDoNotReportUnmatchedIgnoredErrorsFromPathWithCountIfPathWasNotAnalysed(bool $onlyFiles): void + { + $ignoreErrors = [ + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/bootstrap-error.php', + 'count' => 2, + ], + [ + 'message' => '#Fail\.#', + 'path' => __DIR__ . '/data/two-fails.php', + 'count' => 3, + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, [ + __DIR__ . '/data/two-fails.php', + ], $onlyFiles); + $this->assertCount(0, $result); + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $reportUnmatchedIgnoredErrors + */ + public function testIgnoreNextLine(bool $reportUnmatchedIgnoredErrors): void + { + $result = $this->runAnalyser([], $reportUnmatchedIgnoredErrors, [ + __DIR__ . '/data/ignore-next-line.php', + ], true); + $this->assertCount($reportUnmatchedIgnoredErrors ? 4 : 3, $result); + foreach ([10, 30, 34] as $i => $line) { + $this->assertArrayHasKey($i, $result); + $this->assertInstanceOf(Error::class, $result[$i]); + $this->assertSame('Fail.', $result[$i]->getMessage()); + $this->assertSame($line, $result[$i]->getLine()); + } + + if (!$reportUnmatchedIgnoredErrors) { + return; + } + + $this->assertArrayHasKey(3, $result); + $this->assertInstanceOf(Error::class, $result[3]); + $this->assertSame('No error to ignore is reported on line 38.', $result[3]->getMessage()); + $this->assertSame(38, $result[3]->getLine()); + } + + /** + * @dataProvider dataTrueAndFalse + * @param bool $reportUnmatchedIgnoredErrors + */ + public function testIgnoreLine(bool $reportUnmatchedIgnoredErrors): void + { + $result = $this->runAnalyser([], $reportUnmatchedIgnoredErrors, [ + __DIR__ . '/data/ignore-line.php', + ], true); + $this->assertCount($reportUnmatchedIgnoredErrors ? 4 : 3, $result); + foreach ([10, 19, 22] as $i => $line) { + $this->assertArrayHasKey($i, $result); + $this->assertInstanceOf(Error::class, $result[$i]); + $this->assertSame('Fail.', $result[$i]->getMessage()); + $this->assertSame($line, $result[$i]->getLine()); + } + + if (!$reportUnmatchedIgnoredErrors) { + return; + } + + $this->assertArrayHasKey(3, $result); + $this->assertInstanceOf(Error::class, $result[3]); + $this->assertSame('No error to ignore is reported on line 26.', $result[3]->getMessage()); + $this->assertSame(26, $result[3]->getLine()); + } + + /** + * @param mixed[] $ignoreErrors + * @param bool $reportUnmatchedIgnoredErrors + * @param string|string[] $filePaths + * @param bool $onlyFiles + * @return string[]|\PHPStan\Analyser\Error[] + */ + private function runAnalyser( + array $ignoreErrors, + bool $reportUnmatchedIgnoredErrors, + $filePaths, + bool $onlyFiles + ): array { + $analyser = $this->createAnalyser($reportUnmatchedIgnoredErrors); + + if (is_string($filePaths)) { + $filePaths = [$filePaths]; + } + + $ignoredErrorHelper = new IgnoredErrorHelper( + self::getContainer()->getByType(IgnoredRegexValidator::class), + $this->getFileHelper(), + $ignoreErrors, + $reportUnmatchedIgnoredErrors + ); + $ignoredErrorHelperResult = $ignoredErrorHelper->initialize(); + if (count($ignoredErrorHelperResult->getErrors()) > 0) { + return $ignoredErrorHelperResult->getErrors(); + } + + $normalizedFilePaths = array_map(function (string $path): string { + return $this->getFileHelper()->normalizePath($path); + }, $filePaths); + + $analyserResult = $analyser->analyse($normalizedFilePaths); + + $errors = $ignoredErrorHelperResult->process($analyserResult->getErrors(), $onlyFiles, $normalizedFilePaths, $analyserResult->hasReachedInternalErrorsCountLimit()); + if ($analyserResult->hasReachedInternalErrorsCountLimit()) { + $errors[] = sprintf('Reached internal errors count limit of %d, exiting...', 50); + } + + return array_merge( + $errors, + $ignoredErrorHelperResult->getWarnings(), + $analyserResult->getInternalErrors() + ); + } + + private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\Analyser\Analyser + { + $registry = new Registry([ + new AlwaysFailRule(), + ]); + + $broker = $this->createBroker(); + $printer = new \PhpParser\PrettyPrinter\Standard(); + $fileHelper = $this->getFileHelper(); + + /** @var RelativePathHelper $relativePathHelper */ + $relativePathHelper = self::getContainer()->getService('simpleRelativePathHelper'); + $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); + $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); + $typeSpecifier = $this->createTypeSpecifier($printer, $broker); + $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, $relativePathHelper)); + $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + + $nodeScopeResolver = new NodeScopeResolver( + $broker, + self::getReflectors()[0], + $this->getClassReflectionExtensionRegistryProvider(), + $this->getParser(), + $fileTypeMapper, + self::getContainer()->getByType(PhpVersion::class), + $phpDocInheritanceResolver, + $fileHelper, + $typeSpecifier, + self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), + false, + false, + true, + [], + [], + true, + true + ); + $lexer = new \PhpParser\Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); + $fileAnalyser = new FileAnalyser( + $this->createScopeFactory($broker, $typeSpecifier), + $nodeScopeResolver, + new RichParser( + new \PhpParser\Parser\Php7($lexer), + new \PhpParser\NodeVisitor\NameResolver(), + new NodeConnectingVisitor(), + new StatementOrderVisitor() + ), + new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), + $reportUnmatchedIgnoredErrors + ); + + return new Analyser( + $fileAnalyser, + $registry, + $nodeScopeResolver, + 50 + ); + } } diff --git a/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php index dad3508986..4cc2612865 100644 --- a/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php @@ -1,4 +1,6 @@ -fileHelper = self::getContainer()->getByType(FileHelper::class); - } - - public function testMethodIsInClassUsingTrait(): void - { - $errors = $this->runAnalyse([ - __DIR__ . '/traits/Foo.php', - __DIR__ . '/traits/FooTrait.php', - ]); - $this->assertEmpty($errors); - } - - public function testMethodDoesNotExist(): void - { - $errors = $this->runAnalyse([ - __DIR__ . '/traits/Bar.php', - __DIR__ . '/traits/FooTrait.php', - ]); - $this->assertCount(1, $errors); - $error = $errors[0]; - $this->assertSame('Call to an undefined method AnalyseTraits\Bar::doFoo().', $error->getMessage()); - $this->assertSame( - sprintf('%s (in context of class AnalyseTraits\Bar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/FooTrait.php')), - $error->getFile() - ); - $this->assertSame(10, $error->getLine()); - } - - public function testNestedTraits(): void - { - $errors = $this->runAnalyse([ - __DIR__ . '/traits/NestedBar.php', - __DIR__ . '/traits/NestedFooTrait.php', - __DIR__ . '/traits/FooTrait.php', - ]); - $this->assertCount(2, $errors); - $firstError = $errors[0]; - $this->assertSame('Call to an undefined method AnalyseTraits\NestedBar::doFoo().', $firstError->getMessage()); - $this->assertSame( - sprintf('%s (in context of class AnalyseTraits\NestedBar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/FooTrait.php')), - $firstError->getFile() - ); - $this->assertSame(10, $firstError->getLine()); - - $secondError = $errors[1]; - $this->assertSame('Call to an undefined method AnalyseTraits\NestedBar::doNestedFoo().', $secondError->getMessage()); - $this->assertSame( - sprintf('%s (in context of class AnalyseTraits\NestedBar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/NestedFooTrait.php')), - $secondError->getFile() - ); - $this->assertSame(12, $secondError->getLine()); - } - - public function testTraitsAreNotAnalysedDirectly(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/FooTrait.php']); - $this->assertEmpty($errors); - $errors = $this->runAnalyse([__DIR__ . '/traits/NestedFooTrait.php']); - $this->assertEmpty($errors); - } - - public function testClassAndTraitInTheSameFile(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/classAndTrait.php']); - $this->assertEmpty($errors); - } - - public function testTraitMethodAlias(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/trait-aliases.php']); - $this->assertEmpty($errors); - } - - public function testFindErrorsInTrait(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/trait-error.php']); - $this->assertCount(3, $errors); - $this->assertSame('Undefined variable: $undefined', $errors[0]->getMessage()); - $this->assertSame('Call to an undefined method TraitErrors\MyClass::undefined().', $errors[1]->getMessage()); - $this->assertSame('Undefined variable: $undefined', $errors[2]->getMessage()); - } - - public function testTraitInAnonymousClass(): void - { - $errors = $this->runAnalyse( - [ - __DIR__ . '/traits/AnonymousClassUsingTrait.php', - __DIR__ . '/traits/TraitWithTypeSpecification.php', - ] - ); - $this->assertCount(1, $errors); - $this->assertStringContainsString('Access to an undefined property', $errors[0]->getMessage()); - $this->assertSame(18, $errors[0]->getLine()); - } - - public function testDuplicateMethodDefinition(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/duplicateMethod/Lesson.php']); - $this->assertCount(0, $errors); - } - - public function testWrongPropertyType(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/wrongProperty/Foo.php']); - $this->assertCount(2, $errors); - $this->assertSame(15, $errors[0]->getLine()); - $this->assertSame( - $this->fileHelper->normalizePath(__DIR__ . '/traits/wrongProperty/Foo.php'), - $errors[0]->getFile() - ); - $this->assertSame('Property TraitsWrongProperty\Foo::$id (int) does not accept string.', $errors[0]->getMessage()); - - $this->assertSame(17, $errors[1]->getLine()); - $this->assertSame( - $this->fileHelper->normalizePath(__DIR__ . '/traits/wrongProperty/Foo.php'), - $errors[1]->getFile() - ); - $this->assertSame('Property TraitsWrongProperty\Foo::$bar (Ipsum) does not accept int.', $errors[1]->getMessage()); - } - - public function testReturnThis(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/returnThis/Bar.php']); - $this->assertCount(2, $errors); - $this->assertSame(10, $errors[0]->getLine()); - $this->assertSame('Call to an undefined method TraitsReturnThis\Foo::doFoo().', $errors[0]->getMessage()); - $this->assertSame(11, $errors[1]->getLine()); - $this->assertSame('Call to an undefined method TraitsReturnThis\Foo::doFoo().', $errors[1]->getMessage()); - } - - public function testTraitInEval(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/TraitInEvalUse.php']); - $this->assertCount(0, $errors); - } - - public function testParameterNotFoundCrash(): void - { - $errors = $this->runAnalyse([__DIR__ . '/traits/parameter-not-found.php']); - $this->assertCount(0, $errors); - } - - public function testMissingReturnInAbstractTraitMethod(): void - { - $errors = $this->runAnalyse([ - __DIR__ . '/traits/TraitWithAbstractMethod.php', - __DIR__ . '/traits/ClassImplementingTraitWithAbstractMethod.php', - ]); - $this->assertCount(0, $errors); - } - - /** - * @param string[] $files - * @return \PHPStan\Analyser\Error[] - */ - private function runAnalyse(array $files): array - { - $files = array_map(function (string $file): string { - return $this->getFileHelper()->normalizePath($file); - }, $files); - /** @var \PHPStan\Analyser\Analyser $analyser */ - $analyser = self::getContainer()->getByType(Analyser::class); - /** @var \PHPStan\Analyser\Error[] $errors */ - $errors = $analyser->analyse($files)->getErrors(); - return $errors; - } - + /** @var \PHPStan\File\FileHelper */ + private $fileHelper; + + protected function setUp(): void + { + $this->fileHelper = self::getContainer()->getByType(FileHelper::class); + } + + public function testMethodIsInClassUsingTrait(): void + { + $errors = $this->runAnalyse([ + __DIR__ . '/traits/Foo.php', + __DIR__ . '/traits/FooTrait.php', + ]); + $this->assertEmpty($errors); + } + + public function testMethodDoesNotExist(): void + { + $errors = $this->runAnalyse([ + __DIR__ . '/traits/Bar.php', + __DIR__ . '/traits/FooTrait.php', + ]); + $this->assertCount(1, $errors); + $error = $errors[0]; + $this->assertSame('Call to an undefined method AnalyseTraits\Bar::doFoo().', $error->getMessage()); + $this->assertSame( + sprintf('%s (in context of class AnalyseTraits\Bar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/FooTrait.php')), + $error->getFile() + ); + $this->assertSame(10, $error->getLine()); + } + + public function testNestedTraits(): void + { + $errors = $this->runAnalyse([ + __DIR__ . '/traits/NestedBar.php', + __DIR__ . '/traits/NestedFooTrait.php', + __DIR__ . '/traits/FooTrait.php', + ]); + $this->assertCount(2, $errors); + $firstError = $errors[0]; + $this->assertSame('Call to an undefined method AnalyseTraits\NestedBar::doFoo().', $firstError->getMessage()); + $this->assertSame( + sprintf('%s (in context of class AnalyseTraits\NestedBar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/FooTrait.php')), + $firstError->getFile() + ); + $this->assertSame(10, $firstError->getLine()); + + $secondError = $errors[1]; + $this->assertSame('Call to an undefined method AnalyseTraits\NestedBar::doNestedFoo().', $secondError->getMessage()); + $this->assertSame( + sprintf('%s (in context of class AnalyseTraits\NestedBar)', $this->fileHelper->normalizePath(__DIR__ . '/traits/NestedFooTrait.php')), + $secondError->getFile() + ); + $this->assertSame(12, $secondError->getLine()); + } + + public function testTraitsAreNotAnalysedDirectly(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/FooTrait.php']); + $this->assertEmpty($errors); + $errors = $this->runAnalyse([__DIR__ . '/traits/NestedFooTrait.php']); + $this->assertEmpty($errors); + } + + public function testClassAndTraitInTheSameFile(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/classAndTrait.php']); + $this->assertEmpty($errors); + } + + public function testTraitMethodAlias(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/trait-aliases.php']); + $this->assertEmpty($errors); + } + + public function testFindErrorsInTrait(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/trait-error.php']); + $this->assertCount(3, $errors); + $this->assertSame('Undefined variable: $undefined', $errors[0]->getMessage()); + $this->assertSame('Call to an undefined method TraitErrors\MyClass::undefined().', $errors[1]->getMessage()); + $this->assertSame('Undefined variable: $undefined', $errors[2]->getMessage()); + } + + public function testTraitInAnonymousClass(): void + { + $errors = $this->runAnalyse( + [ + __DIR__ . '/traits/AnonymousClassUsingTrait.php', + __DIR__ . '/traits/TraitWithTypeSpecification.php', + ] + ); + $this->assertCount(1, $errors); + $this->assertStringContainsString('Access to an undefined property', $errors[0]->getMessage()); + $this->assertSame(18, $errors[0]->getLine()); + } + + public function testDuplicateMethodDefinition(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/duplicateMethod/Lesson.php']); + $this->assertCount(0, $errors); + } + + public function testWrongPropertyType(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/wrongProperty/Foo.php']); + $this->assertCount(2, $errors); + $this->assertSame(15, $errors[0]->getLine()); + $this->assertSame( + $this->fileHelper->normalizePath(__DIR__ . '/traits/wrongProperty/Foo.php'), + $errors[0]->getFile() + ); + $this->assertSame('Property TraitsWrongProperty\Foo::$id (int) does not accept string.', $errors[0]->getMessage()); + + $this->assertSame(17, $errors[1]->getLine()); + $this->assertSame( + $this->fileHelper->normalizePath(__DIR__ . '/traits/wrongProperty/Foo.php'), + $errors[1]->getFile() + ); + $this->assertSame('Property TraitsWrongProperty\Foo::$bar (Ipsum) does not accept int.', $errors[1]->getMessage()); + } + + public function testReturnThis(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/returnThis/Bar.php']); + $this->assertCount(2, $errors); + $this->assertSame(10, $errors[0]->getLine()); + $this->assertSame('Call to an undefined method TraitsReturnThis\Foo::doFoo().', $errors[0]->getMessage()); + $this->assertSame(11, $errors[1]->getLine()); + $this->assertSame('Call to an undefined method TraitsReturnThis\Foo::doFoo().', $errors[1]->getMessage()); + } + + public function testTraitInEval(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/TraitInEvalUse.php']); + $this->assertCount(0, $errors); + } + + public function testParameterNotFoundCrash(): void + { + $errors = $this->runAnalyse([__DIR__ . '/traits/parameter-not-found.php']); + $this->assertCount(0, $errors); + } + + public function testMissingReturnInAbstractTraitMethod(): void + { + $errors = $this->runAnalyse([ + __DIR__ . '/traits/TraitWithAbstractMethod.php', + __DIR__ . '/traits/ClassImplementingTraitWithAbstractMethod.php', + ]); + $this->assertCount(0, $errors); + } + + /** + * @param string[] $files + * @return \PHPStan\Analyser\Error[] + */ + private function runAnalyse(array $files): array + { + $files = array_map(function (string $file): string { + return $this->getFileHelper()->normalizePath($file); + }, $files); + /** @var \PHPStan\Analyser\Analyser $analyser */ + $analyser = self::getContainer()->getByType(Analyser::class); + /** @var \PHPStan\Analyser\Error[] $errors */ + $errors = $analyser->analyse($files)->getErrors(); + return $errors; + } } diff --git a/tests/PHPStan/Analyser/AnonymousClassNameRule.php b/tests/PHPStan/Analyser/AnonymousClassNameRule.php index a4cf2bddc6..f4167a46ff 100644 --- a/tests/PHPStan/Analyser/AnonymousClassNameRule.php +++ b/tests/PHPStan/Analyser/AnonymousClassNameRule.php @@ -1,4 +1,6 @@ -reflectionProvider = $reflectionProvider; - } - - public function getNodeType(): string - { - return Class_::class; - } - - /** - * @param Class_ $node - * @param Scope $scope - * @return string[] - */ - public function processNode(Node $node, Scope $scope): array - { - $className = isset($node->namespacedName) - ? (string) $node->namespacedName - : (string) $node->name; - try { - $this->reflectionProvider->getClass($className); - } catch (\PHPStan\Broker\ClassNotFoundException $e) { - return ['not found']; - } - - return ['found']; - } - + /** @var ReflectionProvider */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Class_::class; + } + + /** + * @param Class_ $node + * @param Scope $scope + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + $className = isset($node->namespacedName) + ? (string) $node->namespacedName + : (string) $node->name; + try { + $this->reflectionProvider->getClass($className); + } catch (\PHPStan\Broker\ClassNotFoundException $e) { + return ['not found']; + } + + return ['found']; + } } diff --git a/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php b/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php index 2ea1a331d7..7f41959a96 100644 --- a/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php +++ b/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new AnonymousClassNameRule($broker); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new AnonymousClassNameRule($broker); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/anonymous-class-name.php'], [ - [ - 'found', - 6, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/anonymous-class-name.php'], [ + [ + 'found', + 6, + ], + ]); + } } diff --git a/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionTest.php b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionTest.php index bdb8094fe9..6637e9f496 100644 --- a/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionTest.php +++ b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionTest.php @@ -1,4 +1,6 @@ -gatherAssertTypes(__DIR__ . '/data/dynamic-method-throw-type-extension.php'); + } - public function dataFileAsserts(): iterable - { - yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-throw-type-extension.php'); - } - - /** - * @dataProvider dataFileAsserts - * @param string $assertType - * @param string $file - * @param mixed ...$args - */ - public function testFileAsserts( - string $assertType, - string $file, - ...$args - ): void - { - $this->assertFileAsserts($assertType, $file, ...$args); - } - - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__ . '/dynamic-throw-type-extension.neon', - ]; - } + /** + * @dataProvider dataFileAsserts + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void { + $this->assertFileAsserts($assertType, $file, ...$args); + } + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-throw-type-extension.neon', + ]; + } } diff --git a/tests/PHPStan/Analyser/ErrorTest.php b/tests/PHPStan/Analyser/ErrorTest.php index d2347ebbfc..a1d52186b1 100644 --- a/tests/PHPStan/Analyser/ErrorTest.php +++ b/tests/PHPStan/Analyser/ErrorTest.php @@ -1,16 +1,16 @@ -assertSame('Message', $error->getMessage()); - $this->assertSame('file', $error->getFile()); - $this->assertSame(10, $error->getLine()); - } - + public function testError(): void + { + $error = new Error('Message', 'file', 10); + $this->assertSame('Message', $error->getMessage()); + $this->assertSame('file', $error->getFile()); + $this->assertSame(10, $error->getLine()); + } } diff --git a/tests/PHPStan/Analyser/EvaluationOrderRule.php b/tests/PHPStan/Analyser/EvaluationOrderRule.php index de08902369..34cbb4669f 100644 --- a/tests/PHPStan/Analyser/EvaluationOrderRule.php +++ b/tests/PHPStan/Analyser/EvaluationOrderRule.php @@ -1,4 +1,6 @@ -name instanceof Node\Name - ) { - return [$node->name->toString()]; - } - - if ($node instanceof Node\Scalar\String_) { - return [$node->value]; - } - - return []; - } - + public function getNodeType(): string + { + return Node::class; + } + + /** + * @param Node $node + * @param Scope $scope + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + if ( + $node instanceof Node\Expr\FuncCall + && $node->name instanceof Node\Name + ) { + return [$node->name->toString()]; + } + + if ($node instanceof Node\Scalar\String_) { + return [$node->value]; + } + + return []; + } } diff --git a/tests/PHPStan/Analyser/EvaluationOrderTest.php b/tests/PHPStan/Analyser/EvaluationOrderTest.php index f05d0b2c2a..73dcb24f0d 100644 --- a/tests/PHPStan/Analyser/EvaluationOrderTest.php +++ b/tests/PHPStan/Analyser/EvaluationOrderTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/evaluation-order.php'], [ - [ - 'six', - 4, - ], - [ - 'one', - 5, - ], - [ - 'five', - 6, - ], - [ - 'two', - 7, - ], - [ - 'three', - 8, - ], - [ - 'four', - 9, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/evaluation-order.php'], [ + [ + 'six', + 4, + ], + [ + 'one', + 5, + ], + [ + 'five', + 6, + ], + [ + 'two', + 7, + ], + [ + 'three', + 8, + ], + [ + 'four', + 9, + ], + ]); + } } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 466fbc38b7..74a0047497 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1,4 +1,6 @@ -processFile(__DIR__ . '/data/class.php', function (\PhpParser\Node $node, Scope $scope): void { - if (!($node instanceof Exit_)) { - return; - } - - $this->assertSame('SomeNodeScopeResolverNamespace', $scope->getNamespace()); - $this->assertTrue($scope->isInClass()); - $this->assertSame(Foo::class, $scope->getClassReflection()->getName()); - $this->assertSame('doFoo', $scope->getFunctionName()); - $this->assertSame('$this(SomeNodeScopeResolverNamespace\Foo)', $scope->getVariableType('this')->describe(VerbosityLevel::precise())); - $this->assertTrue($scope->hasVariableType('baz')->yes()); - $this->assertTrue($scope->hasVariableType('lorem')->yes()); - $this->assertFalse($scope->hasVariableType('ipsum')->yes()); - $this->assertTrue($scope->hasVariableType('i')->yes()); - $this->assertTrue($scope->hasVariableType('val')->yes()); - $this->assertSame('SomeNodeScopeResolverNamespace\InvalidArgumentException', $scope->getVariableType('exception')->describe(VerbosityLevel::precise())); - $this->assertTrue($scope->hasVariableType('staticVariable')->yes()); - $this->assertSame($scope->getVariableType('staticVariable')->describe(VerbosityLevel::precise()), 'mixed'); - $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType')->yes()); - $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType')->describe(VerbosityLevel::precise()), 'string'); - $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType2')->yes()); - $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType2')->describe(VerbosityLevel::precise()), 'int'); - $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType3')->yes()); - $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType3')->describe(VerbosityLevel::precise()), 'float'); - }); - } - - private function getFileScope(string $filename): Scope - { - /** @var \PHPStan\Analyser\Scope $testScope */ - $testScope = null; - $this->processFile($filename, static function (\PhpParser\Node $node, Scope $scope) use (&$testScope): void { - if (!($node instanceof Exit_)) { - return; - } - - $testScope = $scope; - }); - - return $testScope; - } - - public function dataUnionInCatch(): array - { - return [ - [ - 'CatchUnion\BarException|CatchUnion\FooException', - '$e', - ], - ]; - } - - /** - * @dataProvider dataUnionInCatch - * @param string $description - * @param string $expression - */ - public function testUnionInCatch( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/catch-union.php', - $description, - $expression - ); - } - - public function dataUnionAndIntersection(): array - { - return [ - [ - 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', - '$this->union->foo', - ], - [ - 'UnionIntersection\Bar', - '$this->union->bar', - ], - [ - 'UnionIntersection\Foo', - '$foo->foo', - ], - [ - '*ERROR*', - '$foo->bar', - ], - [ - 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', - '$this->union->doFoo()', - ], - [ - 'UnionIntersection\Bar', - '$this->union->doBar()', - ], - [ - 'UnionIntersection\Foo', - '$foo->doFoo()', - ], - [ - '*ERROR*', - '$foo->doBar()', - ], - [ - 'UnionIntersection\AnotherFoo&UnionIntersection\Foo', - '$foobar->doFoo()', - ], - [ - 'UnionIntersection\Bar', - '$foobar->doBar()', - ], - [ - '1', - '$this->union::FOO_CONSTANT', - ], - [ - '1', - '$this->union::BAR_CONSTANT', - ], - [ - '1', - '$foo::FOO_CONSTANT', - ], - [ - '*ERROR*', - '$foo::BAR_CONSTANT', - ], - [ - '1', - '$foobar::FOO_CONSTANT', - ], - [ - '1', - '$foobar::BAR_CONSTANT', - ], - [ - '\'foo\'', - 'self::IPSUM_CONSTANT', - ], - [ - 'array(1, 2, 3)', - 'parent::PARENT_CONSTANT', - ], - [ - 'UnionIntersection\Foo', - '$foo::doStaticFoo()', - ], - [ - '*ERROR*', - '$foo::doStaticBar()', - ], - [ - 'UnionIntersection\AnotherFoo&UnionIntersection\Foo', - '$foobar::doStaticFoo()', - ], - [ - 'UnionIntersection\Bar', - '$foobar::doStaticBar()', - ], - [ - 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', - '$this->union::doStaticFoo()', - ], - [ - 'UnionIntersection\Bar', - '$this->union::doStaticBar()', - ], - [ - 'object', - '$this->objectUnion', - ], - [ - 'UnionIntersection\SomeInterface', - '$object', - ], - ]; - } - - /** - * @dataProvider dataUnionAndIntersection - * @param string $description - * @param string $expression - */ - public function testUnionAndIntersection( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/union-intersection.php', - $description, - $expression - ); - } - - public function dataAssignInIf(): array - { - $testScope = $this->getFileScope(__DIR__ . '/data/if.php'); - - return [ - [ - $testScope, - 'nonexistentVariable', - TrinaryLogic::createNo(), - ], - [ - $testScope, - 'foo', - TrinaryLogic::createMaybe(), - 'bool', // mixed? - ], - [ - $testScope, - 'lorem', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'callParameter', - TrinaryLogic::createYes(), - '3', - ], - [ - $testScope, - 'arrOne', - TrinaryLogic::createYes(), - 'array(\'one\')', - ], - [ - $testScope, - 'arrTwo', - TrinaryLogic::createYes(), - 'array(\'test\' => \'two\', 0 => Foo)', - ], - [ - $testScope, - 'arrThree', - TrinaryLogic::createYes(), - 'array(\'three\')', - ], - [ - $testScope, - 'inArray', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'i', - TrinaryLogic::createYes(), - 'int', - ], - [ - $testScope, - 'f', - TrinaryLogic::createMaybe(), - 'int', - ], - [ - $testScope, - 'anotherF', - TrinaryLogic::createYes(), - 'int', - ], - [ - $testScope, - 'matches', - TrinaryLogic::createYes(), - 'mixed', - ], - [ - $testScope, - 'anotherArray', - TrinaryLogic::createYes(), - 'array(\'test\' => array(\'another\'))', - ], - [ - $testScope, - 'ifVar', - TrinaryLogic::createYes(), - '1|2|3', - ], - [ - $testScope, - 'ifNotVar', - TrinaryLogic::createMaybe(), - '1|2', - ], - [ - $testScope, - 'ifNestedVar', - TrinaryLogic::createYes(), - '1|2|3', - ], - [ - $testScope, - 'ifNotNestedVar', - TrinaryLogic::createMaybe(), - '1|2|3', - ], - [ - $testScope, - 'variableOnlyInEarlyTerminatingElse', - TrinaryLogic::createNo(), - ], - [ - $testScope, - 'matches2', - TrinaryLogic::createMaybe(), - 'mixed', - ], - [ - $testScope, - 'inTry', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'matches3', - TrinaryLogic::createYes(), - 'mixed', - ], - [ - $testScope, - 'matches4', - TrinaryLogic::createMaybe(), - 'mixed', - ], - [ - $testScope, - 'issetFoo', - TrinaryLogic::createYes(), - 'Foo', - ], - [ - $testScope, - 'issetBar', - TrinaryLogic::createYes(), - 'mixed~null', - ], - [ - $testScope, - 'issetBaz', - TrinaryLogic::createYes(), - 'mixed~null', - ], - [ - $testScope, - 'doWhileVar', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'switchVar', - TrinaryLogic::createYes(), - '1|2|3|4', - ], - [ - $testScope, - 'noSwitchVar', - TrinaryLogic::createMaybe(), - '1', - ], - [ - $testScope, - 'anotherNoSwitchVar', - TrinaryLogic::createMaybe(), - '1', - ], - [ - $testScope, - 'inTryTwo', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'ternaryMatches', - TrinaryLogic::createYes(), - 'mixed', - ], - [ - $testScope, - 'previousI', - TrinaryLogic::createYes(), - '0|1', - ], - [ - $testScope, - 'previousJ', - TrinaryLogic::createYes(), - '0', - ], - [ - $testScope, - 'frame', - TrinaryLogic::createYes(), - 'mixed', - ], - [ - $testScope, - 'listOne', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'listTwo', - TrinaryLogic::createYes(), - '2', - ], - [ - $testScope, - 'e', - TrinaryLogic::createYes(), - 'Exception', - ], - [ - $testScope, - 'exception', - TrinaryLogic::createYes(), - 'Exception', - ], - [ - $testScope, - 'inTryNotInCatch', - TrinaryLogic::createMaybe(), - '1', - ], - [ - $testScope, - 'fooObjectFromTryCatch', - TrinaryLogic::createYes(), - 'InTryCatchFoo', - ], - [ - $testScope, - 'mixedVarFromTryCatch', - TrinaryLogic::createYes(), - '1|1.0', - ], - [ - $testScope, - 'nullableIntegerFromTryCatch', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'anotherNullableIntegerFromTryCatch', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'nullableIntegers', - TrinaryLogic::createYes(), - 'array(1, 2, 3, null)', - ], - [ - $testScope, - 'union', - TrinaryLogic::createYes(), - 'array(1, 2, 3, \'foo\')', - '1|2|3|\'foo\'', - ], - [ - $testScope, - 'trueOrFalse', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'falseOrTrue', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'true', - TrinaryLogic::createYes(), - 'true', - ], - [ - $testScope, - 'false', - TrinaryLogic::createYes(), - 'false', - ], - [ - $testScope, - 'trueOrFalseFromSwitch', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'trueOrFalseInSwitchWithDefault', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'trueOrFalseInSwitchInAllCases', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'trueOrFalseInSwitchInAllCasesWithDefault', - TrinaryLogic::createYes(), - 'bool', - ], - [ - $testScope, - 'trueOrFalseInSwitchInAllCasesWithDefaultCase', - TrinaryLogic::createYes(), - 'true', - ], - [ - $testScope, - 'variableDefinedInSwitchWithOtherCasesWithEarlyTermination', - TrinaryLogic::createYes(), - 'true', - ], - [ - $testScope, - 'anotherVariableDefinedInSwitchWithOtherCasesWithEarlyTermination', - TrinaryLogic::createYes(), - 'true', - ], - [ - $testScope, - 'variableDefinedOnlyInEarlyTerminatingSwitchCases', - TrinaryLogic::createNo(), - ], - [ - $testScope, - 'nullableTrueOrFalse', - TrinaryLogic::createYes(), - 'bool|null', - ], - [ - $testScope, - 'nonexistentVariableOutsideFor', - TrinaryLogic::createMaybe(), - '1', - ], - [ - $testScope, - 'integerOrNullFromFor', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'nonexistentVariableOutsideWhile', - TrinaryLogic::createMaybe(), - '1', - ], - [ - $testScope, - 'integerOrNullFromWhile', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'nonexistentVariableOutsideForeach', - TrinaryLogic::createMaybe(), - 'null', - ], - [ - $testScope, - 'integerOrNullFromForeach', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'notNullableString', - TrinaryLogic::createYes(), - 'string', - ], - [ - $testScope, - 'anotherNotNullableString', - TrinaryLogic::createYes(), - 'string', - ], - [ - $testScope, - 'notNullableObject', - TrinaryLogic::createYes(), - 'Foo', - ], - [ - $testScope, - 'nullableString', - TrinaryLogic::createYes(), - 'string|null', - ], - [ - $testScope, - 'alsoNotNullableString', - TrinaryLogic::createYes(), - 'string', - ], - [ - $testScope, - 'integerOrString', - TrinaryLogic::createYes(), - '\'str\'|int', - ], - [ - $testScope, - 'nullableIntegerAfterNeverCondition', - TrinaryLogic::createYes(), - 'int|null', - ], - [ - $testScope, - 'stillNullableInteger', - TrinaryLogic::createYes(), - '2|null', - ], - [ - $testScope, - 'arrayOfIntegers', - TrinaryLogic::createYes(), - 'array(1, 2, 3)', - ], - [ - $testScope, - 'arrayAccessObject', - TrinaryLogic::createYes(), - \ObjectWithArrayAccess\Foo::class, - ], - [ - $testScope, - 'width', - TrinaryLogic::createYes(), - '2.0', - ], - [ - $testScope, - 'someVariableThatWillGetOverrideInFinally', - TrinaryLogic::createYes(), - '\'foo\'', - ], - [ - $testScope, - 'maybeDefinedButLaterCertainlyDefined', - TrinaryLogic::createYes(), - '2|3', - ], - [ - $testScope, - 'mixed', - TrinaryLogic::createYes(), - 'mixed', // should be mixed~bool+1 - ], - [ - $testScope, - 'variableDefinedInSwitchWithoutEarlyTermination', - TrinaryLogic::createMaybe(), - 'false', - ], - [ - $testScope, - 'anotherVariableDefinedInSwitchWithoutEarlyTermination', - TrinaryLogic::createMaybe(), - 'bool', - ], - [ - $testScope, - 'alwaysDefinedFromSwitch', - TrinaryLogic::createYes(), - '1|null', - ], - [ - $testScope, - 'exceptionFromTryCatch', - TrinaryLogic::createYes(), - '(AnotherException&Throwable)|(Throwable&YetAnotherException)|null', - ], - [ - $testScope, - 'nullOverwrittenInSwitchToOne', - TrinaryLogic::createYes(), - '1', - ], - [ - $testScope, - 'variableFromSwitchShouldBeBool', - TrinaryLogic::createYes(), - 'bool', - ], - ]; - } - - /** - * @dataProvider dataAssignInIf - * @param \PHPStan\Analyser\Scope $scope - * @param string $variableName - * @param \PHPStan\TrinaryLogic $expectedCertainty - * @param string|null $typeDescription - * @param string|null $iterableValueTypeDescription - */ - public function testAssignInIf( - Scope $scope, - string $variableName, - TrinaryLogic $expectedCertainty, - ?string $typeDescription = null, - ?string $iterableValueTypeDescription = null - ): void - { - $this->assertVariables( - $scope, - $variableName, - $expectedCertainty, - $typeDescription, - $iterableValueTypeDescription - ); - } - - public function dataConstantTypes(): array - { - $testScope = $this->getFileScope(__DIR__ . '/data/constantTypes.php'); - - return [ - [ - $testScope, - 'postIncrement', - '2', - ], - [ - $testScope, - 'postDecrement', - '4', - ], - [ - $testScope, - 'preIncrement', - '2', - ], - [ - $testScope, - 'preDecrement', - '4', - ], - [ - $testScope, - 'literalArray', - 'array(\'a\' => 2, \'b\' => 4, \'c\' => 2, \'d\' => 4)', - ], - [ - $testScope, - 'nullIncremented', - '1', - ], - [ - $testScope, - 'nullDecremented', - 'null', - ], - [ - $testScope, - 'incrementInIf', - '1|2|3', - ], - [ - $testScope, - 'anotherIncrementInIf', - '2|3', - ], - [ - $testScope, - 'valueOverwrittenInIf', - '1|2', - ], - [ - $testScope, - 'incrementInForLoop', - 'int', - ], - [ - $testScope, - 'valueOverwrittenInForLoop', - '1|2', - ], - [ - $testScope, - 'arrayOverwrittenInForLoop', - 'array(\'a\' => int, \'b\' => \'bar\'|\'foo\')', - ], - [ - $testScope, - 'anotherValueOverwrittenInIf', - '5|10', - ], - [ - $testScope, - 'intProperty', - 'int', - ], - [ - $testScope, - 'staticIntProperty', - 'int', - ], - [ - $testScope, - 'anotherIntProperty', - '1|2', - ], - [ - $testScope, - 'anotherStaticIntProperty', - '1|2', - ], - [ - $testScope, - 'variableIncrementedInClosurePassedByReference', - 'int', - ], - [ - $testScope, - 'anotherVariableIncrementedInClosure', - '0', - ], - [ - $testScope, - 'yetAnotherVariableInClosurePassedByReference', - 'int', - ], - [ - $testScope, - 'variableIncrementedInFinally', - '1', - ], - ]; - } - - /** - * @dataProvider dataConstantTypes - * @param \PHPStan\Analyser\Scope $scope - * @param string $variableName - * @param string $typeDescription - */ - public function testConstantTypes( - Scope $scope, - string $variableName, - string $typeDescription - ): void - { - $this->assertVariables( - $scope, - $variableName, - TrinaryLogic::createYes(), - $typeDescription, - null - ); - } - - private function assertVariables( - Scope $scope, - string $variableName, - TrinaryLogic $expectedCertainty, - ?string $typeDescription = null, - ?string $iterableValueTypeDescription = null - ): void - { - $certainty = $scope->hasVariableType($variableName); - $this->assertTrue( - $expectedCertainty->equals($certainty), - sprintf( - 'Certainty of variable $%s is %s, expected %s', - $variableName, - $certainty->describe(), - $expectedCertainty->describe() - ) - ); - if (!$expectedCertainty->no()) { - if ($typeDescription === null) { - $this->fail(sprintf('Missing expected type for defined variable $%s.', $variableName)); - } - - $this->assertSame( - $typeDescription, - $scope->getVariableType($variableName)->describe(VerbosityLevel::precise()), - sprintf('Type of variable $%s does not match the expected one.', $variableName) - ); - - if ($iterableValueTypeDescription !== null) { - $this->assertSame( - $iterableValueTypeDescription, - $scope->getVariableType($variableName)->getIterableValueType()->describe(VerbosityLevel::precise()), - sprintf('Iterable value type of variable $%s does not match the expected one.', $variableName) - ); - } - } elseif ($typeDescription !== null) { - $this->fail( - sprintf( - 'No type should be asserted for an undefined variable $%s, %s given.', - $variableName, - $typeDescription - ) - ); - } - } - - public function dataArrayDestructuring(): array - { - return [ - [ - 'mixed', - '$a', - ], - [ - 'mixed', - '$b', - ], - [ - 'mixed', - '$c', - ], - [ - 'mixed', - '$aList', - ], - [ - 'mixed', - '$bList', - ], - [ - 'mixed', - '$cList', - ], - [ - '1', - '$int', - ], - [ - '\'foo\'', - '$string', - ], - [ - 'true', - '$bool', - ], - [ - '*ERROR*', - '$never', - ], - [ - '*ERROR*', - '$nestedNever', - ], - [ - '1', - '$intList', - ], - [ - '\'foo\'', - '$stringList', - ], - [ - 'true', - '$boolList', - ], - [ - '*ERROR*', - '$neverList', - ], - [ - '*ERROR*', - '$nestedNeverList', - ], - [ - '1', - '$foreachInt', - ], - [ - 'false', - '$foreachBool', - ], - [ - '*ERROR*', - '$foreachNever', - ], - [ - '*ERROR*', - '$foreachNestedNever', - ], - [ - '1', - '$foreachIntList', - ], - [ - 'false', - '$foreachBoolList', - ], - [ - '*ERROR*', - '$foreachNeverList', - ], - [ - '*ERROR*', - '$foreachNestedNeverList', - ], - [ - '1|4', - '$u1', - ], - [ - '2|\'bar\'', - '$u2', - ], - [ - '3', - '$u3', - ], - [ - '1|4', - '$foreachU1', - ], - [ - '2|\'bar\'', - '$foreachU2', - ], - [ - '3', - '$foreachU3', - ], - [ - 'string', - '$firstStringArray', - ], - [ - 'string', - '$secondStringArray', - ], - [ - 'string', - '$thirdStringArray', - ], - [ - 'string', - '$fourthStringArray', - ], - [ - 'string', - '$firstStringArrayList', - ], - [ - 'string', - '$secondStringArrayList', - ], - [ - 'string', - '$thirdStringArrayList', - ], - [ - 'string', - '$fourthStringArrayList', - ], - [ - 'string', - '$firstStringArrayForeach', - ], - [ - 'string', - '$secondStringArrayForeach', - ], - [ - 'string', - '$thirdStringArrayForeach', - ], - [ - 'string', - '$fourthStringArrayForeach', - ], - [ - 'string', - '$firstStringArrayForeachList', - ], - [ - 'string', - '$secondStringArrayForeachList', - ], - [ - 'string', - '$thirdStringArrayForeachList', - ], - [ - 'string', - '$fourthStringArrayForeachList', - ], - [ - 'string', - '$dateArray[\'Y\']', - ], - [ - 'string', - '$dateArray[\'m\']', - ], - [ - 'int', - '$dateArray[\'d\']', - ], - [ - 'string', - '$intArrayForRewritingFirstElement[0]', - ], - [ - 'int', - '$intArrayForRewritingFirstElement[1]', - ], - [ - 'stdClass', - '$obj', - ], - [ - 'stdClass', - '$newArray[\'newKey\']', - ], - [ - 'true', - '$assocKey', - ], - [ - '\'foo\'', - '$assocFoo', - ], - [ - '1', - '$assocOne', - ], - [ - '*ERROR*', - '$assocNonExistent', - ], - [ - 'true', - '$dynamicAssocKey', - ], - [ - '\'123\'|true', - '$dynamicAssocStrings', - ], - [ - '1|\'123\'|\'foo\'|true', - '$dynamicAssocMixed', - ], - [ - 'true', - '$dynamicAssocKeyForeach', - ], - [ - '\'123\'|true', - '$dynamicAssocStringsForeach', - ], - [ - '1|\'123\'|\'foo\'|true', - '$dynamicAssocMixedForeach', - ], - [ - 'string', - '$stringFromIterable', - ], - [ - 'string', - '$stringWithVarAnnotation', - ], - [ - 'string', - '$stringWithVarAnnotationInForeach', - ], - ]; - } - - /** - * @dataProvider dataArrayDestructuring - * @param string $description - * @param string $expression - */ - public function testArrayDestructuring( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-destructuring.php', - $description, - $expression - ); - } - - public function dataParameterTypes(): array - { - return [ - [ - 'int', - '$integer', - ], - [ - 'bool', - '$boolean', - ], - [ - 'string', - '$string', - ], - [ - 'float', - '$float', - ], - [ - 'TypesNamespaceTypehints\Lorem', - '$loremObject', - ], - [ - 'mixed', - '$mixed', - ], - [ - 'array', - '$array', - ], - [ - 'bool|null', - '$isNullable', - ], - [ - 'TypesNamespaceTypehints\Lorem', - '$loremObjectRef', - ], - [ - 'TypesNamespaceTypehints\Bar', - '$barObject', - ], - [ - 'TypesNamespaceTypehints\Foo', - '$fooObject', - ], - [ - 'TypesNamespaceTypehints\Bar', - '$anotherBarObject', - ], - [ - 'callable(): mixed', - '$callable', - ], - [ - 'array', - '$variadicStrings', - ], - [ - 'string', - '$variadicStrings[0]', - ], - ]; - } - - /** - * @dataProvider dataParameterTypes - * @param string $typeClass - * @param string $expression - */ - public function testTypehints( - string $typeClass, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/typehints.php', - $typeClass, - $expression - ); - } - - public function dataAnonymousFunctionParameterTypes(): array - { - return [ - [ - 'int', - '$integer', - ], - [ - 'bool', - '$boolean', - ], - [ - 'string', - '$string', - ], - [ - 'float', - '$float', - ], - [ - 'TypesNamespaceTypehints\Lorem', - '$loremObject', - ], - [ - 'mixed', - '$mixed', - ], - [ - 'array', - '$array', - ], - [ - 'bool|null', - '$isNullable', - ], - [ - 'callable(): mixed', - '$callable', - ], - [ - 'TypesNamespaceTypehints\FooWithAnonymousFunction', - '$self', - ], - ]; - } - - /** - * @dataProvider dataAnonymousFunctionParameterTypes - * @param string $description - * @param string $expression - */ - public function testAnonymousFunctionTypehints( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/typehints-anonymous-function.php', - $description, - $expression - ); - } - - public function dataVarAnnotations(): array - { - return [ - [ - 'int', - '$integer', - ], - [ - 'bool', - '$boolean', - ], - [ - 'string', - '$string', - ], - [ - 'float', - '$float', - ], - [ - 'VarAnnotations\Lorem', - '$loremObject', - ], - [ - 'AnotherNamespace\Bar', - '$barObject', - ], - [ - 'mixed', - '$mixed', - ], - [ - 'array', - '$array', - ], - [ - 'bool|null', - '$isNullable', - ], - [ - 'callable(): mixed', - '$callable', - ], - [ - 'callable(int, ...string): void', - '$callableWithTypes', - ], - [ - 'Closure(int, ...string): void', - '$closureWithTypes', - ], - [ - 'VarAnnotations\Foo', - '$self', - ], - [ - 'float', - '$invalidInteger', - ], - [ - 'static(VarAnnotations\Foo)', - '$static', - ], - ]; - } - - /** - * @dataProvider dataVarAnnotations - * @param string $description - * @param string $expression - */ - public function testVarAnnotations( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/var-annotations.php', - $description, - $expression, - [], - [], - [], - [], - 'die', - [], - false - ); - } - - public function dataCasts(): array - { - return [ - [ - 'int', - '$castedInteger', - ], - [ - 'bool', - '$castedBoolean', - ], - [ - 'float', - '$castedFloat', - ], - [ - 'string', - '$castedString', - ], - [ - 'array', - '$castedArray', - ], - [ - 'stdClass', - '$castedObject', - ], - [ - 'TypesNamespaceCasts\Foo', - '$castedFoo', - ], - [ - 'stdClass|TypesNamespaceCasts\Foo', - '$castedArrayOrObject', - ], - [ - '0|1', - '(int) $bool', - ], - [ - '0.0|1.0', - '(float) $bool', - ], - [ - '*ERROR*', - '(int) $foo', - ], - [ - 'true', - '(bool) $foo', - ], - [ - '1', - '(int) true', - ], - [ - '0', - '(int) false', - ], - [ - '5', - '(int) 5.25', - ], - [ - '5.0', - '(float) 5', - ], - [ - '5', - '(int) "5"', - ], - [ - '5.0', - '(float) "5"', - ], - [ - '*ERROR*', - '(int) "blabla"', - ], - [ - '*ERROR*', - '(float) "blabla"', - ], - [ - '0', - '(int) null', - ], - [ - '0.0', - '(float) null', - ], - [ - 'int', - '(int) $str', - ], - [ - 'float', - '(float) $str', - ], - [ - 'array(\'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'foo\' => TypesNamespaceCasts\Foo, \'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'int\' => int, \'\' . "\0" . \'*\' . "\0" . \'protectedInt\' => int, \'publicInt\' => int, \'\' . "\0" . \'TypesNamespaceCasts\\\\Bar\' . "\0" . \'barProperty\' => TypesNamespaceCasts\Bar)', - '(array) $foo', - ], - [ - 'array(1, 2, 3)', - '(array) [1, 2, 3]', - ], - [ - 'array(1)', - '(array) 1', - ], - [ - 'array(1.0)', - '(array) 1.0', - ], - [ - 'array(true)', - '(array) true', - ], - [ - 'array(\'blabla\')', - '(array) "blabla"', - ], - [ - 'array(int)', - '(array) $castedInteger', - ], - [ - 'array', - '(array) $iterable', - ], - [ - 'array', - '(array) new stdClass()', - ], - ]; - } - - /** - * @dataProvider dataCasts - * @param string $desciptiion - * @param string $expression - */ - public function testCasts( - string $desciptiion, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/casts.php', - $desciptiion, - $expression - ); - } - - public function dataUnsetCast(): array - { - return [ - [ - 'null', - '$castedNull', - ], - ]; - } - - /** - * @dataProvider dataUnsetCast - * @param string $desciptiion - * @param string $expression - */ - public function testUnsetCast( - string $desciptiion, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70200) { - $this->markTestSkipped( - 'Test cannot be run on PHP 7.2 and higher - (unset) cast is deprecated.' - ); - } - $this->assertTypes( - __DIR__ . '/data/cast-unset.php', - $desciptiion, - $expression - ); - } - - public function dataDeductedTypes(): array - { - return [ - [ - '1', - '$integerLiteral', - ], - [ - 'true', - '$booleanLiteral', - ], - [ - 'false', - '$anotherBooleanLiteral', - ], - [ - '\'foo\'', - '$stringLiteral', - ], - [ - '1.0', - '$floatLiteral', - ], - [ - '1.0', - '$floatAssignedByRef', - ], - [ - 'null', - '$nullLiteral', - ], - [ - 'TypesNamespaceDeductedTypes\Lorem', - '$loremObjectLiteral', - ], - [ - 'mixed~string', - '$mixedObjectLiteral', - ], - [ - 'static(TypesNamespaceDeductedTypes\Foo)', - '$newStatic', - ], - [ - 'array()', - '$arrayLiteral', - ], - [ - 'string', - '$stringFromFunction', - ], - [ - 'TypesNamespaceFunctions\Foo', - '$fooObjectFromFunction', - ], - [ - 'mixed', - '$mixedFromFunction', - ], - [ - '1', - '\TypesNamespaceDeductedTypes\Foo::INTEGER_CONSTANT', - ], - [ - '1', - 'self::INTEGER_CONSTANT', - ], - [ - '1.0', - 'self::FLOAT_CONSTANT', - ], - [ - '\'foo\'', - 'self::STRING_CONSTANT', - ], - [ - 'array()', - 'self::ARRAY_CONSTANT', - ], - [ - 'true', - 'self::BOOLEAN_CONSTANT', - ], - [ - 'null', - 'self::NULL_CONSTANT', - ], - [ - '1', - '$foo::INTEGER_CONSTANT', - ], - [ - '1.0', - '$foo::FLOAT_CONSTANT', - ], - [ - '\'foo\'', - '$foo::STRING_CONSTANT', - ], - [ - 'array()', - '$foo::ARRAY_CONSTANT', - ], - [ - 'true', - '$foo::BOOLEAN_CONSTANT', - ], - [ - 'null', - '$foo::NULL_CONSTANT', - ], - ]; - } - - /** - * @dataProvider dataDeductedTypes - * @param string $description - * @param string $expression - */ - public function testDeductedTypes( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/function-definitions.php'; - $this->assertTypes( - __DIR__ . '/data/deducted-types.php', - $description, - $expression - ); - } - - public function dataProperties(): array - { - return [ - [ - 'mixed', - '$this->mixedProperty', - ], - [ - 'mixed', - '$this->anotherMixedProperty', - ], - [ - 'mixed', - '$this->yetAnotherMixedProperty', - ], - [ - 'int', - '$this->integerProperty', - ], - [ - 'int', - '$this->anotherIntegerProperty', - ], - [ - 'array', - '$this->arrayPropertyOne', - ], - [ - 'array', - '$this->arrayPropertyOther', - ], - [ - 'PropertiesNamespace\\Lorem', - '$this->objectRelative', - ], - [ - 'SomeOtherNamespace\\Ipsum', - '$this->objectFullyQualified', - ], - [ - 'SomeNamespace\\Amet', - '$this->objectUsed', - ], - [ - '*ERROR*', - '$this->nonexistentProperty', - ], - [ - 'int|null', - '$this->nullableInteger', - ], - [ - 'SomeNamespace\Amet|null', - '$this->nullableObject', - ], - [ - 'PropertiesNamespace\\Foo', - '$this->selfType', - ], - [ - 'static(PropertiesNamespace\Foo)', - '$this->staticType', - ], - [ - 'null', - '$this->nullType', - ], - [ - 'SomeNamespace\Sit', - '$this->inheritedProperty', - ], - [ - 'PropertiesNamespace\Bar', - '$this->barObject->doBar()', - ], - [ - 'mixed', - '$this->invalidTypeProperty', - ], - [ - 'resource', - '$this->resource', - ], - [ - 'array', - '$this->yetAnotherAnotherMixedParameter', - ], - [ - 'mixed', - '$this->yetAnotherAnotherAnotherMixedParameter', - ], - [ - 'string', - 'self::$staticStringProperty', - ], - [ - 'SomeGroupNamespace\One', - '$this->groupUseProperty', - ], - [ - 'SomeGroupNamespace\Two', - '$this->anotherGroupUseProperty', - ], - [ - 'PropertiesNamespace\Bar', - '$this->inheritDocProperty', - ], - [ - 'PropertiesNamespace\Bar', - '$this->inheritDocWithoutCurlyBracesProperty', - ], - [ - 'PropertiesNamespace\Bar', - '$this->implicitInheritDocProperty', - ], - [ - 'int', - '$this->readOnlyProperty', - ], - [ - 'string', - '$this->overriddenReadOnlyProperty', - ], - [ - 'string', - '$this->documentElement', - ], - ]; - } - - /** - * @dataProvider dataProperties - * @param string $description - * @param string $expression - */ - public function testProperties( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/properties.php', - $description, - $expression - ); - } - - public function dataBinaryOperations(): array - { - $typeCallback = static function ($value): string { - if (is_int($value)) { - return (new ConstantIntegerType($value))->describe(VerbosityLevel::precise()); - } elseif (is_float($value)) { - return (new ConstantFloatType($value))->describe(VerbosityLevel::precise()); - } elseif (is_bool($value)) { - return (new ConstantBooleanType($value))->describe(VerbosityLevel::precise()); - } elseif (is_string($value)) { - return (new ConstantStringType($value))->describe(VerbosityLevel::precise()); - } - - throw new \PHPStan\ShouldNotHappenException(); - }; - - return [ - [ - 'false', - 'true && false', - ], - [ - 'true', - 'true || false', - ], - [ - 'true', - 'true xor false', - ], - [ - 'true', - 'false xor true', - ], - [ - 'false', - 'true xor true', - ], - [ - 'false', - 'true xor true', - ], - [ - 'bool', - '$bool xor true', - ], - [ - 'bool', - '$bool xor false', - ], - [ - 'false', - 'true and false', - ], - [ - 'true', - 'true or false', - ], - [ - 'false', - '!true', - ], - [ - $typeCallback(-1), - '-1', - ], - [ - $typeCallback(+1), - '+1', - ], - [ - '*ERROR*', - '+"blabla"', - ], - [ - '123.2', - '+"123.2"', - ], - [ - '*ERROR*', - '-"blabla"', - ], - [ - '-5', - '-5', - ], - [ - '5', - '-(-5)', - ], - [ - 'int', - '-$integer', - ], - [ - '-2|-1', - '-$conditionalInt', - ], - [ - '*ERROR*', - '-$string', - ], - // integer + integer - [ - $typeCallback(1 + 1), - '1 + 1', - ], - [ - $typeCallback(1 - 1), - '1 - 1', - ], - [ - $typeCallback(1 / 2), - '1 / 2', - ], - [ - $typeCallback(1 * 1), - '1 * 1', - ], - [ - $typeCallback(1 ** 1), - '1 ** 1', - ], - [ - $typeCallback(1 % 1), - '1 % 1', - ], - [ - '(float|int)', - '$integer /= 2', - ], - [ - 'int', - '$integer *= 1', - ], - // float + float - [ - $typeCallback(1.2 + 1.4), - '1.2 + 1.4', - ], - [ - $typeCallback(1.2 - 1.4), - '1.2 - 1.4', - ], - [ - $typeCallback(1.2 / 2.4), - '1.2 / 2.4', - ], - [ - $typeCallback(1.2 * 1.4), - '1.2 * 1.4', - ], - [ - $typeCallback(1.2 ** 1.4), - '1.2 ** 1.4', - ], - [ - $typeCallback(3.2 % 2.4), - '3.2 % 2.4', - ], - [ - 'float', - '$float /= 2.4', - ], - [ - 'float', - '$float *= 2.4', - ], - // integer + float - [ - $typeCallback(1 + 1.4), - '1 + 1.4', - ], - [ - $typeCallback(1 - 1.4), - '1 - 1.4', - ], - [ - $typeCallback(1 / 2.4), - '1 / 2.4', - ], - [ - $typeCallback(1 * 1.4), - '1 * 1.4', - ], - [ - $typeCallback(1 ** 1.4), - '1 ** 1.4', - ], - [ - $typeCallback(3 % 2.4), - '3 % 2.4', - ], - [ - 'float', - '$integer /= 2.4', - ], - [ - 'float', - '$integer *= 2.4', - ], - [ - 'int', - '$otherInteger + 1', - ], - [ - 'float', - '$otherInteger + 1.0', - ], - // float + integer - [ - $typeCallback(1.2 + 1), - '1.2 + 1', - ], - [ - $typeCallback(1.2 - 1), - '1.2 - 1', - ], - [ - $typeCallback(1.2 / 2), - '1.2 / 2', - ], - [ - $typeCallback(1.2 * 1), - '1.2 * 1', - ], - [ - 'int', - '$integer * 10', - ], - [ - $typeCallback(1.2 ** 1), - '1.2 ** 1', - ], - [ - '(float|int)', - '$integer ** $integer', - ], - [ - $typeCallback(3.2 % 2), - '3.2 % 2', - ], - [ - 'int', - '$float %= 2.4', - ], - [ - 'float', - '$float **= 2.4', - ], - [ - 'float', - '$float /= 2.4', - ], - [ - 'float', - '$float *= 2', - ], - // boolean - [ - '1', - 'true + false', - ], - // string - [ - "'ab'", - "'a' . 'b'", - ], - [ - $typeCallback(1 . 'b'), - "1 . 'b'", - ], - [ - $typeCallback(1.0 . 'b'), - "1.0 . 'b'", - ], - [ - $typeCallback(1.0 . 2.0), - '1.0 . 2.0', - ], - [ - $typeCallback('foo' <=> 'bar'), - "'foo' <=> 'bar'", - ], - [ - '(float|int)', - '1 + $mixed', - ], - [ - 'float|int', - '1 + $number', - ], - [ - 'float|int', - '$integer + $number', - ], - [ - 'float', - '$float + $float', - ], - [ - 'float', - '$float + $number', - ], - [ - '(float|int)', - '1 / $mixed', - ], - [ - 'float|int', - '1 / $number', - ], - [ - 'float', - '1.0 / $mixed', - ], - [ - 'float', - '1.0 / $number', - ], - [ - '(float|int)', - '$mixed / 1', - ], - [ - 'float|int', - '$number / 1', - ], - [ - 'float', - '$mixed / 1.0', - ], - [ - 'float', - '$number / 1.0', - ], - [ - 'float', - '1.0 + $mixed', - ], - [ - 'float', - '1.0 + $number', - ], - [ - '(float|int)', - '$mixed + 1', - ], - [ - 'float|int', - '$number + 1', - ], - [ - 'float', - '$mixed + 1.0', - ], - [ - 'float', - '$number + 1.0', - ], - [ - '\'foo\'|null', - '$mixed ? "foo" : null', - ], - [ - '12', - '12 ?: null', - ], - [ - '1', - 'true ? 1 : 2', - ], - [ - '2', - 'false ? 1 : 2', - ], - [ - '12|string', - '$string ?: 12', - ], - [ - '12|string', - '$stringOrNull ?: 12', - ], - [ - '12|string', - '@$stringOrNull ?: 12', - ], - [ - 'int|int<1, max>', - '$integer ?: 12', - ], - [ - '\'foo\'', - "'foo' ?? null", // "else" never gets executed - ], - [ - 'string|null', - '$stringOrNull ?? null', - ], - [ - '\'bar\'|\'foo\'', - '$maybeDefinedVariable ?? \'bar\'', - ], - [ - 'string', - '$string ?? \'foo\'', - ], - [ - 'string', - '$stringOrNull ?? \'foo\'', - ], - [ - 'string', - '$string ?? $integer', - ], - [ - 'int|string', - '$stringOrNull ?? $integer', - ], - [ - '\'Foo\'', - '\Foo::class', - ], - [ - '74', - '$line', - ], - [ - (new ConstantStringType(__DIR__ . '/data'))->describe(VerbosityLevel::precise()), - '$dir', - ], - [ - (new ConstantStringType(__DIR__ . '/data/binary.php'))->describe(VerbosityLevel::precise()), - '$file', - ], - [ - '\'BinaryOperations\\\\NestedNamespace\'', - '$namespace', - ], - [ - '\'BinaryOperations\\\\NestedNamespace\\\\Foo\'', - '$class', - ], - [ - '\'BinaryOperations\\\\NestedNamespace\\\\Foo::doFoo\'', - '$method', - ], - [ - '\'doFoo\'', - '$function', - ], - [ - '1', - 'min([1, 2, 3])', - ], - [ - 'array(1, 2, 3)', - 'min([1, 2, 3], [4, 5, 5])', - ], - [ - '1', - 'min(...[1, 2, 3])', - ], - [ - '1', - 'min(...[2, 3, 4], ...[5, 1, 8])', - ], - [ - '0', - 'min(0, ...[1, 2, 3])', - ], - [ - 'array(5, 6, 9)', - 'max([1, 10, 8], [5, 6, 9])', - ], - [ - 'array(1, 1, 1, 1)', - 'max(array(2, 2, 2), array(1, 1, 1, 1))', - ], - [ - 'array', - 'max($arrayOfUnknownIntegers, $arrayOfUnknownIntegers)', - ], - /*[ - 'array(1, 1, 1, 1)', - 'max(array(2, 2, 2), 5, array(1, 1, 1, 1))', - ], - [ - 'array', - 'max($arrayOfUnknownIntegers, $integer, $arrayOfUnknownIntegers)', - ],*/ - [ - '1.1', - 'min(...[1.1, 2.2, 3.3])', - ], - [ - '1.1', - 'min(...[1.1, 2, 3])', - ], - [ - '3', - 'max(...[1, 2, 3])', - ], - [ - '3.3', - 'max(...[1.1, 2.2, 3.3])', - ], - [ - '1', - 'min(1, 2, 3)', - ], - [ - '3', - 'max(1, 2, 3)', - ], - [ - '1.1', - 'min(1.1, 2.2, 3.3)', - ], - [ - '3.3', - 'max(1.1, 2.2, 3.3)', - ], - [ - '1', - 'min(1, 1)', - ], - [ - '*ERROR*', - 'min(1)', - ], - [ - 'int|string', - 'min($integer, $string)', - ], - [ - 'int|string', - 'min([$integer, $string])', - ], - [ - 'int|string', - 'min(...[$integer, $string])', - ], - [ - '\'a\'', - 'min(\'a\', \'b\')', - ], - [ - 'DateTimeImmutable', - 'max(new \DateTimeImmutable("today"), new \DateTimeImmutable("tomorrow"))', - ], - [ - '1', - 'min(1, 2.2, 3.3)', - ], - [ - 'string', - '"Hello $world"', - ], - [ - 'string', - '$string .= "str"', - ], - [ - 'int', - '$integer <<= 2.2', - ], - [ - 'int', - '$float >>= 2.2', - ], - [ - '3', - 'count($arrayOfIntegers)', - ], - [ - 'int<0, max>', - 'count($arrayOfIntegers, \COUNT_RECURSIVE)', - ], - [ - '3', - 'count($arrayOfIntegers, 5)', - ], - [ - '6', - 'count($arrayOfIntegers) + count($arrayOfIntegers)', - ], - [ - 'bool', - '$string === "foo"', - ], - [ - 'true', - '$fooString === "foo"', - ], - [ - 'bool', - '$string !== "foo"', - ], - [ - 'false', - '$fooString !== "foo"', - ], - [ - 'bool', - '$string == "foo"', - ], - [ - 'bool', - '$string != "foo"', - ], - [ - 'true', - '$foo instanceof \BinaryOperations\NestedNamespace\Foo', - ], - [ - 'bool', - '$foo instanceof Bar', - ], - [ - 'true', - 'isset($foo)', - ], - [ - 'true', - 'isset($foo, $one)', - ], - [ - 'false', - 'isset($null)', - ], - [ - 'false', - 'isset($undefinedVariable)', - ], - [ - 'false', - 'isset($foo, $undefinedVariable)', - ], - [ - 'bool', - 'isset($stringOrNull)', - ], - [ - 'false', - 'isset($stringOrNull, $null)', - ], - [ - 'false', - 'isset($stringOrNull, $undefinedVariable)', - ], - [ - 'bool', - 'isset($foo, $stringOrNull)', - ], - [ - 'bool', - 'isset($foo, $stringOrNull)', - ], - [ - 'true', - 'isset($array[\'0\'])', - ], - [ - 'bool', - 'isset($array[$integer])', - ], - [ - 'false', - 'isset($array[$integer], $array[1000])', - ], - [ - 'false', - 'isset($array[$integer], $null)', - ], - [ - 'bool', - 'isset($array[\'0\'], $array[$integer])', - ], - [ - 'bool', - 'isset($foo, $array[$integer])', - ], - [ - 'false', - 'isset($foo, $array[1000])', - ], - [ - 'false', - 'isset($foo, $array[1000])', - ], - [ - 'false', - '!isset($foo)', - ], - [ - 'bool', - 'empty($foo)', - ], - [ - 'bool', - '!empty($foo)', - ], - [ - 'array(int, int, int)', - '$arrayOfIntegers + $arrayOfIntegers', - ], - [ - 'array(int, int, int)', - '$arrayOfIntegers += $arrayOfIntegers', - ], - [ - 'array(0 => 1, 1 => 1, 2 => 1, 3 => 1|2, 4 => 1|3, ?5 => 2|3, ?6 => 3)', - '$conditionalArray + $unshiftedConditionalArray', - ], - [ - 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', - '$unshiftedConditionalArray + $conditionalArray', - ], - [ - 'array(int, int, int)', - '$arrayOfIntegers += ["foo"]', - ], - [ - '*ERROR*', - '$arrayOfIntegers += "foo"', - ], - [ - '3', - '@count($arrayOfIntegers)', - ], - [ - 'array(int, int, int)', - '$anotherArray = $arrayOfIntegers', - ], - [ - 'string|null', - 'var_export()', - ], - [ - 'null', - 'var_export($string)', - ], - [ - 'null', - 'var_export($string, false)', - ], - [ - 'string', - 'var_export($string, true)', - ], - [ - 'bool|string', - 'highlight_string()', - ], - [ - 'bool', - 'highlight_string($string)', - ], - [ - 'bool', - 'highlight_string($string, false)', - ], - [ - 'string', - 'highlight_string($string, true)', - ], - [ - 'bool|string', - 'highlight_file()', - ], - [ - 'bool', - 'highlight_file($string)', - ], - [ - 'bool', - 'highlight_file($string, false)', - ], - [ - 'string', - 'highlight_file($string, true)', - ], - [ - 'string|true', - 'print_r()', - ], - [ - 'true', - 'print_r($string)', - ], - [ - 'true', - 'print_r($string, false)', - ], - [ - 'string', - 'print_r($string, true)', - ], - [ - '1', - '$one++', - ], - [ - '1', - '$one--', - ], - [ - '2', - '++$one', - ], - [ - '0', - '--$one', - ], - [ - '*ERROR*', - '$preIncArray[0]', - ], - [ - '1', - '$preIncArray[1]', - ], - [ - '2', - '$preIncArray[2]', - ], - [ - '*ERROR*', - '$preIncArray[3]', - ], - [ - 'array(1 => 1, 2 => 2)', - '$preIncArray', - ], - [ - 'array(0 => 1, 2 => 3)', - '$postIncArray', - ], - [ - 'array(0 => array(1 => array(2 => 3)), 4 => array(5 => array(6 => 7)))', - '$anotherPostIncArray', - ], - [ - '3', - 'count($array)', - ], - [ - 'int<0, max>', - 'count()', - ], - [ - 'int<0, max>', - 'count($appendingToArrayInBranches)', - ], - [ - '3|4|5', - 'count($conditionalArray)', - ], - [ - '2', - '$array[1]', - ], - [ - '(float|int)', - '$integer / $integer', - ], - [ - '(float|int)', - '$otherInteger / $integer', - ], - [ - '(array|float|int)', - '$mixed + $mixed', - ], - [ - '(float|int)', - '$mixed - $mixed', - ], - [ - '*ERROR*', - '$mixed + []', - ], - [ - '124', - '1 + "123"', - ], - [ - '124.2', - '1 + "123.2"', - ], - [ - '*ERROR*', - '1 + $string', - ], - [ - '*ERROR*', - '1 + "blabla"', - ], - [ - 'array(1, 2, 3)', - '[1, 2, 3] + [4, 5, 6]', - ], - [ - 'array', - '$arrayOfUnknownIntegers + [1, 2, 3]', - ], - [ - '(float|int)', - '$sumWithStaticConst', - ], - [ - '(float|int)', - '$severalSumWithStaticConst1', - ], - [ - '(float|int)', - '$severalSumWithStaticConst2', - ], - [ - '(float|int)', - '$severalSumWithStaticConst3', - ], - [ - '1', - '5 & 3', - ], - [ - 'int', - '$integer & 3', - ], - [ - '\'x\'', - '"x" & "y"', - ], - [ - 'string', - '$string & "x"', - ], - [ - '*ERROR*', - '"bla" & 3', - ], - [ - '1', - '"5" & 3', - ], - [ - '7', - '5 | 3', - ], - [ - 'int', - '$integer | 3', - ], - [ - '\'y\'', - '"x" | "y"', - ], - [ - 'string', - '$string | "x"', - ], - [ - '*ERROR*', - '"bla" | 3', - ], - [ - '7', - '"5" | 3', - ], - [ - '6', - '5 ^ 3', - ], - [ - 'int', - '$integer ^ 3', - ], - [ - '\'' . "\x01" . '\'', - '"x" ^ "y"', - ], - [ - 'string', - '$string ^ "x"', - ], - [ - '*ERROR*', - '"bla" ^ 3', - ], - [ - '6', - '"5" ^ 3', - ], - [ - 'int', - '$integer &= 3', - ], - [ - '*ERROR*', - '$string &= 3', - ], - [ - 'string', - '$string &= "x"', - ], - [ - 'int', - '$integer |= 3', - ], - [ - '*ERROR*', - '$string |= 3', - ], - [ - 'string', - '$string |= "x"', - ], - [ - 'int', - '$integer ^= 3', - ], - [ - '*ERROR*', - '$string ^= 3', - ], - [ - 'string', - '$string ^= "x"', - ], - [ - '\'f\'', - '$fooString[0]', - ], - [ - '*ERROR*', - '$fooString[4]', - ], - [ - 'string', - '$fooString[$integer]', - ], - [ - '\'foo bar\'', - '$foobarString', - ], - [ - '\'foo bar\'', - '"$fooString bar"', - ], - [ - '*ERROR*', - '"$std bar"', - ], - [ - 'array<\'foo\'|int|stdClass>&nonEmpty', - '$arrToPush', - ], - [ - 'array<\'foo\'|int|stdClass>&nonEmpty', - '$arrToPush2', - ], - [ - 'array(0 => \'lorem\', 1 => 5, \'foo\' => stdClass, 2 => \'test\')', - '$arrToUnshift', - ], - [ - 'array<\'lorem\'|int|stdClass>&nonEmpty', - '$arrToUnshift2', - ], - [ - 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', - '$unshiftedConditionalArray', - ], - [ - 'array(\'dirname\' => string, \'basename\' => string, \'filename\' => string, ?\'extension\' => string)', - 'pathinfo($string)', - ], - [ - 'string', - 'pathinfo($string, PATHINFO_DIRNAME)', - ], - [ - 'string', - '$string++', - ], - [ - 'string', - '$string--', - ], - [ - 'string', - '++$string', - ], - [ - 'string', - '--$string', - ], - [ - 'string', - '$incrementedString', - ], - [ - 'string', - '$decrementedString', - ], - [ - '\'foo\'', - '$fooString++', - ], - [ - '\'foo\'', - '$fooString--', - ], - [ - '\'fop\'', - '++$fooString', - ], - [ - '\'foo\'', - '--$fooString', - ], - [ - '\'fop\'', - '$incrementedFooString', - ], - [ - '\'foo\'', - '$decrementedFooString', - ], - [ - 'string', - '$conditionalString . $conditionalString', - ], - [ - 'string', - '$conditionalString . $anotherConditionalString', - ], - [ - 'string', - '$anotherConditionalString . $conditionalString', - ], - [ - '6|7|8', - 'count($conditionalArray) + count($array)', - ], - [ - 'bool', - 'is_numeric($string)', - ], - [ - 'false', - 'is_numeric($fooString)', - ], - [ - 'bool', - 'is_int($mixed)', - ], - [ - 'true', - 'is_int($integer)', - ], - [ - 'false', - 'is_int($string)', - ], - [ - 'bool', - 'in_array(\'foo\', [\'foo\', \'bar\'])', - ], - [ - 'true', - 'in_array(\'foo\', [\'foo\', \'bar\'], true)', - ], - [ - 'false', - 'in_array(\'baz\', [\'foo\', \'bar\'], true)', - ], - [ - 'array(2, 3)', - '$arrToShift', - ], - [ - 'array(1, 2)', - '$arrToPop', - ], - [ - 'class-string', - 'static::class', - ], - [ - '\'NonexistentClass\'', - 'NonexistentClass::class', - ], - [ - 'class-string', - 'parent::class', - ], - [ - 'true', - 'array_key_exists(0, $array)', - ], - [ - 'false', - 'array_key_exists(3, $array)', - ], - [ - 'bool', - 'array_key_exists(3, $conditionalArray)', - ], - [ - 'bool', - 'array_key_exists(\'foo\', $generalArray)', - ], - [ - PHP_VERSION_ID < 80000 ? 'resource' : 'CurlHandle', - 'curl_init()', - ], - [ - PHP_VERSION_ID < 80000 ? 'resource|false' : 'CurlHandle|false', - 'curl_init($string)', - ], - [ - 'string', - 'sprintf($string, $string, 1)', - ], - [ - '\'foo bar\'', - "sprintf('%s %s', 'foo', 'bar')", - ], - [ - 'array()|array(0 => \'password\'|\'username\', ?1 => \'password\')', - '$coalesceArray', - ], - [ - 'array', - '$arrayToBeUnset', - ], - [ - 'array', - '$arrayToBeUnset2', - ], - [ - 'array', - '$shiftedNonEmptyArray', - ], - [ - 'array&nonEmpty', - '$unshiftedArray', - ], - [ - 'array', - '$poppedNonEmptyArray', - ], - [ - 'array&nonEmpty', - '$pushedArray', - ], - [ - 'string|false', - '$simpleXMLReturningXML', - ], - [ - 'string', - '$xmlString', - ], - [ - 'bool', - '$simpleXMLWritingXML', - ], - [ - 'array', - '$simpleXMLRightXpath', - ], - [ - 'array|false', - '$simpleXMLWrongXpath', - ], - [ - 'array|false', - '$simpleXMLUnknownXpath', - ], - [ - 'array|false', - '$namespacedXpath', - ], - ]; - } - - /** - * @dataProvider dataBinaryOperations - * @param string $description - * @param string $expression - */ - public function testBinaryOperations( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/binary.php', - $description, - $expression - ); - } - - public function dataVarStatementAnnotation(): array - { - return [ - [ - 'VarStatementAnnotation\Foo', - '$object', - ], - ]; - } - - /** - * @dataProvider dataVarStatementAnnotation - * @param string $description - * @param string $expression - */ - public function testVarStatementAnnotation( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/var-stmt-annotation.php', - $description, - $expression - ); - } - - public function dataCloneOperators(): array - { - return [ - [ - 'CloneOperators\Foo', - 'clone $fooObject', - ], - ]; - } - - /** - * @dataProvider dataCloneOperators - * @param string $description - * @param string $expression - */ - public function testCloneOperators( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/clone.php', - $description, - $expression - ); - } - - public function dataLiteralArrays(): array - { - return [ - [ - '0', - '$integers[0]', - ], - [ - '1', - '$integers[1]', - ], - [ - '\'foo\'', - '$strings[0]', - ], - [ - '*ERROR*', - '$emptyArray[0]', - ], - [ - '0', - '$mixedArray[0]', - ], - [ - 'true', - '$integers[0] >= $integers[1] - 1', - ], - [ - 'array(\'foo\' => array(\'foo\' => array(\'foo\' => \'bar\')), \'bar\' => array(), \'baz\' => array(\'lorem\' => array()))', - '$nestedArray', - ], - [ - '0', - '$integers[\'0\']', - ], - ]; - } - - /** - * @dataProvider dataLiteralArrays - * @param string $description - * @param string $expression - */ - public function testLiteralArrays( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/literal-arrays.php', - $description, - $expression - ); - } - - public function dataLiteralArraysKeys(): array - { - define('STRING_ONE', '1'); - define('INT_ONE', 1); - define('STRING_FOO', 'foo'); - - return [ - [ - '0|1|2', - "'NoKeysArray'", - ], - [ - '0|1|2', - "'IntegersAndNoKeysArray'", - ], - [ - '0|1|\'foo\'', - "'StringsAndNoKeysArray'", - ], - [ - '1|2|3', - "'IntegersAsStringsAndNoKeysArray'", - ], - [ - '1|2', - "'IntegersAsStringsArray'", - ], - [ - '1|2', - "'IntegersArray'", - ], - [ - '1|2|3', - "'IntegersWithFloatsArray'", - ], - [ - '\'bar\'|\'foo\'', - "'StringsArray'", - ], - [ - '\'\'|\'bar\'|\'baz\'', - "'StringsWithNullArray'", - ], - [ - '1|2|string', - "'IntegersWithStringFromMethodArray'", - ], - [ - '1|2|\'foo\'', - "'IntegersAndStringsArray'", - ], - [ - '0|1', - "'BooleansArray'", - ], - [ - 'int|string', - "'UnknownConstantArray'", - ], - ]; - } - - /** - * @dataProvider dataLiteralArraysKeys - * @param string $description - * @param string $evaluatedPointExpressionType - */ - public function testLiteralArraysKeys( - string $description, - string $evaluatedPointExpressionType - ): void - { - $this->assertTypes( - __DIR__ . '/data/literal-arrays-keys.php', - $description, - '$key', - [], - [], - [], - [], - $evaluatedPointExpressionType - ); - } - - public function dataStringArrayAccess(): array - { - return [ - [ - '*ERROR*', - '$stringFalse', - ], - [ - '*ERROR*', - '$stringObject', - ], - [ - '*ERROR*', - '$stringFloat', - ], - [ - '*ERROR*', - '$stringString', - ], - [ - '*ERROR*', - '$stringArray', - ], - ]; - } - - /** - * @dataProvider dataStringArrayAccess - * @param string $description - * @param string $expression - */ - public function testStringArrayAccess( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/string-array-access.php', - $description, - $expression - ); - } - - public function dataTypeFromFunctionPhpDocs(): array - { - return [ - [ - 'mixed', - '$mixedParameter', - ], - [ - 'MethodPhpDocsNamespace\Bar|MethodPhpDocsNamespace\Foo', - '$unionTypeParameter', - ], - [ - 'int', - '$anotherMixedParameter', - ], - [ - 'mixed', - '$yetAnotherMixedParameter', - ], - [ - 'int', - '$integerParameter', - ], - [ - 'int', - '$anotherIntegerParameter', - ], - [ - 'array', - '$arrayParameterOne', - ], - [ - 'array', - '$arrayParameterOther', - ], - [ - 'MethodPhpDocsNamespace\\Lorem', - '$objectRelative', - ], - [ - 'SomeOtherNamespace\\Ipsum', - '$objectFullyQualified', - ], - [ - 'SomeNamespace\\Amet', - '$objectUsed', - ], - [ - '*ERROR*', - '$nonexistentParameter', - ], - [ - 'int|null', - '$nullableInteger', - ], - [ - 'SomeNamespace\Amet|null', - '$nullableObject', - ], - [ - 'SomeNamespace\Amet|null', - '$anotherNullableObject', - ], - [ - 'null', - '$nullType', - ], - [ - 'MethodPhpDocsNamespace\Bar', - '$barObject->doBar()', - ], - [ - 'MethodPhpDocsNamespace\Bar', - '$conflictedObject', - ], - [ - 'MethodPhpDocsNamespace\Baz', - '$moreSpecifiedObject', - ], - [ - 'MethodPhpDocsNamespace\Baz', - '$moreSpecifiedObject->doFluent()', - ], - [ - 'MethodPhpDocsNamespace\Baz|null', - '$moreSpecifiedObject->doFluentNullable()', - ], - [ - 'MethodPhpDocsNamespace\Baz', - '$moreSpecifiedObject->doFluentArray()[0]', - ], - [ - 'iterable&MethodPhpDocsNamespace\Collection', - '$moreSpecifiedObject->doFluentUnionIterable()', - ], - [ - 'MethodPhpDocsNamespace\Baz', - '$fluentUnionIterableBaz', - ], - [ - 'resource', - '$resource', - ], - [ - 'mixed', - '$yetAnotherAnotherMixedParameter', - ], - [ - 'mixed', - '$yetAnotherAnotherAnotherMixedParameter', - ], - [ - 'void', - '$voidParameter', - ], - [ - 'SomeNamespace\Consecteur', - '$useWithoutAlias', - ], - [ - 'true', - '$true', - ], - [ - 'false', - '$false', - ], - [ - 'true', - '$boolTrue', - ], - [ - 'false', - '$boolFalse', - ], - [ - 'bool', - '$trueBoolean', - ], - [ - 'bool', - '$parameterWithDefaultValueFalse', - ], - ]; - } - - public function dataTypeFromFunctionFunctionPhpDocs(): array - { - return [ - [ - 'MethodPhpDocsNamespace\Foo', - '$fooFunctionResult', - ], - [ - 'MethodPhpDocsNamespace\Bar', - '$barFunctionResult', - ], - ]; - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromFunctionFunctionPhpDocs - * @param string $description - * @param string $expression - */ - public function testTypeFromFunctionPhpDocs( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/functionPhpDocs.php'; - $this->assertTypes( - __DIR__ . '/data/functionPhpDocs.php', - $description, - $expression - ); - } - - public function dataTypeFromFunctionPrefixedPhpDocs(): array - { - return [ - [ - 'MethodPhpDocsNamespace\Foo', - '$fooFunctionResult', - ], - ]; - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromFunctionPrefixedPhpDocs - * @param string $description - * @param string $expression - */ - public function testTypeFromFunctionPhpDocsPsalmPrefix( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/functionPhpDocs-psalmPrefix.php'; - $this->assertTypes( - __DIR__ . '/data/functionPhpDocs-psalmPrefix.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromFunctionPrefixedPhpDocs - * @param string $description - * @param string $expression - */ - public function testTypeFromFunctionPhpDocsPhpstanPrefix( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/functionPhpDocs-phpstanPrefix.php'; - $this->assertTypes( - __DIR__ . '/data/functionPhpDocs-phpstanPrefix.php', - $description, - $expression - ); - } - - public function dataTypeFromMethodPhpDocs(): array - { - return [ - [ - 'MethodPhpDocsNamespace\\Foo', - '$selfType', - ], - [ - 'static(MethodPhpDocsNamespace\Foo)', - '$staticType', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->doFoo()', - ], - [ - 'MethodPhpDocsNamespace\Bar', - 'static::doSomethingStatic()', - ], - [ - 'static(MethodPhpDocsNamespace\Foo)', - 'parent::doLorem()', - ], - [ - 'MethodPhpDocsNamespace\FooParent', - '$parent->doLorem()', - false, - ], - [ - 'static(MethodPhpDocsNamespace\Foo)', - '$this->doLorem()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$differentInstance->doLorem()', - ], - [ - 'static(MethodPhpDocsNamespace\Foo)', - 'parent::doIpsum()', - ], - [ - 'MethodPhpDocsNamespace\FooParent', - '$parent->doIpsum()', - false, - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$differentInstance->doIpsum()', - ], - [ - 'static(MethodPhpDocsNamespace\Foo)', - '$this->doIpsum()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->doBar()[0]', - ], - [ - 'MethodPhpDocsNamespace\Bar', - 'self::doSomethingStatic()', - ], - [ - 'MethodPhpDocsNamespace\Bar', - '\MethodPhpDocsNamespace\Foo::doSomethingStatic()', - ], - [ - '$this(MethodPhpDocsNamespace\Foo)', - 'parent::doThis()', - ], - [ - '$this(MethodPhpDocsNamespace\Foo)|null', - 'parent::doThisNullable()', - ], - [ - '$this(MethodPhpDocsNamespace\Foo)|MethodPhpDocsNamespace\Bar|null', - 'parent::doThisUnion()', - ], - [ - 'MethodPhpDocsNamespace\FooParent', - '$this->returnParent()', - false, - ], - [ - 'MethodPhpDocsNamespace\FooParent', - '$this->returnPhpDocParent()', - false, - ], - [ - 'array', - '$this->returnNulls()', - ], - [ - 'object', - '$objectWithoutNativeTypehint', - ], - [ - 'object', - '$objectWithNativeTypehint', - ], - [ - 'object', - '$this->returnObject()', - ], - [ - 'MethodPhpDocsNamespace\FooParent', - 'new parent()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$inlineSelf', - ], - [ - 'MethodPhpDocsNamespace\Bar', - '$inlineBar', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->phpDocVoidMethod()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->phpDocVoidMethodFromInterface()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->phpDocVoidParentMethod()', - ], - [ - 'MethodPhpDocsNamespace\Foo', - '$this->phpDocWithoutCurlyBracesVoidParentMethod()', - ], - [ - 'array', - '$this->returnsStringArray()', - ], - [ - 'mixed', - '$this->privateMethodWithPhpDoc()', - ], - ]; - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - */ - public function testTypeFromMethodPhpDocs( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromMethodPhpDocsPsalmPrefix( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPsalmPrefix)', $description); - - if ($replaceClass && $expression !== '$this->doFoo()') { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPsalmPrefix)', $description); - if ($description === 'MethodPhpDocsNamespace\Foo') { - $description = 'MethodPhpDocsNamespace\FooPsalmPrefix'; - } - } - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-psalmPrefix.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass = true - */ - public function testTypeFromMethodPhpDocsPhpstanPrefix( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPhpstanPrefix)', $description); - - if ($replaceClass && $expression !== '$this->doFoo()') { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPhpstanPrefix)', $description); - if ($description === 'MethodPhpDocsNamespace\Foo') { - $description = 'MethodPhpDocsNamespace\FooPhpstanPrefix'; - } - } - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-phpstanPrefix.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromTraitPhpDocs( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooWithTrait)', $description); - - if ($replaceClass && $expression !== '$this->doFoo()') { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooWithTrait)', $description); - if ($description === 'MethodPhpDocsNamespace\Foo') { - $description = 'MethodPhpDocsNamespace\FooWithTrait'; - } - } - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-trait.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromMethodPhpDocsInheritDocWithoutCurlyBraces( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - if ($replaceClass) { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooInheritDocChild)', $description); - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooInheritDocChild)', $description); - $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); - if ($expression === '$inlineSelf') { - $description = 'MethodPhpDocsNamespace\FooInheritDocChild'; - } - } - $this->assertTypes( - __DIR__ . '/data/method-phpDocs-inheritdoc-without-curly-braces.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromRecursiveTraitPhpDocs( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooWithRecursiveTrait)', $description); - - if ($replaceClass && $expression !== '$this->doFoo()') { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooWithRecursiveTrait)', $description); - if ($description === 'MethodPhpDocsNamespace\Foo') { - $description = 'MethodPhpDocsNamespace\FooWithRecursiveTrait'; - } - } - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-recursiveTrait.php', - $description, - $expression - ); - } - - public function dataTypeFromTraitPhpDocsInSameFile(): array - { - return [ - [ - 'string', - '$this->getFoo()', - ], - ]; - } - - /** - * @dataProvider dataTypeFromTraitPhpDocsInSameFile - * @param string $description - * @param string $expression - */ - public function testTypeFromTraitPhpDocsInSameFile( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-traitInSameFileAsClass.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromMethodPhpDocsInheritDoc( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - if ($replaceClass) { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooInheritDocChild)', $description); - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooInheritDocChild)', $description); - $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); - if ($expression === '$inlineSelf') { - $description = 'MethodPhpDocsNamespace\FooInheritDocChild'; - } - } - $this->assertTypes( - __DIR__ . '/data/method-phpDocs-inheritdoc.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataTypeFromFunctionPhpDocs - * @dataProvider dataTypeFromMethodPhpDocs - * @param string $description - * @param string $expression - * @param bool $replaceClass - */ - public function testTypeFromMethodPhpDocsImplicitInheritance( - string $description, - string $expression, - bool $replaceClass = true - ): void - { - if ($replaceClass) { - $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild)', $description); - $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild)', $description); - $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); - if ($expression === '$inlineSelf') { - $description = 'MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild'; - } - } - $this->assertTypes( - __DIR__ . '/data/methodPhpDocs-implicitInheritance.php', - $description, - $expression - ); - } - - public function testNotSwitchInstanceof(): void - { - $this->assertTypes( - __DIR__ . '/data/switch-instanceof-not.php', - '*ERROR*', - '$foo' - ); - } - - public function dataSwitchInstanceOf(): array - { - return [ - [ - '*ERROR*', - '$foo', - ], - [ - '*ERROR*', - '$bar', - ], - [ - 'SwitchInstanceOf\Baz', - '$baz', - ], - ]; - } - - /** - * @dataProvider dataSwitchInstanceOf - * @param string $description - * @param string $expression - */ - public function testSwitchInstanceof( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/switch-instanceof.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataSwitchInstanceOf - * @param string $description - * @param string $expression - */ - public function testSwitchInstanceofTruthy( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/switch-instanceof-truthy.php', - $description, - $expression - ); - } - - public function dataSwitchGetClass(): array - { - return [ - [ - 'SwitchGetClass\Lorem', - '$lorem', - "'normalName'", - ], - [ - 'SwitchGetClass\Foo', - '$lorem', - "'selfReferentialName'", - ], - ]; - } - - /** - * @dataProvider dataSwitchGetClass - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testSwitchGetClass( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/switch-get-class.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataSwitchInstanceOfFallthrough(): array - { - return [ - [ - 'SwitchInstanceOfFallthrough\A|SwitchInstanceOfFallthrough\B', - '$object', - ], - ]; - } - - /** - * @dataProvider dataSwitchInstanceOfFallthrough - * @param string $description - * @param string $expression - */ - public function testSwitchInstanceOfFallthrough( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/switch-instanceof-fallthrough.php', - $description, - $expression - ); - } - - public function dataSwitchTypeElimination(): array - { - return [ - [ - 'string', - '$stringOrInt', - ], - ]; - } - - /** - * @dataProvider dataSwitchTypeElimination - * @param string $description - * @param string $expression - */ - public function testSwitchTypeElimination( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/switch-type-elimination.php', - $description, - $expression - ); - } - - public function dataDynamicMethodReturnTypeExtensions(): array - { - return [ - [ - '*ERROR*', - '$em->getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$em->getByPrimary()', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$em->getByPrimary($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$em->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - '$iem->getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$iem->getByPrimary()', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$iem->getByPrimary($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$iem->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - 'EntityManager::getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity()', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity()', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$container[\DynamicMethodReturnTypesNamespace\Foo::class]', - ], - [ - 'object', - 'new \DynamicMethodReturnTypesNamespace\Foo()', - ], - [ - 'object', - 'new \DynamicMethodReturnTypesNamespace\FooWithoutConstructor()', - ], - ]; - } - - /** - * @dataProvider dataDynamicMethodReturnTypeExtensions - * @param string $description - * @param string $expression - */ - public function testDynamicMethodReturnTypeExtensions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/dynamic-method-return-types.php', - $description, - $expression, - [ - new class() implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\EntityManager::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return in_array($methodReflection->getName(), ['getByPrimary'], true); - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $arg = $args[0]->value; - if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType((string) $arg->class); - } - - }, - new class() implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\ComponentContainer::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'offsetGet'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($args[0]->value); - if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType($argType->getValue()); - } - - }, - ], - [ - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\EntityManager::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return in_array($methodReflection->getName(), ['createManagerForEntity'], true); - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $arg = $args[0]->value; - if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType((string) $arg->class); - } - - }, - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\Foo::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === '__construct'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - return new ObjectWithoutClassType(); - } - - }, - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\FooWithoutConstructor::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === '__construct'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - return new ObjectWithoutClassType(); - } - - }, - ] - ); - } - - public function dataDynamicReturnTypeExtensionsOnCompoundTypes(): array - { - return [ - [ - 'DynamicMethodReturnCompoundTypes\Collection', - '$collection->getSelf()', - ], - [ - 'DynamicMethodReturnCompoundTypes\Collection|DynamicMethodReturnCompoundTypes\Foo', - '$collectionOrFoo->getSelf()', - ], - ]; - } - - /** - * @dataProvider dataDynamicReturnTypeExtensionsOnCompoundTypes - * @param string $description - * @param string $expression - */ - public function testDynamicReturnTypeExtensionsOnCompoundTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/dynamic-method-return-compound-types.php', - $description, - $expression, - [ - new class () implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnCompoundTypes\Collection::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'getSelf'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new ObjectType(\DynamicMethodReturnCompoundTypes\Collection::class); - } - - }, - new class () implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnCompoundTypes\Foo::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'getSelf'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new ObjectType(\DynamicMethodReturnCompoundTypes\Foo::class); - } - - }, - ] - ); - } - - public function dataOverwritingVariable(): array - { - return [ - [ - 'mixed', - '$var', - 'new \OverwritingVariable\Bar()', - ], - [ - 'OverwritingVariable\Bar', - '$var', - '$var->methodFoo()', - ], - [ - 'OverwritingVariable\Foo', - '$var', - 'die', - ], - ]; - } - - /** - * @dataProvider dataOverwritingVariable - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpressionType - */ - public function testOverwritingVariable( - string $description, - string $expression, - string $evaluatedPointExpressionType - ): void - { - $this->assertTypes( - __DIR__ . '/data/overwritingVariable.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpressionType - ); - } - - public function dataNegatedInstanceof(): array - { - return [ - [ - 'NegatedInstanceOf\Foo', - '$foo', - ], - [ - 'NegatedInstanceOf\Bar', - '$bar', - ], - [ - 'mixed', - '$lorem', - ], - [ - 'mixed~NegatedInstanceOf\Dolor', - '$dolor', - ], - [ - 'mixed~NegatedInstanceOf\Sit', - '$sit', - ], - [ - 'mixed', - '$mixedFoo', - ], - [ - 'mixed', - '$mixedBar', - ], - [ - 'NegatedInstanceOf\Foo', - '$self', - ], - [ - 'static(NegatedInstanceOf\Foo)', - '$static', - ], - [ - 'NegatedInstanceOf\Foo', - '$anotherFoo', - ], - [ - 'NegatedInstanceOf\Bar&NegatedInstanceOf\Foo', - '$fooAndBar', - ], - ]; - } - - /** - * @dataProvider dataNegatedInstanceof - * @param string $description - * @param string $expression - */ - public function testNegatedInstanceof( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/negated-instanceof.php', - $description, - $expression - ); - } - - public function dataAnonymousFunction(): array - { - return [ - [ - 'string', - '$str', - ], - [ - 'array', - '$arr', - ], - [ - '1', - '$integer', - ], - [ - '*ERROR*', - '$bar', - ], - ]; - } - - /** - * @dataProvider dataAnonymousFunction - * @param string $description - * @param string $expression - */ - public function testAnonymousFunction( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/anonymous-function.php', - $description, - $expression - ); - } - - public function dataForeachArrayType(): array - { - return [ - [ - __DIR__ . '/data/foreach/array-object-type.php', - 'AnotherNamespace\Foo', - '$foo', - ], - [ - __DIR__ . '/data/foreach/array-object-type.php', - 'AnotherNamespace\Foo', - '$foos[0]', - ], - [ - __DIR__ . '/data/foreach/array-object-type.php', - '0', - 'self::ARRAY_CONSTANT[0]', - ], - [ - __DIR__ . '/data/foreach/array-object-type.php', - '\'foo\'', - 'self::MIXED_CONSTANT[1]', - ], - [ - __DIR__ . '/data/foreach/nested-object-type.php', - 'AnotherNamespace\Foo', - '$foo', - ], - [ - __DIR__ . '/data/foreach/nested-object-type.php', - 'AnotherNamespace\Foo', - '$foos[0]', - ], - [ - __DIR__ . '/data/foreach/nested-object-type.php', - 'AnotherNamespace\Foo', - '$fooses[0][0]', - ], - [ - __DIR__ . '/data/foreach/integer-type.php', - 'int', - '$integer', - ], - [ - __DIR__ . '/data/foreach/reusing-specified-variable.php', - '1|2|3', - '$business', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-variable-first.php', - 'mixed', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-variable-second.php', - 'stdClass', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-no-variable.php', - 'bool', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-no-variable-2.php', - '*ERROR*', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-wrong-variable.php', - 'mixed', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-variable-with-reference.php', - 'string', - '$value', - ], - [ - __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', - 'array&nonEmpty', - '$list', - ], - [ - __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', - 'string', - '$key', - ], - [ - __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', - 'float|int|string', - '$value', - ], - [ - __DIR__ . '/data/foreach/foreach-with-complex-value-type.php', - 'float|ForeachWithComplexValueType\Foo', - '$value', - ], - [ - __DIR__ . '/data/foreach/foreach-iterable-with-specified-key-type.php', - 'ForeachWithGenericsPhpDoc\Bar|ForeachWithGenericsPhpDoc\Foo', - '$key', - ], - [ - __DIR__ . '/data/foreach/foreach-iterable-with-specified-key-type.php', - 'float|int|string', - '$value', - ], - [ - __DIR__ . '/data/foreach/foreach-iterable-with-complex-value-type.php', - 'float|ForeachWithComplexValueType\Foo', - '$value', - ], - [ - __DIR__ . '/data/foreach/type-in-comment-key.php', - 'int', - '$key', - ], - ]; - } - - /** - * @dataProvider dataForeachArrayType - * @param string $file - * @param string $description - * @param string $expression - */ - public function testForeachArrayType( - string $file, - string $description, - string $expression - ): void - { - $this->assertTypes( - $file, - $description, - $expression - ); - } - - public function dataOverridingSpecifiedType(): array - { - return [ - [ - __DIR__ . '/data/catch-specified-variable.php', - 'TryCatchWithSpecifiedVariable\FooException', - '$foo', - ], - ]; - } - - /** - * @dataProvider dataOverridingSpecifiedType - * @param string $file - * @param string $description - * @param string $expression - */ - public function testOverridingSpecifiedType( - string $file, - string $description, - string $expression - ): void - { - $this->assertTypes( - $file, - $description, - $expression - ); - } - - public function dataForeachObjectType(): array - { - return [ - [ - __DIR__ . '/data/foreach/object-type.php', - 'ObjectType\\MyKey', - '$keyFromIterator', - "'insideFirstForeach'", - ], - [ - __DIR__ . '/data/foreach/object-type.php', - 'ObjectType\\MyValue', - '$valueFromIterator', - "'insideFirstForeach'", - ], - [ - __DIR__ . '/data/foreach/object-type.php', - 'ObjectType\\MyKey', - '$keyFromAggregate', - "'insideSecondForeach'", - ], - [ - __DIR__ . '/data/foreach/object-type.php', - 'ObjectType\\MyValue', - '$valueFromAggregate', - "'insideSecondForeach'", - ], - [ - __DIR__ . '/data/foreach/object-type.php', - 'mixed', - '$keyFromRecursiveAggregate', - "'insideThirdForeach'", - ], - [ - __DIR__ . '/data/foreach/object-type.php', - 'mixed', - '$valueFromRecursiveAggregate', - "'insideThirdForeach'", - ], - ]; - } - - /** - * @dataProvider dataForeachObjectType - * @param string $file - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testForeachObjectType( - string $file, - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - $file, - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataArrayFunctions(): array - { - return [ - [ - '1', - '$integers[0]', - ], - [ - 'array(string, string, string)', - '$mappedStrings', - ], - [ - 'string', - '$mappedStrings[0]', - ], - [ - '1|2|3', - '$filteredIntegers[0]', - ], - [ - '123', - '$filteredMixed[0]', - ], - [ - '1|2|3', - '$uniquedIntegers[1]', - ], - [ - 'string', - '$reducedIntegersToString', - ], - [ - 'string|null', - '$reducedIntegersToStringWithNull', - ], - [ - 'string', - '$reducedIntegersToStringAnother', - ], - [ - 'null', - '$reducedToNull', - ], - [ - '1|string', - '$reducedIntegersToStringWithInt', - ], - [ - '1', - '$reducedToInt', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_change_key_case($integers)', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - 'array_combine($array, $array2)', - ], - [ - 'array(1 => 2)', - 'array_combine([1], [2])', - ], - [ - 'false', - 'array_combine([1, 2], [3])', - ], - [ - 'array(\'a\' => \'d\', \'b\' => \'e\', \'c\' => \'f\')', - 'array_combine([\'a\', \'b\', \'c\'], [\'d\', \'e\', \'f\'])', - ], - [ - PHP_VERSION_ID < 80000 ? 'array<1|2|3, mixed>|false' : 'array<1|2|3, mixed>', - 'array_combine([1, 2, 3], $array)', - ], - [ - PHP_VERSION_ID < 80000 ? 'array<1|2|3>|false' : 'array<1|2|3>', - 'array_combine($array, [1, 2, 3])', - ], - [ - 'array', - 'array_combine($array, $array)', - ], - [ - 'array', - 'array_combine($stringArray, $stringArray)', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_diff_assoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_diff_key($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_diff_uassoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_diff_ukey($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_diff($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_udiff_assoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_udiff_uassoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_udiff($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_intersect_assoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_intersect_key($integers, [])', - ], - [ - 'array', - 'array_intersect_key(...[$integers, [4, 5, 6]])', - ], - [ - 'array', - 'array_intersect_key(...$generalIntegersInAnotherArray, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_intersect_uassoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_intersect_ukey($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_intersect($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_uintersect_assoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_uintersect_uassoc($integers, [])', - ], - [ - 'array<0|1|2, 1|2|3>', - 'array_uintersect($integers, [])', - ], - [ - 'array(1, 1, 1, 1, 1)', - '$filledIntegers', - ], - [ - 'array(1)', - '$filledIntegersWithKeys', - ], - [ - 'array(1, 2)', - 'array_keys($integerKeys)', - ], - [ - 'array(\'foo\', \'bar\')', - 'array_keys($stringKeys)', - ], - [ - 'array(\'foo\', 1)', - 'array_keys($stringOrIntegerKeys)', - ], - [ - 'array', - 'array_keys($generalStringKeys)', - ], - [ - 'array(\'foo\', stdClass)', - 'array_values($integerKeys)', - ], - [ - 'array', - 'array_values($generalStringKeys)', - ], - [ - 'array', - 'array_merge($stringOrIntegerKeys)', - ], - [ - 'array', - 'array_merge($generalStringKeys, $generalDateTimeValues)', - ], - [ - 'array', - 'array_merge($generalStringKeys, $stringOrIntegerKeys)', - ], - [ - 'array', - 'array_merge($stringOrIntegerKeys, $generalStringKeys)', - ], - [ - 'array', - 'array_merge($stringKeys, $stringOrIntegerKeys)', - ], - [ - 'array', - 'array_merge($stringOrIntegerKeys, $stringKeys)', - ], - [ - 'array', - 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', - ], - [ - 'array', - 'array_merge(...[$generalStringKeys, $generalDateTimeValues])', - ], - [ - 'array', - '$mergedInts', - ], - [ - 'array(5 => \'banana\', 6 => \'banana\', 7 => \'banana\', 8 => \'banana\', 9 => \'banana\', 10 => \'banana\')', - 'array_fill(5, 6, \'banana\')', - ], - [ - 'array&nonEmpty', - 'array_fill(0, 101, \'apple\')', - ], - [ - 'array(-2 => \'pear\', 0 => \'pear\', 1 => \'pear\', 2 => \'pear\')', - 'array_fill(-2, 4, \'pear\')', - ], - [ - 'array&nonEmpty', - 'array_fill($integer, 2, new \stdClass())', - ], - [ - 'array', - 'array_fill(2, $integer, new \stdClass())', - ], - [ - 'array', - 'array_fill_keys($generalStringKeys, new \stdClass())', - ], - [ - 'array(\'foo\' => \'banana\', 5 => \'banana\', 10 => \'banana\', \'bar\' => \'banana\')', - 'array_fill_keys([\'foo\', 5, 10, \'bar\'], \'banana\')', - ], - [ - 'array', - '$mappedStringKeys', - ], - [ - 'array', - '$mappedStringKeysWithUnknownClosureType', - ], - [ - 'array', - '$mappedWrongArray', - ], - [ - 'array', - '$unknownArray', - ], - [ - 'array(\'foo\' => \'banana\', \'bar\' => \'banana\', ?\'baz\' => \'banana\', ?\'lorem\' => \'banana\')', - 'array_fill_keys($conditionalArray, \'banana\')', - ], - [ - 'array(\'foo\' => stdClass, \'bar\' => stdClass, ?\'baz\' => stdClass, ?\'lorem\' => stdClass)', - 'array_map(function (): \stdClass {}, $conditionalKeysArray)', - ], - [ - 'stdClass', - 'array_pop($stringKeys)', - ], - [ - 'array&hasOffset(\'baz\')', - '$stdClassesWithIsset', - ], - [ - 'stdClass', - 'array_pop($stdClassesWithIsset)', - ], - [ - '\'foo\'', - 'array_shift($stringKeys)', - ], - [ - 'int|null', - 'array_pop($generalStringKeys)', - ], - [ - 'int|null', - 'array_shift($generalStringKeys)', - ], - [ - 'null', - 'array_pop([])', - ], - [ - 'null', - 'array_shift([])', - ], - [ - 'array(null, \'\', 1)', - '$constantArrayWithFalseyValues', - ], - [ - 'array(2 => 1)', - '$constantTruthyValues', - ], - [ - 'array', - '$falsey', - ], - [ - 'array()', - 'array_filter($falsey)', - ], - [ - 'array', - '$withFalsey', - ], - [ - 'array', - 'array_filter($withFalsey)', - ], - [ - 'array(\'a\' => 1)', - 'array_filter($union)', - ], - [ - 'array(?0 => true, ?1 => int|int<1, max>)', - 'array_filter($withPossiblyFalsey)', - ], - [ - '(array|null)', - 'array_filter($mixed)', - ], - [ - '1|\'foo\'|false', - 'array_search(new stdClass, $stringOrIntegerKeys, true)', - ], - [ - '\'foo\'', - 'array_search(\'foo\', $stringKeys, true)', - ], - [ - 'int|false', - 'array_search(new DateTimeImmutable(), $generalDateTimeValues, true)', - ], - [ - 'string|false', - 'array_search(9, $generalStringKeys, true)', - ], - [ - 'string|false', - 'array_search(9, $generalStringKeys, false)', - ], - [ - 'string|false', - 'array_search(9, $generalStringKeys)', - ], - [ - 'null', - 'array_search(999, $integer, true)', - ], - [ - 'false', - 'array_search(new stdClass, $generalStringKeys, true)', - ], - [ - 'int|string|false', - 'array_search($mixed, $array, true)', - ], - [ - 'int|string|false', - 'array_search($mixed, $array, false)', - ], - [ - '\'a\'|\'b\'|false', - 'array_search($string, [\'a\' => \'A\', \'b\' => \'B\'], true)', - ], - [ - 'false', - 'array_search($integer, [\'a\' => \'A\', \'b\' => \'B\'], true)', - ], - [ - '\'foo\'|false', - 'array_search($generalIntegerOrString, $stringKeys, true)', - ], - [ - 'int|false', - 'array_search($generalIntegerOrString, $generalArrayOfIntegersOrStrings, true)', - ], - [ - 'int|false', - 'array_search($generalIntegerOrString, $clonedConditionalArray, true)', - ], - [ - 'int|string|false', - 'array_search($generalIntegerOrString, $generalIntegerOrStringKeys, false)', - ], - [ - 'false', - 'array_search(\'id\', $generalIntegerOrStringKeys, true)', - ], - [ - 'int|string|false', - 'array_search(\'id\', $generalIntegerOrStringKeysMixedValues, true)', - ], - [ - 'int|string|false|null', - 'array_search(\'id\', doFoo() ? $generalIntegerOrStringKeys : false, true)', - ], - [ - 'false|null', - 'array_search(\'id\', doFoo() ? [] : false, true)', - ], - [ - 'null', - 'array_search(\'id\', false, true)', - ], - [ - 'null', - 'array_search(\'id\', false)', - ], - [ - 'int|string|false', - 'array_search(\'id\', $thisDoesNotExistAndIsMixed, true)', - ], - [ - 'int|string|false', - 'array_search(\'id\', doFoo() ? $thisDoesNotExistAndIsMixedInUnion : false, true)', - ], - [ - 'int|string|false', - 'array_search(1, $generalIntegers, true)', - ], - [ - 'int|string|false', - 'array_search(1, $generalIntegers, false)', - ], - [ - 'int|string|false', - 'array_search(1, $generalIntegers)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 0)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1, null, true)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1, 2)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1, 2, true)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1, -1)', - ], - [ - 'array', - 'array_slice($generalStringKeys, 1, -1, true)', - ], - [ - 'array', - 'array_slice($generalStringKeys, -2)', - ], - [ - 'array', - 'array_slice($generalStringKeys, -2, 1, true)', - ], - [ - 'array', - 'array_slice($unknownArray, 0)', - ], - [ - 'array', - 'array_slice($unknownArray, 1)', - ], - [ - 'array', - 'array_slice($unknownArray, 1, null, true)', - ], - [ - 'array', - 'array_slice($unknownArray, 1, 2)', - ], - [ - 'array', - 'array_slice($unknownArray, 1, 2, true)', - ], - [ - 'array', - 'array_slice($unknownArray, 1, -1)', - ], - [ - 'array', - 'array_slice($unknownArray, 1, -1, true)', - ], - [ - 'array', - 'array_slice($unknownArray, -2)', - ], - [ - 'array', - 'array_slice($unknownArray, -2, 1, true)', - ], - [ - 'array(0 => bool, 1 => int, 2 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, 0)', - ], - [ - 'array(0 => int, 1 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, 1)', - ], - [ - 'array(1 => int, 2 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, 1, null, true)', - ], - [ - 'array(0 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, 2, 3)', - ], - [ - 'array(2 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, 2, 3, true)', - ], - [ - 'array(int, \'\')', - 'array_slice($withPossiblyFalsey, 1, -1)', - ], - [ - 'array(1 => int, 2 => \'\')', - 'array_slice($withPossiblyFalsey, 1, -1, true)', - ], - [ - 'array(0 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, -2, null)', - ], - [ - 'array(2 => \'\', \'a\' => 0)', - 'array_slice($withPossiblyFalsey, -2, null, true)', - ], - [ - 'array(\'baz\' => \'qux\')|array(0 => \'\', \'a\' => 0)', - 'array_slice($unionArrays, 1)', - ], - [ - 'array(\'a\' => 0)|array(\'baz\' => \'qux\')', - 'array_slice($unionArrays, -1, null, true)', - ], - [ - 'array(0 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 2 => \'quux\', \'quuz\' => \'corge\', 3 => \'grault\')', - '$slicedOffset', - ], - [ - 'array(4 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 0 => \'quux\', \'quuz\' => \'corge\', 5 => \'grault\')', - '$slicedOffsetWithKeys', - ], - [ - '0|1', - 'key($mixedValues)', - ], - [ - 'int|null', - 'key($falsey)', - ], - [ - 'string|null', - 'key($generalStringKeys)', - ], - [ - 'int|string|null', - 'key($generalIntegerOrStringKeysMixedValues)', - ], - [ - '\'foo\'', - '$poppedFoo', - ], - [ - 'int', - 'array_rand([1 => 1, 2 => "2"])', - ], - [ - 'string', - 'array_rand(["a" => 1, "b" => "2"])', - ], - [ - 'int|string', - 'array_rand(["a" => 1, 2 => "b"])', - ], - [ - 'int|string', - 'array_rand([1 => 1, 2 => "b", $mixed => $mixed])', - ], - [ - 'int', - 'array_rand([1 => 1, 2 => "b"], 1)', - ], - [ - 'string', - 'array_rand(["a" => 1, "b" => "b"], 1)', - ], - [ - 'int|string', - 'array_rand(["a" => 1, 2 => "b"], 1)', - ], - [ - 'int|string', - 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], 1)', - ], - [ - 'array', - 'array_rand([1 => 1, 2 => "b"], 2)', - ], - [ - 'array', - 'array_rand(["a" => 1, "b" => "b"], 2)', - ], - [ - 'array', - 'array_rand(["a" => 1, 2 => "b"], 2)', - ], - [ - 'array', - 'array_rand([1 => 1, 2 => "2", $mixed => $mixed], 2)', - ], - [ - 'array|int', - 'array_rand([1 => 1, 2 => "b"], $mixed)', - ], - [ - 'array|string', - 'array_rand(["a" => 1, "b" => "b"], $mixed)', - ], - [ - 'array|int|string', - 'array_rand(["a" => 1, 2 => "b"], $mixed)', - ], - [ - 'array|int|string', - 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], $mixed)', - ], - ]; - } - - /** - * @dataProvider dataArrayFunctions - * @param string $description - * @param string $expression - */ - public function testArrayFunctions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-functions.php', - $description, - $expression - ); - } - - public function dataFunctions(): array - { - return [ - [ - 'string', - '$microtimeStringWithoutArg', - ], - [ - 'string', - '$microtimeString', - ], - [ - 'float', - '$microtimeFloat', - ], - [ - 'float|string', - '$microtimeDefault', - ], - [ - '(float|string)', - '$microtimeBenevolent', - ], - [ - 'int', - '$strtotimeNow', - ], - [ - 'false', - '$strtotimeInvalid', - ], - [ - 'int|false', - '$strtotimeUnknown', - ], - [ - '(int|false)', - '$strtotimeUnknown2', - ], - [ - 'int|false', - '$strtotimeCrash', - ], - [ - '-1', - '$versionCompare1', - ], - [ - '-1|1', - '$versionCompare2', - ], - [ - '-1|0|1', - '$versionCompare3', - ], - [ - '-1|0|1', - '$versionCompare4', - ], - [ - 'true', - '$versionCompare5', - ], - [ - 'bool', - '$versionCompare6', - ], - [ - 'bool', - '$versionCompare7', - ], - [ - 'bool', - '$versionCompare8', - ], - [ - 'int', - '$mbStrlenWithoutEncoding', - ], - [ - 'int', - '$mbStrlenWithValidEncoding', - ], - [ - 'int', - '$mbStrlenWithValidEncodingAlias', - ], - [ - 'false', - '$mbStrlenWithInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'int|false' : 'int', - '$mbStrlenWithValidAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'int|false' : 'int', - '$mbStrlenWithUnknownEncoding', - ], - [ - 'string', - '$mbHttpOutputWithoutEncoding', - ], - [ - 'true', - '$mbHttpOutputWithValidEncoding', - ], - [ - 'false', - '$mbHttpOutputWithInvalidEncoding', - ], - [ - 'bool', - '$mbHttpOutputWithValidAndInvalidEncoding', - ], - [ - 'bool', - '$mbHttpOutputWithUnknownEncoding', - ], - [ - 'string', - '$mbRegexEncodingWithoutEncoding', - ], - [ - 'true', - '$mbRegexEncodingWithValidEncoding', - ], - [ - 'false', - '$mbRegexEncodingWithInvalidEncoding', - ], - [ - 'bool', - '$mbRegexEncodingWithValidAndInvalidEncoding', - ], - [ - 'bool', - '$mbRegexEncodingWithUnknownEncoding', - ], - [ - 'string', - '$mbInternalEncodingWithoutEncoding', - ], - [ - 'true', - '$mbInternalEncodingWithValidEncoding', - ], - [ - 'false', - '$mbInternalEncodingWithInvalidEncoding', - ], - [ - 'bool', - '$mbInternalEncodingWithValidAndInvalidEncoding', - ], - [ - 'bool', - '$mbInternalEncodingWithUnknownEncoding', - ], - [ - 'array', - '$mbEncodingAliasesWithValidEncoding', - ], - [ - 'false', - '$mbEncodingAliasesWithInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbEncodingAliasesWithValidAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbEncodingAliasesWithUnknownEncoding', - ], - [ - 'string', - '$mbChrWithoutEncoding', - ], - [ - 'string', - '$mbChrWithValidEncoding', - ], - [ - 'false', - '$mbChrWithInvalidEncoding', - ], - [ - 'string|false', - '$mbChrWithValidAndInvalidEncoding', - ], - [ - 'string|false', - '$mbChrWithUnknownEncoding', - ], - [ - 'int', - '$mbOrdWithoutEncoding', - ], - [ - 'int', - '$mbOrdWithValidEncoding', - ], - [ - 'false', - '$mbOrdWithInvalidEncoding', - ], - [ - 'int|false', - '$mbOrdWithValidAndInvalidEncoding', - ], - [ - 'int|false', - '$mbOrdWithUnknownEncoding', - ], - [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', - '$gettimeofdayArrayWithoutArg', - ], - [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', - '$gettimeofdayArray', - ], - [ - 'float', - '$gettimeofdayFloat', - ], - [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float', - '$gettimeofdayDefault', - ], - [ - '(array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float)', - '$gettimeofdayBenevolent', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$strSplitConstantStringWithoutDefinedParameters', - ], - [ - "array('a', 'b', 'c', 'd', 'e', 'f')", - '$strSplitConstantStringWithoutDefinedSplitLength', - ], - [ - 'array', - '$strSplitStringWithoutDefinedSplitLength', - ], - [ - "array('a', 'b', 'c', 'd', 'e', 'f')", - '$strSplitConstantStringWithOneSplitLength', - ], - [ - "array('abcdef')", - '$strSplitConstantStringWithGreaterSplitLengthThanStringLength', - ], - [ - 'false', - '$strSplitConstantStringWithFailureSplitLength', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$strSplitConstantStringWithInvalidSplitLengthType', - ], - [ - 'array', - '$strSplitConstantStringWithVariableStringAndConstantSplitLength', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$strSplitConstantStringWithVariableStringAndVariableSplitLength', - ], - // parse_url - [ - 'mixed', - '$parseUrlWithoutParameters', - ], - [ - "array('scheme' => 'http', 'host' => 'abc.def')", - '$parseUrlConstantUrlWithoutComponent1', - ], - [ - "array('scheme' => 'http', 'host' => 'def.abc')", - '$parseUrlConstantUrlWithoutComponent2', - ], - [ - "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", - '$parseUrlConstantUrlUnknownComponent', - ], - [ - 'null', - '$parseUrlConstantUrlWithComponentNull', - ], - [ - "'this-is-fragment'", - '$parseUrlConstantUrlWithComponentSet', - ], - [ - 'false', - '$parseUrlConstantUrlWithComponentInvalid', - ], - [ - 'false', - '$parseUrlStringUrlWithComponentInvalid', - ], - [ - 'int|false|null', - '$parseUrlStringUrlWithComponentPort', - ], - [ - "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", - '$parseUrlStringUrlWithoutComponent', - ], - [ - "array('path' => 'abc.def')", - "parse_url('abc.def')", - ], - [ - 'null', - "parse_url('abc.def', PHP_URL_SCHEME)", - ], - [ - "'http'", - "parse_url('http://abc.def', PHP_URL_SCHEME)", - ], - [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', - '$stat', - ], - [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', - '$lstat', - ], - [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', - '$fstat', - ], - [ - 'string', - '$base64DecodeWithoutStrict', - ], - [ - 'string', - '$base64DecodeWithStrictDisabled', - ], - [ - 'string|false', - '$base64DecodeWithStrictEnabled', - ], - [ - 'string', - '$base64DecodeDefault', - ], - [ - '(string|false)', - '$base64DecodeBenevolent', - ], - [ - '*ERROR*', - '$strWordCountWithoutParameters', - ], - [ - '*ERROR*', - '$strWordCountWithTooManyParams', - ], - [ - 'int', - '$strWordCountStr', - ], - [ - 'int', - '$strWordCountStrType0', - ], - [ - 'array', - '$strWordCountStrType1', - ], - [ - 'array', - '$strWordCountStrType1Extra', - ], - [ - 'array', - '$strWordCountStrType2', - ], - [ - 'array', - '$strWordCountStrType2Extra', - ], - [ - 'array|int|false', - '$strWordCountStrTypeIndeterminant', - ], - [ - 'string', - '$hashHmacMd5', - ], - [ - 'string', - '$hashHmacSha256', - ], - [ - 'false', - '$hashHmacNonCryptographic', - ], - [ - 'false', - '$hashHmacRandom', - ], - [ - 'string', - '$hashHmacVariable', - ], - [ - 'string|false', - '$hashHmacFileMd5', - ], - [ - 'string|false', - '$hashHmacFileSha256', - ], - [ - 'false', - '$hashHmacFileNonCryptographic', - ], - [ - 'false', - '$hashHmacFileRandom', - ], - [ - '(string|false)', - '$hashHmacFileVariable', - ], - [ - 'string', - '$hash', - ], - [ - 'string', - '$hashRaw', - ], - [ - 'false', - '$hashRandom', - ], - [ - 'string', - '$hashMixed', - ], - ]; - } - - /** - * @dataProvider dataFunctions - * @param string $description - * @param string $expression - */ - public function testFunctions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/functions.php', - $description, - $expression - ); - } - - public function dataDioFunctions(): array - { - return [ - [ - 'array(\'device\' => int, \'inode\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'device_type\' => int, \'size\' => int, \'blocksize\' => int, \'blocks\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int)|null', - '$stat', - ], - ]; - } - - /** - * @dataProvider dataDioFunctions - * @param string $description - * @param string $expression - */ - public function testDioFunctions( - string $description, - string $expression - ): void - { - if (!function_exists('dio_stat')) { - $this->markTestSkipped('This test requires DIO extension.'); - } - $this->assertTypes( - __DIR__ . '/data/dio-functions.php', - $description, - $expression - ); - } - - public function dataSsh2Functions(): array - { - return [ - [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', - '$ssh2SftpStat', - ], - ]; - } - - /** - * @dataProvider dataSsh2Functions - * @param string $description - * @param string $expression - */ - public function testSsh2Functions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/ssh2-functions.php', - $description, - $expression - ); - } - - public function dataRangeFunction(): array - { - return [ - [ - 'array(2, 3, 4, 5)', - 'range(2, 5)', - ], - [ - 'array(2, 4)', - 'range(2, 5, 2)', - ], - [ - 'array(2.0, 3.0, 4.0, 5.0)', - 'range(2, 5, 1.0)', - ], - [ - 'array(2.1, 3.1, 4.1)', - 'range(2.1, 5)', - ], - [ - 'array', - 'range(2, 5, $integer)', - ], - [ - 'array', - 'range($float, 5, $integer)', - ], - [ - 'array', - 'range($float, $mixed, $integer)', - ], - [ - 'array', - 'range($integer, $mixed)', - ], - [ - 'array(0 => 1, ?1 => 2)', - 'range(1, doFoo() ? 1 : 2)', - ], - [ - 'array(0 => -1|1, ?1 => 0|2, ?2 => 1, ?3 => 2)', - 'range(doFoo() ? -1 : 1, doFoo() ? 1 : 2)', - ], - [ - 'array(3, 2, 1, 0, -1)', - 'range(3, -1)', - ], - [ - 'array&nonEmpty', - 'range(0, 50)', - ], - ]; - } - - /** - * @dataProvider dataRangeFunction - * @param string $description - * @param string $expression - */ - public function testRangeFunction( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/range-function.php', - $description, - $expression - ); - } - - public function dataSpecifiedTypesUsingIsFunctions(): array - { - return [ - [ - 'int', - '$integer', - ], - [ - 'int', - '$anotherInteger', - ], - [ - 'int', - '$longInteger', - ], - [ - 'float', - '$float', - ], - [ - 'float', - '$doubleFloat', - ], - [ - 'float', - '$realFloat', - ], - [ - 'null', - '$null', - ], - [ - 'array', - '$array', - ], - [ - 'bool', - '$bool', - ], - [ - 'callable(): mixed', - '$callable', - ], - [ - 'resource', - '$resource', - ], - [ - 'int', - '$yetAnotherInteger', - ], - [ - '*ERROR*', - '$mixedInteger', - ], - [ - 'string', - '$string', - ], - [ - 'object', - '$object', - ], - [ - 'int', - '$intOrStdClass', - ], - [ - 'Foo', - '$foo', - ], - [ - 'Foo', - '$anotherFoo', - ], - [ - 'class-string|Foo', - '$subClassOfFoo', - ], - [ - 'Foo', - '$subClassOfFoo2', - ], - [ - 'class-string|object', - '$subClassOfFoo3', - ], - [ - 'object', - '$subClassOfFoo4', - ], - [ - 'class-string|Foo', - '$subClassOfFoo5', - ], - [ - 'class-string|object', - '$subClassOfFoo6', - ], - [ - 'Foo', - '$subClassOfFoo7', - ], - [ - 'object', - '$subClassOfFoo8', - ], - [ - 'object', - '$subClassOfFoo9', - ], - [ - 'object', - '$subClassOfFoo10', - ], - [ - 'Foo', - '$subClassOfFoo11', - ], - [ - 'Foo', - '$subClassOfFoo12', - ], - [ - 'Foo', - '$subClassOfFoo13', - ], - [ - 'object', - '$subClassOfFoo14', - ], - [ - 'class-string|Foo', - '$subClassOfFoo15', - ], - [ - 'Bar|class-string|Foo', - '$subClassOfFoo16', - ], - ]; - } - - /** - * @dataProvider dataSpecifiedTypesUsingIsFunctions - * @param string $description - * @param string $expression - */ - public function testSpecifiedTypesUsingIsFunctions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/specifiedTypesUsingIsFunctions.php', - $description, - $expression - ); - } - - public function dataTypeSpecifyingExtensions(): array - { - return [ - [ - 'string', - '$foo', - true, - ], - [ - 'int', - '$bar', - true, - ], - [ - 'string|null', - '$foo', - false, - ], - [ - 'int|null', - '$bar', - false, - ], - [ - 'string', - '$foo', - null, - ], - [ - 'int', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions.php', - $description, - $expression, - [], - [], - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], - 'die', - [], - false - ); - } - - public function dataTypeSpecifyingExtensions2(): array - { - return [ - [ - 'string|null', - '$foo', - true, - ], - [ - 'int|null', - '$bar', - true, - ], - [ - 'string|null', - '$foo', - false, - ], - [ - 'int|null', - '$bar', - false, - ], - [ - 'string|null', - '$foo', - null, - ], - [ - 'int|null', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions2 - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions2( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions2.php', - $description, - $expression, - [], - [], - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)] - ); - } - - public function dataTypeSpecifyingExtensions3(): array - { - return [ - [ - 'string', - '$foo', - false, - ], - [ - 'int', - '$bar', - false, - ], - [ - 'string|null', - '$foo', - true, - ], - [ - 'int|null', - '$bar', - true, - ], - [ - 'string', - '$foo', - null, - ], - [ - 'int', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions3 - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions3( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions3.php', - $description, - $expression, - [], - [], - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], - 'die', - [], - false - ); - } - - public function dataIterable(): array - { - return [ - [ - 'iterable', - '$this->iterableProperty', - ], - [ - 'iterable', - '$iterableSpecifiedLater', - ], - [ - 'iterable', - '$iterableWithoutTypehint', - ], - [ - 'mixed', - '$iterableWithoutTypehint[0]', - ], - [ - 'iterable', - '$iterableWithIterableTypehint', - ], - [ - 'mixed', - '$iterableWithIterableTypehint[0]', - ], - [ - 'mixed', - '$mixed', - ], - [ - 'iterable', - '$iterableWithConcreteTypehint', - ], - [ - 'mixed', - '$iterableWithConcreteTypehint[0]', - ], - [ - 'Iterables\Bar', - '$bar', - ], - [ - 'iterable', - '$this->doBar()', - ], - [ - 'iterable', - '$this->doBaz()', - ], - [ - 'Iterables\Baz', - '$baz', - ], - [ - 'array', - '$arrayWithIterableTypehint', - ], - [ - 'mixed', - '$arrayWithIterableTypehint[0]', - ], - [ - 'iterable&Iterables\Collection', - '$unionIterableType', - ], - [ - 'Iterables\Bar', - '$unionBar', - ], - [ - 'array&nonEmpty', - '$mixedUnionIterableType', - ], - [ - 'iterable&Iterables\Collection', - '$unionIterableIterableType', - ], - [ - 'mixed', - '$mixedBar', - ], - [ - 'Iterables\Bar', - '$iterableUnionBar', - ], - [ - 'Iterables\Bar', - '$unionBarFromMethod', - ], - [ - 'iterable', - '$this->stringIterableProperty', - ], - [ - 'iterable', - '$this->mixedIterableProperty', - ], - [ - 'iterable', - '$integers', - ], - [ - 'iterable', - '$mixeds', - ], - [ - 'iterable', - '$this->returnIterableMixed()', - ], - [ - 'iterable', - '$this->returnIterableString()', - ], - [ - 'int|iterable', - '$this->iterablePropertyAlsoWithSomethingElse', - ], - [ - 'int|iterable', - '$this->iterablePropertyWithTwoItemTypes', - ], - [ - 'array|Iterables\CollectionOfIntegers', - '$this->collectionOfIntegersOrArrayOfStrings', - ], - [ - 'Generator', - '$generatorOfFoos', - ], - [ - 'Iterables\Foo', - '$fooFromGenerator', - ], - [ - 'ArrayObject', - '$arrayObject', - ], - [ - 'int', - '$arrayObjectKey', - ], - [ - 'string', - '$arrayObjectValue', - ], - ]; - } - - /** - * @dataProvider dataIterable - * @param string $description - * @param string $expression - */ - public function testIterable( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/iterable.php', - $description, - $expression - ); - } - - public function dataArrayAccess(): array - { - return [ - [ - 'string', - '$this->returnArrayOfStrings()[0]', - ], - [ - 'mixed', - '$this->returnMixed()[0]', - ], - [ - 'int', - '$this->returnSelfWithIterableInt()[0]', - ], - [ - 'int', - '$this[0]', - ], - ]; - } - - /** - * @dataProvider dataArrayAccess - * @param string $description - * @param string $expression - */ - public function testArrayAccess( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-accessable.php', - $description, - $expression - ); - } - - public function dataVoid(): array - { - return [ - [ - 'void', - '$this->doFoo()', - ], - [ - 'void', - '$this->doBar()', - ], - [ - 'void', - '$this->doConflictingVoid()', - ], - ]; - } - - /** - * @dataProvider dataVoid - * @param string $description - * @param string $expression - */ - public function testVoid( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/void.php', - $description, - $expression - ); - } - - public function dataNullableReturnTypes(): array - { - return [ - [ - 'int|null', - '$this->doFoo()', - ], - [ - 'int|null', - '$this->doBar()', - ], - [ - 'int|null', - '$this->doConflictingNullable()', - ], - [ - 'int', - '$this->doAnotherConflictingNullable()', - ], - ]; - } - - /** - * @dataProvider dataNullableReturnTypes - * @param string $description - * @param string $expression - */ - public function testNullableReturnTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/nullable-returnTypes.php', - $description, - $expression - ); - } - - public function dataTernary(): array - { - return [ - [ - 'bool|null', - '$boolOrNull', - ], - [ - 'bool', - '$boolOrNull !== null ? $boolOrNull : false', - ], - [ - 'bool', - '$bool', - ], - [ - 'true|null', - '$short', - ], - [ - 'bool', - '$c', - ], - [ - 'bool', - '$isQux', - ], - ]; - } - - /** - * @dataProvider dataTernary - * @param string $description - * @param string $expression - */ - public function testTernary( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/ternary.php', - $description, - $expression - ); - } - - public function dataHeredoc(): array - { - return [ - [ - '\'foo\'', - '$heredoc', - ], - [ - '\'bar\'', - '$nowdoc', - ], - ]; - } - - /** - * @dataProvider dataHeredoc - * @param string $description - * @param string $expression - */ - public function testHeredoc( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/heredoc.php', - $description, - $expression - ); - } - - public function dataTypeElimination(): array - { - return [ - [ - 'null', - '$foo', - "'nullForSure'", - ], - [ - 'TypeElimination\Foo', - '$foo', - "'notNullForSure'", - ], - [ - 'TypeElimination\Foo', - '$foo', - "'notNullForSure2'", - ], - [ - 'null', - '$foo', - "'nullForSure2'", - ], - [ - 'null', - '$foo', - "'nullForSure3'", - ], - [ - 'TypeElimination\Foo', - '$foo', - "'notNullForSure3'", - ], - [ - 'null', - '$foo', - "'yodaNullForSure'", - ], - [ - 'TypeElimination\Foo', - '$foo', - "'yodaNotNullForSure'", - ], - [ - 'false', - '$intOrFalse', - "'falseForSure'", - ], - [ - 'int', - '$intOrFalse', - "'intForSure'", - ], - [ - 'false', - '$intOrFalse', - "'yodaFalseForSure'", - ], - [ - 'int', - '$intOrFalse', - "'yodaIntForSure'", - ], - [ - 'true', - '$intOrTrue', - "'trueForSure'", - ], - [ - 'int', - '$intOrTrue', - "'anotherIntForSure'", - ], - [ - 'true', - '$intOrTrue', - "'yodaTrueForSure'", - ], - [ - 'int', - '$intOrTrue', - "'yodaAnotherIntForSure'", - ], - [ - 'TypeElimination\Foo', - '$fooOrBarOrBaz', - "'fooForSure'", - ], - [ - 'TypeElimination\Bar|TypeElimination\Baz', - '$fooOrBarOrBaz', - "'barOrBazForSure'", - ], - [ - 'TypeElimination\Bar', - '$fooOrBarOrBaz', - "'barForSure'", - ], - [ - 'TypeElimination\Baz', - '$fooOrBarOrBaz', - "'bazForSure'", - ], - [ - 'TypeElimination\Bar|TypeElimination\Baz', - '$fooOrBarOrBaz', - "'anotherBarOrBazForSure'", - ], - [ - 'TypeElimination\Foo', - '$fooOrBarOrBaz', - "'anotherFooForSure'", - ], - [ - 'string|null', - '$result', - "'stringOrNullForSure'", - ], - [ - 'int', - '$intOrFalse', - "'yetAnotherIntForSure'", - ], - [ - 'int', - '$intOrTrue', - "'yetYetAnotherIntForSure'", - ], - [ - 'TypeElimination\Foo|null', - '$fooOrStringOrNull', - "'fooOrNull'", - ], - [ - 'string', - '$fooOrStringOrNull', - "'stringForSure'", - ], - [ - 'string', - '$fooOrStringOrNull', - "'anotherStringForSure'", - ], - [ - 'null', - '$this->bar', - "'propertyNullForSure'", - ], - [ - 'TypeElimination\Bar', - '$this->bar', - "'propertyNotNullForSure'", - ], - ]; - } - - /** - * @dataProvider dataTypeElimination - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testTypeElimination( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-elimination.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataMisleadingTypes(): array - { - return [ - [ - 'MisleadingTypes\boolean', - '$foo->misleadingBoolReturnType()', - ], - [ - 'MisleadingTypes\integer', - '$foo->misleadingIntReturnType()', - ], - [ - 'mixed', - '$foo->misleadingMixedReturnType()', - ], - ]; - } - - /** - * @dataProvider dataMisleadingTypes - * @param string $description - * @param string $expression - */ - public function testMisleadingTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/misleading-types.php', - $description, - $expression - ); - } - - public function dataMisleadingTypesWithoutNamespace(): array - { - return [ - [ - 'boolean', // would have been "bool" for a real boolean - '$foo->misleadingBoolReturnType()', - ], - [ - 'integer', - '$foo->misleadingIntReturnType()', - ], - ]; - } - - /** - * @dataProvider dataMisleadingTypesWithoutNamespace - * @param string $description - * @param string $expression - */ - public function testMisleadingTypesWithoutNamespace( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/misleading-types-without-namespace.php', - $description, - $expression - ); - } - - public function dataUnresolvableTypes(): array - { - return [ - [ - 'mixed', - '$arrayWithTooManyArgs', - ], - [ - 'mixed', - '$iterableWithTooManyArgs', - ], - [ - 'Foo', - '$genericFoo', - ], - ]; - } - - /** - * @dataProvider dataUnresolvableTypes - * @param string $description - * @param string $expression - */ - public function testUnresolvableTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/unresolvable-types.php', - $description, - $expression - ); - } - - public function dataCombineTypes(): array - { - return [ - [ - 'string|null', - '$x', - ], - [ - '1|null', - '$y', - ], - ]; - } - - /** - * @dataProvider dataCombineTypes - * @param string $description - * @param string $expression - */ - public function testCombineTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/combine-types.php', - $description, - $expression - ); - } - - public function dataConstants(): array - { - define('ConstantsForNodeScopeResolverTest\\FOO_CONSTANT', 1); - - return [ - [ - '1', - '$foo', - ], - [ - '*ERROR*', - 'NONEXISTENT_CONSTANT', - ], - [ - "'bar'", - '\\BAR_CONSTANT', - ], - [ - 'mixed', - '\\BAZ_CONSTANT', - ], - ]; - } - - /** - * @dataProvider dataConstants - * @param string $description - * @param string $expression - */ - public function testConstants( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/constants.php', - $description, - $expression - ); - } - - public function dataFinally(): array - { - return [ - [ - '1|\'foo\'', - '$integerOrString', - ], - [ - 'FinallyNamespace\BarException|FinallyNamespace\FooException|null', - '$fooOrBarException', - ], - ]; - } - - /** - * @dataProvider dataFinally - * @param string $description - * @param string $expression - */ - public function testFinally( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/finally.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataFinally - * @param string $description - * @param string $expression - */ - public function testFinallyWithEarlyTermination( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/finally-with-early-termination.php', - $description, - $expression - ); - } - - public function dataInheritDocFromInterface(): array - { - return [ - [ - 'string', - '$string', - ], - ]; - } - - /** - * @dataProvider dataInheritDocFromInterface - * @param string $description - * @param string $expression - */ - public function testInheritDocFromInterface( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/inheritdoc-from-interface.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataInheritDocFromInterface - * @param string $description - * @param string $expression - */ - public function testInheritDocWithoutCurlyBracesFromInterface( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface.php', - $description, - $expression - ); - } - - public function dataInheritDocFromInterface2(): array - { - return [ - [ - 'int', - '$int', - ], - ]; - } - - /** - * @dataProvider dataInheritDocFromInterface2 - * @param string $description - * @param string $expression - */ - public function testInheritDocFromInterface2( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/inheritdoc-from-interface2-definition.php'; - $this->assertTypes( - __DIR__ . '/data/inheritdoc-from-interface2.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataInheritDocFromInterface2 - * @param string $description - * @param string $expression - */ - public function testInheritDocWithoutCurlyBracesFromInterface2( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface2-definition.php'; - $this->assertTypes( - __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface2.php', - $description, - $expression - ); - } - - public function dataInheritDocFromTrait(): array - { - return [ - [ - 'string', - '$string', - ], - ]; - } - - /** - * @dataProvider dataInheritDocFromTrait - * @param string $description - * @param string $expression - */ - public function testInheritDocFromTrait( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/inheritdoc-from-trait.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataInheritDocFromTrait - * @param string $description - * @param string $expression - */ - public function testInheritDocWithoutCurlyBracesFromTrait( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait.php', - $description, - $expression - ); - } - - public function dataInheritDocFromTrait2(): array - { - return [ - [ - 'string', - '$string', - ], - ]; - } - - /** - * @dataProvider dataInheritDocFromTrait2 - * @param string $description - * @param string $expression - */ - public function testInheritDocFromTrait2( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/inheritdoc-from-trait2-definition.php'; - require_once __DIR__ . '/data/inheritdoc-from-trait2-definition2.php'; - $this->assertTypes( - __DIR__ . '/data/inheritdoc-from-trait2.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataInheritDocFromTrait2 - * @param string $description - * @param string $expression - */ - public function testInheritDocWithoutCurlyBracesFromTrait2( - string $description, - string $expression - ): void - { - require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2-definition.php'; - require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2-definition2.php'; - $this->assertTypes( - __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2.php', - $description, - $expression - ); - } - - public function dataResolveStatic(): array - { - return [ - [ - 'ResolveStatic\Foo', - '\ResolveStatic\Foo::create()', - ], - [ - 'ResolveStatic\Bar', - '\ResolveStatic\Bar::create()', - ], - [ - 'array(\'foo\' => ResolveStatic\Bar)', - '$bar->returnConstantArray()', - ], - [ - 'ResolveStatic\Bar|null', - '$bar->nullabilityNotInSync()', - ], - [ - 'ResolveStatic\Bar', - '$bar->anotherNullabilityNotInSync()', - ], - ]; - } - - /** - * @dataProvider dataResolveStatic - * @param string $description - * @param string $expression - */ - public function testResolveStatic( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/resolve-static.php', - $description, - $expression - ); - } - - public function dataLoopVariables(): array - { - return [ - [ - 'LoopVariables\Foo|LoopVariables\Lorem|null', - '$foo', - "'begin'", - ], - [ - 'LoopVariables\Foo', - '$foo', - "'afterAssign'", - ], - [ - 'LoopVariables\Foo', - '$foo', - "'end'", - ], - [ - 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem|null', - '$foo', - "'afterLoop'", - ], - [ - 'int|null', - '$nullableVal', - "'begin'", - ], - [ - 'null', - '$nullableVal', - "'nullableValIf'", - ], - [ - 'int', - '$nullableVal', - "'nullableValElse'", - ], - [ - 'int|null', - '$nullableVal', - "'afterLoop'", - ], - [ - 'LoopVariables\Foo|false', - '$falseOrObject', - "'begin'", - ], - [ - 'LoopVariables\Foo', - '$falseOrObject', - "'end'", - ], - [ - 'LoopVariables\Foo|false', - '$falseOrObject', - "'afterLoop'", - ], - ]; - } - - public function dataForeachLoopVariables(): array - { - return [ - [ - '1|2|3', - '$val', - "'begin'", - ], - [ - '0|1|2', - '$key', - "'begin'", - ], - [ - '1|2|3|null', - '$val', - "'afterLoop'", - ], - [ - '0|1|2|null', - '$key', - "'afterLoop'", - ], - [ - '1|2|3|null', - '$emptyForeachVal', - "'afterLoop'", - ], - [ - '0|1|2|null', - '$emptyForeachKey', - "'afterLoop'", - ], - [ - '1|2|3', - '$nullableInt', - "'end'", - ], - [ - 'array&nonEmpty', - '$integers', - "'end'", - ], - [ - 'array', - '$integers', - "'afterLoop'", - ], - [ - 'array', - '$this->property', - "'begin'", - ], - [ - 'array&nonEmpty', - '$this->property', - "'end'", - ], - [ - 'array', - '$this->property', - "'afterLoop'", - ], - [ - 'int', - '$i', - "'begin'", - ], - [ - 'int', - '$i', - "'end'", - ], - [ - 'int', - '$i', - "'afterLoop'", - ], - ]; - } - - public function dataWhileLoopVariables(): array - { - return [ - [ - 'int', - '$i', - "'begin'", - ], - [ - 'int', - '$i', - "'end'", - ], - [ - 'int', - '$i', - "'afterLoop'", - ], - ]; - } - - - public function dataForLoopVariables(): array - { - return [ - [ - 'int', - '$i', - "'begin'", - ], - [ - 'int', - '$i', - "'end'", - ], - [ - 'int', - '$i', - "'afterLoop'", - ], - ]; - } - - - - /** - * @dataProvider dataLoopVariables - * @dataProvider dataForeachLoopVariables - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testForeachLoopVariables( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/foreach-loop-variables.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - /** - * @dataProvider dataLoopVariables - * @dataProvider dataWhileLoopVariables - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testWhileLoopVariables( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/while-loop-variables.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - /** - * @dataProvider dataLoopVariables - * @dataProvider dataForLoopVariables - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testForLoopVariables( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/for-loop-variables.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataDoWhileLoopVariables(): array - { - return [ - [ - 'LoopVariables\Foo|LoopVariables\Lorem|null', - '$foo', - "'begin'", - ], - [ - 'LoopVariables\Foo', - '$foo', - "'afterAssign'", - ], - [ - 'LoopVariables\Foo', - '$foo', - "'end'", - ], - [ - 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem', - '$foo', - "'afterLoop'", - ], - [ - 'int', - '$i', - "'begin'", - ], - [ - 'int', - '$i', - "'end'", - ], - [ - 'int', - '$i', - "'afterLoop'", - ], - [ - 'int|null', - '$nullableVal', - "'begin'", - ], - [ - 'null', - '$nullableVal', - "'nullableValIf'", - ], - [ - 'int', - '$nullableVal', - "'nullableValElse'", - ], - [ - 'int', - '$nullableVal', - "'afterLoop'", - ], - [ - 'LoopVariables\Foo|false', - '$falseOrObject', - "'begin'", - ], - [ - 'LoopVariables\Foo', - '$falseOrObject', - "'end'", - ], - [ - 'LoopVariables\Foo|false', - '$falseOrObject', - "'afterLoop'", - ], - [ - 'LoopVariables\Foo|false', - '$anotherFalseOrObject', - "'begin'", - ], - [ - 'LoopVariables\Foo', - '$anotherFalseOrObject', - "'end'", - ], - [ - 'LoopVariables\Foo', - '$anotherFalseOrObject', - "'afterLoop'", - ], - - ]; - } - - /** - * @dataProvider dataDoWhileLoopVariables - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testDoWhileLoopVariables( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/do-while-loop-variables.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataMultipleClassesInOneFile(): array - { - return [ - [ - 'MultipleClasses\Foo', - '$self', - "'Foo'", - ], - [ - 'MultipleClasses\Bar', - '$self', - "'Bar'", - ], - ]; - } - - /** - * @dataProvider dataMultipleClassesInOneFile - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testMultipleClassesInOneFile( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/multiple-classes-per-file.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataCallingMultipleClassesInOneFile(): array - { - return [ - [ - 'MultipleClasses\Foo', - '$foo->returnSelf()', - ], - [ - 'MultipleClasses\Bar', - '$bar->returnSelf()', - ], - ]; - } - - /** - * @dataProvider dataCallingMultipleClassesInOneFile - * @param string $description - * @param string $expression - */ - public function testCallingMultipleClassesInOneFile( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/calling-multiple-classes-per-file.php', - $description, - $expression - ); - } - - public function dataExplode(): array - { - return [ - [ - 'array&nonEmpty', - '$sureArray', - ], - [ - PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*', - '$sureFalse', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$arrayOrFalse', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$anotherArrayOrFalse', - ], - [ - PHP_VERSION_ID < 80000 ? '(array|false)' : 'array', - '$benevolentArrayOrFalse', - ], - ]; - } - - /** - * @dataProvider dataExplode - * @param string $description - * @param string $expression - */ - public function testExplode( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/explode.php', - $description, - $expression - ); - } - - public function dataArrayPointerFunctions(): array - { - return [ - [ - 'mixed', - 'reset()', - ], - [ - 'stdClass|false', - 'reset($generalArray)', - ], - [ - 'mixed', - 'reset($somethingElse)', - ], - [ - 'false', - 'reset($emptyConstantArray)', - ], - [ - '1', - 'reset($constantArray)', - ], - [ - '\'baz\'|\'foo\'', - 'reset($conditionalArray)', - ], - [ - 'mixed', - 'end()', - ], - [ - 'stdClass|false', - 'end($generalArray)', - ], - [ - 'mixed', - 'end($somethingElse)', - ], - [ - 'false', - 'end($emptyConstantArray)', - ], - [ - '2', - 'end($constantArray)', - ], - [ - '\'bar\'|\'baz\'', - 'end($secondConditionalArray)', - ], - ]; - } - - /** - * @dataProvider dataArrayPointerFunctions - * @param string $description - * @param string $expression - */ - public function testArrayPointerFunctions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-pointer-functions.php', - $description, - $expression - ); - } - - public function dataReplaceFunctions(): array - { - return [ - [ - 'string', - '$expectedString', - ], - [ - 'string|null', - '$expectedString2', - ], - [ - 'string|null', - '$anotherExpectedString', - ], - [ - 'array(\'a\' => string, \'b\' => string)', - '$expectedArray', - ], - [ - 'array(\'a\' => string, \'b\' => string)|null', - '$expectedArray2', - ], - [ - 'array(\'a\' => string, \'b\' => string)|null', - '$anotherExpectedArray', - ], - [ - 'array|string', - '$expectedArrayOrString', - ], - [ - '(array|string)', - '$expectedBenevolentArrayOrString', - ], - [ - 'array|string|null', - '$expectedArrayOrString2', - ], - [ - 'array|string|null', - '$anotherExpectedArrayOrString', - ], - [ - 'array(\'a\' => string, \'b\' => string)|null', - 'preg_replace_callback_array($callbacks, $array)', - ], - [ - 'string|null', - 'preg_replace_callback_array($callbacks, $string)', - ], - [ - 'string', - 'str_replace(\'.\', \':\', $intOrStringKey)', - ], - [ - 'string', - 'str_ireplace(\'.\', \':\', $intOrStringKey)', - ], - ]; - } - - /** - * @dataProvider dataReplaceFunctions - * @param string $description - * @param string $expression - */ - public function testReplaceFunctions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/replaceFunctions.php', - $description, - $expression - ); - } - - public function dataFilterVar(): Generator - { - $typesAndFilters = [ - 'string' => [ - 'FILTER_DEFAULT', - 'FILTER_UNSAFE_RAW', - 'FILTER_SANITIZE_EMAIL', - 'FILTER_SANITIZE_ENCODED', - 'FILTER_SANITIZE_NUMBER_FLOAT', - 'FILTER_SANITIZE_NUMBER_INT', - 'FILTER_SANITIZE_SPECIAL_CHARS', - 'FILTER_SANITIZE_STRING', - 'FILTER_SANITIZE_URL', - 'FILTER_VALIDATE_EMAIL', - 'FILTER_VALIDATE_IP', - '$filterIp', - 'FILTER_VALIDATE_MAC', - 'FILTER_VALIDATE_REGEXP', - 'FILTER_VALIDATE_URL', - ], - 'int' => ['FILTER_VALIDATE_INT'], - 'float' => ['FILTER_VALIDATE_FLOAT'], - ]; - - if (defined('FILTER_SANITIZE_MAGIC_QUOTES')) { - $typesAndFilters['string'][] = 'FILTER_SANITIZE_MAGIC_QUOTES'; - } - - if (defined('FILTER_SANITIZE_ADD_SLASHES')) { - $typesAndFilters['string'][] = 'FILTER_SANITIZE_ADD_SLASHES'; - } - - $typeAndFlags = [ - ['%s|false', ''], - ['%s|false', ', $mixed'], - ['%s|false', ', ["flags" => $mixed]'], - ['%s|null', ', FILTER_NULL_ON_FAILURE'], - ['%s|null', ', ["flags" => FILTER_NULL_ON_FAILURE]'], - ['%s|null', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4]'], - ['%s|null', ', ["flags" => $nullFilter]'], - ['Analyser|%s', ', ["options" => ["default" => new Analyser]]'], - ['array<%s|null>', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY'], - ['array<%s|null>', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY | FILTER_FLAG_IPV4'], - ['array<%s|false>', ', FILTER_FORCE_ARRAY'], - ['array<%s|null>', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], - ['array<%s|false>', ', ["flags" => FILTER_FORCE_ARRAY | FILTER_FLAG_IPV4]'], - ['array<%s|false>', ', ["flags" => $forceArrayFilter]'], - ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_FORCE_ARRAY]'], - ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], - ]; - - foreach ($typesAndFilters as $filterType => $filters) { - foreach ($filters as $filter) { - foreach ($typeAndFlags as [$type, $flag]) { - yield [ - sprintf($type, $filterType), - sprintf('filter_var($mixed, %s %s)', $filter, $flag), - ]; - } - } - } - - $boolFlags = [ - ['bool', ''], - ['bool', ', $mixed'], - ['bool', ', ["flags" => $mixed]'], - ['bool|null', ', FILTER_NULL_ON_FAILURE'], - ['bool|null', ', ["flags" => FILTER_NULL_ON_FAILURE]'], - ['bool|null', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4]'], - ['bool|null', ', ["flags" => $nullFilter]'], - ['Analyser|bool', ', ["options" => ["default" => new Analyser]]'], - ['bool', ', ["options" => ["default" => true]]'], - ['array', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY'], - ['array', ', FILTER_FORCE_ARRAY'], - ['array', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], - ['array', ', ["flags" => $forceArrayFilter]'], - ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_FORCE_ARRAY]'], - ['array',', ["options" => ["default" => false], "flags" => FILTER_FORCE_ARRAY]'], - ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], - ]; - - foreach ($boolFlags as [$type, $flags]) { - yield [ - $type, - sprintf('filter_var($mixed, FILTER_VALIDATE_BOOLEAN %s)', $flags), - ]; - } - - //edge cases - yield 'unknown filter' => [ - 'mixed', - 'filter_var($mixed, $mixed)', - ]; - - yield 'default that is the same type as result' => [ - 'string', - 'filter_var($mixed, FILTER_SANITIZE_URL, ["options" => ["default" => "foo"]])', - ]; - - yield 'no second variable' => [ - 'string|false', - 'filter_var($mixed)', - ]; - } - - public function dataFilterVarUnchanged(): array - { - return [ - [ - '12', - 'filter_var(12, FILTER_VALIDATE_INT)', - ], - [ - 'false', - 'filter_var(false, FILTER_VALIDATE_BOOLEAN)', - ], - [ - 'array', - 'filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY)', - ], - [ - 'array', - 'filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE)', - ], - [ - '3.27', - 'filter_var(3.27, FILTER_VALIDATE_FLOAT)', - ], - [ - '3.27', - 'filter_var(3.27, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE)', - ], - [ - 'int', - 'filter_var(rand(), FILTER_VALIDATE_INT)', - ], - [ - '12.0', - 'filter_var(12, FILTER_VALIDATE_FLOAT)', - ], - ]; - } - - /** - * @dataProvider dataFilterVar - * @dataProvider dataFilterVarUnchanged - * @param string $description - * @param string $expression - */ - public function testFilterVar( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/filterVar.php', - $description, - $expression - ); - } - - public function dataClosureWithUsePassedByReference(): array - { - return [ - [ - 'false', - '$progressStarted', - "'beforeCallback'", - ], - [ - 'false', - '$anotherVariable', - "'beforeCallback'", - ], - [ - '1|bool', - '$progressStarted', - "'inCallbackBeforeAssign'", - ], - [ - 'false', - '$anotherVariable', - "'inCallbackBeforeAssign'", - ], - [ - 'null', - '$untouchedPassedByRef', - "'inCallbackBeforeAssign'", - ], - [ - '1|true', - '$progressStarted', - "'inCallbackAfterAssign'", - ], - [ - 'true', - '$anotherVariable', - "'inCallbackAfterAssign'", - ], - [ - '1|bool', - '$progressStarted', - "'afterCallback'", - ], - [ - 'false', - '$anotherVariable', - "'afterCallback'", - ], - [ - 'null', - '$untouchedPassedByRef', - "'afterCallback'", - ], - [ - '1', - '$incrementedInside', - "'beforeCallback'", - ], - [ - 'int', - '$incrementedInside', - "'inCallbackBeforeAssign'", - ], - [ - 'int', - '$incrementedInside', - "'inCallbackAfterAssign'", - ], - [ - 'int', - '$incrementedInside', - "'afterCallback'", - ], - [ - 'null', - '$fooOrNull', - "'beforeCallback'", - ], - [ - 'ClosurePassedByReference\Foo|null', - '$fooOrNull', - "'inCallbackBeforeAssign'", - ], - [ - 'ClosurePassedByReference\Foo', - '$fooOrNull', - "'inCallbackAfterAssign'", - ], - [ - 'ClosurePassedByReference\Foo|null', - '$fooOrNull', - "'afterCallback'", - ], - ]; - } - - /** - * @dataProvider dataClosureWithUsePassedByReference - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testClosureWithUsePassedByReference( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/closure-passed-by-reference.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataClosureWithUsePassedByReferenceInMethodCall(): array - { - return [ - [ - 'int|null', - '$five', - ], - ]; - } - - /** - * @dataProvider dataClosureWithUsePassedByReferenceInMethodCall - * @param string $description - * @param string $expression - */ - public function testClosureWithUsePassedByReferenceInMethodCall( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/closure-passed-by-reference-in-call.php', - $description, - $expression - ); - } - - public function dataClosureWithUsePassedByReferenceReturn(): array - { - return [ - [ - 'null', - '$fooOrNull', - "'beforeCallback'", - ], - [ - 'ClosurePassedByReference\Foo|null', - '$fooOrNull', - "'inCallbackBeforeAssign'", - ], - [ - 'ClosurePassedByReference\Foo', - '$fooOrNull', - "'inCallbackAfterAssign'", - ], - [ - 'ClosurePassedByReference\Foo|null', - '$fooOrNull', - "'afterCallback'", - ], - ]; - } - - public function dataStaticClosure(): array - { - return [ - [ - '*ERROR*', - '$this', - ], - ]; - } - - /** - * @dataProvider dataStaticClosure - * @param string $description - * @param string $expression - */ - public function testStaticClosure( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/static-closure.php', - $description, - $expression - ); - } - - /** - * @dataProvider dataClosureWithUsePassedByReferenceReturn - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testClosureWithUsePassedByReferenceReturn( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/closure-passed-by-reference-return.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataClosureWithInferredTypehint(): array - { - return [ - [ - 'DateTime|stdClass', - '$foo', - ], - [ - 'mixed', - '$bar', - ], - ]; - } - - /** - * @dataProvider dataClosureWithInferredTypehint - * @param string $description - * @param string $expression - */ - public function testClosureWithInferredTypehint( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/closure-inferred-typehint.php', - $description, - $expression, - [], - [], - [], - [], - 'die', - [], - false - ); - } - - public function dataTraitsPhpDocs(): array - { - return [ - [ - 'mixed', - '$this->propertyWithoutPhpDoc', - ], - [ - 'TraitPhpDocsTwo\TraitPropertyType', - '$this->traitProperty', - ], - [ - 'TraitPhpDocs\PropertyTypeFromClass', - '$this->conflictingProperty', - ], - /*[ - 'TraitPhpDocsTwo\AmbiguousPropertyType', - '$this->bogusProperty', - ],*/ - [ - 'TraitPhpDocs\BogusPropertyType', - '$this->anotherBogusProperty', - ], - [ - 'TraitPhpDocsTwo\BogusPropertyType', - '$this->differentBogusProperty', - ], - [ - 'string', - '$this->methodWithoutPhpDoc()', - ], - [ - 'TraitPhpDocsTwo\TraitMethodType', - '$this->traitMethod()', - ], - [ - 'TraitPhpDocs\MethodTypeFromClass', - '$this->conflictingMethod()', - ], - [ - 'TraitPhpDocs\AmbiguousMethodType', - '$this->bogusMethod()', - ], - [ - 'TraitPhpDocs\BogusMethodType', - '$this->anotherBogusMethod()', - ], - [ - 'TraitPhpDocsTwo\BogusMethodType', - '$this->differentBogusMethod()', - ], - [ - 'TraitPhpDocsTwo\DuplicateMethodType', - '$this->methodInMoreTraits()', - ], - [ - 'TraitPhpDocsThree\AnotherDuplicateMethodType', - '$this->anotherMethodInMoreTraits()', - ], - [ - 'TraitPhpDocsTwo\YetAnotherDuplicateMethodType', - '$this->yetAnotherMethodInMoreTraits()', - ], - [ - 'TraitPhpDocsThree\YetAnotherDuplicateMethodType', - '$this->aliasedYetAnotherMethodInMoreTraits()', - ], - [ - 'TraitPhpDocsThree\YetYetAnotherDuplicateMethodType', - '$this->yetYetAnotherMethodInMoreTraits()', - ], - [ - 'TraitPhpDocsTwo\YetYetAnotherDuplicateMethodType', - '$this->aliasedYetYetAnotherMethodInMoreTraits()', - ], - [ - 'int', - '$this->propertyFromTraitUsingTrait', - ], - [ - 'string', - '$this->methodFromTraitUsingTrait()', - ], - [ - 'TraitPhpDocsThree\Foo', - '$this->loremTraitProperty', - ], - ]; - } - - /** - * @dataProvider dataTraitsPhpDocs - * @param string $description - * @param string $expression - */ - public function testTraitsPhpDocs( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/traits/traits.php', - $description, - $expression - ); - } - - public function dataPassedByReference(): array - { - return [ - [ - 'array(1, 2, 3)', - '$arr', - ], - [ - 'mixed', - '$matches', - ], - [ - 'mixed', - '$s', - ], - ]; - } - - /** - * @dataProvider dataPassedByReference - * @param string $description - * @param string $expression - */ - public function testPassedByReference( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/passed-by-reference.php', - $description, - $expression - ); - } - - public function dataCallables(): array - { - return [ - [ - 'int', - '$foo()', - ], - [ - 'string', - '$closure()', - ], - [ - 'Callables\\Bar', - '$arrayWithStaticMethod()', - ], - [ - 'float', - '$stringWithStaticMethod()', - ], - [ - 'float', - '$arrayWithInstanceMethod()', - ], - [ - 'mixed', - '$closureObject()', - ], - ]; - } - - /** - * @dataProvider dataCallables - * @param string $description - * @param string $expression - */ - public function testCallables( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/callables.php', - $description, - $expression - ); - } - - public function dataArrayKeysInBranches(): array - { - return [ - [ - 'array(\'i\' => int, \'j\' => int, \'k\' => int, \'key\' => DateTimeImmutable, \'l\' => 1, \'m\' => 5, ?\'n\' => \'str\')', - '$array', - ], - [ - 'array', - '$generalArray', - ], - [ - 'mixed', // should be DateTimeImmutable - '$generalArray[\'key\']', - ], - [ - 'array(0 => \'foo\', 1 => \'bar\', ?2 => \'baz\')', - '$arrayAppendedInIf', - ], - [ - 'array', - '$arrayAppendedInForeach', - ], - [ - 'array', - '$anotherArrayAppendedInForeach', - ], - [ - '\'str\'', - '$array[\'n\']', - ], - [ - 'int', - '$incremented', - ], - [ - '0|1', - '$setFromZeroToOne', - ], - ]; - } - - /** - * @dataProvider dataArrayKeysInBranches - * @param string $description - * @param string $expression - */ - public function testArrayKeysInBranches( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-keys-branches.php', - $description, - $expression - ); - } - - public function dataSpecifiedFunctionCall(): array - { - return [ - [ - 'true', - 'is_file($autoloadFile)', - "'first'", - ], - [ - 'true', - 'is_file($autoloadFile)', - "'second'", - ], - [ - 'true', - 'is_file($autoloadFile)', - "'third'", - ], - [ - 'bool', - 'is_file($autoloadFile)', - "'fourth'", - ], - [ - 'true', - 'is_file($autoloadFile)', - "'fifth'", - ], - ]; - } - - /** - * @dataProvider dataSpecifiedFunctionCall - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testSpecifiedFunctionCall( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/specified-function-call.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataElementsOnMixed(): array - { - return [ - [ - 'mixed', - '$mixed->foo', - ], - [ - 'mixed', - '$mixed->foo->bar', - ], - [ - 'mixed', - '$mixed->foo()', - ], - [ - 'mixed', - '$mixed->foo()->bar()', - ], - [ - 'mixed', - '$mixed::TEST_CONSTANT', - ], - ]; - } - - /** - * @dataProvider dataElementsOnMixed - * @param string $description - * @param string $expression - */ - public function testElementsOnMixed( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/mixed-elements.php', - $description, - $expression - ); - } - - public function dataCaseInsensitivePhpDocTypes(): array - { - return [ - [ - 'Foo\Bar', - '$this->bar', - ], - [ - 'Foo\Baz', - '$this->lorem', - ], - ]; - } - - /** - * @dataProvider dataCaseInsensitivePhpDocTypes - * @param string $description - * @param string $expression - */ - public function testCaseInsensitivePhpDocTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/case-insensitive-phpdocs.php', - $description, - $expression - ); - } - - public function dataConstantTypeAfterDuplicateCondition(): array - { - return [ - [ - '0', - '$a', - "'inCondition'", - ], - [ - '0', - '$b', - "'inCondition'", - ], - [ - '0', - '$c', - "'inCondition'", - ], - [ - 'int', - '$a', - "'afterFirst'", - ], - [ - 'int', - '$b', - "'afterFirst'", - ], - [ - '0', - '$c', - "'afterFirst'", - ], - [ - 'int|int<1, max>', - '$a', - "'afterSecond'", - ], - [ - 'int', - '$b', - "'afterSecond'", - ], - [ - '0', - '$c', - "'afterSecond'", - ], - [ - 'int|int<1, max>', - '$a', - "'afterThird'", - ], - [ - 'int|int<1, max>', - '$b', - "'afterThird'", - ], - [ - '0', - '$c', - "'afterThird'", - ], - ]; - } - - /** - * @dataProvider dataConstantTypeAfterDuplicateCondition - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testConstantTypeAfterDuplicateCondition( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/constant-types-duplicate-condition.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataAnonymousClass(): array - { - return [ - [ - '$this(AnonymousClass3301acd9e9d13ba9bbce9581cdb00699)', - '$this', - "'inside'", - ], - [ - 'AnonymousClass3301acd9e9d13ba9bbce9581cdb00699', - '$foo', - "'outside'", - ], - [ - 'AnonymousClassName\Foo', - '$this->fooProperty', - "'inside'", - ], - [ - 'AnonymousClassName\Foo', - '$foo->fooProperty', - "'outside'", - ], - [ - 'AnonymousClassName\Foo', - '$this->doFoo()', - "'inside'", - ], - [ - 'AnonymousClassName\Foo', - '$foo->doFoo()', - "'outside'", - ], - ]; - } - - /** - * @dataProvider dataAnonymousClass - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testAnonymousClassName( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/anonymous-class-name.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataAnonymousClassInTrait(): array - { - return [ - [ - '$this(AnonymousClass3de0a9734314db9dec21ba308363ff9a)', - '$this', - ], - ]; - } - - /** - * @dataProvider dataAnonymousClassInTrait - * @param string $description - * @param string $expression - */ - public function testAnonymousClassNameInTrait( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/anonymous-class-name-in-trait.php', - $description, - $expression - ); - } - - public function dataDynamicConstants(): array - { - return [ - [ - 'string', - 'DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', - ], - [ - "'abc123def'", - 'DynamicConstants\DynamicConstantClass::PURE_CONSTANT_IN_CLASS', - ], - [ - "'xyz'", - 'DynamicConstants\NoDynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', - ], - [ - 'bool', - 'GLOBAL_DYNAMIC_CONSTANT', - ], - [ - '123', - 'GLOBAL_PURE_CONSTANT', - ], - ]; - } - - /** - * @dataProvider dataDynamicConstants - * @param string $description - * @param string $expression - */ - public function testDynamicConstants( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/dynamic-constant.php', - $description, - $expression, - [], - [], - [], - [], - 'die', - [ - 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', - 'GLOBAL_DYNAMIC_CONSTANT', - ] - ); - } - - public function dataIsset(): array - { - return [ - [ - '2|3', - '$array[\'b\']', - ], - [ - 'array(\'a\' => 1|2|3, \'b\' => 2|3, ?\'c\' => 4)', - '$array', - ], - [ - 'array(\'a\' => 1|2|3, \'b\' => 2|3|null, ?\'c\' => 4)', - '$arrayCopy', - ], - [ - 'array(\'a\' => 1|2|3, ?\'c\' => 4)', - '$anotherArrayCopy', - ], - [ - 'array', - '$yetAnotherArrayCopy', - ], - [ - 'mixed~null', - '$mixedIsset', - ], - [ - 'array&hasOffset(\'a\')', - '$mixedArrayKeyExists', - ], - [ - 'array&hasOffset(\'a\')', - '$integers', - ], - [ - 'int', - '$integers[\'a\']', - ], - [ - 'false', - '$lookup[\'derp\'] ?? false', - ], - [ - 'true', - '$lookup[\'foo\'] ?? false', - ], - [ - 'bool', - '$lookup[$a] ?? false', - ], - [ - '\'foo\'|false', - '$nullableArray[\'a\'] ?? false', - ], - [ - '\'bar\'', - '$nullableArray[\'b\'] ?? false', - ], - [ - '\'baz\'|false', - '$nullableArray[\'c\'] ?? false', - ], - ]; - } - - /** - * @dataProvider dataIsset - * @param string $description - * @param string $expression - */ - public function testIsset( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/isset.php', - $description, - $expression - ); - } - - public function dataPropertyArrayAssignment(): array - { - return [ - [ - 'mixed', - '$this->property', - "'start'", - ], - [ - 'array()', - '$this->property', - "'emptyArray'", - ], - [ - '*ERROR*', - '$this->property[\'foo\']', - "'emptyArray'", - ], - [ - 'array(\'foo\' => 1)', - '$this->property', - "'afterAssignment'", - ], - [ - '1', - '$this->property[\'foo\']', - "'afterAssignment'", - ], - ]; - } - - /** - * @dataProvider dataPropertyArrayAssignment - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testPropertyArrayAssignment( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/property-array.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataInArray(): array - { - return [ - [ - '\'bar\'|\'foo\'', - '$s', - ], - [ - 'string', - '$mixed', - ], - [ - 'string', - '$r', - ], - [ - '\'foo\'', - '$fooOrBarOrBaz', - ], - ]; - } - - /** - * @dataProvider dataInArray - * @param string $description - * @param string $expression - */ - public function testInArray( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/in-array.php', - $description, - $expression - ); - } - - public function dataGetParentClass(): array - { - return [ - [ - 'false', - 'get_parent_class()', - ], - [ - 'class-string|false', - 'get_parent_class($s)', - ], - [ - 'false', - 'get_parent_class(\ParentClass\Foo::class)', - ], - [ - 'class-string|false', - 'get_parent_class(NonexistentClass::class)', - ], - [ - 'class-string|false', - 'get_parent_class(1)', - ], - [ - "'ParentClass\\\\Foo'", - 'get_parent_class(\ParentClass\Bar::class)', - ], - [ - 'false', - 'get_parent_class()', - "'inParentClass'", - ], - [ - 'false', - 'get_parent_class($this)', - "'inParentClass'", - ], - [ - 'class-string', - 'get_class($this)', - "'inParentClass'", - ], - [ - '\'ParentClass\\\\Foo\'', - 'get_class()', - "'inParentClass'", - ], - [ - 'false', - 'get_class()', - ], - [ - "'ParentClass\\\\Foo'", - 'get_parent_class()', - "'inChildClass'", - ], - [ - "'ParentClass\\\\Foo'", - 'get_parent_class($this)', - "'inChildClass'", - ], - [ - 'class-string|false', - 'get_parent_class()', - "'inTrait'", - ], - [ - 'class-string|false', - 'get_parent_class($this)', - "'inTrait'", - ], - ]; - } - - /** - * @dataProvider dataGetParentClass - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testGetParentClass( - string $description, - string $expression, - string $evaluatedPointExpression = 'die' - ): void - { - $this->assertTypes( - __DIR__ . '/data/get-parent-class.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataIsCountable(): array - { - return [ - [ - 'array|Countable', - '$union', - "'is'", - ], - [ - 'string', - '$union', - "'is_not'", - ], - ]; - } - - /** - * @dataProvider dataIsCountable - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testIsCountable( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - $this->assertTypes( - __DIR__ . '/data/is_countable.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression - ); - } - - public function dataPhp73Functions(): array - { - return [ - [ - 'string|false', - 'json_encode($mixed)', - ], - [ - 'string', - 'json_encode($mixed, JSON_THROW_ON_ERROR)', - ], - [ - 'string', - 'json_encode($mixed, JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', - ], - [ - 'string', - 'json_encode($mixed, $integer | JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', - ], - [ - 'mixed', - 'json_decode($mixed)', - ], - [ - 'mixed~false', - 'json_decode($mixed, false, 512, JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', - ], - [ - 'mixed~false', - 'json_decode($mixed, false, 512, $integer | JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', - ], - [ - 'int|string|null', - 'array_key_first($mixedArray)', - ], - [ - 'int|string|null', - 'array_key_last($mixedArray)', - ], - [ - '(int|string)', - 'array_key_first($nonEmptyArray)', - ], - [ - '(int|string)', - 'array_key_last($nonEmptyArray)', - ], - [ - 'string|null', - 'array_key_first($arrayWithStringKeys)', - ], - [ - 'string|null', - 'array_key_last($arrayWithStringKeys)', - ], - [ - 'null', - 'array_key_first($emptyArray)', - ], - [ - 'null', - 'array_key_last($emptyArray)', - ], - [ - '0', - 'array_key_first($literalArray)', - ], - [ - '2', - 'array_key_last($literalArray)', - ], - [ - '0', - 'array_key_first($anotherLiteralArray)', - ], - [ - '2|3', - 'array_key_last($anotherLiteralArray)', - ], - [ - 'array(int, int)', - '$hrtime1', - ], - [ - 'array(int, int)', - '$hrtime2', - ], - [ - '(float|int)', - '$hrtime3', - ], - [ - 'array(int, int)|float|int', - '$hrtime4', - ], - ]; - } - - /** - * @dataProvider dataPhp73Functions - * @param string $description - * @param string $expression - */ - public function testPhp73Functions( - string $description, - string $expression - ): void - { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('Test requires PHP 7.3'); - } - $this->assertTypes( - __DIR__ . '/data/php73_functions.php', - $description, - $expression - ); - } - - public function dataPhp74Functions(): array - { - return [ - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithoutDefinedParameters', - ], - [ - "array('a', 'b', 'c', 'd', 'e', 'f')", - '$mbStrSplitConstantStringWithoutDefinedSplitLength', - ], - [ - 'array', - '$mbStrSplitStringWithoutDefinedSplitLength', - ], - [ - "array('a', 'b', 'c', 'd', 'e', 'f')", - '$mbStrSplitConstantStringWithOneSplitLength', - ], - [ - "array('abcdef')", - '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength', - ], - [ - 'false', - '$mbStrSplitConstantStringWithFailureSplitLength', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithInvalidSplitLengthType', - ], - [ - 'array', - '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', - ], - [ - "array('a', 'b', 'c', 'd', 'e', 'f')", - '$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', - ], - [ - "array('abcdef')", - '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', - ], - [ - 'array', - '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding', - ], - [ - 'false', - '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding', - ], - [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', - '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding', - ], - ]; - } - - /** - * @dataProvider dataPhp74Functions - * @param string $description - * @param string $expression - */ - public function testPhp74Functions( - string $description, - string $expression - ): void - { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4'); - } - $this->assertTypes( - __DIR__ . '/data/php74_functions.php', - $description, - $expression - ); - } - - public function dataUnionMethods(): array - { - return [ - [ - 'UnionMethods\Bar|UnionMethods\Foo', - '$something->doSomething()', - ], - [ - 'UnionMethods\Bar|UnionMethods\Foo', - '$something::doSomething()', - ], - ]; - } - - /** - * @dataProvider dataUnionMethods - * @param string $description - * @param string $expression - */ - public function testUnionMethods( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/union-methods.php', - $description, - $expression - ); - } - - public function dataUnionProperties(): array - { - return [ - [ - 'UnionProperties\Bar|UnionProperties\Foo', - '$something->doSomething', - ], - [ - 'UnionProperties\Bar|UnionProperties\Foo', - '$something::$doSomething', - ], - ]; - } - - /** - * @dataProvider dataUnionProperties - * @param string $description - * @param string $expression - */ - public function testUnionProperties( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/union-properties.php', - $description, - $expression - ); - } - - public function dataAssignmentInCondition(): array - { - return [ - [ - 'AssignmentInCondition\Foo', - '$bar', - ], - ]; - } - - /** - * @dataProvider dataAssignmentInCondition - * @param string $description - * @param string $expression - */ - public function testAssignmentInCondition( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/assignment-in-condition.php', - $description, - $expression - ); - } - - public function dataGeneralizeScope(): array - { - return [ - [ - "array int, 'loadCount' => int, 'removeCount' => int, 'saveCount' => int)>>", - '$statistics', - ], - ]; - } - - /** - * @dataProvider dataGeneralizeScope - * @param string $description - * @param string $expression - */ - public function testGeneralizeScope( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/generalize-scope.php', - $description, - $expression - ); - } - - public function dataGeneralizeScopeRecursiveType(): array - { - return [ - [ - 'array()|array(\'foo\' => array)', - '$data', - ], - ]; - } - - /** - * @dataProvider dataGeneralizeScopeRecursiveType - * @param string $description - * @param string $expression - */ - public function testGeneralizeScopeRecursiveType( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/generalize-scope-recursive.php', - $description, - $expression - ); - } - - public function dataArrayShapesInPhpDoc(): array - { - return [ - [ - 'array(0 => string, 1 => ArrayShapesInPhpDoc\Foo, \'foo\' => ArrayShapesInPhpDoc\Bar, 2 => ArrayShapesInPhpDoc\Baz)', - '$one', - ], - [ - 'array(0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', - '$two', - ], - [ - 'array(?0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', - '$three', - ], - ]; - } - - /** - * @dataProvider dataArrayShapesInPhpDoc - * @param string $description - * @param string $expression - */ - public function testArrayShapesInPhpDoc( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/array-shapes.php', - $description, - $expression - ); - } - - public function dataInferPrivatePropertyTypeFromConstructor(): array - { - return [ - [ - 'int', - '$this->intProp', - ], - [ - 'string', - '$this->stringProp', - ], - [ - 'InferPrivatePropertyTypeFromConstructor\Bar|InferPrivatePropertyTypeFromConstructor\Foo', - '$this->unionProp', - ], - [ - 'stdClass', - '$this->stdClassProp', - ], - [ - 'stdClass', - '$this->unrelatedDocComment', - ], - [ - 'mixed', - '$this->explicitMixed', - ], - [ - 'bool', - '$this->bool', - ], - [ - 'array', - '$this->array', - ], - ]; - } - - /** - * @dataProvider dataInferPrivatePropertyTypeFromConstructor - * @param string $description - * @param string $expression - */ - public function testInferPrivatePropertyTypeFromConstructor( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/infer-private-property-type-from-constructor.php', - $description, - $expression - ); - } - - public function dataPropertyNativeTypes(): array - { - return [ - [ - 'string', - '$this->stringProp', - ], - [ - 'PropertyNativeTypes\Foo', - '$this->selfProp', - ], - [ - 'array', - '$this->integersProp', - ], - ]; - } - - /** - * @dataProvider dataPropertyNativeTypes - * @param string $description - * @param string $expression - */ - public function testPropertyNativeTypes( - string $description, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/property-native-types.php', - $description, - $expression - ); - } - - public function dataArrowFunctions(): array - { - return [ - [ - 'Closure(string): 1', - '$x', - ], - [ - '1', - '$x()', - ], - [ - 'array(\'a\' => 1, \'b\' => 2)', - '$y()', - ], - ]; - } - - /** - * @dataProvider dataArrowFunctions - * @param string $description - * @param string $expression - */ - public function testArrowFunctions( - string $description, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/arrow-functions.php', - $description, - $expression - ); - } - - public function dataArrowFunctionsInside(): array - { - return [ - [ - 'int', - '$i', - ], - [ - 'string', - '$s', - ], - [ - '*ERROR*', - '$t', - ], - ]; - } - - /** - * @dataProvider dataArrowFunctionsInside - * @param string $description - * @param string $expression - */ - public function testArrowFunctionsInside( - string $description, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/arrow-functions-inside.php', - $description, - $expression - ); - } - - public function dataCoalesceAssign(): array - { - return [ - [ - 'string', - '$string ??= 1', - ], - [ - '1|string', - '$nullableString ??= 1', - ], - [ - '\'foo\'', - '$emptyArray[\'foo\'] ??= \'foo\'', - ], - [ - '\'foo\'', - '$arrayWithFoo[\'foo\'] ??= \'bar\'', - ], - [ - '\'bar\'|\'foo\'', - '$arrayWithMaybeFoo[\'foo\'] ??= \'bar\'', - ], - [ - 'array(\'foo\' => \'foo\')', - '$arrayAfterAssignment', - ], - [ - 'array(\'foo\' => \'foo\')', - '$arrayWithFooAfterAssignment', - ], - [ - '\'foo\'', - '$nonexistentVariableAfterAssignment', - ], - [ - '\'bar\'|\'foo\'', - '$maybeNonexistentVariableAfterAssignment', - ], - ]; - } - - /** - * @dataProvider dataCoalesceAssign - * @param string $description - * @param string $expression - */ - public function testCoalesceAssign( - string $description, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/coalesce-assign.php', - $description, - $expression - ); - } - - public function dataArraySpread(): array - { - return [ - [ - 'array', - '$integersOne', - ], - [ - 'array', - '$integersTwo', - ], - [ - 'array(1, 2, 3, 4, 5, 6, 7)', - '$integersThree', - ], - [ - 'array', - '$integersFour', - ], - [ - 'array', - '$integersFive', - ], - [ - 'array(1, 2, 3, 4, 5, 6, 7)', - '$integersSix', - ], - [ - 'array(1, 2, 3, 4, 5, 6, 7)', - '$integersSeven', - ], - ]; - } - - /** - * @dataProvider dataArraySpread - * @param string $description - * @param string $expression - */ - public function testArraySpread( - string $description, - string $expression - ): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/array-spread.php', - $description, - $expression - ); - } - - public function dataPhp74FunctionsIn73(): array - { - return [ - [ - '*ERROR*', - 'password_algos()', - ], - ]; - } - - /** - * @dataProvider dataPhp74FunctionsIn73 - * @param string $description - * @param string $expression - */ - public function testPhp74FunctionsIn73( - string $description, - string $expression - ): void - { - if (PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP >= 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/die-73.php', - $description, - $expression - ); - } - - public function dataPhp74FunctionsIn74(): array - { - return [ - [ - 'array', - 'password_algos()', - ], - ]; - } - - /** - * @dataProvider dataPhp74FunctionsIn74 - * @param string $description - * @param string $expression - */ - public function testPhp74FunctionsIn74( - string $description, - string $expression - ): void - { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/die-74.php', - $description, - $expression - ); - } - - public function dataTryCatchScope(): array - { - return [ - [ - 'TryCatchScope\Foo', - '$resource', - "'first'", - ], - [ - 'TryCatchScope\Foo|null', - '$resource', - "'second'", - ], - [ - 'TryCatchScope\Foo|null', - '$resource', - "'third'", - ], - ]; - } - - /** - * @dataProvider dataTryCatchScope - * @param string $description - * @param string $expression - * @param string $evaluatedPointExpression - */ - public function testTryCatchScope( - string $description, - string $expression, - string $evaluatedPointExpression - ): void - { - foreach ([true, false] as $polluteCatchScopeWithTryAssignments) { - $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; - - try { - $this->assertTypes( - __DIR__ . '/data/try-catch-scope.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression, - [], - false - ); - } catch (\PHPUnit\Framework\ExpectationFailedException $e) { - throw new \PHPUnit\Framework\ExpectationFailedException( - sprintf( - '%s (polluteCatchScopeWithTryAssignments: %s)', - $e->getMessage(), - $polluteCatchScopeWithTryAssignments ? 'true' : 'false' - ), - $e->getComparisonFailure() - ); - } - } - } - - /** - * @param string $file - * @param string $description - * @param string $expression - * @param DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions - * @param DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions - * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions - * @param string $evaluatedPointExpression - * @param string[] $dynamicConstantNames - * @param bool $useCache - */ - private function assertTypes( - string $file, - string $description, - string $expression, - array $dynamicMethodReturnTypeExtensions = [], - array $dynamicStaticMethodReturnTypeExtensions = [], - array $methodTypeSpecifyingExtensions = [], - array $staticMethodTypeSpecifyingExtensions = [], - string $evaluatedPointExpression = 'die', - array $dynamicConstantNames = [], - bool $useCache = true - ): void - { - $assertType = function (Scope $scope) use ($expression, $description, $evaluatedPointExpression): void { - /** @var \PhpParser\Node\Stmt\Expression $expressionNode */ - $expressionNode = $this->getParser()->parseString(sprintf('getType($expressionNode->expr); - $this->assertTypeDescribe( - $description, - $type, - sprintf('%s at %s', $expression, $evaluatedPointExpression) - ); - }; - if ($useCache && isset(self::$assertTypesCache[$file][$evaluatedPointExpression])) { - $assertType(self::$assertTypesCache[$file][$evaluatedPointExpression]); - return; - } - $this->processFile( - $file, - static function (\PhpParser\Node $node, Scope $scope) use ($file, $evaluatedPointExpression, $assertType): void { - if ($node instanceof VirtualNode) { - return; - } - $printer = new \PhpParser\PrettyPrinter\Standard(); - $printedNode = $printer->prettyPrint([$node]); - if ($printedNode !== $evaluatedPointExpression) { - return; - } - - self::$assertTypesCache[$file][$evaluatedPointExpression] = $scope; - - $assertType($scope); - }, - $dynamicMethodReturnTypeExtensions, - $dynamicStaticMethodReturnTypeExtensions, - $methodTypeSpecifyingExtensions, - $staticMethodTypeSpecifyingExtensions, - $dynamicConstantNames - ); - } - - public static function getAdditionalConfigFiles(): array - { - return [__DIR__ . '/typeAliases.neon']; - } - - public function dataDeclareStrictTypes(): array - { - return [ - [ - __DIR__ . '/data/declareWeakTypes.php', - false, - ], - [ - __DIR__ . '/data/noDeclare.php', - false, - ], - [ - __DIR__ . '/data/declareStrictTypes.php', - true, - ], - ]; - } - - /** - * @dataProvider dataDeclareStrictTypes - * @param string $file - * @param bool $result - */ - public function testDeclareStrictTypes(string $file, bool $result): void - { - $this->processFile($file, function (\PhpParser\Node $node, Scope $scope) use ($result): void { - if (!($node instanceof Exit_)) { - return; - } - - $this->assertSame($result, $scope->isDeclareStrictTypes()); - }); - } - - public function testEarlyTermination(): void - { - $this->processFile(__DIR__ . '/data/early-termination.php', function (\PhpParser\Node $node, Scope $scope): void { - if (!($node instanceof Exit_)) { - return; - } - - $this->assertTrue($scope->hasVariableType('something')->yes()); - $this->assertTrue($scope->hasVariableType('var')->yes()); - $this->assertTrue($scope->hasVariableType('foo')->no()); - }); - } - - protected function getEarlyTerminatingMethodCalls(): array - { - return [ - \EarlyTermination\Foo::class => [ - 'doFoo', - 'doBar', - ], - ]; - } - - protected function getEarlyTerminatingFunctionCalls(): array - { - return ['baz']; - } - - private function assertTypeDescribe( - string $expectedDescription, - Type $actualType, - string $label = '' - ): void - { - $actualDescription = $actualType->describe(VerbosityLevel::precise()); - $this->assertSame( - $expectedDescription, - $actualDescription, - $label - ); - } - - /** @return string[] */ - protected function getAdditionalAnalysedFiles(): array - { - return [ - __DIR__ . '/data/methodPhpDocs-trait-defined.php', - __DIR__ . '/data/anonymous-class-name-in-trait-trait.php', - ]; - } - + /** @var Scope[][] */ + private static $assertTypesCache = []; + + public function testClassMethodScope(): void + { + $this->processFile(__DIR__ . '/data/class.php', function (\PhpParser\Node $node, Scope $scope): void { + if (!($node instanceof Exit_)) { + return; + } + + $this->assertSame('SomeNodeScopeResolverNamespace', $scope->getNamespace()); + $this->assertTrue($scope->isInClass()); + $this->assertSame(Foo::class, $scope->getClassReflection()->getName()); + $this->assertSame('doFoo', $scope->getFunctionName()); + $this->assertSame('$this(SomeNodeScopeResolverNamespace\Foo)', $scope->getVariableType('this')->describe(VerbosityLevel::precise())); + $this->assertTrue($scope->hasVariableType('baz')->yes()); + $this->assertTrue($scope->hasVariableType('lorem')->yes()); + $this->assertFalse($scope->hasVariableType('ipsum')->yes()); + $this->assertTrue($scope->hasVariableType('i')->yes()); + $this->assertTrue($scope->hasVariableType('val')->yes()); + $this->assertSame('SomeNodeScopeResolverNamespace\InvalidArgumentException', $scope->getVariableType('exception')->describe(VerbosityLevel::precise())); + $this->assertTrue($scope->hasVariableType('staticVariable')->yes()); + $this->assertSame($scope->getVariableType('staticVariable')->describe(VerbosityLevel::precise()), 'mixed'); + $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType')->yes()); + $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType')->describe(VerbosityLevel::precise()), 'string'); + $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType2')->yes()); + $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType2')->describe(VerbosityLevel::precise()), 'int'); + $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType3')->yes()); + $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType3')->describe(VerbosityLevel::precise()), 'float'); + }); + } + + private function getFileScope(string $filename): Scope + { + /** @var \PHPStan\Analyser\Scope $testScope */ + $testScope = null; + $this->processFile($filename, static function (\PhpParser\Node $node, Scope $scope) use (&$testScope): void { + if (!($node instanceof Exit_)) { + return; + } + + $testScope = $scope; + }); + + return $testScope; + } + + public function dataUnionInCatch(): array + { + return [ + [ + 'CatchUnion\BarException|CatchUnion\FooException', + '$e', + ], + ]; + } + + /** + * @dataProvider dataUnionInCatch + * @param string $description + * @param string $expression + */ + public function testUnionInCatch( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/catch-union.php', + $description, + $expression + ); + } + + public function dataUnionAndIntersection(): array + { + return [ + [ + 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', + '$this->union->foo', + ], + [ + 'UnionIntersection\Bar', + '$this->union->bar', + ], + [ + 'UnionIntersection\Foo', + '$foo->foo', + ], + [ + '*ERROR*', + '$foo->bar', + ], + [ + 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', + '$this->union->doFoo()', + ], + [ + 'UnionIntersection\Bar', + '$this->union->doBar()', + ], + [ + 'UnionIntersection\Foo', + '$foo->doFoo()', + ], + [ + '*ERROR*', + '$foo->doBar()', + ], + [ + 'UnionIntersection\AnotherFoo&UnionIntersection\Foo', + '$foobar->doFoo()', + ], + [ + 'UnionIntersection\Bar', + '$foobar->doBar()', + ], + [ + '1', + '$this->union::FOO_CONSTANT', + ], + [ + '1', + '$this->union::BAR_CONSTANT', + ], + [ + '1', + '$foo::FOO_CONSTANT', + ], + [ + '*ERROR*', + '$foo::BAR_CONSTANT', + ], + [ + '1', + '$foobar::FOO_CONSTANT', + ], + [ + '1', + '$foobar::BAR_CONSTANT', + ], + [ + '\'foo\'', + 'self::IPSUM_CONSTANT', + ], + [ + 'array(1, 2, 3)', + 'parent::PARENT_CONSTANT', + ], + [ + 'UnionIntersection\Foo', + '$foo::doStaticFoo()', + ], + [ + '*ERROR*', + '$foo::doStaticBar()', + ], + [ + 'UnionIntersection\AnotherFoo&UnionIntersection\Foo', + '$foobar::doStaticFoo()', + ], + [ + 'UnionIntersection\Bar', + '$foobar::doStaticBar()', + ], + [ + 'UnionIntersection\AnotherFoo|UnionIntersection\Foo', + '$this->union::doStaticFoo()', + ], + [ + 'UnionIntersection\Bar', + '$this->union::doStaticBar()', + ], + [ + 'object', + '$this->objectUnion', + ], + [ + 'UnionIntersection\SomeInterface', + '$object', + ], + ]; + } + + /** + * @dataProvider dataUnionAndIntersection + * @param string $description + * @param string $expression + */ + public function testUnionAndIntersection( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/union-intersection.php', + $description, + $expression + ); + } + + public function dataAssignInIf(): array + { + $testScope = $this->getFileScope(__DIR__ . '/data/if.php'); + + return [ + [ + $testScope, + 'nonexistentVariable', + TrinaryLogic::createNo(), + ], + [ + $testScope, + 'foo', + TrinaryLogic::createMaybe(), + 'bool', // mixed? + ], + [ + $testScope, + 'lorem', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'callParameter', + TrinaryLogic::createYes(), + '3', + ], + [ + $testScope, + 'arrOne', + TrinaryLogic::createYes(), + 'array(\'one\')', + ], + [ + $testScope, + 'arrTwo', + TrinaryLogic::createYes(), + 'array(\'test\' => \'two\', 0 => Foo)', + ], + [ + $testScope, + 'arrThree', + TrinaryLogic::createYes(), + 'array(\'three\')', + ], + [ + $testScope, + 'inArray', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'i', + TrinaryLogic::createYes(), + 'int', + ], + [ + $testScope, + 'f', + TrinaryLogic::createMaybe(), + 'int', + ], + [ + $testScope, + 'anotherF', + TrinaryLogic::createYes(), + 'int', + ], + [ + $testScope, + 'matches', + TrinaryLogic::createYes(), + 'mixed', + ], + [ + $testScope, + 'anotherArray', + TrinaryLogic::createYes(), + 'array(\'test\' => array(\'another\'))', + ], + [ + $testScope, + 'ifVar', + TrinaryLogic::createYes(), + '1|2|3', + ], + [ + $testScope, + 'ifNotVar', + TrinaryLogic::createMaybe(), + '1|2', + ], + [ + $testScope, + 'ifNestedVar', + TrinaryLogic::createYes(), + '1|2|3', + ], + [ + $testScope, + 'ifNotNestedVar', + TrinaryLogic::createMaybe(), + '1|2|3', + ], + [ + $testScope, + 'variableOnlyInEarlyTerminatingElse', + TrinaryLogic::createNo(), + ], + [ + $testScope, + 'matches2', + TrinaryLogic::createMaybe(), + 'mixed', + ], + [ + $testScope, + 'inTry', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'matches3', + TrinaryLogic::createYes(), + 'mixed', + ], + [ + $testScope, + 'matches4', + TrinaryLogic::createMaybe(), + 'mixed', + ], + [ + $testScope, + 'issetFoo', + TrinaryLogic::createYes(), + 'Foo', + ], + [ + $testScope, + 'issetBar', + TrinaryLogic::createYes(), + 'mixed~null', + ], + [ + $testScope, + 'issetBaz', + TrinaryLogic::createYes(), + 'mixed~null', + ], + [ + $testScope, + 'doWhileVar', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'switchVar', + TrinaryLogic::createYes(), + '1|2|3|4', + ], + [ + $testScope, + 'noSwitchVar', + TrinaryLogic::createMaybe(), + '1', + ], + [ + $testScope, + 'anotherNoSwitchVar', + TrinaryLogic::createMaybe(), + '1', + ], + [ + $testScope, + 'inTryTwo', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'ternaryMatches', + TrinaryLogic::createYes(), + 'mixed', + ], + [ + $testScope, + 'previousI', + TrinaryLogic::createYes(), + '0|1', + ], + [ + $testScope, + 'previousJ', + TrinaryLogic::createYes(), + '0', + ], + [ + $testScope, + 'frame', + TrinaryLogic::createYes(), + 'mixed', + ], + [ + $testScope, + 'listOne', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'listTwo', + TrinaryLogic::createYes(), + '2', + ], + [ + $testScope, + 'e', + TrinaryLogic::createYes(), + 'Exception', + ], + [ + $testScope, + 'exception', + TrinaryLogic::createYes(), + 'Exception', + ], + [ + $testScope, + 'inTryNotInCatch', + TrinaryLogic::createMaybe(), + '1', + ], + [ + $testScope, + 'fooObjectFromTryCatch', + TrinaryLogic::createYes(), + 'InTryCatchFoo', + ], + [ + $testScope, + 'mixedVarFromTryCatch', + TrinaryLogic::createYes(), + '1|1.0', + ], + [ + $testScope, + 'nullableIntegerFromTryCatch', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'anotherNullableIntegerFromTryCatch', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'nullableIntegers', + TrinaryLogic::createYes(), + 'array(1, 2, 3, null)', + ], + [ + $testScope, + 'union', + TrinaryLogic::createYes(), + 'array(1, 2, 3, \'foo\')', + '1|2|3|\'foo\'', + ], + [ + $testScope, + 'trueOrFalse', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'falseOrTrue', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'true', + TrinaryLogic::createYes(), + 'true', + ], + [ + $testScope, + 'false', + TrinaryLogic::createYes(), + 'false', + ], + [ + $testScope, + 'trueOrFalseFromSwitch', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'trueOrFalseInSwitchWithDefault', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'trueOrFalseInSwitchInAllCases', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'trueOrFalseInSwitchInAllCasesWithDefault', + TrinaryLogic::createYes(), + 'bool', + ], + [ + $testScope, + 'trueOrFalseInSwitchInAllCasesWithDefaultCase', + TrinaryLogic::createYes(), + 'true', + ], + [ + $testScope, + 'variableDefinedInSwitchWithOtherCasesWithEarlyTermination', + TrinaryLogic::createYes(), + 'true', + ], + [ + $testScope, + 'anotherVariableDefinedInSwitchWithOtherCasesWithEarlyTermination', + TrinaryLogic::createYes(), + 'true', + ], + [ + $testScope, + 'variableDefinedOnlyInEarlyTerminatingSwitchCases', + TrinaryLogic::createNo(), + ], + [ + $testScope, + 'nullableTrueOrFalse', + TrinaryLogic::createYes(), + 'bool|null', + ], + [ + $testScope, + 'nonexistentVariableOutsideFor', + TrinaryLogic::createMaybe(), + '1', + ], + [ + $testScope, + 'integerOrNullFromFor', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'nonexistentVariableOutsideWhile', + TrinaryLogic::createMaybe(), + '1', + ], + [ + $testScope, + 'integerOrNullFromWhile', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'nonexistentVariableOutsideForeach', + TrinaryLogic::createMaybe(), + 'null', + ], + [ + $testScope, + 'integerOrNullFromForeach', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'notNullableString', + TrinaryLogic::createYes(), + 'string', + ], + [ + $testScope, + 'anotherNotNullableString', + TrinaryLogic::createYes(), + 'string', + ], + [ + $testScope, + 'notNullableObject', + TrinaryLogic::createYes(), + 'Foo', + ], + [ + $testScope, + 'nullableString', + TrinaryLogic::createYes(), + 'string|null', + ], + [ + $testScope, + 'alsoNotNullableString', + TrinaryLogic::createYes(), + 'string', + ], + [ + $testScope, + 'integerOrString', + TrinaryLogic::createYes(), + '\'str\'|int', + ], + [ + $testScope, + 'nullableIntegerAfterNeverCondition', + TrinaryLogic::createYes(), + 'int|null', + ], + [ + $testScope, + 'stillNullableInteger', + TrinaryLogic::createYes(), + '2|null', + ], + [ + $testScope, + 'arrayOfIntegers', + TrinaryLogic::createYes(), + 'array(1, 2, 3)', + ], + [ + $testScope, + 'arrayAccessObject', + TrinaryLogic::createYes(), + \ObjectWithArrayAccess\Foo::class, + ], + [ + $testScope, + 'width', + TrinaryLogic::createYes(), + '2.0', + ], + [ + $testScope, + 'someVariableThatWillGetOverrideInFinally', + TrinaryLogic::createYes(), + '\'foo\'', + ], + [ + $testScope, + 'maybeDefinedButLaterCertainlyDefined', + TrinaryLogic::createYes(), + '2|3', + ], + [ + $testScope, + 'mixed', + TrinaryLogic::createYes(), + 'mixed', // should be mixed~bool+1 + ], + [ + $testScope, + 'variableDefinedInSwitchWithoutEarlyTermination', + TrinaryLogic::createMaybe(), + 'false', + ], + [ + $testScope, + 'anotherVariableDefinedInSwitchWithoutEarlyTermination', + TrinaryLogic::createMaybe(), + 'bool', + ], + [ + $testScope, + 'alwaysDefinedFromSwitch', + TrinaryLogic::createYes(), + '1|null', + ], + [ + $testScope, + 'exceptionFromTryCatch', + TrinaryLogic::createYes(), + '(AnotherException&Throwable)|(Throwable&YetAnotherException)|null', + ], + [ + $testScope, + 'nullOverwrittenInSwitchToOne', + TrinaryLogic::createYes(), + '1', + ], + [ + $testScope, + 'variableFromSwitchShouldBeBool', + TrinaryLogic::createYes(), + 'bool', + ], + ]; + } + + /** + * @dataProvider dataAssignInIf + * @param \PHPStan\Analyser\Scope $scope + * @param string $variableName + * @param \PHPStan\TrinaryLogic $expectedCertainty + * @param string|null $typeDescription + * @param string|null $iterableValueTypeDescription + */ + public function testAssignInIf( + Scope $scope, + string $variableName, + TrinaryLogic $expectedCertainty, + ?string $typeDescription = null, + ?string $iterableValueTypeDescription = null + ): void { + $this->assertVariables( + $scope, + $variableName, + $expectedCertainty, + $typeDescription, + $iterableValueTypeDescription + ); + } + + public function dataConstantTypes(): array + { + $testScope = $this->getFileScope(__DIR__ . '/data/constantTypes.php'); + + return [ + [ + $testScope, + 'postIncrement', + '2', + ], + [ + $testScope, + 'postDecrement', + '4', + ], + [ + $testScope, + 'preIncrement', + '2', + ], + [ + $testScope, + 'preDecrement', + '4', + ], + [ + $testScope, + 'literalArray', + 'array(\'a\' => 2, \'b\' => 4, \'c\' => 2, \'d\' => 4)', + ], + [ + $testScope, + 'nullIncremented', + '1', + ], + [ + $testScope, + 'nullDecremented', + 'null', + ], + [ + $testScope, + 'incrementInIf', + '1|2|3', + ], + [ + $testScope, + 'anotherIncrementInIf', + '2|3', + ], + [ + $testScope, + 'valueOverwrittenInIf', + '1|2', + ], + [ + $testScope, + 'incrementInForLoop', + 'int', + ], + [ + $testScope, + 'valueOverwrittenInForLoop', + '1|2', + ], + [ + $testScope, + 'arrayOverwrittenInForLoop', + 'array(\'a\' => int, \'b\' => \'bar\'|\'foo\')', + ], + [ + $testScope, + 'anotherValueOverwrittenInIf', + '5|10', + ], + [ + $testScope, + 'intProperty', + 'int', + ], + [ + $testScope, + 'staticIntProperty', + 'int', + ], + [ + $testScope, + 'anotherIntProperty', + '1|2', + ], + [ + $testScope, + 'anotherStaticIntProperty', + '1|2', + ], + [ + $testScope, + 'variableIncrementedInClosurePassedByReference', + 'int', + ], + [ + $testScope, + 'anotherVariableIncrementedInClosure', + '0', + ], + [ + $testScope, + 'yetAnotherVariableInClosurePassedByReference', + 'int', + ], + [ + $testScope, + 'variableIncrementedInFinally', + '1', + ], + ]; + } + + /** + * @dataProvider dataConstantTypes + * @param \PHPStan\Analyser\Scope $scope + * @param string $variableName + * @param string $typeDescription + */ + public function testConstantTypes( + Scope $scope, + string $variableName, + string $typeDescription + ): void { + $this->assertVariables( + $scope, + $variableName, + TrinaryLogic::createYes(), + $typeDescription, + null + ); + } + + private function assertVariables( + Scope $scope, + string $variableName, + TrinaryLogic $expectedCertainty, + ?string $typeDescription = null, + ?string $iterableValueTypeDescription = null + ): void { + $certainty = $scope->hasVariableType($variableName); + $this->assertTrue( + $expectedCertainty->equals($certainty), + sprintf( + 'Certainty of variable $%s is %s, expected %s', + $variableName, + $certainty->describe(), + $expectedCertainty->describe() + ) + ); + if (!$expectedCertainty->no()) { + if ($typeDescription === null) { + $this->fail(sprintf('Missing expected type for defined variable $%s.', $variableName)); + } + + $this->assertSame( + $typeDescription, + $scope->getVariableType($variableName)->describe(VerbosityLevel::precise()), + sprintf('Type of variable $%s does not match the expected one.', $variableName) + ); + + if ($iterableValueTypeDescription !== null) { + $this->assertSame( + $iterableValueTypeDescription, + $scope->getVariableType($variableName)->getIterableValueType()->describe(VerbosityLevel::precise()), + sprintf('Iterable value type of variable $%s does not match the expected one.', $variableName) + ); + } + } elseif ($typeDescription !== null) { + $this->fail( + sprintf( + 'No type should be asserted for an undefined variable $%s, %s given.', + $variableName, + $typeDescription + ) + ); + } + } + + public function dataArrayDestructuring(): array + { + return [ + [ + 'mixed', + '$a', + ], + [ + 'mixed', + '$b', + ], + [ + 'mixed', + '$c', + ], + [ + 'mixed', + '$aList', + ], + [ + 'mixed', + '$bList', + ], + [ + 'mixed', + '$cList', + ], + [ + '1', + '$int', + ], + [ + '\'foo\'', + '$string', + ], + [ + 'true', + '$bool', + ], + [ + '*ERROR*', + '$never', + ], + [ + '*ERROR*', + '$nestedNever', + ], + [ + '1', + '$intList', + ], + [ + '\'foo\'', + '$stringList', + ], + [ + 'true', + '$boolList', + ], + [ + '*ERROR*', + '$neverList', + ], + [ + '*ERROR*', + '$nestedNeverList', + ], + [ + '1', + '$foreachInt', + ], + [ + 'false', + '$foreachBool', + ], + [ + '*ERROR*', + '$foreachNever', + ], + [ + '*ERROR*', + '$foreachNestedNever', + ], + [ + '1', + '$foreachIntList', + ], + [ + 'false', + '$foreachBoolList', + ], + [ + '*ERROR*', + '$foreachNeverList', + ], + [ + '*ERROR*', + '$foreachNestedNeverList', + ], + [ + '1|4', + '$u1', + ], + [ + '2|\'bar\'', + '$u2', + ], + [ + '3', + '$u3', + ], + [ + '1|4', + '$foreachU1', + ], + [ + '2|\'bar\'', + '$foreachU2', + ], + [ + '3', + '$foreachU3', + ], + [ + 'string', + '$firstStringArray', + ], + [ + 'string', + '$secondStringArray', + ], + [ + 'string', + '$thirdStringArray', + ], + [ + 'string', + '$fourthStringArray', + ], + [ + 'string', + '$firstStringArrayList', + ], + [ + 'string', + '$secondStringArrayList', + ], + [ + 'string', + '$thirdStringArrayList', + ], + [ + 'string', + '$fourthStringArrayList', + ], + [ + 'string', + '$firstStringArrayForeach', + ], + [ + 'string', + '$secondStringArrayForeach', + ], + [ + 'string', + '$thirdStringArrayForeach', + ], + [ + 'string', + '$fourthStringArrayForeach', + ], + [ + 'string', + '$firstStringArrayForeachList', + ], + [ + 'string', + '$secondStringArrayForeachList', + ], + [ + 'string', + '$thirdStringArrayForeachList', + ], + [ + 'string', + '$fourthStringArrayForeachList', + ], + [ + 'string', + '$dateArray[\'Y\']', + ], + [ + 'string', + '$dateArray[\'m\']', + ], + [ + 'int', + '$dateArray[\'d\']', + ], + [ + 'string', + '$intArrayForRewritingFirstElement[0]', + ], + [ + 'int', + '$intArrayForRewritingFirstElement[1]', + ], + [ + 'stdClass', + '$obj', + ], + [ + 'stdClass', + '$newArray[\'newKey\']', + ], + [ + 'true', + '$assocKey', + ], + [ + '\'foo\'', + '$assocFoo', + ], + [ + '1', + '$assocOne', + ], + [ + '*ERROR*', + '$assocNonExistent', + ], + [ + 'true', + '$dynamicAssocKey', + ], + [ + '\'123\'|true', + '$dynamicAssocStrings', + ], + [ + '1|\'123\'|\'foo\'|true', + '$dynamicAssocMixed', + ], + [ + 'true', + '$dynamicAssocKeyForeach', + ], + [ + '\'123\'|true', + '$dynamicAssocStringsForeach', + ], + [ + '1|\'123\'|\'foo\'|true', + '$dynamicAssocMixedForeach', + ], + [ + 'string', + '$stringFromIterable', + ], + [ + 'string', + '$stringWithVarAnnotation', + ], + [ + 'string', + '$stringWithVarAnnotationInForeach', + ], + ]; + } + + /** + * @dataProvider dataArrayDestructuring + * @param string $description + * @param string $expression + */ + public function testArrayDestructuring( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-destructuring.php', + $description, + $expression + ); + } + + public function dataParameterTypes(): array + { + return [ + [ + 'int', + '$integer', + ], + [ + 'bool', + '$boolean', + ], + [ + 'string', + '$string', + ], + [ + 'float', + '$float', + ], + [ + 'TypesNamespaceTypehints\Lorem', + '$loremObject', + ], + [ + 'mixed', + '$mixed', + ], + [ + 'array', + '$array', + ], + [ + 'bool|null', + '$isNullable', + ], + [ + 'TypesNamespaceTypehints\Lorem', + '$loremObjectRef', + ], + [ + 'TypesNamespaceTypehints\Bar', + '$barObject', + ], + [ + 'TypesNamespaceTypehints\Foo', + '$fooObject', + ], + [ + 'TypesNamespaceTypehints\Bar', + '$anotherBarObject', + ], + [ + 'callable(): mixed', + '$callable', + ], + [ + 'array', + '$variadicStrings', + ], + [ + 'string', + '$variadicStrings[0]', + ], + ]; + } + + /** + * @dataProvider dataParameterTypes + * @param string $typeClass + * @param string $expression + */ + public function testTypehints( + string $typeClass, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/typehints.php', + $typeClass, + $expression + ); + } + + public function dataAnonymousFunctionParameterTypes(): array + { + return [ + [ + 'int', + '$integer', + ], + [ + 'bool', + '$boolean', + ], + [ + 'string', + '$string', + ], + [ + 'float', + '$float', + ], + [ + 'TypesNamespaceTypehints\Lorem', + '$loremObject', + ], + [ + 'mixed', + '$mixed', + ], + [ + 'array', + '$array', + ], + [ + 'bool|null', + '$isNullable', + ], + [ + 'callable(): mixed', + '$callable', + ], + [ + 'TypesNamespaceTypehints\FooWithAnonymousFunction', + '$self', + ], + ]; + } + + /** + * @dataProvider dataAnonymousFunctionParameterTypes + * @param string $description + * @param string $expression + */ + public function testAnonymousFunctionTypehints( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/typehints-anonymous-function.php', + $description, + $expression + ); + } + + public function dataVarAnnotations(): array + { + return [ + [ + 'int', + '$integer', + ], + [ + 'bool', + '$boolean', + ], + [ + 'string', + '$string', + ], + [ + 'float', + '$float', + ], + [ + 'VarAnnotations\Lorem', + '$loremObject', + ], + [ + 'AnotherNamespace\Bar', + '$barObject', + ], + [ + 'mixed', + '$mixed', + ], + [ + 'array', + '$array', + ], + [ + 'bool|null', + '$isNullable', + ], + [ + 'callable(): mixed', + '$callable', + ], + [ + 'callable(int, ...string): void', + '$callableWithTypes', + ], + [ + 'Closure(int, ...string): void', + '$closureWithTypes', + ], + [ + 'VarAnnotations\Foo', + '$self', + ], + [ + 'float', + '$invalidInteger', + ], + [ + 'static(VarAnnotations\Foo)', + '$static', + ], + ]; + } + + /** + * @dataProvider dataVarAnnotations + * @param string $description + * @param string $expression + */ + public function testVarAnnotations( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/var-annotations.php', + $description, + $expression, + [], + [], + [], + [], + 'die', + [], + false + ); + } + + public function dataCasts(): array + { + return [ + [ + 'int', + '$castedInteger', + ], + [ + 'bool', + '$castedBoolean', + ], + [ + 'float', + '$castedFloat', + ], + [ + 'string', + '$castedString', + ], + [ + 'array', + '$castedArray', + ], + [ + 'stdClass', + '$castedObject', + ], + [ + 'TypesNamespaceCasts\Foo', + '$castedFoo', + ], + [ + 'stdClass|TypesNamespaceCasts\Foo', + '$castedArrayOrObject', + ], + [ + '0|1', + '(int) $bool', + ], + [ + '0.0|1.0', + '(float) $bool', + ], + [ + '*ERROR*', + '(int) $foo', + ], + [ + 'true', + '(bool) $foo', + ], + [ + '1', + '(int) true', + ], + [ + '0', + '(int) false', + ], + [ + '5', + '(int) 5.25', + ], + [ + '5.0', + '(float) 5', + ], + [ + '5', + '(int) "5"', + ], + [ + '5.0', + '(float) "5"', + ], + [ + '*ERROR*', + '(int) "blabla"', + ], + [ + '*ERROR*', + '(float) "blabla"', + ], + [ + '0', + '(int) null', + ], + [ + '0.0', + '(float) null', + ], + [ + 'int', + '(int) $str', + ], + [ + 'float', + '(float) $str', + ], + [ + 'array(\'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'foo\' => TypesNamespaceCasts\Foo, \'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'int\' => int, \'\' . "\0" . \'*\' . "\0" . \'protectedInt\' => int, \'publicInt\' => int, \'\' . "\0" . \'TypesNamespaceCasts\\\\Bar\' . "\0" . \'barProperty\' => TypesNamespaceCasts\Bar)', + '(array) $foo', + ], + [ + 'array(1, 2, 3)', + '(array) [1, 2, 3]', + ], + [ + 'array(1)', + '(array) 1', + ], + [ + 'array(1.0)', + '(array) 1.0', + ], + [ + 'array(true)', + '(array) true', + ], + [ + 'array(\'blabla\')', + '(array) "blabla"', + ], + [ + 'array(int)', + '(array) $castedInteger', + ], + [ + 'array', + '(array) $iterable', + ], + [ + 'array', + '(array) new stdClass()', + ], + ]; + } + + /** + * @dataProvider dataCasts + * @param string $desciptiion + * @param string $expression + */ + public function testCasts( + string $desciptiion, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/casts.php', + $desciptiion, + $expression + ); + } + + public function dataUnsetCast(): array + { + return [ + [ + 'null', + '$castedNull', + ], + ]; + } + + /** + * @dataProvider dataUnsetCast + * @param string $desciptiion + * @param string $expression + */ + public function testUnsetCast( + string $desciptiion, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70200) { + $this->markTestSkipped( + 'Test cannot be run on PHP 7.2 and higher - (unset) cast is deprecated.' + ); + } + $this->assertTypes( + __DIR__ . '/data/cast-unset.php', + $desciptiion, + $expression + ); + } + + public function dataDeductedTypes(): array + { + return [ + [ + '1', + '$integerLiteral', + ], + [ + 'true', + '$booleanLiteral', + ], + [ + 'false', + '$anotherBooleanLiteral', + ], + [ + '\'foo\'', + '$stringLiteral', + ], + [ + '1.0', + '$floatLiteral', + ], + [ + '1.0', + '$floatAssignedByRef', + ], + [ + 'null', + '$nullLiteral', + ], + [ + 'TypesNamespaceDeductedTypes\Lorem', + '$loremObjectLiteral', + ], + [ + 'mixed~string', + '$mixedObjectLiteral', + ], + [ + 'static(TypesNamespaceDeductedTypes\Foo)', + '$newStatic', + ], + [ + 'array()', + '$arrayLiteral', + ], + [ + 'string', + '$stringFromFunction', + ], + [ + 'TypesNamespaceFunctions\Foo', + '$fooObjectFromFunction', + ], + [ + 'mixed', + '$mixedFromFunction', + ], + [ + '1', + '\TypesNamespaceDeductedTypes\Foo::INTEGER_CONSTANT', + ], + [ + '1', + 'self::INTEGER_CONSTANT', + ], + [ + '1.0', + 'self::FLOAT_CONSTANT', + ], + [ + '\'foo\'', + 'self::STRING_CONSTANT', + ], + [ + 'array()', + 'self::ARRAY_CONSTANT', + ], + [ + 'true', + 'self::BOOLEAN_CONSTANT', + ], + [ + 'null', + 'self::NULL_CONSTANT', + ], + [ + '1', + '$foo::INTEGER_CONSTANT', + ], + [ + '1.0', + '$foo::FLOAT_CONSTANT', + ], + [ + '\'foo\'', + '$foo::STRING_CONSTANT', + ], + [ + 'array()', + '$foo::ARRAY_CONSTANT', + ], + [ + 'true', + '$foo::BOOLEAN_CONSTANT', + ], + [ + 'null', + '$foo::NULL_CONSTANT', + ], + ]; + } + + /** + * @dataProvider dataDeductedTypes + * @param string $description + * @param string $expression + */ + public function testDeductedTypes( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/function-definitions.php'; + $this->assertTypes( + __DIR__ . '/data/deducted-types.php', + $description, + $expression + ); + } + + public function dataProperties(): array + { + return [ + [ + 'mixed', + '$this->mixedProperty', + ], + [ + 'mixed', + '$this->anotherMixedProperty', + ], + [ + 'mixed', + '$this->yetAnotherMixedProperty', + ], + [ + 'int', + '$this->integerProperty', + ], + [ + 'int', + '$this->anotherIntegerProperty', + ], + [ + 'array', + '$this->arrayPropertyOne', + ], + [ + 'array', + '$this->arrayPropertyOther', + ], + [ + 'PropertiesNamespace\\Lorem', + '$this->objectRelative', + ], + [ + 'SomeOtherNamespace\\Ipsum', + '$this->objectFullyQualified', + ], + [ + 'SomeNamespace\\Amet', + '$this->objectUsed', + ], + [ + '*ERROR*', + '$this->nonexistentProperty', + ], + [ + 'int|null', + '$this->nullableInteger', + ], + [ + 'SomeNamespace\Amet|null', + '$this->nullableObject', + ], + [ + 'PropertiesNamespace\\Foo', + '$this->selfType', + ], + [ + 'static(PropertiesNamespace\Foo)', + '$this->staticType', + ], + [ + 'null', + '$this->nullType', + ], + [ + 'SomeNamespace\Sit', + '$this->inheritedProperty', + ], + [ + 'PropertiesNamespace\Bar', + '$this->barObject->doBar()', + ], + [ + 'mixed', + '$this->invalidTypeProperty', + ], + [ + 'resource', + '$this->resource', + ], + [ + 'array', + '$this->yetAnotherAnotherMixedParameter', + ], + [ + 'mixed', + '$this->yetAnotherAnotherAnotherMixedParameter', + ], + [ + 'string', + 'self::$staticStringProperty', + ], + [ + 'SomeGroupNamespace\One', + '$this->groupUseProperty', + ], + [ + 'SomeGroupNamespace\Two', + '$this->anotherGroupUseProperty', + ], + [ + 'PropertiesNamespace\Bar', + '$this->inheritDocProperty', + ], + [ + 'PropertiesNamespace\Bar', + '$this->inheritDocWithoutCurlyBracesProperty', + ], + [ + 'PropertiesNamespace\Bar', + '$this->implicitInheritDocProperty', + ], + [ + 'int', + '$this->readOnlyProperty', + ], + [ + 'string', + '$this->overriddenReadOnlyProperty', + ], + [ + 'string', + '$this->documentElement', + ], + ]; + } + + /** + * @dataProvider dataProperties + * @param string $description + * @param string $expression + */ + public function testProperties( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/properties.php', + $description, + $expression + ); + } + + public function dataBinaryOperations(): array + { + $typeCallback = static function ($value): string { + if (is_int($value)) { + return (new ConstantIntegerType($value))->describe(VerbosityLevel::precise()); + } elseif (is_float($value)) { + return (new ConstantFloatType($value))->describe(VerbosityLevel::precise()); + } elseif (is_bool($value)) { + return (new ConstantBooleanType($value))->describe(VerbosityLevel::precise()); + } elseif (is_string($value)) { + return (new ConstantStringType($value))->describe(VerbosityLevel::precise()); + } + + throw new \PHPStan\ShouldNotHappenException(); + }; + + return [ + [ + 'false', + 'true && false', + ], + [ + 'true', + 'true || false', + ], + [ + 'true', + 'true xor false', + ], + [ + 'true', + 'false xor true', + ], + [ + 'false', + 'true xor true', + ], + [ + 'false', + 'true xor true', + ], + [ + 'bool', + '$bool xor true', + ], + [ + 'bool', + '$bool xor false', + ], + [ + 'false', + 'true and false', + ], + [ + 'true', + 'true or false', + ], + [ + 'false', + '!true', + ], + [ + $typeCallback(-1), + '-1', + ], + [ + $typeCallback(+1), + '+1', + ], + [ + '*ERROR*', + '+"blabla"', + ], + [ + '123.2', + '+"123.2"', + ], + [ + '*ERROR*', + '-"blabla"', + ], + [ + '-5', + '-5', + ], + [ + '5', + '-(-5)', + ], + [ + 'int', + '-$integer', + ], + [ + '-2|-1', + '-$conditionalInt', + ], + [ + '*ERROR*', + '-$string', + ], + // integer + integer + [ + $typeCallback(1 + 1), + '1 + 1', + ], + [ + $typeCallback(1 - 1), + '1 - 1', + ], + [ + $typeCallback(1 / 2), + '1 / 2', + ], + [ + $typeCallback(1 * 1), + '1 * 1', + ], + [ + $typeCallback(1 ** 1), + '1 ** 1', + ], + [ + $typeCallback(1 % 1), + '1 % 1', + ], + [ + '(float|int)', + '$integer /= 2', + ], + [ + 'int', + '$integer *= 1', + ], + // float + float + [ + $typeCallback(1.2 + 1.4), + '1.2 + 1.4', + ], + [ + $typeCallback(1.2 - 1.4), + '1.2 - 1.4', + ], + [ + $typeCallback(1.2 / 2.4), + '1.2 / 2.4', + ], + [ + $typeCallback(1.2 * 1.4), + '1.2 * 1.4', + ], + [ + $typeCallback(1.2 ** 1.4), + '1.2 ** 1.4', + ], + [ + $typeCallback(3.2 % 2.4), + '3.2 % 2.4', + ], + [ + 'float', + '$float /= 2.4', + ], + [ + 'float', + '$float *= 2.4', + ], + // integer + float + [ + $typeCallback(1 + 1.4), + '1 + 1.4', + ], + [ + $typeCallback(1 - 1.4), + '1 - 1.4', + ], + [ + $typeCallback(1 / 2.4), + '1 / 2.4', + ], + [ + $typeCallback(1 * 1.4), + '1 * 1.4', + ], + [ + $typeCallback(1 ** 1.4), + '1 ** 1.4', + ], + [ + $typeCallback(3 % 2.4), + '3 % 2.4', + ], + [ + 'float', + '$integer /= 2.4', + ], + [ + 'float', + '$integer *= 2.4', + ], + [ + 'int', + '$otherInteger + 1', + ], + [ + 'float', + '$otherInteger + 1.0', + ], + // float + integer + [ + $typeCallback(1.2 + 1), + '1.2 + 1', + ], + [ + $typeCallback(1.2 - 1), + '1.2 - 1', + ], + [ + $typeCallback(1.2 / 2), + '1.2 / 2', + ], + [ + $typeCallback(1.2 * 1), + '1.2 * 1', + ], + [ + 'int', + '$integer * 10', + ], + [ + $typeCallback(1.2 ** 1), + '1.2 ** 1', + ], + [ + '(float|int)', + '$integer ** $integer', + ], + [ + $typeCallback(3.2 % 2), + '3.2 % 2', + ], + [ + 'int', + '$float %= 2.4', + ], + [ + 'float', + '$float **= 2.4', + ], + [ + 'float', + '$float /= 2.4', + ], + [ + 'float', + '$float *= 2', + ], + // boolean + [ + '1', + 'true + false', + ], + // string + [ + "'ab'", + "'a' . 'b'", + ], + [ + $typeCallback(1 . 'b'), + "1 . 'b'", + ], + [ + $typeCallback(1.0 . 'b'), + "1.0 . 'b'", + ], + [ + $typeCallback(1.0 . 2.0), + '1.0 . 2.0', + ], + [ + $typeCallback('foo' <=> 'bar'), + "'foo' <=> 'bar'", + ], + [ + '(float|int)', + '1 + $mixed', + ], + [ + 'float|int', + '1 + $number', + ], + [ + 'float|int', + '$integer + $number', + ], + [ + 'float', + '$float + $float', + ], + [ + 'float', + '$float + $number', + ], + [ + '(float|int)', + '1 / $mixed', + ], + [ + 'float|int', + '1 / $number', + ], + [ + 'float', + '1.0 / $mixed', + ], + [ + 'float', + '1.0 / $number', + ], + [ + '(float|int)', + '$mixed / 1', + ], + [ + 'float|int', + '$number / 1', + ], + [ + 'float', + '$mixed / 1.0', + ], + [ + 'float', + '$number / 1.0', + ], + [ + 'float', + '1.0 + $mixed', + ], + [ + 'float', + '1.0 + $number', + ], + [ + '(float|int)', + '$mixed + 1', + ], + [ + 'float|int', + '$number + 1', + ], + [ + 'float', + '$mixed + 1.0', + ], + [ + 'float', + '$number + 1.0', + ], + [ + '\'foo\'|null', + '$mixed ? "foo" : null', + ], + [ + '12', + '12 ?: null', + ], + [ + '1', + 'true ? 1 : 2', + ], + [ + '2', + 'false ? 1 : 2', + ], + [ + '12|string', + '$string ?: 12', + ], + [ + '12|string', + '$stringOrNull ?: 12', + ], + [ + '12|string', + '@$stringOrNull ?: 12', + ], + [ + 'int|int<1, max>', + '$integer ?: 12', + ], + [ + '\'foo\'', + "'foo' ?? null", // "else" never gets executed + ], + [ + 'string|null', + '$stringOrNull ?? null', + ], + [ + '\'bar\'|\'foo\'', + '$maybeDefinedVariable ?? \'bar\'', + ], + [ + 'string', + '$string ?? \'foo\'', + ], + [ + 'string', + '$stringOrNull ?? \'foo\'', + ], + [ + 'string', + '$string ?? $integer', + ], + [ + 'int|string', + '$stringOrNull ?? $integer', + ], + [ + '\'Foo\'', + '\Foo::class', + ], + [ + '74', + '$line', + ], + [ + (new ConstantStringType(__DIR__ . '/data'))->describe(VerbosityLevel::precise()), + '$dir', + ], + [ + (new ConstantStringType(__DIR__ . '/data/binary.php'))->describe(VerbosityLevel::precise()), + '$file', + ], + [ + '\'BinaryOperations\\\\NestedNamespace\'', + '$namespace', + ], + [ + '\'BinaryOperations\\\\NestedNamespace\\\\Foo\'', + '$class', + ], + [ + '\'BinaryOperations\\\\NestedNamespace\\\\Foo::doFoo\'', + '$method', + ], + [ + '\'doFoo\'', + '$function', + ], + [ + '1', + 'min([1, 2, 3])', + ], + [ + 'array(1, 2, 3)', + 'min([1, 2, 3], [4, 5, 5])', + ], + [ + '1', + 'min(...[1, 2, 3])', + ], + [ + '1', + 'min(...[2, 3, 4], ...[5, 1, 8])', + ], + [ + '0', + 'min(0, ...[1, 2, 3])', + ], + [ + 'array(5, 6, 9)', + 'max([1, 10, 8], [5, 6, 9])', + ], + [ + 'array(1, 1, 1, 1)', + 'max(array(2, 2, 2), array(1, 1, 1, 1))', + ], + [ + 'array', + 'max($arrayOfUnknownIntegers, $arrayOfUnknownIntegers)', + ], + /*[ + 'array(1, 1, 1, 1)', + 'max(array(2, 2, 2), 5, array(1, 1, 1, 1))', + ], + [ + 'array', + 'max($arrayOfUnknownIntegers, $integer, $arrayOfUnknownIntegers)', + ],*/ + [ + '1.1', + 'min(...[1.1, 2.2, 3.3])', + ], + [ + '1.1', + 'min(...[1.1, 2, 3])', + ], + [ + '3', + 'max(...[1, 2, 3])', + ], + [ + '3.3', + 'max(...[1.1, 2.2, 3.3])', + ], + [ + '1', + 'min(1, 2, 3)', + ], + [ + '3', + 'max(1, 2, 3)', + ], + [ + '1.1', + 'min(1.1, 2.2, 3.3)', + ], + [ + '3.3', + 'max(1.1, 2.2, 3.3)', + ], + [ + '1', + 'min(1, 1)', + ], + [ + '*ERROR*', + 'min(1)', + ], + [ + 'int|string', + 'min($integer, $string)', + ], + [ + 'int|string', + 'min([$integer, $string])', + ], + [ + 'int|string', + 'min(...[$integer, $string])', + ], + [ + '\'a\'', + 'min(\'a\', \'b\')', + ], + [ + 'DateTimeImmutable', + 'max(new \DateTimeImmutable("today"), new \DateTimeImmutable("tomorrow"))', + ], + [ + '1', + 'min(1, 2.2, 3.3)', + ], + [ + 'string', + '"Hello $world"', + ], + [ + 'string', + '$string .= "str"', + ], + [ + 'int', + '$integer <<= 2.2', + ], + [ + 'int', + '$float >>= 2.2', + ], + [ + '3', + 'count($arrayOfIntegers)', + ], + [ + 'int<0, max>', + 'count($arrayOfIntegers, \COUNT_RECURSIVE)', + ], + [ + '3', + 'count($arrayOfIntegers, 5)', + ], + [ + '6', + 'count($arrayOfIntegers) + count($arrayOfIntegers)', + ], + [ + 'bool', + '$string === "foo"', + ], + [ + 'true', + '$fooString === "foo"', + ], + [ + 'bool', + '$string !== "foo"', + ], + [ + 'false', + '$fooString !== "foo"', + ], + [ + 'bool', + '$string == "foo"', + ], + [ + 'bool', + '$string != "foo"', + ], + [ + 'true', + '$foo instanceof \BinaryOperations\NestedNamespace\Foo', + ], + [ + 'bool', + '$foo instanceof Bar', + ], + [ + 'true', + 'isset($foo)', + ], + [ + 'true', + 'isset($foo, $one)', + ], + [ + 'false', + 'isset($null)', + ], + [ + 'false', + 'isset($undefinedVariable)', + ], + [ + 'false', + 'isset($foo, $undefinedVariable)', + ], + [ + 'bool', + 'isset($stringOrNull)', + ], + [ + 'false', + 'isset($stringOrNull, $null)', + ], + [ + 'false', + 'isset($stringOrNull, $undefinedVariable)', + ], + [ + 'bool', + 'isset($foo, $stringOrNull)', + ], + [ + 'bool', + 'isset($foo, $stringOrNull)', + ], + [ + 'true', + 'isset($array[\'0\'])', + ], + [ + 'bool', + 'isset($array[$integer])', + ], + [ + 'false', + 'isset($array[$integer], $array[1000])', + ], + [ + 'false', + 'isset($array[$integer], $null)', + ], + [ + 'bool', + 'isset($array[\'0\'], $array[$integer])', + ], + [ + 'bool', + 'isset($foo, $array[$integer])', + ], + [ + 'false', + 'isset($foo, $array[1000])', + ], + [ + 'false', + 'isset($foo, $array[1000])', + ], + [ + 'false', + '!isset($foo)', + ], + [ + 'bool', + 'empty($foo)', + ], + [ + 'bool', + '!empty($foo)', + ], + [ + 'array(int, int, int)', + '$arrayOfIntegers + $arrayOfIntegers', + ], + [ + 'array(int, int, int)', + '$arrayOfIntegers += $arrayOfIntegers', + ], + [ + 'array(0 => 1, 1 => 1, 2 => 1, 3 => 1|2, 4 => 1|3, ?5 => 2|3, ?6 => 3)', + '$conditionalArray + $unshiftedConditionalArray', + ], + [ + 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', + '$unshiftedConditionalArray + $conditionalArray', + ], + [ + 'array(int, int, int)', + '$arrayOfIntegers += ["foo"]', + ], + [ + '*ERROR*', + '$arrayOfIntegers += "foo"', + ], + [ + '3', + '@count($arrayOfIntegers)', + ], + [ + 'array(int, int, int)', + '$anotherArray = $arrayOfIntegers', + ], + [ + 'string|null', + 'var_export()', + ], + [ + 'null', + 'var_export($string)', + ], + [ + 'null', + 'var_export($string, false)', + ], + [ + 'string', + 'var_export($string, true)', + ], + [ + 'bool|string', + 'highlight_string()', + ], + [ + 'bool', + 'highlight_string($string)', + ], + [ + 'bool', + 'highlight_string($string, false)', + ], + [ + 'string', + 'highlight_string($string, true)', + ], + [ + 'bool|string', + 'highlight_file()', + ], + [ + 'bool', + 'highlight_file($string)', + ], + [ + 'bool', + 'highlight_file($string, false)', + ], + [ + 'string', + 'highlight_file($string, true)', + ], + [ + 'string|true', + 'print_r()', + ], + [ + 'true', + 'print_r($string)', + ], + [ + 'true', + 'print_r($string, false)', + ], + [ + 'string', + 'print_r($string, true)', + ], + [ + '1', + '$one++', + ], + [ + '1', + '$one--', + ], + [ + '2', + '++$one', + ], + [ + '0', + '--$one', + ], + [ + '*ERROR*', + '$preIncArray[0]', + ], + [ + '1', + '$preIncArray[1]', + ], + [ + '2', + '$preIncArray[2]', + ], + [ + '*ERROR*', + '$preIncArray[3]', + ], + [ + 'array(1 => 1, 2 => 2)', + '$preIncArray', + ], + [ + 'array(0 => 1, 2 => 3)', + '$postIncArray', + ], + [ + 'array(0 => array(1 => array(2 => 3)), 4 => array(5 => array(6 => 7)))', + '$anotherPostIncArray', + ], + [ + '3', + 'count($array)', + ], + [ + 'int<0, max>', + 'count()', + ], + [ + 'int<0, max>', + 'count($appendingToArrayInBranches)', + ], + [ + '3|4|5', + 'count($conditionalArray)', + ], + [ + '2', + '$array[1]', + ], + [ + '(float|int)', + '$integer / $integer', + ], + [ + '(float|int)', + '$otherInteger / $integer', + ], + [ + '(array|float|int)', + '$mixed + $mixed', + ], + [ + '(float|int)', + '$mixed - $mixed', + ], + [ + '*ERROR*', + '$mixed + []', + ], + [ + '124', + '1 + "123"', + ], + [ + '124.2', + '1 + "123.2"', + ], + [ + '*ERROR*', + '1 + $string', + ], + [ + '*ERROR*', + '1 + "blabla"', + ], + [ + 'array(1, 2, 3)', + '[1, 2, 3] + [4, 5, 6]', + ], + [ + 'array', + '$arrayOfUnknownIntegers + [1, 2, 3]', + ], + [ + '(float|int)', + '$sumWithStaticConst', + ], + [ + '(float|int)', + '$severalSumWithStaticConst1', + ], + [ + '(float|int)', + '$severalSumWithStaticConst2', + ], + [ + '(float|int)', + '$severalSumWithStaticConst3', + ], + [ + '1', + '5 & 3', + ], + [ + 'int', + '$integer & 3', + ], + [ + '\'x\'', + '"x" & "y"', + ], + [ + 'string', + '$string & "x"', + ], + [ + '*ERROR*', + '"bla" & 3', + ], + [ + '1', + '"5" & 3', + ], + [ + '7', + '5 | 3', + ], + [ + 'int', + '$integer | 3', + ], + [ + '\'y\'', + '"x" | "y"', + ], + [ + 'string', + '$string | "x"', + ], + [ + '*ERROR*', + '"bla" | 3', + ], + [ + '7', + '"5" | 3', + ], + [ + '6', + '5 ^ 3', + ], + [ + 'int', + '$integer ^ 3', + ], + [ + '\'' . "\x01" . '\'', + '"x" ^ "y"', + ], + [ + 'string', + '$string ^ "x"', + ], + [ + '*ERROR*', + '"bla" ^ 3', + ], + [ + '6', + '"5" ^ 3', + ], + [ + 'int', + '$integer &= 3', + ], + [ + '*ERROR*', + '$string &= 3', + ], + [ + 'string', + '$string &= "x"', + ], + [ + 'int', + '$integer |= 3', + ], + [ + '*ERROR*', + '$string |= 3', + ], + [ + 'string', + '$string |= "x"', + ], + [ + 'int', + '$integer ^= 3', + ], + [ + '*ERROR*', + '$string ^= 3', + ], + [ + 'string', + '$string ^= "x"', + ], + [ + '\'f\'', + '$fooString[0]', + ], + [ + '*ERROR*', + '$fooString[4]', + ], + [ + 'string', + '$fooString[$integer]', + ], + [ + '\'foo bar\'', + '$foobarString', + ], + [ + '\'foo bar\'', + '"$fooString bar"', + ], + [ + '*ERROR*', + '"$std bar"', + ], + [ + 'array<\'foo\'|int|stdClass>&nonEmpty', + '$arrToPush', + ], + [ + 'array<\'foo\'|int|stdClass>&nonEmpty', + '$arrToPush2', + ], + [ + 'array(0 => \'lorem\', 1 => 5, \'foo\' => stdClass, 2 => \'test\')', + '$arrToUnshift', + ], + [ + 'array<\'lorem\'|int|stdClass>&nonEmpty', + '$arrToUnshift2', + ], + [ + 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', + '$unshiftedConditionalArray', + ], + [ + 'array(\'dirname\' => string, \'basename\' => string, \'filename\' => string, ?\'extension\' => string)', + 'pathinfo($string)', + ], + [ + 'string', + 'pathinfo($string, PATHINFO_DIRNAME)', + ], + [ + 'string', + '$string++', + ], + [ + 'string', + '$string--', + ], + [ + 'string', + '++$string', + ], + [ + 'string', + '--$string', + ], + [ + 'string', + '$incrementedString', + ], + [ + 'string', + '$decrementedString', + ], + [ + '\'foo\'', + '$fooString++', + ], + [ + '\'foo\'', + '$fooString--', + ], + [ + '\'fop\'', + '++$fooString', + ], + [ + '\'foo\'', + '--$fooString', + ], + [ + '\'fop\'', + '$incrementedFooString', + ], + [ + '\'foo\'', + '$decrementedFooString', + ], + [ + 'string', + '$conditionalString . $conditionalString', + ], + [ + 'string', + '$conditionalString . $anotherConditionalString', + ], + [ + 'string', + '$anotherConditionalString . $conditionalString', + ], + [ + '6|7|8', + 'count($conditionalArray) + count($array)', + ], + [ + 'bool', + 'is_numeric($string)', + ], + [ + 'false', + 'is_numeric($fooString)', + ], + [ + 'bool', + 'is_int($mixed)', + ], + [ + 'true', + 'is_int($integer)', + ], + [ + 'false', + 'is_int($string)', + ], + [ + 'bool', + 'in_array(\'foo\', [\'foo\', \'bar\'])', + ], + [ + 'true', + 'in_array(\'foo\', [\'foo\', \'bar\'], true)', + ], + [ + 'false', + 'in_array(\'baz\', [\'foo\', \'bar\'], true)', + ], + [ + 'array(2, 3)', + '$arrToShift', + ], + [ + 'array(1, 2)', + '$arrToPop', + ], + [ + 'class-string', + 'static::class', + ], + [ + '\'NonexistentClass\'', + 'NonexistentClass::class', + ], + [ + 'class-string', + 'parent::class', + ], + [ + 'true', + 'array_key_exists(0, $array)', + ], + [ + 'false', + 'array_key_exists(3, $array)', + ], + [ + 'bool', + 'array_key_exists(3, $conditionalArray)', + ], + [ + 'bool', + 'array_key_exists(\'foo\', $generalArray)', + ], + [ + PHP_VERSION_ID < 80000 ? 'resource' : 'CurlHandle', + 'curl_init()', + ], + [ + PHP_VERSION_ID < 80000 ? 'resource|false' : 'CurlHandle|false', + 'curl_init($string)', + ], + [ + 'string', + 'sprintf($string, $string, 1)', + ], + [ + '\'foo bar\'', + "sprintf('%s %s', 'foo', 'bar')", + ], + [ + 'array()|array(0 => \'password\'|\'username\', ?1 => \'password\')', + '$coalesceArray', + ], + [ + 'array', + '$arrayToBeUnset', + ], + [ + 'array', + '$arrayToBeUnset2', + ], + [ + 'array', + '$shiftedNonEmptyArray', + ], + [ + 'array&nonEmpty', + '$unshiftedArray', + ], + [ + 'array', + '$poppedNonEmptyArray', + ], + [ + 'array&nonEmpty', + '$pushedArray', + ], + [ + 'string|false', + '$simpleXMLReturningXML', + ], + [ + 'string', + '$xmlString', + ], + [ + 'bool', + '$simpleXMLWritingXML', + ], + [ + 'array', + '$simpleXMLRightXpath', + ], + [ + 'array|false', + '$simpleXMLWrongXpath', + ], + [ + 'array|false', + '$simpleXMLUnknownXpath', + ], + [ + 'array|false', + '$namespacedXpath', + ], + ]; + } + + /** + * @dataProvider dataBinaryOperations + * @param string $description + * @param string $expression + */ + public function testBinaryOperations( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/binary.php', + $description, + $expression + ); + } + + public function dataVarStatementAnnotation(): array + { + return [ + [ + 'VarStatementAnnotation\Foo', + '$object', + ], + ]; + } + + /** + * @dataProvider dataVarStatementAnnotation + * @param string $description + * @param string $expression + */ + public function testVarStatementAnnotation( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/var-stmt-annotation.php', + $description, + $expression + ); + } + + public function dataCloneOperators(): array + { + return [ + [ + 'CloneOperators\Foo', + 'clone $fooObject', + ], + ]; + } + + /** + * @dataProvider dataCloneOperators + * @param string $description + * @param string $expression + */ + public function testCloneOperators( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/clone.php', + $description, + $expression + ); + } + + public function dataLiteralArrays(): array + { + return [ + [ + '0', + '$integers[0]', + ], + [ + '1', + '$integers[1]', + ], + [ + '\'foo\'', + '$strings[0]', + ], + [ + '*ERROR*', + '$emptyArray[0]', + ], + [ + '0', + '$mixedArray[0]', + ], + [ + 'true', + '$integers[0] >= $integers[1] - 1', + ], + [ + 'array(\'foo\' => array(\'foo\' => array(\'foo\' => \'bar\')), \'bar\' => array(), \'baz\' => array(\'lorem\' => array()))', + '$nestedArray', + ], + [ + '0', + '$integers[\'0\']', + ], + ]; + } + + /** + * @dataProvider dataLiteralArrays + * @param string $description + * @param string $expression + */ + public function testLiteralArrays( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/literal-arrays.php', + $description, + $expression + ); + } + + public function dataLiteralArraysKeys(): array + { + define('STRING_ONE', '1'); + define('INT_ONE', 1); + define('STRING_FOO', 'foo'); + + return [ + [ + '0|1|2', + "'NoKeysArray'", + ], + [ + '0|1|2', + "'IntegersAndNoKeysArray'", + ], + [ + '0|1|\'foo\'', + "'StringsAndNoKeysArray'", + ], + [ + '1|2|3', + "'IntegersAsStringsAndNoKeysArray'", + ], + [ + '1|2', + "'IntegersAsStringsArray'", + ], + [ + '1|2', + "'IntegersArray'", + ], + [ + '1|2|3', + "'IntegersWithFloatsArray'", + ], + [ + '\'bar\'|\'foo\'', + "'StringsArray'", + ], + [ + '\'\'|\'bar\'|\'baz\'', + "'StringsWithNullArray'", + ], + [ + '1|2|string', + "'IntegersWithStringFromMethodArray'", + ], + [ + '1|2|\'foo\'', + "'IntegersAndStringsArray'", + ], + [ + '0|1', + "'BooleansArray'", + ], + [ + 'int|string', + "'UnknownConstantArray'", + ], + ]; + } + + /** + * @dataProvider dataLiteralArraysKeys + * @param string $description + * @param string $evaluatedPointExpressionType + */ + public function testLiteralArraysKeys( + string $description, + string $evaluatedPointExpressionType + ): void { + $this->assertTypes( + __DIR__ . '/data/literal-arrays-keys.php', + $description, + '$key', + [], + [], + [], + [], + $evaluatedPointExpressionType + ); + } + + public function dataStringArrayAccess(): array + { + return [ + [ + '*ERROR*', + '$stringFalse', + ], + [ + '*ERROR*', + '$stringObject', + ], + [ + '*ERROR*', + '$stringFloat', + ], + [ + '*ERROR*', + '$stringString', + ], + [ + '*ERROR*', + '$stringArray', + ], + ]; + } + + /** + * @dataProvider dataStringArrayAccess + * @param string $description + * @param string $expression + */ + public function testStringArrayAccess( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/string-array-access.php', + $description, + $expression + ); + } + + public function dataTypeFromFunctionPhpDocs(): array + { + return [ + [ + 'mixed', + '$mixedParameter', + ], + [ + 'MethodPhpDocsNamespace\Bar|MethodPhpDocsNamespace\Foo', + '$unionTypeParameter', + ], + [ + 'int', + '$anotherMixedParameter', + ], + [ + 'mixed', + '$yetAnotherMixedParameter', + ], + [ + 'int', + '$integerParameter', + ], + [ + 'int', + '$anotherIntegerParameter', + ], + [ + 'array', + '$arrayParameterOne', + ], + [ + 'array', + '$arrayParameterOther', + ], + [ + 'MethodPhpDocsNamespace\\Lorem', + '$objectRelative', + ], + [ + 'SomeOtherNamespace\\Ipsum', + '$objectFullyQualified', + ], + [ + 'SomeNamespace\\Amet', + '$objectUsed', + ], + [ + '*ERROR*', + '$nonexistentParameter', + ], + [ + 'int|null', + '$nullableInteger', + ], + [ + 'SomeNamespace\Amet|null', + '$nullableObject', + ], + [ + 'SomeNamespace\Amet|null', + '$anotherNullableObject', + ], + [ + 'null', + '$nullType', + ], + [ + 'MethodPhpDocsNamespace\Bar', + '$barObject->doBar()', + ], + [ + 'MethodPhpDocsNamespace\Bar', + '$conflictedObject', + ], + [ + 'MethodPhpDocsNamespace\Baz', + '$moreSpecifiedObject', + ], + [ + 'MethodPhpDocsNamespace\Baz', + '$moreSpecifiedObject->doFluent()', + ], + [ + 'MethodPhpDocsNamespace\Baz|null', + '$moreSpecifiedObject->doFluentNullable()', + ], + [ + 'MethodPhpDocsNamespace\Baz', + '$moreSpecifiedObject->doFluentArray()[0]', + ], + [ + 'iterable&MethodPhpDocsNamespace\Collection', + '$moreSpecifiedObject->doFluentUnionIterable()', + ], + [ + 'MethodPhpDocsNamespace\Baz', + '$fluentUnionIterableBaz', + ], + [ + 'resource', + '$resource', + ], + [ + 'mixed', + '$yetAnotherAnotherMixedParameter', + ], + [ + 'mixed', + '$yetAnotherAnotherAnotherMixedParameter', + ], + [ + 'void', + '$voidParameter', + ], + [ + 'SomeNamespace\Consecteur', + '$useWithoutAlias', + ], + [ + 'true', + '$true', + ], + [ + 'false', + '$false', + ], + [ + 'true', + '$boolTrue', + ], + [ + 'false', + '$boolFalse', + ], + [ + 'bool', + '$trueBoolean', + ], + [ + 'bool', + '$parameterWithDefaultValueFalse', + ], + ]; + } + + public function dataTypeFromFunctionFunctionPhpDocs(): array + { + return [ + [ + 'MethodPhpDocsNamespace\Foo', + '$fooFunctionResult', + ], + [ + 'MethodPhpDocsNamespace\Bar', + '$barFunctionResult', + ], + ]; + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromFunctionFunctionPhpDocs + * @param string $description + * @param string $expression + */ + public function testTypeFromFunctionPhpDocs( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/functionPhpDocs.php'; + $this->assertTypes( + __DIR__ . '/data/functionPhpDocs.php', + $description, + $expression + ); + } + + public function dataTypeFromFunctionPrefixedPhpDocs(): array + { + return [ + [ + 'MethodPhpDocsNamespace\Foo', + '$fooFunctionResult', + ], + ]; + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromFunctionPrefixedPhpDocs + * @param string $description + * @param string $expression + */ + public function testTypeFromFunctionPhpDocsPsalmPrefix( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/functionPhpDocs-psalmPrefix.php'; + $this->assertTypes( + __DIR__ . '/data/functionPhpDocs-psalmPrefix.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromFunctionPrefixedPhpDocs + * @param string $description + * @param string $expression + */ + public function testTypeFromFunctionPhpDocsPhpstanPrefix( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/functionPhpDocs-phpstanPrefix.php'; + $this->assertTypes( + __DIR__ . '/data/functionPhpDocs-phpstanPrefix.php', + $description, + $expression + ); + } + + public function dataTypeFromMethodPhpDocs(): array + { + return [ + [ + 'MethodPhpDocsNamespace\\Foo', + '$selfType', + ], + [ + 'static(MethodPhpDocsNamespace\Foo)', + '$staticType', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->doFoo()', + ], + [ + 'MethodPhpDocsNamespace\Bar', + 'static::doSomethingStatic()', + ], + [ + 'static(MethodPhpDocsNamespace\Foo)', + 'parent::doLorem()', + ], + [ + 'MethodPhpDocsNamespace\FooParent', + '$parent->doLorem()', + false, + ], + [ + 'static(MethodPhpDocsNamespace\Foo)', + '$this->doLorem()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$differentInstance->doLorem()', + ], + [ + 'static(MethodPhpDocsNamespace\Foo)', + 'parent::doIpsum()', + ], + [ + 'MethodPhpDocsNamespace\FooParent', + '$parent->doIpsum()', + false, + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$differentInstance->doIpsum()', + ], + [ + 'static(MethodPhpDocsNamespace\Foo)', + '$this->doIpsum()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->doBar()[0]', + ], + [ + 'MethodPhpDocsNamespace\Bar', + 'self::doSomethingStatic()', + ], + [ + 'MethodPhpDocsNamespace\Bar', + '\MethodPhpDocsNamespace\Foo::doSomethingStatic()', + ], + [ + '$this(MethodPhpDocsNamespace\Foo)', + 'parent::doThis()', + ], + [ + '$this(MethodPhpDocsNamespace\Foo)|null', + 'parent::doThisNullable()', + ], + [ + '$this(MethodPhpDocsNamespace\Foo)|MethodPhpDocsNamespace\Bar|null', + 'parent::doThisUnion()', + ], + [ + 'MethodPhpDocsNamespace\FooParent', + '$this->returnParent()', + false, + ], + [ + 'MethodPhpDocsNamespace\FooParent', + '$this->returnPhpDocParent()', + false, + ], + [ + 'array', + '$this->returnNulls()', + ], + [ + 'object', + '$objectWithoutNativeTypehint', + ], + [ + 'object', + '$objectWithNativeTypehint', + ], + [ + 'object', + '$this->returnObject()', + ], + [ + 'MethodPhpDocsNamespace\FooParent', + 'new parent()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$inlineSelf', + ], + [ + 'MethodPhpDocsNamespace\Bar', + '$inlineBar', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->phpDocVoidMethod()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->phpDocVoidMethodFromInterface()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->phpDocVoidParentMethod()', + ], + [ + 'MethodPhpDocsNamespace\Foo', + '$this->phpDocWithoutCurlyBracesVoidParentMethod()', + ], + [ + 'array', + '$this->returnsStringArray()', + ], + [ + 'mixed', + '$this->privateMethodWithPhpDoc()', + ], + ]; + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + */ + public function testTypeFromMethodPhpDocs( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromMethodPhpDocsPsalmPrefix( + string $description, + string $expression, + bool $replaceClass = true + ): void { + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPsalmPrefix)', $description); + + if ($replaceClass && $expression !== '$this->doFoo()') { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPsalmPrefix)', $description); + if ($description === 'MethodPhpDocsNamespace\Foo') { + $description = 'MethodPhpDocsNamespace\FooPsalmPrefix'; + } + } + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-psalmPrefix.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass = true + */ + public function testTypeFromMethodPhpDocsPhpstanPrefix( + string $description, + string $expression, + bool $replaceClass = true + ): void { + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPhpstanPrefix)', $description); + + if ($replaceClass && $expression !== '$this->doFoo()') { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPhpstanPrefix)', $description); + if ($description === 'MethodPhpDocsNamespace\Foo') { + $description = 'MethodPhpDocsNamespace\FooPhpstanPrefix'; + } + } + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-phpstanPrefix.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromTraitPhpDocs( + string $description, + string $expression, + bool $replaceClass = true + ): void { + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooWithTrait)', $description); + + if ($replaceClass && $expression !== '$this->doFoo()') { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooWithTrait)', $description); + if ($description === 'MethodPhpDocsNamespace\Foo') { + $description = 'MethodPhpDocsNamespace\FooWithTrait'; + } + } + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-trait.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromMethodPhpDocsInheritDocWithoutCurlyBraces( + string $description, + string $expression, + bool $replaceClass = true + ): void { + if ($replaceClass) { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooInheritDocChild)', $description); + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooInheritDocChild)', $description); + $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); + if ($expression === '$inlineSelf') { + $description = 'MethodPhpDocsNamespace\FooInheritDocChild'; + } + } + $this->assertTypes( + __DIR__ . '/data/method-phpDocs-inheritdoc-without-curly-braces.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromRecursiveTraitPhpDocs( + string $description, + string $expression, + bool $replaceClass = true + ): void { + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooWithRecursiveTrait)', $description); + + if ($replaceClass && $expression !== '$this->doFoo()') { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooWithRecursiveTrait)', $description); + if ($description === 'MethodPhpDocsNamespace\Foo') { + $description = 'MethodPhpDocsNamespace\FooWithRecursiveTrait'; + } + } + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-recursiveTrait.php', + $description, + $expression + ); + } + + public function dataTypeFromTraitPhpDocsInSameFile(): array + { + return [ + [ + 'string', + '$this->getFoo()', + ], + ]; + } + + /** + * @dataProvider dataTypeFromTraitPhpDocsInSameFile + * @param string $description + * @param string $expression + */ + public function testTypeFromTraitPhpDocsInSameFile( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-traitInSameFileAsClass.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromMethodPhpDocsInheritDoc( + string $description, + string $expression, + bool $replaceClass = true + ): void { + if ($replaceClass) { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooInheritDocChild)', $description); + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooInheritDocChild)', $description); + $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); + if ($expression === '$inlineSelf') { + $description = 'MethodPhpDocsNamespace\FooInheritDocChild'; + } + } + $this->assertTypes( + __DIR__ . '/data/method-phpDocs-inheritdoc.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataTypeFromFunctionPhpDocs + * @dataProvider dataTypeFromMethodPhpDocs + * @param string $description + * @param string $expression + * @param bool $replaceClass + */ + public function testTypeFromMethodPhpDocsImplicitInheritance( + string $description, + string $expression, + bool $replaceClass = true + ): void { + if ($replaceClass) { + $description = str_replace('$this(MethodPhpDocsNamespace\Foo)', '$this(MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild)', $description); + $description = str_replace('static(MethodPhpDocsNamespace\Foo)', 'static(MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild)', $description); + $description = str_replace('MethodPhpDocsNamespace\FooParent', 'MethodPhpDocsNamespace\Foo', $description); + if ($expression === '$inlineSelf') { + $description = 'MethodPhpDocsNamespace\FooPhpDocsImplicitInheritanceChild'; + } + } + $this->assertTypes( + __DIR__ . '/data/methodPhpDocs-implicitInheritance.php', + $description, + $expression + ); + } + + public function testNotSwitchInstanceof(): void + { + $this->assertTypes( + __DIR__ . '/data/switch-instanceof-not.php', + '*ERROR*', + '$foo' + ); + } + + public function dataSwitchInstanceOf(): array + { + return [ + [ + '*ERROR*', + '$foo', + ], + [ + '*ERROR*', + '$bar', + ], + [ + 'SwitchInstanceOf\Baz', + '$baz', + ], + ]; + } + + /** + * @dataProvider dataSwitchInstanceOf + * @param string $description + * @param string $expression + */ + public function testSwitchInstanceof( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/switch-instanceof.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataSwitchInstanceOf + * @param string $description + * @param string $expression + */ + public function testSwitchInstanceofTruthy( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/switch-instanceof-truthy.php', + $description, + $expression + ); + } + + public function dataSwitchGetClass(): array + { + return [ + [ + 'SwitchGetClass\Lorem', + '$lorem', + "'normalName'", + ], + [ + 'SwitchGetClass\Foo', + '$lorem', + "'selfReferentialName'", + ], + ]; + } + + /** + * @dataProvider dataSwitchGetClass + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testSwitchGetClass( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/switch-get-class.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataSwitchInstanceOfFallthrough(): array + { + return [ + [ + 'SwitchInstanceOfFallthrough\A|SwitchInstanceOfFallthrough\B', + '$object', + ], + ]; + } + + /** + * @dataProvider dataSwitchInstanceOfFallthrough + * @param string $description + * @param string $expression + */ + public function testSwitchInstanceOfFallthrough( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/switch-instanceof-fallthrough.php', + $description, + $expression + ); + } + + public function dataSwitchTypeElimination(): array + { + return [ + [ + 'string', + '$stringOrInt', + ], + ]; + } + + /** + * @dataProvider dataSwitchTypeElimination + * @param string $description + * @param string $expression + */ + public function testSwitchTypeElimination( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/switch-type-elimination.php', + $description, + $expression + ); + } + + public function dataDynamicMethodReturnTypeExtensions(): array + { + return [ + [ + '*ERROR*', + '$em->getByFoo($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Entity', + '$em->getByPrimary()', + ], + [ + 'DynamicMethodReturnTypesNamespace\Entity', + '$em->getByPrimary($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Foo', + '$em->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', + ], + [ + '*ERROR*', + '$iem->getByFoo($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Entity', + '$iem->getByPrimary()', + ], + [ + 'DynamicMethodReturnTypesNamespace\Entity', + '$iem->getByPrimary($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Foo', + '$iem->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', + ], + [ + '*ERROR*', + 'EntityManager::getByFoo($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\EntityManager', + '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity()', + ], + [ + 'DynamicMethodReturnTypesNamespace\EntityManager', + '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Foo', + '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', + ], + [ + '*ERROR*', + '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::getByFoo($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\EntityManager', + '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity()', + ], + [ + 'DynamicMethodReturnTypesNamespace\EntityManager', + '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity($foo)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Foo', + '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', + ], + [ + 'DynamicMethodReturnTypesNamespace\Foo', + '$container[\DynamicMethodReturnTypesNamespace\Foo::class]', + ], + [ + 'object', + 'new \DynamicMethodReturnTypesNamespace\Foo()', + ], + [ + 'object', + 'new \DynamicMethodReturnTypesNamespace\FooWithoutConstructor()', + ], + ]; + } + + /** + * @dataProvider dataDynamicMethodReturnTypeExtensions + * @param string $description + * @param string $expression + */ + public function testDynamicMethodReturnTypeExtensions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/dynamic-method-return-types.php', + $description, + $expression, + [ + new class() implements DynamicMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\EntityManager::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), ['getByPrimary'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $arg = $args[0]->value; + if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + if (!($arg->class instanceof \PhpParser\Node\Name)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType((string) $arg->class); + } + }, + new class() implements DynamicMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\ComponentContainer::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'offsetGet'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[0]->value); + if (!$argType instanceof ConstantStringType) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType($argType->getValue()); + } + }, + ], + [ + new class() implements DynamicStaticMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\EntityManager::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), ['createManagerForEntity'], true); + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $arg = $args[0]->value; + if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + if (!($arg->class instanceof \PhpParser\Node\Name)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType((string) $arg->class); + } + }, + new class() implements DynamicStaticMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\Foo::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + return new ObjectWithoutClassType(); + } + }, + new class() implements DynamicStaticMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\FooWithoutConstructor::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + return new ObjectWithoutClassType(); + } + }, + ] + ); + } + + public function dataDynamicReturnTypeExtensionsOnCompoundTypes(): array + { + return [ + [ + 'DynamicMethodReturnCompoundTypes\Collection', + '$collection->getSelf()', + ], + [ + 'DynamicMethodReturnCompoundTypes\Collection|DynamicMethodReturnCompoundTypes\Foo', + '$collectionOrFoo->getSelf()', + ], + ]; + } + + /** + * @dataProvider dataDynamicReturnTypeExtensionsOnCompoundTypes + * @param string $description + * @param string $expression + */ + public function testDynamicReturnTypeExtensionsOnCompoundTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/dynamic-method-return-compound-types.php', + $description, + $expression, + [ + new class() implements DynamicMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnCompoundTypes\Collection::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'getSelf'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new ObjectType(\DynamicMethodReturnCompoundTypes\Collection::class); + } + }, + new class() implements DynamicMethodReturnTypeExtension { + public function getClass(): string + { + return \DynamicMethodReturnCompoundTypes\Foo::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'getSelf'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new ObjectType(\DynamicMethodReturnCompoundTypes\Foo::class); + } + }, + ] + ); + } + + public function dataOverwritingVariable(): array + { + return [ + [ + 'mixed', + '$var', + 'new \OverwritingVariable\Bar()', + ], + [ + 'OverwritingVariable\Bar', + '$var', + '$var->methodFoo()', + ], + [ + 'OverwritingVariable\Foo', + '$var', + 'die', + ], + ]; + } + + /** + * @dataProvider dataOverwritingVariable + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpressionType + */ + public function testOverwritingVariable( + string $description, + string $expression, + string $evaluatedPointExpressionType + ): void { + $this->assertTypes( + __DIR__ . '/data/overwritingVariable.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpressionType + ); + } + + public function dataNegatedInstanceof(): array + { + return [ + [ + 'NegatedInstanceOf\Foo', + '$foo', + ], + [ + 'NegatedInstanceOf\Bar', + '$bar', + ], + [ + 'mixed', + '$lorem', + ], + [ + 'mixed~NegatedInstanceOf\Dolor', + '$dolor', + ], + [ + 'mixed~NegatedInstanceOf\Sit', + '$sit', + ], + [ + 'mixed', + '$mixedFoo', + ], + [ + 'mixed', + '$mixedBar', + ], + [ + 'NegatedInstanceOf\Foo', + '$self', + ], + [ + 'static(NegatedInstanceOf\Foo)', + '$static', + ], + [ + 'NegatedInstanceOf\Foo', + '$anotherFoo', + ], + [ + 'NegatedInstanceOf\Bar&NegatedInstanceOf\Foo', + '$fooAndBar', + ], + ]; + } + + /** + * @dataProvider dataNegatedInstanceof + * @param string $description + * @param string $expression + */ + public function testNegatedInstanceof( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/negated-instanceof.php', + $description, + $expression + ); + } + + public function dataAnonymousFunction(): array + { + return [ + [ + 'string', + '$str', + ], + [ + 'array', + '$arr', + ], + [ + '1', + '$integer', + ], + [ + '*ERROR*', + '$bar', + ], + ]; + } + + /** + * @dataProvider dataAnonymousFunction + * @param string $description + * @param string $expression + */ + public function testAnonymousFunction( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/anonymous-function.php', + $description, + $expression + ); + } + + public function dataForeachArrayType(): array + { + return [ + [ + __DIR__ . '/data/foreach/array-object-type.php', + 'AnotherNamespace\Foo', + '$foo', + ], + [ + __DIR__ . '/data/foreach/array-object-type.php', + 'AnotherNamespace\Foo', + '$foos[0]', + ], + [ + __DIR__ . '/data/foreach/array-object-type.php', + '0', + 'self::ARRAY_CONSTANT[0]', + ], + [ + __DIR__ . '/data/foreach/array-object-type.php', + '\'foo\'', + 'self::MIXED_CONSTANT[1]', + ], + [ + __DIR__ . '/data/foreach/nested-object-type.php', + 'AnotherNamespace\Foo', + '$foo', + ], + [ + __DIR__ . '/data/foreach/nested-object-type.php', + 'AnotherNamespace\Foo', + '$foos[0]', + ], + [ + __DIR__ . '/data/foreach/nested-object-type.php', + 'AnotherNamespace\Foo', + '$fooses[0][0]', + ], + [ + __DIR__ . '/data/foreach/integer-type.php', + 'int', + '$integer', + ], + [ + __DIR__ . '/data/foreach/reusing-specified-variable.php', + '1|2|3', + '$business', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-variable-first.php', + 'mixed', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-variable-second.php', + 'stdClass', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-no-variable.php', + 'bool', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-no-variable-2.php', + '*ERROR*', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-wrong-variable.php', + 'mixed', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-variable-with-reference.php', + 'string', + '$value', + ], + [ + __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', + 'array&nonEmpty', + '$list', + ], + [ + __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', + 'string', + '$key', + ], + [ + __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', + 'float|int|string', + '$value', + ], + [ + __DIR__ . '/data/foreach/foreach-with-complex-value-type.php', + 'float|ForeachWithComplexValueType\Foo', + '$value', + ], + [ + __DIR__ . '/data/foreach/foreach-iterable-with-specified-key-type.php', + 'ForeachWithGenericsPhpDoc\Bar|ForeachWithGenericsPhpDoc\Foo', + '$key', + ], + [ + __DIR__ . '/data/foreach/foreach-iterable-with-specified-key-type.php', + 'float|int|string', + '$value', + ], + [ + __DIR__ . '/data/foreach/foreach-iterable-with-complex-value-type.php', + 'float|ForeachWithComplexValueType\Foo', + '$value', + ], + [ + __DIR__ . '/data/foreach/type-in-comment-key.php', + 'int', + '$key', + ], + ]; + } + + /** + * @dataProvider dataForeachArrayType + * @param string $file + * @param string $description + * @param string $expression + */ + public function testForeachArrayType( + string $file, + string $description, + string $expression + ): void { + $this->assertTypes( + $file, + $description, + $expression + ); + } + + public function dataOverridingSpecifiedType(): array + { + return [ + [ + __DIR__ . '/data/catch-specified-variable.php', + 'TryCatchWithSpecifiedVariable\FooException', + '$foo', + ], + ]; + } + + /** + * @dataProvider dataOverridingSpecifiedType + * @param string $file + * @param string $description + * @param string $expression + */ + public function testOverridingSpecifiedType( + string $file, + string $description, + string $expression + ): void { + $this->assertTypes( + $file, + $description, + $expression + ); + } + + public function dataForeachObjectType(): array + { + return [ + [ + __DIR__ . '/data/foreach/object-type.php', + 'ObjectType\\MyKey', + '$keyFromIterator', + "'insideFirstForeach'", + ], + [ + __DIR__ . '/data/foreach/object-type.php', + 'ObjectType\\MyValue', + '$valueFromIterator', + "'insideFirstForeach'", + ], + [ + __DIR__ . '/data/foreach/object-type.php', + 'ObjectType\\MyKey', + '$keyFromAggregate', + "'insideSecondForeach'", + ], + [ + __DIR__ . '/data/foreach/object-type.php', + 'ObjectType\\MyValue', + '$valueFromAggregate', + "'insideSecondForeach'", + ], + [ + __DIR__ . '/data/foreach/object-type.php', + 'mixed', + '$keyFromRecursiveAggregate', + "'insideThirdForeach'", + ], + [ + __DIR__ . '/data/foreach/object-type.php', + 'mixed', + '$valueFromRecursiveAggregate', + "'insideThirdForeach'", + ], + ]; + } + + /** + * @dataProvider dataForeachObjectType + * @param string $file + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testForeachObjectType( + string $file, + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + $file, + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataArrayFunctions(): array + { + return [ + [ + '1', + '$integers[0]', + ], + [ + 'array(string, string, string)', + '$mappedStrings', + ], + [ + 'string', + '$mappedStrings[0]', + ], + [ + '1|2|3', + '$filteredIntegers[0]', + ], + [ + '123', + '$filteredMixed[0]', + ], + [ + '1|2|3', + '$uniquedIntegers[1]', + ], + [ + 'string', + '$reducedIntegersToString', + ], + [ + 'string|null', + '$reducedIntegersToStringWithNull', + ], + [ + 'string', + '$reducedIntegersToStringAnother', + ], + [ + 'null', + '$reducedToNull', + ], + [ + '1|string', + '$reducedIntegersToStringWithInt', + ], + [ + '1', + '$reducedToInt', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_change_key_case($integers)', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + 'array_combine($array, $array2)', + ], + [ + 'array(1 => 2)', + 'array_combine([1], [2])', + ], + [ + 'false', + 'array_combine([1, 2], [3])', + ], + [ + 'array(\'a\' => \'d\', \'b\' => \'e\', \'c\' => \'f\')', + 'array_combine([\'a\', \'b\', \'c\'], [\'d\', \'e\', \'f\'])', + ], + [ + PHP_VERSION_ID < 80000 ? 'array<1|2|3, mixed>|false' : 'array<1|2|3, mixed>', + 'array_combine([1, 2, 3], $array)', + ], + [ + PHP_VERSION_ID < 80000 ? 'array<1|2|3>|false' : 'array<1|2|3>', + 'array_combine($array, [1, 2, 3])', + ], + [ + 'array', + 'array_combine($array, $array)', + ], + [ + 'array', + 'array_combine($stringArray, $stringArray)', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_diff_assoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_diff_key($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_diff_uassoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_diff_ukey($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_diff($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_udiff_assoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_udiff_uassoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_udiff($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_intersect_assoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_intersect_key($integers, [])', + ], + [ + 'array', + 'array_intersect_key(...[$integers, [4, 5, 6]])', + ], + [ + 'array', + 'array_intersect_key(...$generalIntegersInAnotherArray, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_intersect_uassoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_intersect_ukey($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_intersect($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_uintersect_assoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_uintersect_uassoc($integers, [])', + ], + [ + 'array<0|1|2, 1|2|3>', + 'array_uintersect($integers, [])', + ], + [ + 'array(1, 1, 1, 1, 1)', + '$filledIntegers', + ], + [ + 'array(1)', + '$filledIntegersWithKeys', + ], + [ + 'array(1, 2)', + 'array_keys($integerKeys)', + ], + [ + 'array(\'foo\', \'bar\')', + 'array_keys($stringKeys)', + ], + [ + 'array(\'foo\', 1)', + 'array_keys($stringOrIntegerKeys)', + ], + [ + 'array', + 'array_keys($generalStringKeys)', + ], + [ + 'array(\'foo\', stdClass)', + 'array_values($integerKeys)', + ], + [ + 'array', + 'array_values($generalStringKeys)', + ], + [ + 'array', + 'array_merge($stringOrIntegerKeys)', + ], + [ + 'array', + 'array_merge($generalStringKeys, $generalDateTimeValues)', + ], + [ + 'array', + 'array_merge($generalStringKeys, $stringOrIntegerKeys)', + ], + [ + 'array', + 'array_merge($stringOrIntegerKeys, $generalStringKeys)', + ], + [ + 'array', + 'array_merge($stringKeys, $stringOrIntegerKeys)', + ], + [ + 'array', + 'array_merge($stringOrIntegerKeys, $stringKeys)', + ], + [ + 'array', + 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', + ], + [ + 'array', + 'array_merge(...[$generalStringKeys, $generalDateTimeValues])', + ], + [ + 'array', + '$mergedInts', + ], + [ + 'array(5 => \'banana\', 6 => \'banana\', 7 => \'banana\', 8 => \'banana\', 9 => \'banana\', 10 => \'banana\')', + 'array_fill(5, 6, \'banana\')', + ], + [ + 'array&nonEmpty', + 'array_fill(0, 101, \'apple\')', + ], + [ + 'array(-2 => \'pear\', 0 => \'pear\', 1 => \'pear\', 2 => \'pear\')', + 'array_fill(-2, 4, \'pear\')', + ], + [ + 'array&nonEmpty', + 'array_fill($integer, 2, new \stdClass())', + ], + [ + 'array', + 'array_fill(2, $integer, new \stdClass())', + ], + [ + 'array', + 'array_fill_keys($generalStringKeys, new \stdClass())', + ], + [ + 'array(\'foo\' => \'banana\', 5 => \'banana\', 10 => \'banana\', \'bar\' => \'banana\')', + 'array_fill_keys([\'foo\', 5, 10, \'bar\'], \'banana\')', + ], + [ + 'array', + '$mappedStringKeys', + ], + [ + 'array', + '$mappedStringKeysWithUnknownClosureType', + ], + [ + 'array', + '$mappedWrongArray', + ], + [ + 'array', + '$unknownArray', + ], + [ + 'array(\'foo\' => \'banana\', \'bar\' => \'banana\', ?\'baz\' => \'banana\', ?\'lorem\' => \'banana\')', + 'array_fill_keys($conditionalArray, \'banana\')', + ], + [ + 'array(\'foo\' => stdClass, \'bar\' => stdClass, ?\'baz\' => stdClass, ?\'lorem\' => stdClass)', + 'array_map(function (): \stdClass {}, $conditionalKeysArray)', + ], + [ + 'stdClass', + 'array_pop($stringKeys)', + ], + [ + 'array&hasOffset(\'baz\')', + '$stdClassesWithIsset', + ], + [ + 'stdClass', + 'array_pop($stdClassesWithIsset)', + ], + [ + '\'foo\'', + 'array_shift($stringKeys)', + ], + [ + 'int|null', + 'array_pop($generalStringKeys)', + ], + [ + 'int|null', + 'array_shift($generalStringKeys)', + ], + [ + 'null', + 'array_pop([])', + ], + [ + 'null', + 'array_shift([])', + ], + [ + 'array(null, \'\', 1)', + '$constantArrayWithFalseyValues', + ], + [ + 'array(2 => 1)', + '$constantTruthyValues', + ], + [ + 'array', + '$falsey', + ], + [ + 'array()', + 'array_filter($falsey)', + ], + [ + 'array', + '$withFalsey', + ], + [ + 'array', + 'array_filter($withFalsey)', + ], + [ + 'array(\'a\' => 1)', + 'array_filter($union)', + ], + [ + 'array(?0 => true, ?1 => int|int<1, max>)', + 'array_filter($withPossiblyFalsey)', + ], + [ + '(array|null)', + 'array_filter($mixed)', + ], + [ + '1|\'foo\'|false', + 'array_search(new stdClass, $stringOrIntegerKeys, true)', + ], + [ + '\'foo\'', + 'array_search(\'foo\', $stringKeys, true)', + ], + [ + 'int|false', + 'array_search(new DateTimeImmutable(), $generalDateTimeValues, true)', + ], + [ + 'string|false', + 'array_search(9, $generalStringKeys, true)', + ], + [ + 'string|false', + 'array_search(9, $generalStringKeys, false)', + ], + [ + 'string|false', + 'array_search(9, $generalStringKeys)', + ], + [ + 'null', + 'array_search(999, $integer, true)', + ], + [ + 'false', + 'array_search(new stdClass, $generalStringKeys, true)', + ], + [ + 'int|string|false', + 'array_search($mixed, $array, true)', + ], + [ + 'int|string|false', + 'array_search($mixed, $array, false)', + ], + [ + '\'a\'|\'b\'|false', + 'array_search($string, [\'a\' => \'A\', \'b\' => \'B\'], true)', + ], + [ + 'false', + 'array_search($integer, [\'a\' => \'A\', \'b\' => \'B\'], true)', + ], + [ + '\'foo\'|false', + 'array_search($generalIntegerOrString, $stringKeys, true)', + ], + [ + 'int|false', + 'array_search($generalIntegerOrString, $generalArrayOfIntegersOrStrings, true)', + ], + [ + 'int|false', + 'array_search($generalIntegerOrString, $clonedConditionalArray, true)', + ], + [ + 'int|string|false', + 'array_search($generalIntegerOrString, $generalIntegerOrStringKeys, false)', + ], + [ + 'false', + 'array_search(\'id\', $generalIntegerOrStringKeys, true)', + ], + [ + 'int|string|false', + 'array_search(\'id\', $generalIntegerOrStringKeysMixedValues, true)', + ], + [ + 'int|string|false|null', + 'array_search(\'id\', doFoo() ? $generalIntegerOrStringKeys : false, true)', + ], + [ + 'false|null', + 'array_search(\'id\', doFoo() ? [] : false, true)', + ], + [ + 'null', + 'array_search(\'id\', false, true)', + ], + [ + 'null', + 'array_search(\'id\', false)', + ], + [ + 'int|string|false', + 'array_search(\'id\', $thisDoesNotExistAndIsMixed, true)', + ], + [ + 'int|string|false', + 'array_search(\'id\', doFoo() ? $thisDoesNotExistAndIsMixedInUnion : false, true)', + ], + [ + 'int|string|false', + 'array_search(1, $generalIntegers, true)', + ], + [ + 'int|string|false', + 'array_search(1, $generalIntegers, false)', + ], + [ + 'int|string|false', + 'array_search(1, $generalIntegers)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 0)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1, null, true)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1, 2)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1, 2, true)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1, -1)', + ], + [ + 'array', + 'array_slice($generalStringKeys, 1, -1, true)', + ], + [ + 'array', + 'array_slice($generalStringKeys, -2)', + ], + [ + 'array', + 'array_slice($generalStringKeys, -2, 1, true)', + ], + [ + 'array', + 'array_slice($unknownArray, 0)', + ], + [ + 'array', + 'array_slice($unknownArray, 1)', + ], + [ + 'array', + 'array_slice($unknownArray, 1, null, true)', + ], + [ + 'array', + 'array_slice($unknownArray, 1, 2)', + ], + [ + 'array', + 'array_slice($unknownArray, 1, 2, true)', + ], + [ + 'array', + 'array_slice($unknownArray, 1, -1)', + ], + [ + 'array', + 'array_slice($unknownArray, 1, -1, true)', + ], + [ + 'array', + 'array_slice($unknownArray, -2)', + ], + [ + 'array', + 'array_slice($unknownArray, -2, 1, true)', + ], + [ + 'array(0 => bool, 1 => int, 2 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, 0)', + ], + [ + 'array(0 => int, 1 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, 1)', + ], + [ + 'array(1 => int, 2 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, 1, null, true)', + ], + [ + 'array(0 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, 2, 3)', + ], + [ + 'array(2 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, 2, 3, true)', + ], + [ + 'array(int, \'\')', + 'array_slice($withPossiblyFalsey, 1, -1)', + ], + [ + 'array(1 => int, 2 => \'\')', + 'array_slice($withPossiblyFalsey, 1, -1, true)', + ], + [ + 'array(0 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, -2, null)', + ], + [ + 'array(2 => \'\', \'a\' => 0)', + 'array_slice($withPossiblyFalsey, -2, null, true)', + ], + [ + 'array(\'baz\' => \'qux\')|array(0 => \'\', \'a\' => 0)', + 'array_slice($unionArrays, 1)', + ], + [ + 'array(\'a\' => 0)|array(\'baz\' => \'qux\')', + 'array_slice($unionArrays, -1, null, true)', + ], + [ + 'array(0 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 2 => \'quux\', \'quuz\' => \'corge\', 3 => \'grault\')', + '$slicedOffset', + ], + [ + 'array(4 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 0 => \'quux\', \'quuz\' => \'corge\', 5 => \'grault\')', + '$slicedOffsetWithKeys', + ], + [ + '0|1', + 'key($mixedValues)', + ], + [ + 'int|null', + 'key($falsey)', + ], + [ + 'string|null', + 'key($generalStringKeys)', + ], + [ + 'int|string|null', + 'key($generalIntegerOrStringKeysMixedValues)', + ], + [ + '\'foo\'', + '$poppedFoo', + ], + [ + 'int', + 'array_rand([1 => 1, 2 => "2"])', + ], + [ + 'string', + 'array_rand(["a" => 1, "b" => "2"])', + ], + [ + 'int|string', + 'array_rand(["a" => 1, 2 => "b"])', + ], + [ + 'int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed])', + ], + [ + 'int', + 'array_rand([1 => 1, 2 => "b"], 1)', + ], + [ + 'string', + 'array_rand(["a" => 1, "b" => "b"], 1)', + ], + [ + 'int|string', + 'array_rand(["a" => 1, 2 => "b"], 1)', + ], + [ + 'int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], 1)', + ], + [ + 'array', + 'array_rand([1 => 1, 2 => "b"], 2)', + ], + [ + 'array', + 'array_rand(["a" => 1, "b" => "b"], 2)', + ], + [ + 'array', + 'array_rand(["a" => 1, 2 => "b"], 2)', + ], + [ + 'array', + 'array_rand([1 => 1, 2 => "2", $mixed => $mixed], 2)', + ], + [ + 'array|int', + 'array_rand([1 => 1, 2 => "b"], $mixed)', + ], + [ + 'array|string', + 'array_rand(["a" => 1, "b" => "b"], $mixed)', + ], + [ + 'array|int|string', + 'array_rand(["a" => 1, 2 => "b"], $mixed)', + ], + [ + 'array|int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], $mixed)', + ], + ]; + } + + /** + * @dataProvider dataArrayFunctions + * @param string $description + * @param string $expression + */ + public function testArrayFunctions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-functions.php', + $description, + $expression + ); + } + + public function dataFunctions(): array + { + return [ + [ + 'string', + '$microtimeStringWithoutArg', + ], + [ + 'string', + '$microtimeString', + ], + [ + 'float', + '$microtimeFloat', + ], + [ + 'float|string', + '$microtimeDefault', + ], + [ + '(float|string)', + '$microtimeBenevolent', + ], + [ + 'int', + '$strtotimeNow', + ], + [ + 'false', + '$strtotimeInvalid', + ], + [ + 'int|false', + '$strtotimeUnknown', + ], + [ + '(int|false)', + '$strtotimeUnknown2', + ], + [ + 'int|false', + '$strtotimeCrash', + ], + [ + '-1', + '$versionCompare1', + ], + [ + '-1|1', + '$versionCompare2', + ], + [ + '-1|0|1', + '$versionCompare3', + ], + [ + '-1|0|1', + '$versionCompare4', + ], + [ + 'true', + '$versionCompare5', + ], + [ + 'bool', + '$versionCompare6', + ], + [ + 'bool', + '$versionCompare7', + ], + [ + 'bool', + '$versionCompare8', + ], + [ + 'int', + '$mbStrlenWithoutEncoding', + ], + [ + 'int', + '$mbStrlenWithValidEncoding', + ], + [ + 'int', + '$mbStrlenWithValidEncodingAlias', + ], + [ + 'false', + '$mbStrlenWithInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'int|false' : 'int', + '$mbStrlenWithValidAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'int|false' : 'int', + '$mbStrlenWithUnknownEncoding', + ], + [ + 'string', + '$mbHttpOutputWithoutEncoding', + ], + [ + 'true', + '$mbHttpOutputWithValidEncoding', + ], + [ + 'false', + '$mbHttpOutputWithInvalidEncoding', + ], + [ + 'bool', + '$mbHttpOutputWithValidAndInvalidEncoding', + ], + [ + 'bool', + '$mbHttpOutputWithUnknownEncoding', + ], + [ + 'string', + '$mbRegexEncodingWithoutEncoding', + ], + [ + 'true', + '$mbRegexEncodingWithValidEncoding', + ], + [ + 'false', + '$mbRegexEncodingWithInvalidEncoding', + ], + [ + 'bool', + '$mbRegexEncodingWithValidAndInvalidEncoding', + ], + [ + 'bool', + '$mbRegexEncodingWithUnknownEncoding', + ], + [ + 'string', + '$mbInternalEncodingWithoutEncoding', + ], + [ + 'true', + '$mbInternalEncodingWithValidEncoding', + ], + [ + 'false', + '$mbInternalEncodingWithInvalidEncoding', + ], + [ + 'bool', + '$mbInternalEncodingWithValidAndInvalidEncoding', + ], + [ + 'bool', + '$mbInternalEncodingWithUnknownEncoding', + ], + [ + 'array', + '$mbEncodingAliasesWithValidEncoding', + ], + [ + 'false', + '$mbEncodingAliasesWithInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbEncodingAliasesWithValidAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbEncodingAliasesWithUnknownEncoding', + ], + [ + 'string', + '$mbChrWithoutEncoding', + ], + [ + 'string', + '$mbChrWithValidEncoding', + ], + [ + 'false', + '$mbChrWithInvalidEncoding', + ], + [ + 'string|false', + '$mbChrWithValidAndInvalidEncoding', + ], + [ + 'string|false', + '$mbChrWithUnknownEncoding', + ], + [ + 'int', + '$mbOrdWithoutEncoding', + ], + [ + 'int', + '$mbOrdWithValidEncoding', + ], + [ + 'false', + '$mbOrdWithInvalidEncoding', + ], + [ + 'int|false', + '$mbOrdWithValidAndInvalidEncoding', + ], + [ + 'int|false', + '$mbOrdWithUnknownEncoding', + ], + [ + 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', + '$gettimeofdayArrayWithoutArg', + ], + [ + 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', + '$gettimeofdayArray', + ], + [ + 'float', + '$gettimeofdayFloat', + ], + [ + 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float', + '$gettimeofdayDefault', + ], + [ + '(array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float)', + '$gettimeofdayBenevolent', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$strSplitConstantStringWithoutDefinedParameters', + ], + [ + "array('a', 'b', 'c', 'd', 'e', 'f')", + '$strSplitConstantStringWithoutDefinedSplitLength', + ], + [ + 'array', + '$strSplitStringWithoutDefinedSplitLength', + ], + [ + "array('a', 'b', 'c', 'd', 'e', 'f')", + '$strSplitConstantStringWithOneSplitLength', + ], + [ + "array('abcdef')", + '$strSplitConstantStringWithGreaterSplitLengthThanStringLength', + ], + [ + 'false', + '$strSplitConstantStringWithFailureSplitLength', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$strSplitConstantStringWithInvalidSplitLengthType', + ], + [ + 'array', + '$strSplitConstantStringWithVariableStringAndConstantSplitLength', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$strSplitConstantStringWithVariableStringAndVariableSplitLength', + ], + // parse_url + [ + 'mixed', + '$parseUrlWithoutParameters', + ], + [ + "array('scheme' => 'http', 'host' => 'abc.def')", + '$parseUrlConstantUrlWithoutComponent1', + ], + [ + "array('scheme' => 'http', 'host' => 'def.abc')", + '$parseUrlConstantUrlWithoutComponent2', + ], + [ + "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", + '$parseUrlConstantUrlUnknownComponent', + ], + [ + 'null', + '$parseUrlConstantUrlWithComponentNull', + ], + [ + "'this-is-fragment'", + '$parseUrlConstantUrlWithComponentSet', + ], + [ + 'false', + '$parseUrlConstantUrlWithComponentInvalid', + ], + [ + 'false', + '$parseUrlStringUrlWithComponentInvalid', + ], + [ + 'int|false|null', + '$parseUrlStringUrlWithComponentPort', + ], + [ + "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", + '$parseUrlStringUrlWithoutComponent', + ], + [ + "array('path' => 'abc.def')", + "parse_url('abc.def')", + ], + [ + 'null', + "parse_url('abc.def', PHP_URL_SCHEME)", + ], + [ + "'http'", + "parse_url('http://abc.def', PHP_URL_SCHEME)", + ], + [ + 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + '$stat', + ], + [ + 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + '$lstat', + ], + [ + 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + '$fstat', + ], + [ + 'string', + '$base64DecodeWithoutStrict', + ], + [ + 'string', + '$base64DecodeWithStrictDisabled', + ], + [ + 'string|false', + '$base64DecodeWithStrictEnabled', + ], + [ + 'string', + '$base64DecodeDefault', + ], + [ + '(string|false)', + '$base64DecodeBenevolent', + ], + [ + '*ERROR*', + '$strWordCountWithoutParameters', + ], + [ + '*ERROR*', + '$strWordCountWithTooManyParams', + ], + [ + 'int', + '$strWordCountStr', + ], + [ + 'int', + '$strWordCountStrType0', + ], + [ + 'array', + '$strWordCountStrType1', + ], + [ + 'array', + '$strWordCountStrType1Extra', + ], + [ + 'array', + '$strWordCountStrType2', + ], + [ + 'array', + '$strWordCountStrType2Extra', + ], + [ + 'array|int|false', + '$strWordCountStrTypeIndeterminant', + ], + [ + 'string', + '$hashHmacMd5', + ], + [ + 'string', + '$hashHmacSha256', + ], + [ + 'false', + '$hashHmacNonCryptographic', + ], + [ + 'false', + '$hashHmacRandom', + ], + [ + 'string', + '$hashHmacVariable', + ], + [ + 'string|false', + '$hashHmacFileMd5', + ], + [ + 'string|false', + '$hashHmacFileSha256', + ], + [ + 'false', + '$hashHmacFileNonCryptographic', + ], + [ + 'false', + '$hashHmacFileRandom', + ], + [ + '(string|false)', + '$hashHmacFileVariable', + ], + [ + 'string', + '$hash', + ], + [ + 'string', + '$hashRaw', + ], + [ + 'false', + '$hashRandom', + ], + [ + 'string', + '$hashMixed', + ], + ]; + } + + /** + * @dataProvider dataFunctions + * @param string $description + * @param string $expression + */ + public function testFunctions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/functions.php', + $description, + $expression + ); + } + + public function dataDioFunctions(): array + { + return [ + [ + 'array(\'device\' => int, \'inode\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'device_type\' => int, \'size\' => int, \'blocksize\' => int, \'blocks\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int)|null', + '$stat', + ], + ]; + } + + /** + * @dataProvider dataDioFunctions + * @param string $description + * @param string $expression + */ + public function testDioFunctions( + string $description, + string $expression + ): void { + if (!function_exists('dio_stat')) { + $this->markTestSkipped('This test requires DIO extension.'); + } + $this->assertTypes( + __DIR__ . '/data/dio-functions.php', + $description, + $expression + ); + } + + public function dataSsh2Functions(): array + { + return [ + [ + 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + '$ssh2SftpStat', + ], + ]; + } + + /** + * @dataProvider dataSsh2Functions + * @param string $description + * @param string $expression + */ + public function testSsh2Functions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/ssh2-functions.php', + $description, + $expression + ); + } + + public function dataRangeFunction(): array + { + return [ + [ + 'array(2, 3, 4, 5)', + 'range(2, 5)', + ], + [ + 'array(2, 4)', + 'range(2, 5, 2)', + ], + [ + 'array(2.0, 3.0, 4.0, 5.0)', + 'range(2, 5, 1.0)', + ], + [ + 'array(2.1, 3.1, 4.1)', + 'range(2.1, 5)', + ], + [ + 'array', + 'range(2, 5, $integer)', + ], + [ + 'array', + 'range($float, 5, $integer)', + ], + [ + 'array', + 'range($float, $mixed, $integer)', + ], + [ + 'array', + 'range($integer, $mixed)', + ], + [ + 'array(0 => 1, ?1 => 2)', + 'range(1, doFoo() ? 1 : 2)', + ], + [ + 'array(0 => -1|1, ?1 => 0|2, ?2 => 1, ?3 => 2)', + 'range(doFoo() ? -1 : 1, doFoo() ? 1 : 2)', + ], + [ + 'array(3, 2, 1, 0, -1)', + 'range(3, -1)', + ], + [ + 'array&nonEmpty', + 'range(0, 50)', + ], + ]; + } + + /** + * @dataProvider dataRangeFunction + * @param string $description + * @param string $expression + */ + public function testRangeFunction( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/range-function.php', + $description, + $expression + ); + } + + public function dataSpecifiedTypesUsingIsFunctions(): array + { + return [ + [ + 'int', + '$integer', + ], + [ + 'int', + '$anotherInteger', + ], + [ + 'int', + '$longInteger', + ], + [ + 'float', + '$float', + ], + [ + 'float', + '$doubleFloat', + ], + [ + 'float', + '$realFloat', + ], + [ + 'null', + '$null', + ], + [ + 'array', + '$array', + ], + [ + 'bool', + '$bool', + ], + [ + 'callable(): mixed', + '$callable', + ], + [ + 'resource', + '$resource', + ], + [ + 'int', + '$yetAnotherInteger', + ], + [ + '*ERROR*', + '$mixedInteger', + ], + [ + 'string', + '$string', + ], + [ + 'object', + '$object', + ], + [ + 'int', + '$intOrStdClass', + ], + [ + 'Foo', + '$foo', + ], + [ + 'Foo', + '$anotherFoo', + ], + [ + 'class-string|Foo', + '$subClassOfFoo', + ], + [ + 'Foo', + '$subClassOfFoo2', + ], + [ + 'class-string|object', + '$subClassOfFoo3', + ], + [ + 'object', + '$subClassOfFoo4', + ], + [ + 'class-string|Foo', + '$subClassOfFoo5', + ], + [ + 'class-string|object', + '$subClassOfFoo6', + ], + [ + 'Foo', + '$subClassOfFoo7', + ], + [ + 'object', + '$subClassOfFoo8', + ], + [ + 'object', + '$subClassOfFoo9', + ], + [ + 'object', + '$subClassOfFoo10', + ], + [ + 'Foo', + '$subClassOfFoo11', + ], + [ + 'Foo', + '$subClassOfFoo12', + ], + [ + 'Foo', + '$subClassOfFoo13', + ], + [ + 'object', + '$subClassOfFoo14', + ], + [ + 'class-string|Foo', + '$subClassOfFoo15', + ], + [ + 'Bar|class-string|Foo', + '$subClassOfFoo16', + ], + ]; + } + + /** + * @dataProvider dataSpecifiedTypesUsingIsFunctions + * @param string $description + * @param string $expression + */ + public function testSpecifiedTypesUsingIsFunctions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/specifiedTypesUsingIsFunctions.php', + $description, + $expression + ); + } + + public function dataTypeSpecifyingExtensions(): array + { + return [ + [ + 'string', + '$foo', + true, + ], + [ + 'int', + '$bar', + true, + ], + [ + 'string|null', + '$foo', + false, + ], + [ + 'int|null', + '$bar', + false, + ], + [ + 'string', + '$foo', + null, + ], + [ + 'int', + '$bar', + null, + ], + ]; + } + + /** + * @dataProvider dataTypeSpecifyingExtensions + * @param string $description + * @param string $expression + * @param bool|null $nullContext + */ + public function testTypeSpecifyingExtensions( + string $description, + string $expression, + ?bool $nullContext + ): void { + $this->assertTypes( + __DIR__ . '/data/type-specifying-extensions.php', + $description, + $expression, + [], + [], + [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], + [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], + 'die', + [], + false + ); + } + + public function dataTypeSpecifyingExtensions2(): array + { + return [ + [ + 'string|null', + '$foo', + true, + ], + [ + 'int|null', + '$bar', + true, + ], + [ + 'string|null', + '$foo', + false, + ], + [ + 'int|null', + '$bar', + false, + ], + [ + 'string|null', + '$foo', + null, + ], + [ + 'int|null', + '$bar', + null, + ], + ]; + } + + /** + * @dataProvider dataTypeSpecifyingExtensions2 + * @param string $description + * @param string $expression + * @param bool|null $nullContext + */ + public function testTypeSpecifyingExtensions2( + string $description, + string $expression, + ?bool $nullContext + ): void { + $this->assertTypes( + __DIR__ . '/data/type-specifying-extensions2.php', + $description, + $expression, + [], + [], + [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], + [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)] + ); + } + + public function dataTypeSpecifyingExtensions3(): array + { + return [ + [ + 'string', + '$foo', + false, + ], + [ + 'int', + '$bar', + false, + ], + [ + 'string|null', + '$foo', + true, + ], + [ + 'int|null', + '$bar', + true, + ], + [ + 'string', + '$foo', + null, + ], + [ + 'int', + '$bar', + null, + ], + ]; + } + + /** + * @dataProvider dataTypeSpecifyingExtensions3 + * @param string $description + * @param string $expression + * @param bool|null $nullContext + */ + public function testTypeSpecifyingExtensions3( + string $description, + string $expression, + ?bool $nullContext + ): void { + $this->assertTypes( + __DIR__ . '/data/type-specifying-extensions3.php', + $description, + $expression, + [], + [], + [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], + [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], + 'die', + [], + false + ); + } + + public function dataIterable(): array + { + return [ + [ + 'iterable', + '$this->iterableProperty', + ], + [ + 'iterable', + '$iterableSpecifiedLater', + ], + [ + 'iterable', + '$iterableWithoutTypehint', + ], + [ + 'mixed', + '$iterableWithoutTypehint[0]', + ], + [ + 'iterable', + '$iterableWithIterableTypehint', + ], + [ + 'mixed', + '$iterableWithIterableTypehint[0]', + ], + [ + 'mixed', + '$mixed', + ], + [ + 'iterable', + '$iterableWithConcreteTypehint', + ], + [ + 'mixed', + '$iterableWithConcreteTypehint[0]', + ], + [ + 'Iterables\Bar', + '$bar', + ], + [ + 'iterable', + '$this->doBar()', + ], + [ + 'iterable', + '$this->doBaz()', + ], + [ + 'Iterables\Baz', + '$baz', + ], + [ + 'array', + '$arrayWithIterableTypehint', + ], + [ + 'mixed', + '$arrayWithIterableTypehint[0]', + ], + [ + 'iterable&Iterables\Collection', + '$unionIterableType', + ], + [ + 'Iterables\Bar', + '$unionBar', + ], + [ + 'array&nonEmpty', + '$mixedUnionIterableType', + ], + [ + 'iterable&Iterables\Collection', + '$unionIterableIterableType', + ], + [ + 'mixed', + '$mixedBar', + ], + [ + 'Iterables\Bar', + '$iterableUnionBar', + ], + [ + 'Iterables\Bar', + '$unionBarFromMethod', + ], + [ + 'iterable', + '$this->stringIterableProperty', + ], + [ + 'iterable', + '$this->mixedIterableProperty', + ], + [ + 'iterable', + '$integers', + ], + [ + 'iterable', + '$mixeds', + ], + [ + 'iterable', + '$this->returnIterableMixed()', + ], + [ + 'iterable', + '$this->returnIterableString()', + ], + [ + 'int|iterable', + '$this->iterablePropertyAlsoWithSomethingElse', + ], + [ + 'int|iterable', + '$this->iterablePropertyWithTwoItemTypes', + ], + [ + 'array|Iterables\CollectionOfIntegers', + '$this->collectionOfIntegersOrArrayOfStrings', + ], + [ + 'Generator', + '$generatorOfFoos', + ], + [ + 'Iterables\Foo', + '$fooFromGenerator', + ], + [ + 'ArrayObject', + '$arrayObject', + ], + [ + 'int', + '$arrayObjectKey', + ], + [ + 'string', + '$arrayObjectValue', + ], + ]; + } + + /** + * @dataProvider dataIterable + * @param string $description + * @param string $expression + */ + public function testIterable( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/iterable.php', + $description, + $expression + ); + } + + public function dataArrayAccess(): array + { + return [ + [ + 'string', + '$this->returnArrayOfStrings()[0]', + ], + [ + 'mixed', + '$this->returnMixed()[0]', + ], + [ + 'int', + '$this->returnSelfWithIterableInt()[0]', + ], + [ + 'int', + '$this[0]', + ], + ]; + } + + /** + * @dataProvider dataArrayAccess + * @param string $description + * @param string $expression + */ + public function testArrayAccess( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-accessable.php', + $description, + $expression + ); + } + + public function dataVoid(): array + { + return [ + [ + 'void', + '$this->doFoo()', + ], + [ + 'void', + '$this->doBar()', + ], + [ + 'void', + '$this->doConflictingVoid()', + ], + ]; + } + + /** + * @dataProvider dataVoid + * @param string $description + * @param string $expression + */ + public function testVoid( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/void.php', + $description, + $expression + ); + } + + public function dataNullableReturnTypes(): array + { + return [ + [ + 'int|null', + '$this->doFoo()', + ], + [ + 'int|null', + '$this->doBar()', + ], + [ + 'int|null', + '$this->doConflictingNullable()', + ], + [ + 'int', + '$this->doAnotherConflictingNullable()', + ], + ]; + } + + /** + * @dataProvider dataNullableReturnTypes + * @param string $description + * @param string $expression + */ + public function testNullableReturnTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/nullable-returnTypes.php', + $description, + $expression + ); + } + + public function dataTernary(): array + { + return [ + [ + 'bool|null', + '$boolOrNull', + ], + [ + 'bool', + '$boolOrNull !== null ? $boolOrNull : false', + ], + [ + 'bool', + '$bool', + ], + [ + 'true|null', + '$short', + ], + [ + 'bool', + '$c', + ], + [ + 'bool', + '$isQux', + ], + ]; + } + + /** + * @dataProvider dataTernary + * @param string $description + * @param string $expression + */ + public function testTernary( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/ternary.php', + $description, + $expression + ); + } + + public function dataHeredoc(): array + { + return [ + [ + '\'foo\'', + '$heredoc', + ], + [ + '\'bar\'', + '$nowdoc', + ], + ]; + } + + /** + * @dataProvider dataHeredoc + * @param string $description + * @param string $expression + */ + public function testHeredoc( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/heredoc.php', + $description, + $expression + ); + } + + public function dataTypeElimination(): array + { + return [ + [ + 'null', + '$foo', + "'nullForSure'", + ], + [ + 'TypeElimination\Foo', + '$foo', + "'notNullForSure'", + ], + [ + 'TypeElimination\Foo', + '$foo', + "'notNullForSure2'", + ], + [ + 'null', + '$foo', + "'nullForSure2'", + ], + [ + 'null', + '$foo', + "'nullForSure3'", + ], + [ + 'TypeElimination\Foo', + '$foo', + "'notNullForSure3'", + ], + [ + 'null', + '$foo', + "'yodaNullForSure'", + ], + [ + 'TypeElimination\Foo', + '$foo', + "'yodaNotNullForSure'", + ], + [ + 'false', + '$intOrFalse', + "'falseForSure'", + ], + [ + 'int', + '$intOrFalse', + "'intForSure'", + ], + [ + 'false', + '$intOrFalse', + "'yodaFalseForSure'", + ], + [ + 'int', + '$intOrFalse', + "'yodaIntForSure'", + ], + [ + 'true', + '$intOrTrue', + "'trueForSure'", + ], + [ + 'int', + '$intOrTrue', + "'anotherIntForSure'", + ], + [ + 'true', + '$intOrTrue', + "'yodaTrueForSure'", + ], + [ + 'int', + '$intOrTrue', + "'yodaAnotherIntForSure'", + ], + [ + 'TypeElimination\Foo', + '$fooOrBarOrBaz', + "'fooForSure'", + ], + [ + 'TypeElimination\Bar|TypeElimination\Baz', + '$fooOrBarOrBaz', + "'barOrBazForSure'", + ], + [ + 'TypeElimination\Bar', + '$fooOrBarOrBaz', + "'barForSure'", + ], + [ + 'TypeElimination\Baz', + '$fooOrBarOrBaz', + "'bazForSure'", + ], + [ + 'TypeElimination\Bar|TypeElimination\Baz', + '$fooOrBarOrBaz', + "'anotherBarOrBazForSure'", + ], + [ + 'TypeElimination\Foo', + '$fooOrBarOrBaz', + "'anotherFooForSure'", + ], + [ + 'string|null', + '$result', + "'stringOrNullForSure'", + ], + [ + 'int', + '$intOrFalse', + "'yetAnotherIntForSure'", + ], + [ + 'int', + '$intOrTrue', + "'yetYetAnotherIntForSure'", + ], + [ + 'TypeElimination\Foo|null', + '$fooOrStringOrNull', + "'fooOrNull'", + ], + [ + 'string', + '$fooOrStringOrNull', + "'stringForSure'", + ], + [ + 'string', + '$fooOrStringOrNull', + "'anotherStringForSure'", + ], + [ + 'null', + '$this->bar', + "'propertyNullForSure'", + ], + [ + 'TypeElimination\Bar', + '$this->bar', + "'propertyNotNullForSure'", + ], + ]; + } + + /** + * @dataProvider dataTypeElimination + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testTypeElimination( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/type-elimination.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataMisleadingTypes(): array + { + return [ + [ + 'MisleadingTypes\boolean', + '$foo->misleadingBoolReturnType()', + ], + [ + 'MisleadingTypes\integer', + '$foo->misleadingIntReturnType()', + ], + [ + 'mixed', + '$foo->misleadingMixedReturnType()', + ], + ]; + } + + /** + * @dataProvider dataMisleadingTypes + * @param string $description + * @param string $expression + */ + public function testMisleadingTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/misleading-types.php', + $description, + $expression + ); + } + + public function dataMisleadingTypesWithoutNamespace(): array + { + return [ + [ + 'boolean', // would have been "bool" for a real boolean + '$foo->misleadingBoolReturnType()', + ], + [ + 'integer', + '$foo->misleadingIntReturnType()', + ], + ]; + } + + /** + * @dataProvider dataMisleadingTypesWithoutNamespace + * @param string $description + * @param string $expression + */ + public function testMisleadingTypesWithoutNamespace( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/misleading-types-without-namespace.php', + $description, + $expression + ); + } + + public function dataUnresolvableTypes(): array + { + return [ + [ + 'mixed', + '$arrayWithTooManyArgs', + ], + [ + 'mixed', + '$iterableWithTooManyArgs', + ], + [ + 'Foo', + '$genericFoo', + ], + ]; + } + + /** + * @dataProvider dataUnresolvableTypes + * @param string $description + * @param string $expression + */ + public function testUnresolvableTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/unresolvable-types.php', + $description, + $expression + ); + } + + public function dataCombineTypes(): array + { + return [ + [ + 'string|null', + '$x', + ], + [ + '1|null', + '$y', + ], + ]; + } + + /** + * @dataProvider dataCombineTypes + * @param string $description + * @param string $expression + */ + public function testCombineTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/combine-types.php', + $description, + $expression + ); + } + + public function dataConstants(): array + { + define('ConstantsForNodeScopeResolverTest\\FOO_CONSTANT', 1); + + return [ + [ + '1', + '$foo', + ], + [ + '*ERROR*', + 'NONEXISTENT_CONSTANT', + ], + [ + "'bar'", + '\\BAR_CONSTANT', + ], + [ + 'mixed', + '\\BAZ_CONSTANT', + ], + ]; + } + + /** + * @dataProvider dataConstants + * @param string $description + * @param string $expression + */ + public function testConstants( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/constants.php', + $description, + $expression + ); + } + + public function dataFinally(): array + { + return [ + [ + '1|\'foo\'', + '$integerOrString', + ], + [ + 'FinallyNamespace\BarException|FinallyNamespace\FooException|null', + '$fooOrBarException', + ], + ]; + } + + /** + * @dataProvider dataFinally + * @param string $description + * @param string $expression + */ + public function testFinally( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/finally.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataFinally + * @param string $description + * @param string $expression + */ + public function testFinallyWithEarlyTermination( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/finally-with-early-termination.php', + $description, + $expression + ); + } + + public function dataInheritDocFromInterface(): array + { + return [ + [ + 'string', + '$string', + ], + ]; + } + + /** + * @dataProvider dataInheritDocFromInterface + * @param string $description + * @param string $expression + */ + public function testInheritDocFromInterface( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/inheritdoc-from-interface.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataInheritDocFromInterface + * @param string $description + * @param string $expression + */ + public function testInheritDocWithoutCurlyBracesFromInterface( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface.php', + $description, + $expression + ); + } + + public function dataInheritDocFromInterface2(): array + { + return [ + [ + 'int', + '$int', + ], + ]; + } + + /** + * @dataProvider dataInheritDocFromInterface2 + * @param string $description + * @param string $expression + */ + public function testInheritDocFromInterface2( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/inheritdoc-from-interface2-definition.php'; + $this->assertTypes( + __DIR__ . '/data/inheritdoc-from-interface2.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataInheritDocFromInterface2 + * @param string $description + * @param string $expression + */ + public function testInheritDocWithoutCurlyBracesFromInterface2( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface2-definition.php'; + $this->assertTypes( + __DIR__ . '/data/inheritdoc-without-curly-braces-from-interface2.php', + $description, + $expression + ); + } + + public function dataInheritDocFromTrait(): array + { + return [ + [ + 'string', + '$string', + ], + ]; + } + + /** + * @dataProvider dataInheritDocFromTrait + * @param string $description + * @param string $expression + */ + public function testInheritDocFromTrait( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/inheritdoc-from-trait.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataInheritDocFromTrait + * @param string $description + * @param string $expression + */ + public function testInheritDocWithoutCurlyBracesFromTrait( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait.php', + $description, + $expression + ); + } + + public function dataInheritDocFromTrait2(): array + { + return [ + [ + 'string', + '$string', + ], + ]; + } + + /** + * @dataProvider dataInheritDocFromTrait2 + * @param string $description + * @param string $expression + */ + public function testInheritDocFromTrait2( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/inheritdoc-from-trait2-definition.php'; + require_once __DIR__ . '/data/inheritdoc-from-trait2-definition2.php'; + $this->assertTypes( + __DIR__ . '/data/inheritdoc-from-trait2.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataInheritDocFromTrait2 + * @param string $description + * @param string $expression + */ + public function testInheritDocWithoutCurlyBracesFromTrait2( + string $description, + string $expression + ): void { + require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2-definition.php'; + require_once __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2-definition2.php'; + $this->assertTypes( + __DIR__ . '/data/inheritdoc-without-curly-braces-from-trait2.php', + $description, + $expression + ); + } + + public function dataResolveStatic(): array + { + return [ + [ + 'ResolveStatic\Foo', + '\ResolveStatic\Foo::create()', + ], + [ + 'ResolveStatic\Bar', + '\ResolveStatic\Bar::create()', + ], + [ + 'array(\'foo\' => ResolveStatic\Bar)', + '$bar->returnConstantArray()', + ], + [ + 'ResolveStatic\Bar|null', + '$bar->nullabilityNotInSync()', + ], + [ + 'ResolveStatic\Bar', + '$bar->anotherNullabilityNotInSync()', + ], + ]; + } + + /** + * @dataProvider dataResolveStatic + * @param string $description + * @param string $expression + */ + public function testResolveStatic( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/resolve-static.php', + $description, + $expression + ); + } + + public function dataLoopVariables(): array + { + return [ + [ + 'LoopVariables\Foo|LoopVariables\Lorem|null', + '$foo', + "'begin'", + ], + [ + 'LoopVariables\Foo', + '$foo', + "'afterAssign'", + ], + [ + 'LoopVariables\Foo', + '$foo', + "'end'", + ], + [ + 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem|null', + '$foo', + "'afterLoop'", + ], + [ + 'int|null', + '$nullableVal', + "'begin'", + ], + [ + 'null', + '$nullableVal', + "'nullableValIf'", + ], + [ + 'int', + '$nullableVal', + "'nullableValElse'", + ], + [ + 'int|null', + '$nullableVal', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'begin'", + ], + [ + 'LoopVariables\Foo', + '$falseOrObject', + "'end'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'afterLoop'", + ], + ]; + } + + public function dataForeachLoopVariables(): array + { + return [ + [ + '1|2|3', + '$val', + "'begin'", + ], + [ + '0|1|2', + '$key', + "'begin'", + ], + [ + '1|2|3|null', + '$val', + "'afterLoop'", + ], + [ + '0|1|2|null', + '$key', + "'afterLoop'", + ], + [ + '1|2|3|null', + '$emptyForeachVal', + "'afterLoop'", + ], + [ + '0|1|2|null', + '$emptyForeachKey', + "'afterLoop'", + ], + [ + '1|2|3', + '$nullableInt', + "'end'", + ], + [ + 'array&nonEmpty', + '$integers', + "'end'", + ], + [ + 'array', + '$integers', + "'afterLoop'", + ], + [ + 'array', + '$this->property', + "'begin'", + ], + [ + 'array&nonEmpty', + '$this->property', + "'end'", + ], + [ + 'array', + '$this->property', + "'afterLoop'", + ], + [ + 'int', + '$i', + "'begin'", + ], + [ + 'int', + '$i', + "'end'", + ], + [ + 'int', + '$i', + "'afterLoop'", + ], + ]; + } + + public function dataWhileLoopVariables(): array + { + return [ + [ + 'int', + '$i', + "'begin'", + ], + [ + 'int', + '$i', + "'end'", + ], + [ + 'int', + '$i', + "'afterLoop'", + ], + ]; + } + + + public function dataForLoopVariables(): array + { + return [ + [ + 'int', + '$i', + "'begin'", + ], + [ + 'int', + '$i', + "'end'", + ], + [ + 'int', + '$i', + "'afterLoop'", + ], + ]; + } + + + + /** + * @dataProvider dataLoopVariables + * @dataProvider dataForeachLoopVariables + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testForeachLoopVariables( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/foreach-loop-variables.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + /** + * @dataProvider dataLoopVariables + * @dataProvider dataWhileLoopVariables + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testWhileLoopVariables( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/while-loop-variables.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + /** + * @dataProvider dataLoopVariables + * @dataProvider dataForLoopVariables + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testForLoopVariables( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/for-loop-variables.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataDoWhileLoopVariables(): array + { + return [ + [ + 'LoopVariables\Foo|LoopVariables\Lorem|null', + '$foo', + "'begin'", + ], + [ + 'LoopVariables\Foo', + '$foo', + "'afterAssign'", + ], + [ + 'LoopVariables\Foo', + '$foo', + "'end'", + ], + [ + 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem', + '$foo', + "'afterLoop'", + ], + [ + 'int', + '$i', + "'begin'", + ], + [ + 'int', + '$i', + "'end'", + ], + [ + 'int', + '$i', + "'afterLoop'", + ], + [ + 'int|null', + '$nullableVal', + "'begin'", + ], + [ + 'null', + '$nullableVal', + "'nullableValIf'", + ], + [ + 'int', + '$nullableVal', + "'nullableValElse'", + ], + [ + 'int', + '$nullableVal', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'begin'", + ], + [ + 'LoopVariables\Foo', + '$falseOrObject', + "'end'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo|false', + '$anotherFalseOrObject', + "'begin'", + ], + [ + 'LoopVariables\Foo', + '$anotherFalseOrObject', + "'end'", + ], + [ + 'LoopVariables\Foo', + '$anotherFalseOrObject', + "'afterLoop'", + ], + + ]; + } + + /** + * @dataProvider dataDoWhileLoopVariables + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testDoWhileLoopVariables( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/do-while-loop-variables.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataMultipleClassesInOneFile(): array + { + return [ + [ + 'MultipleClasses\Foo', + '$self', + "'Foo'", + ], + [ + 'MultipleClasses\Bar', + '$self', + "'Bar'", + ], + ]; + } + + /** + * @dataProvider dataMultipleClassesInOneFile + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testMultipleClassesInOneFile( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/multiple-classes-per-file.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataCallingMultipleClassesInOneFile(): array + { + return [ + [ + 'MultipleClasses\Foo', + '$foo->returnSelf()', + ], + [ + 'MultipleClasses\Bar', + '$bar->returnSelf()', + ], + ]; + } + + /** + * @dataProvider dataCallingMultipleClassesInOneFile + * @param string $description + * @param string $expression + */ + public function testCallingMultipleClassesInOneFile( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/calling-multiple-classes-per-file.php', + $description, + $expression + ); + } + + public function dataExplode(): array + { + return [ + [ + 'array&nonEmpty', + '$sureArray', + ], + [ + PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*', + '$sureFalse', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$arrayOrFalse', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$anotherArrayOrFalse', + ], + [ + PHP_VERSION_ID < 80000 ? '(array|false)' : 'array', + '$benevolentArrayOrFalse', + ], + ]; + } + + /** + * @dataProvider dataExplode + * @param string $description + * @param string $expression + */ + public function testExplode( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/explode.php', + $description, + $expression + ); + } + + public function dataArrayPointerFunctions(): array + { + return [ + [ + 'mixed', + 'reset()', + ], + [ + 'stdClass|false', + 'reset($generalArray)', + ], + [ + 'mixed', + 'reset($somethingElse)', + ], + [ + 'false', + 'reset($emptyConstantArray)', + ], + [ + '1', + 'reset($constantArray)', + ], + [ + '\'baz\'|\'foo\'', + 'reset($conditionalArray)', + ], + [ + 'mixed', + 'end()', + ], + [ + 'stdClass|false', + 'end($generalArray)', + ], + [ + 'mixed', + 'end($somethingElse)', + ], + [ + 'false', + 'end($emptyConstantArray)', + ], + [ + '2', + 'end($constantArray)', + ], + [ + '\'bar\'|\'baz\'', + 'end($secondConditionalArray)', + ], + ]; + } + + /** + * @dataProvider dataArrayPointerFunctions + * @param string $description + * @param string $expression + */ + public function testArrayPointerFunctions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-pointer-functions.php', + $description, + $expression + ); + } + + public function dataReplaceFunctions(): array + { + return [ + [ + 'string', + '$expectedString', + ], + [ + 'string|null', + '$expectedString2', + ], + [ + 'string|null', + '$anotherExpectedString', + ], + [ + 'array(\'a\' => string, \'b\' => string)', + '$expectedArray', + ], + [ + 'array(\'a\' => string, \'b\' => string)|null', + '$expectedArray2', + ], + [ + 'array(\'a\' => string, \'b\' => string)|null', + '$anotherExpectedArray', + ], + [ + 'array|string', + '$expectedArrayOrString', + ], + [ + '(array|string)', + '$expectedBenevolentArrayOrString', + ], + [ + 'array|string|null', + '$expectedArrayOrString2', + ], + [ + 'array|string|null', + '$anotherExpectedArrayOrString', + ], + [ + 'array(\'a\' => string, \'b\' => string)|null', + 'preg_replace_callback_array($callbacks, $array)', + ], + [ + 'string|null', + 'preg_replace_callback_array($callbacks, $string)', + ], + [ + 'string', + 'str_replace(\'.\', \':\', $intOrStringKey)', + ], + [ + 'string', + 'str_ireplace(\'.\', \':\', $intOrStringKey)', + ], + ]; + } + + /** + * @dataProvider dataReplaceFunctions + * @param string $description + * @param string $expression + */ + public function testReplaceFunctions( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/replaceFunctions.php', + $description, + $expression + ); + } + + public function dataFilterVar(): Generator + { + $typesAndFilters = [ + 'string' => [ + 'FILTER_DEFAULT', + 'FILTER_UNSAFE_RAW', + 'FILTER_SANITIZE_EMAIL', + 'FILTER_SANITIZE_ENCODED', + 'FILTER_SANITIZE_NUMBER_FLOAT', + 'FILTER_SANITIZE_NUMBER_INT', + 'FILTER_SANITIZE_SPECIAL_CHARS', + 'FILTER_SANITIZE_STRING', + 'FILTER_SANITIZE_URL', + 'FILTER_VALIDATE_EMAIL', + 'FILTER_VALIDATE_IP', + '$filterIp', + 'FILTER_VALIDATE_MAC', + 'FILTER_VALIDATE_REGEXP', + 'FILTER_VALIDATE_URL', + ], + 'int' => ['FILTER_VALIDATE_INT'], + 'float' => ['FILTER_VALIDATE_FLOAT'], + ]; + + if (defined('FILTER_SANITIZE_MAGIC_QUOTES')) { + $typesAndFilters['string'][] = 'FILTER_SANITIZE_MAGIC_QUOTES'; + } + + if (defined('FILTER_SANITIZE_ADD_SLASHES')) { + $typesAndFilters['string'][] = 'FILTER_SANITIZE_ADD_SLASHES'; + } + + $typeAndFlags = [ + ['%s|false', ''], + ['%s|false', ', $mixed'], + ['%s|false', ', ["flags" => $mixed]'], + ['%s|null', ', FILTER_NULL_ON_FAILURE'], + ['%s|null', ', ["flags" => FILTER_NULL_ON_FAILURE]'], + ['%s|null', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4]'], + ['%s|null', ', ["flags" => $nullFilter]'], + ['Analyser|%s', ', ["options" => ["default" => new Analyser]]'], + ['array<%s|null>', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY'], + ['array<%s|null>', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY | FILTER_FLAG_IPV4'], + ['array<%s|false>', ', FILTER_FORCE_ARRAY'], + ['array<%s|null>', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], + ['array<%s|false>', ', ["flags" => FILTER_FORCE_ARRAY | FILTER_FLAG_IPV4]'], + ['array<%s|false>', ', ["flags" => $forceArrayFilter]'], + ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_FORCE_ARRAY]'], + ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], + ]; + + foreach ($typesAndFilters as $filterType => $filters) { + foreach ($filters as $filter) { + foreach ($typeAndFlags as [$type, $flag]) { + yield [ + sprintf($type, $filterType), + sprintf('filter_var($mixed, %s %s)', $filter, $flag), + ]; + } + } + } + + $boolFlags = [ + ['bool', ''], + ['bool', ', $mixed'], + ['bool', ', ["flags" => $mixed]'], + ['bool|null', ', FILTER_NULL_ON_FAILURE'], + ['bool|null', ', ["flags" => FILTER_NULL_ON_FAILURE]'], + ['bool|null', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4]'], + ['bool|null', ', ["flags" => $nullFilter]'], + ['Analyser|bool', ', ["options" => ["default" => new Analyser]]'], + ['bool', ', ["options" => ["default" => true]]'], + ['array', ', FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY'], + ['array', ', FILTER_FORCE_ARRAY'], + ['array', ', ["flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], + ['array', ', ["flags" => $forceArrayFilter]'], + ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_FORCE_ARRAY]'], + ['array',', ["options" => ["default" => false], "flags" => FILTER_FORCE_ARRAY]'], + ['array',', ["options" => ["default" => new Analyser], "flags" => FILTER_NULL_ON_FAILURE | FILTER_FORCE_ARRAY]'], + ]; + + foreach ($boolFlags as [$type, $flags]) { + yield [ + $type, + sprintf('filter_var($mixed, FILTER_VALIDATE_BOOLEAN %s)', $flags), + ]; + } + + //edge cases + yield 'unknown filter' => [ + 'mixed', + 'filter_var($mixed, $mixed)', + ]; + + yield 'default that is the same type as result' => [ + 'string', + 'filter_var($mixed, FILTER_SANITIZE_URL, ["options" => ["default" => "foo"]])', + ]; + + yield 'no second variable' => [ + 'string|false', + 'filter_var($mixed)', + ]; + } + + public function dataFilterVarUnchanged(): array + { + return [ + [ + '12', + 'filter_var(12, FILTER_VALIDATE_INT)', + ], + [ + 'false', + 'filter_var(false, FILTER_VALIDATE_BOOLEAN)', + ], + [ + 'array', + 'filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY)', + ], + [ + 'array', + 'filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE)', + ], + [ + '3.27', + 'filter_var(3.27, FILTER_VALIDATE_FLOAT)', + ], + [ + '3.27', + 'filter_var(3.27, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE)', + ], + [ + 'int', + 'filter_var(rand(), FILTER_VALIDATE_INT)', + ], + [ + '12.0', + 'filter_var(12, FILTER_VALIDATE_FLOAT)', + ], + ]; + } + + /** + * @dataProvider dataFilterVar + * @dataProvider dataFilterVarUnchanged + * @param string $description + * @param string $expression + */ + public function testFilterVar( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/filterVar.php', + $description, + $expression + ); + } + + public function dataClosureWithUsePassedByReference(): array + { + return [ + [ + 'false', + '$progressStarted', + "'beforeCallback'", + ], + [ + 'false', + '$anotherVariable', + "'beforeCallback'", + ], + [ + '1|bool', + '$progressStarted', + "'inCallbackBeforeAssign'", + ], + [ + 'false', + '$anotherVariable', + "'inCallbackBeforeAssign'", + ], + [ + 'null', + '$untouchedPassedByRef', + "'inCallbackBeforeAssign'", + ], + [ + '1|true', + '$progressStarted', + "'inCallbackAfterAssign'", + ], + [ + 'true', + '$anotherVariable', + "'inCallbackAfterAssign'", + ], + [ + '1|bool', + '$progressStarted', + "'afterCallback'", + ], + [ + 'false', + '$anotherVariable', + "'afterCallback'", + ], + [ + 'null', + '$untouchedPassedByRef', + "'afterCallback'", + ], + [ + '1', + '$incrementedInside', + "'beforeCallback'", + ], + [ + 'int', + '$incrementedInside', + "'inCallbackBeforeAssign'", + ], + [ + 'int', + '$incrementedInside', + "'inCallbackAfterAssign'", + ], + [ + 'int', + '$incrementedInside', + "'afterCallback'", + ], + [ + 'null', + '$fooOrNull', + "'beforeCallback'", + ], + [ + 'ClosurePassedByReference\Foo|null', + '$fooOrNull', + "'inCallbackBeforeAssign'", + ], + [ + 'ClosurePassedByReference\Foo', + '$fooOrNull', + "'inCallbackAfterAssign'", + ], + [ + 'ClosurePassedByReference\Foo|null', + '$fooOrNull', + "'afterCallback'", + ], + ]; + } + + /** + * @dataProvider dataClosureWithUsePassedByReference + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testClosureWithUsePassedByReference( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/closure-passed-by-reference.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataClosureWithUsePassedByReferenceInMethodCall(): array + { + return [ + [ + 'int|null', + '$five', + ], + ]; + } + + /** + * @dataProvider dataClosureWithUsePassedByReferenceInMethodCall + * @param string $description + * @param string $expression + */ + public function testClosureWithUsePassedByReferenceInMethodCall( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/closure-passed-by-reference-in-call.php', + $description, + $expression + ); + } + + public function dataClosureWithUsePassedByReferenceReturn(): array + { + return [ + [ + 'null', + '$fooOrNull', + "'beforeCallback'", + ], + [ + 'ClosurePassedByReference\Foo|null', + '$fooOrNull', + "'inCallbackBeforeAssign'", + ], + [ + 'ClosurePassedByReference\Foo', + '$fooOrNull', + "'inCallbackAfterAssign'", + ], + [ + 'ClosurePassedByReference\Foo|null', + '$fooOrNull', + "'afterCallback'", + ], + ]; + } + + public function dataStaticClosure(): array + { + return [ + [ + '*ERROR*', + '$this', + ], + ]; + } + + /** + * @dataProvider dataStaticClosure + * @param string $description + * @param string $expression + */ + public function testStaticClosure( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/static-closure.php', + $description, + $expression + ); + } + + /** + * @dataProvider dataClosureWithUsePassedByReferenceReturn + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testClosureWithUsePassedByReferenceReturn( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/closure-passed-by-reference-return.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataClosureWithInferredTypehint(): array + { + return [ + [ + 'DateTime|stdClass', + '$foo', + ], + [ + 'mixed', + '$bar', + ], + ]; + } + + /** + * @dataProvider dataClosureWithInferredTypehint + * @param string $description + * @param string $expression + */ + public function testClosureWithInferredTypehint( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/closure-inferred-typehint.php', + $description, + $expression, + [], + [], + [], + [], + 'die', + [], + false + ); + } + + public function dataTraitsPhpDocs(): array + { + return [ + [ + 'mixed', + '$this->propertyWithoutPhpDoc', + ], + [ + 'TraitPhpDocsTwo\TraitPropertyType', + '$this->traitProperty', + ], + [ + 'TraitPhpDocs\PropertyTypeFromClass', + '$this->conflictingProperty', + ], + /*[ + 'TraitPhpDocsTwo\AmbiguousPropertyType', + '$this->bogusProperty', + ],*/ + [ + 'TraitPhpDocs\BogusPropertyType', + '$this->anotherBogusProperty', + ], + [ + 'TraitPhpDocsTwo\BogusPropertyType', + '$this->differentBogusProperty', + ], + [ + 'string', + '$this->methodWithoutPhpDoc()', + ], + [ + 'TraitPhpDocsTwo\TraitMethodType', + '$this->traitMethod()', + ], + [ + 'TraitPhpDocs\MethodTypeFromClass', + '$this->conflictingMethod()', + ], + [ + 'TraitPhpDocs\AmbiguousMethodType', + '$this->bogusMethod()', + ], + [ + 'TraitPhpDocs\BogusMethodType', + '$this->anotherBogusMethod()', + ], + [ + 'TraitPhpDocsTwo\BogusMethodType', + '$this->differentBogusMethod()', + ], + [ + 'TraitPhpDocsTwo\DuplicateMethodType', + '$this->methodInMoreTraits()', + ], + [ + 'TraitPhpDocsThree\AnotherDuplicateMethodType', + '$this->anotherMethodInMoreTraits()', + ], + [ + 'TraitPhpDocsTwo\YetAnotherDuplicateMethodType', + '$this->yetAnotherMethodInMoreTraits()', + ], + [ + 'TraitPhpDocsThree\YetAnotherDuplicateMethodType', + '$this->aliasedYetAnotherMethodInMoreTraits()', + ], + [ + 'TraitPhpDocsThree\YetYetAnotherDuplicateMethodType', + '$this->yetYetAnotherMethodInMoreTraits()', + ], + [ + 'TraitPhpDocsTwo\YetYetAnotherDuplicateMethodType', + '$this->aliasedYetYetAnotherMethodInMoreTraits()', + ], + [ + 'int', + '$this->propertyFromTraitUsingTrait', + ], + [ + 'string', + '$this->methodFromTraitUsingTrait()', + ], + [ + 'TraitPhpDocsThree\Foo', + '$this->loremTraitProperty', + ], + ]; + } + + /** + * @dataProvider dataTraitsPhpDocs + * @param string $description + * @param string $expression + */ + public function testTraitsPhpDocs( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/traits/traits.php', + $description, + $expression + ); + } + + public function dataPassedByReference(): array + { + return [ + [ + 'array(1, 2, 3)', + '$arr', + ], + [ + 'mixed', + '$matches', + ], + [ + 'mixed', + '$s', + ], + ]; + } + + /** + * @dataProvider dataPassedByReference + * @param string $description + * @param string $expression + */ + public function testPassedByReference( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/passed-by-reference.php', + $description, + $expression + ); + } + + public function dataCallables(): array + { + return [ + [ + 'int', + '$foo()', + ], + [ + 'string', + '$closure()', + ], + [ + 'Callables\\Bar', + '$arrayWithStaticMethod()', + ], + [ + 'float', + '$stringWithStaticMethod()', + ], + [ + 'float', + '$arrayWithInstanceMethod()', + ], + [ + 'mixed', + '$closureObject()', + ], + ]; + } + + /** + * @dataProvider dataCallables + * @param string $description + * @param string $expression + */ + public function testCallables( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/callables.php', + $description, + $expression + ); + } + + public function dataArrayKeysInBranches(): array + { + return [ + [ + 'array(\'i\' => int, \'j\' => int, \'k\' => int, \'key\' => DateTimeImmutable, \'l\' => 1, \'m\' => 5, ?\'n\' => \'str\')', + '$array', + ], + [ + 'array', + '$generalArray', + ], + [ + 'mixed', // should be DateTimeImmutable + '$generalArray[\'key\']', + ], + [ + 'array(0 => \'foo\', 1 => \'bar\', ?2 => \'baz\')', + '$arrayAppendedInIf', + ], + [ + 'array', + '$arrayAppendedInForeach', + ], + [ + 'array', + '$anotherArrayAppendedInForeach', + ], + [ + '\'str\'', + '$array[\'n\']', + ], + [ + 'int', + '$incremented', + ], + [ + '0|1', + '$setFromZeroToOne', + ], + ]; + } + + /** + * @dataProvider dataArrayKeysInBranches + * @param string $description + * @param string $expression + */ + public function testArrayKeysInBranches( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-keys-branches.php', + $description, + $expression + ); + } + + public function dataSpecifiedFunctionCall(): array + { + return [ + [ + 'true', + 'is_file($autoloadFile)', + "'first'", + ], + [ + 'true', + 'is_file($autoloadFile)', + "'second'", + ], + [ + 'true', + 'is_file($autoloadFile)', + "'third'", + ], + [ + 'bool', + 'is_file($autoloadFile)', + "'fourth'", + ], + [ + 'true', + 'is_file($autoloadFile)', + "'fifth'", + ], + ]; + } + + /** + * @dataProvider dataSpecifiedFunctionCall + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testSpecifiedFunctionCall( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/specified-function-call.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataElementsOnMixed(): array + { + return [ + [ + 'mixed', + '$mixed->foo', + ], + [ + 'mixed', + '$mixed->foo->bar', + ], + [ + 'mixed', + '$mixed->foo()', + ], + [ + 'mixed', + '$mixed->foo()->bar()', + ], + [ + 'mixed', + '$mixed::TEST_CONSTANT', + ], + ]; + } + + /** + * @dataProvider dataElementsOnMixed + * @param string $description + * @param string $expression + */ + public function testElementsOnMixed( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/mixed-elements.php', + $description, + $expression + ); + } + + public function dataCaseInsensitivePhpDocTypes(): array + { + return [ + [ + 'Foo\Bar', + '$this->bar', + ], + [ + 'Foo\Baz', + '$this->lorem', + ], + ]; + } + + /** + * @dataProvider dataCaseInsensitivePhpDocTypes + * @param string $description + * @param string $expression + */ + public function testCaseInsensitivePhpDocTypes( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/case-insensitive-phpdocs.php', + $description, + $expression + ); + } + + public function dataConstantTypeAfterDuplicateCondition(): array + { + return [ + [ + '0', + '$a', + "'inCondition'", + ], + [ + '0', + '$b', + "'inCondition'", + ], + [ + '0', + '$c', + "'inCondition'", + ], + [ + 'int', + '$a', + "'afterFirst'", + ], + [ + 'int', + '$b', + "'afterFirst'", + ], + [ + '0', + '$c', + "'afterFirst'", + ], + [ + 'int|int<1, max>', + '$a', + "'afterSecond'", + ], + [ + 'int', + '$b', + "'afterSecond'", + ], + [ + '0', + '$c', + "'afterSecond'", + ], + [ + 'int|int<1, max>', + '$a', + "'afterThird'", + ], + [ + 'int|int<1, max>', + '$b', + "'afterThird'", + ], + [ + '0', + '$c', + "'afterThird'", + ], + ]; + } + + /** + * @dataProvider dataConstantTypeAfterDuplicateCondition + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testConstantTypeAfterDuplicateCondition( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/constant-types-duplicate-condition.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataAnonymousClass(): array + { + return [ + [ + '$this(AnonymousClass3301acd9e9d13ba9bbce9581cdb00699)', + '$this', + "'inside'", + ], + [ + 'AnonymousClass3301acd9e9d13ba9bbce9581cdb00699', + '$foo', + "'outside'", + ], + [ + 'AnonymousClassName\Foo', + '$this->fooProperty', + "'inside'", + ], + [ + 'AnonymousClassName\Foo', + '$foo->fooProperty', + "'outside'", + ], + [ + 'AnonymousClassName\Foo', + '$this->doFoo()', + "'inside'", + ], + [ + 'AnonymousClassName\Foo', + '$foo->doFoo()', + "'outside'", + ], + ]; + } + + /** + * @dataProvider dataAnonymousClass + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testAnonymousClassName( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/anonymous-class-name.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataAnonymousClassInTrait(): array + { + return [ + [ + '$this(AnonymousClass3de0a9734314db9dec21ba308363ff9a)', + '$this', + ], + ]; + } + + /** + * @dataProvider dataAnonymousClassInTrait + * @param string $description + * @param string $expression + */ + public function testAnonymousClassNameInTrait( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/anonymous-class-name-in-trait.php', + $description, + $expression + ); + } + + public function dataDynamicConstants(): array + { + return [ + [ + 'string', + 'DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', + ], + [ + "'abc123def'", + 'DynamicConstants\DynamicConstantClass::PURE_CONSTANT_IN_CLASS', + ], + [ + "'xyz'", + 'DynamicConstants\NoDynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', + ], + [ + 'bool', + 'GLOBAL_DYNAMIC_CONSTANT', + ], + [ + '123', + 'GLOBAL_PURE_CONSTANT', + ], + ]; + } + + /** + * @dataProvider dataDynamicConstants + * @param string $description + * @param string $expression + */ + public function testDynamicConstants( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/dynamic-constant.php', + $description, + $expression, + [], + [], + [], + [], + 'die', + [ + 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', + 'GLOBAL_DYNAMIC_CONSTANT', + ] + ); + } + + public function dataIsset(): array + { + return [ + [ + '2|3', + '$array[\'b\']', + ], + [ + 'array(\'a\' => 1|2|3, \'b\' => 2|3, ?\'c\' => 4)', + '$array', + ], + [ + 'array(\'a\' => 1|2|3, \'b\' => 2|3|null, ?\'c\' => 4)', + '$arrayCopy', + ], + [ + 'array(\'a\' => 1|2|3, ?\'c\' => 4)', + '$anotherArrayCopy', + ], + [ + 'array', + '$yetAnotherArrayCopy', + ], + [ + 'mixed~null', + '$mixedIsset', + ], + [ + 'array&hasOffset(\'a\')', + '$mixedArrayKeyExists', + ], + [ + 'array&hasOffset(\'a\')', + '$integers', + ], + [ + 'int', + '$integers[\'a\']', + ], + [ + 'false', + '$lookup[\'derp\'] ?? false', + ], + [ + 'true', + '$lookup[\'foo\'] ?? false', + ], + [ + 'bool', + '$lookup[$a] ?? false', + ], + [ + '\'foo\'|false', + '$nullableArray[\'a\'] ?? false', + ], + [ + '\'bar\'', + '$nullableArray[\'b\'] ?? false', + ], + [ + '\'baz\'|false', + '$nullableArray[\'c\'] ?? false', + ], + ]; + } + + /** + * @dataProvider dataIsset + * @param string $description + * @param string $expression + */ + public function testIsset( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/isset.php', + $description, + $expression + ); + } + + public function dataPropertyArrayAssignment(): array + { + return [ + [ + 'mixed', + '$this->property', + "'start'", + ], + [ + 'array()', + '$this->property', + "'emptyArray'", + ], + [ + '*ERROR*', + '$this->property[\'foo\']', + "'emptyArray'", + ], + [ + 'array(\'foo\' => 1)', + '$this->property', + "'afterAssignment'", + ], + [ + '1', + '$this->property[\'foo\']', + "'afterAssignment'", + ], + ]; + } + + /** + * @dataProvider dataPropertyArrayAssignment + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testPropertyArrayAssignment( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/property-array.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataInArray(): array + { + return [ + [ + '\'bar\'|\'foo\'', + '$s', + ], + [ + 'string', + '$mixed', + ], + [ + 'string', + '$r', + ], + [ + '\'foo\'', + '$fooOrBarOrBaz', + ], + ]; + } + + /** + * @dataProvider dataInArray + * @param string $description + * @param string $expression + */ + public function testInArray( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/in-array.php', + $description, + $expression + ); + } + + public function dataGetParentClass(): array + { + return [ + [ + 'false', + 'get_parent_class()', + ], + [ + 'class-string|false', + 'get_parent_class($s)', + ], + [ + 'false', + 'get_parent_class(\ParentClass\Foo::class)', + ], + [ + 'class-string|false', + 'get_parent_class(NonexistentClass::class)', + ], + [ + 'class-string|false', + 'get_parent_class(1)', + ], + [ + "'ParentClass\\\\Foo'", + 'get_parent_class(\ParentClass\Bar::class)', + ], + [ + 'false', + 'get_parent_class()', + "'inParentClass'", + ], + [ + 'false', + 'get_parent_class($this)', + "'inParentClass'", + ], + [ + 'class-string', + 'get_class($this)', + "'inParentClass'", + ], + [ + '\'ParentClass\\\\Foo\'', + 'get_class()', + "'inParentClass'", + ], + [ + 'false', + 'get_class()', + ], + [ + "'ParentClass\\\\Foo'", + 'get_parent_class()', + "'inChildClass'", + ], + [ + "'ParentClass\\\\Foo'", + 'get_parent_class($this)', + "'inChildClass'", + ], + [ + 'class-string|false', + 'get_parent_class()', + "'inTrait'", + ], + [ + 'class-string|false', + 'get_parent_class($this)', + "'inTrait'", + ], + ]; + } + + /** + * @dataProvider dataGetParentClass + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testGetParentClass( + string $description, + string $expression, + string $evaluatedPointExpression = 'die' + ): void { + $this->assertTypes( + __DIR__ . '/data/get-parent-class.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataIsCountable(): array + { + return [ + [ + 'array|Countable', + '$union', + "'is'", + ], + [ + 'string', + '$union', + "'is_not'", + ], + ]; + } + + /** + * @dataProvider dataIsCountable + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testIsCountable( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + $this->assertTypes( + __DIR__ . '/data/is_countable.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression + ); + } + + public function dataPhp73Functions(): array + { + return [ + [ + 'string|false', + 'json_encode($mixed)', + ], + [ + 'string', + 'json_encode($mixed, JSON_THROW_ON_ERROR)', + ], + [ + 'string', + 'json_encode($mixed, JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', + ], + [ + 'string', + 'json_encode($mixed, $integer | JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', + ], + [ + 'mixed', + 'json_decode($mixed)', + ], + [ + 'mixed~false', + 'json_decode($mixed, false, 512, JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', + ], + [ + 'mixed~false', + 'json_decode($mixed, false, 512, $integer | JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)', + ], + [ + 'int|string|null', + 'array_key_first($mixedArray)', + ], + [ + 'int|string|null', + 'array_key_last($mixedArray)', + ], + [ + '(int|string)', + 'array_key_first($nonEmptyArray)', + ], + [ + '(int|string)', + 'array_key_last($nonEmptyArray)', + ], + [ + 'string|null', + 'array_key_first($arrayWithStringKeys)', + ], + [ + 'string|null', + 'array_key_last($arrayWithStringKeys)', + ], + [ + 'null', + 'array_key_first($emptyArray)', + ], + [ + 'null', + 'array_key_last($emptyArray)', + ], + [ + '0', + 'array_key_first($literalArray)', + ], + [ + '2', + 'array_key_last($literalArray)', + ], + [ + '0', + 'array_key_first($anotherLiteralArray)', + ], + [ + '2|3', + 'array_key_last($anotherLiteralArray)', + ], + [ + 'array(int, int)', + '$hrtime1', + ], + [ + 'array(int, int)', + '$hrtime2', + ], + [ + '(float|int)', + '$hrtime3', + ], + [ + 'array(int, int)|float|int', + '$hrtime4', + ], + ]; + } + + /** + * @dataProvider dataPhp73Functions + * @param string $description + * @param string $expression + */ + public function testPhp73Functions( + string $description, + string $expression + ): void { + if (PHP_VERSION_ID < 70300) { + $this->markTestSkipped('Test requires PHP 7.3'); + } + $this->assertTypes( + __DIR__ . '/data/php73_functions.php', + $description, + $expression + ); + } + + public function dataPhp74Functions(): array + { + return [ + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithoutDefinedParameters', + ], + [ + "array('a', 'b', 'c', 'd', 'e', 'f')", + '$mbStrSplitConstantStringWithoutDefinedSplitLength', + ], + [ + 'array', + '$mbStrSplitStringWithoutDefinedSplitLength', + ], + [ + "array('a', 'b', 'c', 'd', 'e', 'f')", + '$mbStrSplitConstantStringWithOneSplitLength', + ], + [ + "array('abcdef')", + '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength', + ], + [ + 'false', + '$mbStrSplitConstantStringWithFailureSplitLength', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithInvalidSplitLengthType', + ], + [ + 'array', + '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', + ], + [ + "array('a', 'b', 'c', 'd', 'e', 'f')", + '$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', + ], + [ + "array('abcdef')", + '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', + ], + [ + 'array', + '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding', + ], + [ + 'false', + '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding', + ], + [ + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding', + ], + ]; + } + + /** + * @dataProvider dataPhp74Functions + * @param string $description + * @param string $expression + */ + public function testPhp74Functions( + string $description, + string $expression + ): void { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4'); + } + $this->assertTypes( + __DIR__ . '/data/php74_functions.php', + $description, + $expression + ); + } + + public function dataUnionMethods(): array + { + return [ + [ + 'UnionMethods\Bar|UnionMethods\Foo', + '$something->doSomething()', + ], + [ + 'UnionMethods\Bar|UnionMethods\Foo', + '$something::doSomething()', + ], + ]; + } + + /** + * @dataProvider dataUnionMethods + * @param string $description + * @param string $expression + */ + public function testUnionMethods( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/union-methods.php', + $description, + $expression + ); + } + + public function dataUnionProperties(): array + { + return [ + [ + 'UnionProperties\Bar|UnionProperties\Foo', + '$something->doSomething', + ], + [ + 'UnionProperties\Bar|UnionProperties\Foo', + '$something::$doSomething', + ], + ]; + } + + /** + * @dataProvider dataUnionProperties + * @param string $description + * @param string $expression + */ + public function testUnionProperties( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/union-properties.php', + $description, + $expression + ); + } + + public function dataAssignmentInCondition(): array + { + return [ + [ + 'AssignmentInCondition\Foo', + '$bar', + ], + ]; + } + + /** + * @dataProvider dataAssignmentInCondition + * @param string $description + * @param string $expression + */ + public function testAssignmentInCondition( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/assignment-in-condition.php', + $description, + $expression + ); + } + + public function dataGeneralizeScope(): array + { + return [ + [ + "array int, 'loadCount' => int, 'removeCount' => int, 'saveCount' => int)>>", + '$statistics', + ], + ]; + } + + /** + * @dataProvider dataGeneralizeScope + * @param string $description + * @param string $expression + */ + public function testGeneralizeScope( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/generalize-scope.php', + $description, + $expression + ); + } + + public function dataGeneralizeScopeRecursiveType(): array + { + return [ + [ + 'array()|array(\'foo\' => array)', + '$data', + ], + ]; + } + + /** + * @dataProvider dataGeneralizeScopeRecursiveType + * @param string $description + * @param string $expression + */ + public function testGeneralizeScopeRecursiveType( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/generalize-scope-recursive.php', + $description, + $expression + ); + } + + public function dataArrayShapesInPhpDoc(): array + { + return [ + [ + 'array(0 => string, 1 => ArrayShapesInPhpDoc\Foo, \'foo\' => ArrayShapesInPhpDoc\Bar, 2 => ArrayShapesInPhpDoc\Baz)', + '$one', + ], + [ + 'array(0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', + '$two', + ], + [ + 'array(?0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', + '$three', + ], + ]; + } + + /** + * @dataProvider dataArrayShapesInPhpDoc + * @param string $description + * @param string $expression + */ + public function testArrayShapesInPhpDoc( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/array-shapes.php', + $description, + $expression + ); + } + + public function dataInferPrivatePropertyTypeFromConstructor(): array + { + return [ + [ + 'int', + '$this->intProp', + ], + [ + 'string', + '$this->stringProp', + ], + [ + 'InferPrivatePropertyTypeFromConstructor\Bar|InferPrivatePropertyTypeFromConstructor\Foo', + '$this->unionProp', + ], + [ + 'stdClass', + '$this->stdClassProp', + ], + [ + 'stdClass', + '$this->unrelatedDocComment', + ], + [ + 'mixed', + '$this->explicitMixed', + ], + [ + 'bool', + '$this->bool', + ], + [ + 'array', + '$this->array', + ], + ]; + } + + /** + * @dataProvider dataInferPrivatePropertyTypeFromConstructor + * @param string $description + * @param string $expression + */ + public function testInferPrivatePropertyTypeFromConstructor( + string $description, + string $expression + ): void { + $this->assertTypes( + __DIR__ . '/data/infer-private-property-type-from-constructor.php', + $description, + $expression + ); + } + + public function dataPropertyNativeTypes(): array + { + return [ + [ + 'string', + '$this->stringProp', + ], + [ + 'PropertyNativeTypes\Foo', + '$this->selfProp', + ], + [ + 'array', + '$this->integersProp', + ], + ]; + } + + /** + * @dataProvider dataPropertyNativeTypes + * @param string $description + * @param string $expression + */ + public function testPropertyNativeTypes( + string $description, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/property-native-types.php', + $description, + $expression + ); + } + + public function dataArrowFunctions(): array + { + return [ + [ + 'Closure(string): 1', + '$x', + ], + [ + '1', + '$x()', + ], + [ + 'array(\'a\' => 1, \'b\' => 2)', + '$y()', + ], + ]; + } + + /** + * @dataProvider dataArrowFunctions + * @param string $description + * @param string $expression + */ + public function testArrowFunctions( + string $description, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/arrow-functions.php', + $description, + $expression + ); + } + + public function dataArrowFunctionsInside(): array + { + return [ + [ + 'int', + '$i', + ], + [ + 'string', + '$s', + ], + [ + '*ERROR*', + '$t', + ], + ]; + } + + /** + * @dataProvider dataArrowFunctionsInside + * @param string $description + * @param string $expression + */ + public function testArrowFunctionsInside( + string $description, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/arrow-functions-inside.php', + $description, + $expression + ); + } + + public function dataCoalesceAssign(): array + { + return [ + [ + 'string', + '$string ??= 1', + ], + [ + '1|string', + '$nullableString ??= 1', + ], + [ + '\'foo\'', + '$emptyArray[\'foo\'] ??= \'foo\'', + ], + [ + '\'foo\'', + '$arrayWithFoo[\'foo\'] ??= \'bar\'', + ], + [ + '\'bar\'|\'foo\'', + '$arrayWithMaybeFoo[\'foo\'] ??= \'bar\'', + ], + [ + 'array(\'foo\' => \'foo\')', + '$arrayAfterAssignment', + ], + [ + 'array(\'foo\' => \'foo\')', + '$arrayWithFooAfterAssignment', + ], + [ + '\'foo\'', + '$nonexistentVariableAfterAssignment', + ], + [ + '\'bar\'|\'foo\'', + '$maybeNonexistentVariableAfterAssignment', + ], + ]; + } + + /** + * @dataProvider dataCoalesceAssign + * @param string $description + * @param string $expression + */ + public function testCoalesceAssign( + string $description, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/coalesce-assign.php', + $description, + $expression + ); + } + + public function dataArraySpread(): array + { + return [ + [ + 'array', + '$integersOne', + ], + [ + 'array', + '$integersTwo', + ], + [ + 'array(1, 2, 3, 4, 5, 6, 7)', + '$integersThree', + ], + [ + 'array', + '$integersFour', + ], + [ + 'array', + '$integersFive', + ], + [ + 'array(1, 2, 3, 4, 5, 6, 7)', + '$integersSix', + ], + [ + 'array(1, 2, 3, 4, 5, 6, 7)', + '$integersSeven', + ], + ]; + } + + /** + * @dataProvider dataArraySpread + * @param string $description + * @param string $expression + */ + public function testArraySpread( + string $description, + string $expression + ): void { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/array-spread.php', + $description, + $expression + ); + } + + public function dataPhp74FunctionsIn73(): array + { + return [ + [ + '*ERROR*', + 'password_algos()', + ], + ]; + } + + /** + * @dataProvider dataPhp74FunctionsIn73 + * @param string $description + * @param string $expression + */ + public function testPhp74FunctionsIn73( + string $description, + string $expression + ): void { + if (PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP >= 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/die-73.php', + $description, + $expression + ); + } + + public function dataPhp74FunctionsIn74(): array + { + return [ + [ + 'array', + 'password_algos()', + ], + ]; + } + + /** + * @dataProvider dataPhp74FunctionsIn74 + * @param string $description + * @param string $expression + */ + public function testPhp74FunctionsIn74( + string $description, + string $expression + ): void { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->assertTypes( + __DIR__ . '/data/die-74.php', + $description, + $expression + ); + } + + public function dataTryCatchScope(): array + { + return [ + [ + 'TryCatchScope\Foo', + '$resource', + "'first'", + ], + [ + 'TryCatchScope\Foo|null', + '$resource', + "'second'", + ], + [ + 'TryCatchScope\Foo|null', + '$resource', + "'third'", + ], + ]; + } + + /** + * @dataProvider dataTryCatchScope + * @param string $description + * @param string $expression + * @param string $evaluatedPointExpression + */ + public function testTryCatchScope( + string $description, + string $expression, + string $evaluatedPointExpression + ): void { + foreach ([true, false] as $polluteCatchScopeWithTryAssignments) { + $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; + + try { + $this->assertTypes( + __DIR__ . '/data/try-catch-scope.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression, + [], + false + ); + } catch (\PHPUnit\Framework\ExpectationFailedException $e) { + throw new \PHPUnit\Framework\ExpectationFailedException( + sprintf( + '%s (polluteCatchScopeWithTryAssignments: %s)', + $e->getMessage(), + $polluteCatchScopeWithTryAssignments ? 'true' : 'false' + ), + $e->getComparisonFailure() + ); + } + } + } + + /** + * @param string $file + * @param string $description + * @param string $expression + * @param DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions + * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions + * @param string $evaluatedPointExpression + * @param string[] $dynamicConstantNames + * @param bool $useCache + */ + private function assertTypes( + string $file, + string $description, + string $expression, + array $dynamicMethodReturnTypeExtensions = [], + array $dynamicStaticMethodReturnTypeExtensions = [], + array $methodTypeSpecifyingExtensions = [], + array $staticMethodTypeSpecifyingExtensions = [], + string $evaluatedPointExpression = 'die', + array $dynamicConstantNames = [], + bool $useCache = true + ): void { + $assertType = function (Scope $scope) use ($expression, $description, $evaluatedPointExpression): void { + /** @var \PhpParser\Node\Stmt\Expression $expressionNode */ + $expressionNode = $this->getParser()->parseString(sprintf('getType($expressionNode->expr); + $this->assertTypeDescribe( + $description, + $type, + sprintf('%s at %s', $expression, $evaluatedPointExpression) + ); + }; + if ($useCache && isset(self::$assertTypesCache[$file][$evaluatedPointExpression])) { + $assertType(self::$assertTypesCache[$file][$evaluatedPointExpression]); + return; + } + $this->processFile( + $file, + static function (\PhpParser\Node $node, Scope $scope) use ($file, $evaluatedPointExpression, $assertType): void { + if ($node instanceof VirtualNode) { + return; + } + $printer = new \PhpParser\PrettyPrinter\Standard(); + $printedNode = $printer->prettyPrint([$node]); + if ($printedNode !== $evaluatedPointExpression) { + return; + } + + self::$assertTypesCache[$file][$evaluatedPointExpression] = $scope; + + $assertType($scope); + }, + $dynamicMethodReturnTypeExtensions, + $dynamicStaticMethodReturnTypeExtensions, + $methodTypeSpecifyingExtensions, + $staticMethodTypeSpecifyingExtensions, + $dynamicConstantNames + ); + } + + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/typeAliases.neon']; + } + + public function dataDeclareStrictTypes(): array + { + return [ + [ + __DIR__ . '/data/declareWeakTypes.php', + false, + ], + [ + __DIR__ . '/data/noDeclare.php', + false, + ], + [ + __DIR__ . '/data/declareStrictTypes.php', + true, + ], + ]; + } + + /** + * @dataProvider dataDeclareStrictTypes + * @param string $file + * @param bool $result + */ + public function testDeclareStrictTypes(string $file, bool $result): void + { + $this->processFile($file, function (\PhpParser\Node $node, Scope $scope) use ($result): void { + if (!($node instanceof Exit_)) { + return; + } + + $this->assertSame($result, $scope->isDeclareStrictTypes()); + }); + } + + public function testEarlyTermination(): void + { + $this->processFile(__DIR__ . '/data/early-termination.php', function (\PhpParser\Node $node, Scope $scope): void { + if (!($node instanceof Exit_)) { + return; + } + + $this->assertTrue($scope->hasVariableType('something')->yes()); + $this->assertTrue($scope->hasVariableType('var')->yes()); + $this->assertTrue($scope->hasVariableType('foo')->no()); + }); + } + + protected function getEarlyTerminatingMethodCalls(): array + { + return [ + \EarlyTermination\Foo::class => [ + 'doFoo', + 'doBar', + ], + ]; + } + + protected function getEarlyTerminatingFunctionCalls(): array + { + return ['baz']; + } + + private function assertTypeDescribe( + string $expectedDescription, + Type $actualType, + string $label = '' + ): void { + $actualDescription = $actualType->describe(VerbosityLevel::precise()); + $this->assertSame( + $expectedDescription, + $actualDescription, + $label + ); + } + + /** @return string[] */ + protected function getAdditionalAnalysedFiles(): array + { + return [ + __DIR__ . '/data/methodPhpDocs-trait-defined.php', + __DIR__ . '/data/anonymous-class-name-in-trait-trait.php', + ]; + } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 943d2c70d3..1bfda2cfb7 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1,4 +1,6 @@ -gatherAssertTypes(__DIR__ . '/data/bug2574.php'); - - require_once __DIR__ . '/data/bug2577.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug2577.php'); - - require_once __DIR__ . '/data/generics.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/generics.php'); - - require_once __DIR__ . '/data/generic-class-string.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-class-string.php'); - - require_once __DIR__ . '/data/instanceof.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/integer-range-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type-extensions.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/intersection-static.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/static-properties.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/static-methods.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2612.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2677.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2676.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/psalm-prefix-unresolvable.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/complex-generics-example.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2648.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2740.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2822.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-parameter-remapping.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-constructors.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/list-type.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2835.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2443.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2750.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2850.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2863.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/native-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/type-change-after-array-access-assignment.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator_to_array.php'); - - if (self::$useStaticReflectionProvider || extension_loaded('ds')) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/ext-ds.php'); - } - if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70401) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/arrow-function-return-type.php'); - } - yield from $this->gatherAssertTypes(__DIR__ . '/data/is-numeric.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/is-a.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3142.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-shapes-keys-strings.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1216.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/const-expr-phpdoc-type.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3226.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2001.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2232.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3009.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-var.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-param.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-return.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-template.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3266.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3269.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/assign-nested-arrays.php'); - if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3276.php'); - } - yield from $this->gatherAssertTypes(__DIR__ . '/data/shadowed-trait-methods.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/const-in-functions.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/const-in-functions-namespaced.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/root-scope-maybe-defined.php'); - if (PHP_VERSION_ID < 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3336.php'); - } - if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/catch-without-variable.php'); - } - yield from $this->gatherAssertTypes(__DIR__ . '/data/mixed-typehint.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-typehint-without-null-in-phpdoc.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/override-root-scope-variable.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bitwise-not.php'); - if (extension_loaded('gd')) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/graphics-draw-return-types.php'); - } - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/unionTypes.php'); - } - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/mixedType.php'); - } - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/staticReturnType.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax-arrays.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array-key-type.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3133.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-2550.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2899.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_split.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bcmath-dynamic-return.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3875.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2611.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3548.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3866.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1014.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-pr-339.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/pow.php'); - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-expr.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-on-expr.php'); - } - - if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3961-php8.php'); - } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3961.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1924.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/extra-int-types.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/count-type.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816-2.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3985.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-slice.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3990.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3991.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3993.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3997.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4016.php'); - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/promoted-properties-types.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/early-termination-phpdoc.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3915.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2378.php'); - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/match-expr.php'); - } - - if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/nullsafe.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/specified-types-closure-use.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/cast-to-numeric-string.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2539.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2733.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3132.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1233.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/comparison-operators.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3880.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/inc-dec-in-conditions.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4099.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3760.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2997.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1657.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2945.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4207.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4206.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-empty-array.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4205.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variable-certainty.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1865.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-non-empty-array.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/foreach-dependent-key-value.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variables-type-guard-same-as-type.php'); - - if (PHP_VERSION_ID >= 70400 || self::$useStaticReflectionProvider) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variables-arrow-function.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-801.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1209.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2980.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3986.php'); - - if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4188.php'); - } - - if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4339.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4343.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-method.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4351.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-use.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-declare.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4398.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4415.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/compact.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4500.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4504.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4436.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-3777.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2549.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1945.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2003.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-651.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1283.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4538.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/proc_get_status.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4552.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1897.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1801.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2927.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4558.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4557.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4209.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4209-2.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2869.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3024.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3134.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4577.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4579.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3321.php'); - - require_once __DIR__ . '/../Rules/Generics/data/bug-3769.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Generics/data/bug-3769.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof-class-string.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4498.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4587.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4606.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3922.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types-unwrapping.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types-unwrapping-covariant.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-incomplete-constructor.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator-iterator.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4642.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-4643.php'); - require_once __DIR__ . '/data/throw-points/helpers.php'; - if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/php8/null-safe-method-call.php'); - } - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/and.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/array.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/array-dim-fetch.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/assign.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/assign-op.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/do-while.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/for.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/foreach.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/func-call.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/if.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/method-call.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/or.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/property-fetch.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/static-call.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/switch.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/throw.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/try-catch-finally.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/variable.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/while.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/try-catch.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-override.php'); - require_once __DIR__ . '/data/phpdoc-pseudotype-namespace.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-namespace.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-global.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-traits.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4423.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-unions.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-parent.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4247.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4267.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2231.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3558.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3351.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4213.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4657.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4707.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4545.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4714.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4725.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4733.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4326.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-987.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3677.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4215.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4695.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2977.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3190.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/ternary-specified-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-560.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/do-not-remember-impure-functions.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4190.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/clear-stat-cache.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-static.php'); - - require_once __DIR__ . '/data/invalidate-object-argument-function.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-function.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4588.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4091.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3382.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4177.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2288.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1157.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1597.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3617.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-778.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2969.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3004.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3710.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3822.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-505.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1670.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1219.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3302.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1511.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4434.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4231.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4287.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4700.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-in-closure-bind.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/multi-assign.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/generics-reduce-types-first.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4803.php'); - - require_once __DIR__ . '/data/type-aliases.php'; - - yield from $this->gatherAssertTypes(__DIR__ . '/data/type-aliases.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4650.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2906.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeDynamicReturnTypes.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4821.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4838.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4879.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4820.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4822.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4816.php'); - - if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4757.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4814.php'); - } - - /** - * @dataProvider dataFileAsserts - * @param string $assertType - * @param string $file - * @param mixed ...$args - */ - public function testFileAsserts( - string $assertType, - string $file, - ...$args - ): void - { - $this->assertFileAsserts($assertType, $file, ...$args); - } - - public static function getAdditionalConfigFiles(): array - { - return [__DIR__ . '/typeAliases.neon']; - } - + public function dataFileAsserts(): iterable + { + require_once __DIR__ . '/data/bug2574.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug2574.php'); + + require_once __DIR__ . '/data/bug2577.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug2577.php'); + + require_once __DIR__ . '/data/generics.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/generics.php'); + + require_once __DIR__ . '/data/generic-class-string.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-class-string.php'); + + require_once __DIR__ . '/data/instanceof.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/integer-range-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type-extensions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/intersection-static.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/static-properties.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/static-methods.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2612.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2677.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2676.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/psalm-prefix-unresolvable.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/complex-generics-example.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2648.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2740.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2822.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-parameter-remapping.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-constructors.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/list-type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2835.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2443.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2750.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2850.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2863.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/native-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-change-after-array-access-assignment.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator_to_array.php'); + + if (self::$useStaticReflectionProvider || extension_loaded('ds')) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/ext-ds.php'); + } + if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70401) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/arrow-function-return-type.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/is-numeric.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/is-a.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3142.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-shapes-keys-strings.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1216.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/const-expr-phpdoc-type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3226.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2001.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2232.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3009.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-var.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-param.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-return.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-template.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3266.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3269.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/assign-nested-arrays.php'); + if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3276.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/shadowed-trait-methods.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/const-in-functions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/const-in-functions-namespaced.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/root-scope-maybe-defined.php'); + if (PHP_VERSION_ID < 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3336.php'); + } + if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/catch-without-variable.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/mixed-typehint.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-typehint-without-null-in-phpdoc.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/override-root-scope-variable.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bitwise-not.php'); + if (extension_loaded('gd')) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/graphics-draw-return-types.php'); + } + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/unionTypes.php'); + } + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/mixedType.php'); + } + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/staticReturnType.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax-arrays.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array-key-type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3133.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-2550.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2899.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_split.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bcmath-dynamic-return.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3875.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2611.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3548.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3866.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1014.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-pr-339.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/pow.php'); + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-expr.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-on-expr.php'); + } + + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3961-php8.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3961.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1924.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/extra-int-types.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/count-type.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816-2.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3985.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-slice.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3990.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3991.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3993.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3997.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4016.php'); + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/promoted-properties-types.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/early-termination-phpdoc.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3915.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2378.php'); + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/match-expr.php'); + } + + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/nullsafe.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/specified-types-closure-use.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/cast-to-numeric-string.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2539.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2733.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3132.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1233.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/comparison-operators.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3880.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/inc-dec-in-conditions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4099.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3760.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2997.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1657.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2945.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4207.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4206.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-empty-array.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4205.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variable-certainty.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1865.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-non-empty-array.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/foreach-dependent-key-value.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variables-type-guard-same-as-type.php'); + + if (PHP_VERSION_ID >= 70400 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/dependent-variables-arrow-function.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-801.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1209.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2980.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3986.php'); + + if (PHP_VERSION_ID >= 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4188.php'); + } + + if (PHP_VERSION_ID >= 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4339.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4343.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-method.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4351.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-use.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-declare.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4398.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4415.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/compact.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4500.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4504.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4436.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-3777.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2549.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1945.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2003.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-651.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1283.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4538.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/proc_get_status.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4552.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1897.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1801.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2927.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4558.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4557.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4209.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4209-2.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2869.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3024.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3134.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4577.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4579.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3321.php'); + + require_once __DIR__ . '/../Rules/Generics/data/bug-3769.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Generics/data/bug-3769.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof-class-string.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4498.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4587.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4606.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3922.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types-unwrapping.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-types-unwrapping-covariant.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/nested-generic-incomplete-constructor.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator-iterator.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4642.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-4643.php'); + require_once __DIR__ . '/data/throw-points/helpers.php'; + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/php8/null-safe-method-call.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/and.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/array.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/array-dim-fetch.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/assign.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/assign-op.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/do-while.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/for.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/foreach.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/func-call.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/if.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/method-call.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/or.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/property-fetch.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/static-call.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/switch.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/throw.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/try-catch-finally.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/variable.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/while.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-points/try-catch.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-override.php'); + require_once __DIR__ . '/data/phpdoc-pseudotype-namespace.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-namespace.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-global.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-traits.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4423.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-unions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-parent.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4247.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4267.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2231.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3558.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3351.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4213.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4657.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4707.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4545.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4714.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4725.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4733.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4326.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-987.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3677.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4215.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4695.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2977.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3190.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/ternary-specified-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-560.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/do-not-remember-impure-functions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4190.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/clear-stat-cache.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-static.php'); + + require_once __DIR__ . '/data/invalidate-object-argument-function.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/data/invalidate-object-argument-function.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4588.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4091.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3382.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4177.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2288.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1157.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1597.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3617.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-778.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2969.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3004.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3710.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3822.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-505.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1670.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1219.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3302.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1511.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4434.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4231.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4287.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4700.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-in-closure-bind.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/multi-assign.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generics-reduce-types-first.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4803.php'); + + require_once __DIR__ . '/data/type-aliases.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-aliases.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4650.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2906.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/DateTimeDynamicReturnTypes.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4821.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4838.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4879.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4820.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4822.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4816.php'); + + if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4757.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4814.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/typeAliases.neon']; + } } diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 4c1f9c663c..6e87d73748 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -1,4 +1,6 @@ - 1)', + ], + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantIntegerType(1), + new ConstantIntegerType(1), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantIntegerType(2), + new ConstantIntegerType(1), + ]), + 'array(\'a\' => int, \'b\' => 1)', + ], + [ + new ConstantArrayType([ + new ConstantStringType('a'), + ], [ + new ConstantIntegerType(1), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantIntegerType(1), + new ConstantIntegerType(1), + ]), + 'array', + ], + [ + new ConstantArrayType([ + new ConstantStringType('a'), + ], [ + new ConstantIntegerType(1), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ]), + 'array', + ], + ]; + } - public function dataGeneralize(): array - { - return [ - [ - new ConstantStringType('a'), - new ConstantStringType('a'), - '\'a\'', - ], - [ - new ConstantStringType('a'), - new ConstantStringType('b'), - 'string', - ], - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - 'int', - ], - [ - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ]), - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ]), - 'int', - ], - [ - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantStringType('foo'), - ]), - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantStringType('foo'), - ]), - '0|1|\'foo\'', - ], - [ - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantStringType('foo'), - ]), - new UnionType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantStringType('foo'), - ]), - '\'foo\'|int', - ], - [ - new ConstantBooleanType(false), - new UnionType([ - new ObjectType('Foo'), - new ConstantBooleanType(false), - ]), - 'Foo|false', - ], - [ - new UnionType([ - new ObjectType('Foo'), - new ConstantBooleanType(false), - ]), - new ConstantBooleanType(false), - 'Foo|false', - ], - [ - new ObjectType('Foo'), - new ConstantBooleanType(false), - 'Foo', - ], - [ - new ConstantArrayType([ - new ConstantStringType('a'), - ], [ - new ConstantIntegerType(1), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - ], [ - new ConstantIntegerType(1), - ]), - 'array(\'a\' => 1)', - ], - [ - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(1), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new ConstantIntegerType(2), - new ConstantIntegerType(1), - ]), - 'array(\'a\' => int, \'b\' => 1)', - ], - [ - new ConstantArrayType([ - new ConstantStringType('a'), - ], [ - new ConstantIntegerType(1), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(1), - ]), - 'array', - ], - [ - new ConstantArrayType([ - new ConstantStringType('a'), - ], [ - new ConstantIntegerType(1), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ]), - 'array', - ], - ]; - } - - /** - * @dataProvider dataGeneralize - * @param Type $a - * @param Type $b - * @param string $expectedTypeDescription - */ - public function testGeneralize(Type $a, Type $b, string $expectedTypeDescription): void - { - /** @var ScopeFactory $scopeFactory */ - $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); - $scopeA = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $a); - $scopeB = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $b); - $resultScope = $scopeA->generalizeWith($scopeB); - $this->assertSame($expectedTypeDescription, $resultScope->getVariableType('a')->describe(VerbosityLevel::precise())); - } - - public function testGetConstantType(): void - { - /** @var ScopeFactory $scopeFactory */ - $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); - $scope = $scopeFactory->create(ScopeContext::create(__DIR__ . '/data/compiler-halt-offset.php')); - $node = new ConstFetch(new FullyQualified('__COMPILER_HALT_OFFSET__')); - $type = $scope->getType($node); - $this->assertSame('int', $type->describe(VerbosityLevel::precise())); - } + /** + * @dataProvider dataGeneralize + * @param Type $a + * @param Type $b + * @param string $expectedTypeDescription + */ + public function testGeneralize(Type $a, Type $b, string $expectedTypeDescription): void + { + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + $scopeA = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $a); + $scopeB = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $b); + $resultScope = $scopeA->generalizeWith($scopeB); + $this->assertSame($expectedTypeDescription, $resultScope->getVariableType('a')->describe(VerbosityLevel::precise())); + } + public function testGetConstantType(): void + { + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + $scope = $scopeFactory->create(ScopeContext::create(__DIR__ . '/data/compiler-halt-offset.php')); + $node = new ConstFetch(new FullyQualified('__COMPILER_HALT_OFFSET__')); + $type = $scope->getType($node); + $this->assertSame('int', $type->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index 966db5f6ab..1a4f9d4b75 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -1,4 +1,6 @@ -getService('currentPhpVersionRichParser'); - - /** @var Stmt[] $stmts */ - $stmts = $parser->parseString(sprintf('getService('currentPhpVersionRichParser'); - /** @var NodeScopeResolver $nodeScopeResolver */ - $nodeScopeResolver = self::getContainer()->getByType(NodeScopeResolver::class); - /** @var ScopeFactory $scopeFactory */ - $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); - $scope = $scopeFactory->create(ScopeContext::create('test.php')) - ->assignVariable('string', new StringType()) - ->assignVariable('x', new IntegerType()) - ->assignVariable('cond', new MixedType()) - ->assignVariable('arr', new ArrayType(new MixedType(), new MixedType())); - $result = $nodeScopeResolver->processStmtNodes( - new Stmt\Namespace_(null, $stmts), - $stmts, - $scope, - static function (): void { - } - ); - $this->assertSame($expectedIsAlwaysTerminating, $result->isAlwaysTerminating()); - } + /** @var Stmt[] $stmts */ + $stmts = $parser->parseString(sprintf('getByType(NodeScopeResolver::class); + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + $scope = $scopeFactory->create(ScopeContext::create('test.php')) + ->assignVariable('string', new StringType()) + ->assignVariable('x', new IntegerType()) + ->assignVariable('cond', new MixedType()) + ->assignVariable('arr', new ArrayType(new MixedType(), new MixedType())); + $result = $nodeScopeResolver->processStmtNodes( + new Stmt\Namespace_(null, $stmts), + $stmts, + $scope, + static function (): void { + } + ); + $this->assertSame($expectedIsAlwaysTerminating, $result->isAlwaysTerminating()); + } } diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextTest.php b/tests/PHPStan/Analyser/TypeSpecifierContextTest.php index ac18184fd0..fc9b6124e0 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierContextTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierContextTest.php @@ -1,90 +1,90 @@ -assertSame($results[0], $context->true()); - $this->assertSame($results[1], $context->truthy()); - $this->assertSame($results[2], $context->false()); - $this->assertSame($results[3], $context->falsey()); - $this->assertSame($results[4], $context->null()); - } - - public function dataNegate(): array - { - return [ - [ - TypeSpecifierContext::createTrue()->negate(), - [false, true, true, true, false], - ], - [ - TypeSpecifierContext::createTruthy()->negate(), - [false, false, true, true, false], - ], - [ - TypeSpecifierContext::createFalse()->negate(), - [true, true, false, true, false], - ], - [ - TypeSpecifierContext::createFalsey()->negate(), - [true, true, false, false, false], - ], - ]; - } + /** + * @dataProvider dataContext + * @param \PHPStan\Analyser\TypeSpecifierContext $context + * @param bool[] $results + */ + public function testContext(TypeSpecifierContext $context, array $results): void + { + $this->assertSame($results[0], $context->true()); + $this->assertSame($results[1], $context->truthy()); + $this->assertSame($results[2], $context->false()); + $this->assertSame($results[3], $context->falsey()); + $this->assertSame($results[4], $context->null()); + } - /** - * @dataProvider dataNegate - * @param \PHPStan\Analyser\TypeSpecifierContext $context - * @param bool[] $results - */ - public function testNegate(TypeSpecifierContext $context, array $results): void - { - $this->assertSame($results[0], $context->true()); - $this->assertSame($results[1], $context->truthy()); - $this->assertSame($results[2], $context->false()); - $this->assertSame($results[3], $context->falsey()); - $this->assertSame($results[4], $context->null()); - } + public function dataNegate(): array + { + return [ + [ + TypeSpecifierContext::createTrue()->negate(), + [false, true, true, true, false], + ], + [ + TypeSpecifierContext::createTruthy()->negate(), + [false, false, true, true, false], + ], + [ + TypeSpecifierContext::createFalse()->negate(), + [true, true, false, true, false], + ], + [ + TypeSpecifierContext::createFalsey()->negate(), + [true, true, false, false, false], + ], + ]; + } - public function testNegateNull(): void - { - $this->expectException(\PHPStan\ShouldNotHappenException::class); - TypeSpecifierContext::createNull()->negate(); - } + /** + * @dataProvider dataNegate + * @param \PHPStan\Analyser\TypeSpecifierContext $context + * @param bool[] $results + */ + public function testNegate(TypeSpecifierContext $context, array $results): void + { + $this->assertSame($results[0], $context->true()); + $this->assertSame($results[1], $context->truthy()); + $this->assertSame($results[2], $context->false()); + $this->assertSame($results[3], $context->falsey()); + $this->assertSame($results[4], $context->null()); + } + public function testNegateNull(): void + { + $this->expectException(\PHPStan\ShouldNotHappenException::class); + TypeSpecifierContext::createNull()->negate(); + } } diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index b6bdb05a90..caef72c778 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1,4 +1,6 @@ -createBroker(); - $this->printer = new \PhpParser\PrettyPrinter\Standard(); - $this->typeSpecifier = $this->createTypeSpecifier($this->printer, $broker); - $this->scope = $this->createScopeFactory($broker, $this->typeSpecifier)->create(ScopeContext::create('')); - $this->scope = $this->scope->enterClass($broker->getClass('DateTime')); - $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar')); - $this->scope = $this->scope->assignVariable('stringOrNull', new UnionType([new StringType(), new NullType()])); - $this->scope = $this->scope->assignVariable('string', new StringType()); - $this->scope = $this->scope->assignVariable('barOrNull', new UnionType([new ObjectType('Bar'), new NullType()])); - $this->scope = $this->scope->assignVariable('barOrFalse', new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)])); - $this->scope = $this->scope->assignVariable('stringOrFalse', new UnionType([new StringType(), new ConstantBooleanType(false)])); - $this->scope = $this->scope->assignVariable('array', new ArrayType(new MixedType(), new MixedType())); - $this->scope = $this->scope->assignVariable('foo', new MixedType()); - $this->scope = $this->scope->assignVariable('classString', new ClassStringType()); - $this->scope = $this->scope->assignVariable('genericClassString', new GenericClassStringType(new ObjectType('Bar'))); - } + /** @var Scope */ + private $scope; - /** - * @dataProvider dataCondition - * @param Expr $expr - * @param mixed[] $expectedPositiveResult - * @param mixed[] $expectedNegatedResult - */ - public function testCondition(Expr $expr, array $expectedPositiveResult, array $expectedNegatedResult): void - { - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this->scope, $expr, TypeSpecifierContext::createTruthy()); - $actualResult = $this->toReadableResult($specifiedTypes); - $this->assertSame($expectedPositiveResult, $actualResult, sprintf('if (%s)', $this->printer->prettyPrintExpr($expr))); + protected function setUp(): void + { + $broker = $this->createBroker(); + $this->printer = new \PhpParser\PrettyPrinter\Standard(); + $this->typeSpecifier = $this->createTypeSpecifier($this->printer, $broker); + $this->scope = $this->createScopeFactory($broker, $this->typeSpecifier)->create(ScopeContext::create('')); + $this->scope = $this->scope->enterClass($broker->getClass('DateTime')); + $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar')); + $this->scope = $this->scope->assignVariable('stringOrNull', new UnionType([new StringType(), new NullType()])); + $this->scope = $this->scope->assignVariable('string', new StringType()); + $this->scope = $this->scope->assignVariable('barOrNull', new UnionType([new ObjectType('Bar'), new NullType()])); + $this->scope = $this->scope->assignVariable('barOrFalse', new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)])); + $this->scope = $this->scope->assignVariable('stringOrFalse', new UnionType([new StringType(), new ConstantBooleanType(false)])); + $this->scope = $this->scope->assignVariable('array', new ArrayType(new MixedType(), new MixedType())); + $this->scope = $this->scope->assignVariable('foo', new MixedType()); + $this->scope = $this->scope->assignVariable('classString', new ClassStringType()); + $this->scope = $this->scope->assignVariable('genericClassString', new GenericClassStringType(new ObjectType('Bar'))); + } - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this->scope, $expr, TypeSpecifierContext::createFalsey()); - $actualResult = $this->toReadableResult($specifiedTypes); - $this->assertSame($expectedNegatedResult, $actualResult, sprintf('if not (%s)', $this->printer->prettyPrintExpr($expr))); - } + /** + * @dataProvider dataCondition + * @param Expr $expr + * @param mixed[] $expectedPositiveResult + * @param mixed[] $expectedNegatedResult + */ + public function testCondition(Expr $expr, array $expectedPositiveResult, array $expectedNegatedResult): void + { + $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this->scope, $expr, TypeSpecifierContext::createTruthy()); + $actualResult = $this->toReadableResult($specifiedTypes); + $this->assertSame($expectedPositiveResult, $actualResult, sprintf('if (%s)', $this->printer->prettyPrintExpr($expr))); - public function dataCondition(): array - { - return [ - [ - $this->createFunctionCall('is_int'), - ['$foo' => 'int'], - ['$foo' => '~int'], - ], - [ - $this->createFunctionCall('is_numeric'), - ['$foo' => 'float|int|(string&numeric)'], - ['$foo' => '~float|int'], - ], - [ - $this->createFunctionCall('is_scalar'), - ['$foo' => 'bool|float|int|string'], - ['$foo' => '~bool|float|int|string'], - ], - [ - new Expr\BinaryOp\BooleanAnd( - $this->createFunctionCall('is_int'), - $this->createFunctionCall('random') - ), - ['$foo' => 'int'], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_int'), - $this->createFunctionCall('random') - ), - [], - ['$foo' => '~int'], - ], - [ - new Expr\BinaryOp\LogicalAnd( - $this->createFunctionCall('is_int'), - $this->createFunctionCall('random') - ), - ['$foo' => 'int'], - [], - ], - [ - new Expr\BinaryOp\LogicalOr( - $this->createFunctionCall('is_int'), - $this->createFunctionCall('random') - ), - [], - ['$foo' => '~int'], - ], - [ - new Expr\BooleanNot($this->createFunctionCall('is_int')), - ['$foo' => '~int'], - ['$foo' => 'int'], - ], + $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this->scope, $expr, TypeSpecifierContext::createFalsey()); + $actualResult = $this->toReadableResult($specifiedTypes); + $this->assertSame($expectedNegatedResult, $actualResult, sprintf('if not (%s)', $this->printer->prettyPrintExpr($expr))); + } - [ - new Expr\BinaryOp\BooleanAnd( - new Expr\BooleanNot($this->createFunctionCall('is_int')), - $this->createFunctionCall('random') - ), - ['$foo' => '~int'], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - new Expr\BooleanNot($this->createFunctionCall('is_int')), - $this->createFunctionCall('random') - ), - [], - ['$foo' => 'int'], - ], - [ - new Expr\BooleanNot(new Expr\BooleanNot($this->createFunctionCall('is_int'))), - ['$foo' => 'int'], - ['$foo' => '~int'], - ], - [ - $this->createInstanceOf('Foo'), - ['$foo' => 'Foo'], - ['$foo' => '~Foo'], - ], - [ - new Expr\BooleanNot($this->createInstanceOf('Foo')), - ['$foo' => '~Foo'], - ['$foo' => 'Foo'], - ], - [ - new Expr\Instanceof_( - new Variable('foo'), - new Variable('className') - ), - ['$foo' => 'object'], - [], - ], - [ - new Equal( - new FuncCall(new Name('get_class'), [ - new Arg(new Variable('foo')), - ]), - new String_('Foo') - ), - ['$foo' => 'Foo'], - ['$foo' => '~Foo'], - ], - [ - new Equal( - new String_('Foo'), - new FuncCall(new Name('get_class'), [ - new Arg(new Variable('foo')), - ]) - ), - ['$foo' => 'Foo'], - ['$foo' => '~Foo'], - ], - [ - new BooleanNot( - new Expr\Instanceof_( - new Variable('foo'), - new Variable('className') - ) - ), - [], - ['$foo' => 'object'], - ], - [ - new Variable('foo'), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new Variable('foo'), - $this->createFunctionCall('random') - ), - ['$foo' => self::SURE_NOT_FALSEY], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - new Variable('foo'), - $this->createFunctionCall('random') - ), - [], - ['$foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\BooleanNot(new Variable('bar')), - ['$bar' => self::SURE_NOT_TRUTHY], - ['$bar' => self::SURE_NOT_FALSEY], - ], + public function dataCondition(): array + { + return [ + [ + $this->createFunctionCall('is_int'), + ['$foo' => 'int'], + ['$foo' => '~int'], + ], + [ + $this->createFunctionCall('is_numeric'), + ['$foo' => 'float|int|(string&numeric)'], + ['$foo' => '~float|int'], + ], + [ + $this->createFunctionCall('is_scalar'), + ['$foo' => 'bool|float|int|string'], + ['$foo' => '~bool|float|int|string'], + ], + [ + new Expr\BinaryOp\BooleanAnd( + $this->createFunctionCall('is_int'), + $this->createFunctionCall('random') + ), + ['$foo' => 'int'], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_int'), + $this->createFunctionCall('random') + ), + [], + ['$foo' => '~int'], + ], + [ + new Expr\BinaryOp\LogicalAnd( + $this->createFunctionCall('is_int'), + $this->createFunctionCall('random') + ), + ['$foo' => 'int'], + [], + ], + [ + new Expr\BinaryOp\LogicalOr( + $this->createFunctionCall('is_int'), + $this->createFunctionCall('random') + ), + [], + ['$foo' => '~int'], + ], + [ + new Expr\BooleanNot($this->createFunctionCall('is_int')), + ['$foo' => '~int'], + ['$foo' => 'int'], + ], - [ - new PropertyFetch(new Variable('this'), 'foo'), - ['$this->foo' => self::SURE_NOT_FALSEY], - ['$this->foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new PropertyFetch(new Variable('this'), 'foo'), - $this->createFunctionCall('random') - ), - ['$this->foo' => self::SURE_NOT_FALSEY], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - new PropertyFetch(new Variable('this'), 'foo'), - $this->createFunctionCall('random') - ), - [], - ['$this->foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\BooleanNot(new PropertyFetch(new Variable('this'), 'foo')), - ['$this->foo' => self::SURE_NOT_TRUTHY], - ['$this->foo' => self::SURE_NOT_FALSEY], - ], + [ + new Expr\BinaryOp\BooleanAnd( + new Expr\BooleanNot($this->createFunctionCall('is_int')), + $this->createFunctionCall('random') + ), + ['$foo' => '~int'], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + new Expr\BooleanNot($this->createFunctionCall('is_int')), + $this->createFunctionCall('random') + ), + [], + ['$foo' => 'int'], + ], + [ + new Expr\BooleanNot(new Expr\BooleanNot($this->createFunctionCall('is_int'))), + ['$foo' => 'int'], + ['$foo' => '~int'], + ], + [ + $this->createInstanceOf('Foo'), + ['$foo' => 'Foo'], + ['$foo' => '~Foo'], + ], + [ + new Expr\BooleanNot($this->createInstanceOf('Foo')), + ['$foo' => '~Foo'], + ['$foo' => 'Foo'], + ], + [ + new Expr\Instanceof_( + new Variable('foo'), + new Variable('className') + ), + ['$foo' => 'object'], + [], + ], + [ + new Equal( + new FuncCall(new Name('get_class'), [ + new Arg(new Variable('foo')), + ]), + new String_('Foo') + ), + ['$foo' => 'Foo'], + ['$foo' => '~Foo'], + ], + [ + new Equal( + new String_('Foo'), + new FuncCall(new Name('get_class'), [ + new Arg(new Variable('foo')), + ]) + ), + ['$foo' => 'Foo'], + ['$foo' => '~Foo'], + ], + [ + new BooleanNot( + new Expr\Instanceof_( + new Variable('foo'), + new Variable('className') + ) + ), + [], + ['$foo' => 'object'], + ], + [ + new Variable('foo'), + ['$foo' => self::SURE_NOT_FALSEY], + ['$foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new Variable('foo'), + $this->createFunctionCall('random') + ), + ['$foo' => self::SURE_NOT_FALSEY], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + new Variable('foo'), + $this->createFunctionCall('random') + ), + [], + ['$foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\BooleanNot(new Variable('bar')), + ['$bar' => self::SURE_NOT_TRUTHY], + ['$bar' => self::SURE_NOT_FALSEY], + ], - [ - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_int'), - $this->createFunctionCall('is_string') - ), - ['$foo' => 'int|string'], - ['$foo' => '~int|string'], - ], - [ - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_int'), - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_string'), - $this->createFunctionCall('is_bool') - ) - ), - ['$foo' => 'bool|int|string'], - ['$foo' => '~bool|int|string'], - ], - [ - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_int', 'foo'), - $this->createFunctionCall('is_string', 'bar') - ), - [], - ['$foo' => '~int', '$bar' => '~string'], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new Expr\BinaryOp\BooleanOr( - $this->createFunctionCall('is_int', 'foo'), - $this->createFunctionCall('is_string', 'foo') - ), - $this->createFunctionCall('random') - ), - ['$foo' => 'int|string'], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - new Expr\BinaryOp\BooleanAnd( - $this->createFunctionCall('is_int', 'foo'), - $this->createFunctionCall('is_string', 'foo') - ), - $this->createFunctionCall('random') - ), - [], - ['$foo' => '~*NEVER*'], - ], - [ - new Expr\BinaryOp\BooleanOr( - new Expr\BinaryOp\BooleanAnd( - $this->createFunctionCall('is_int', 'foo'), - $this->createFunctionCall('is_string', 'bar') - ), - $this->createFunctionCall('random') - ), - [], - [], - ], - [ - new Expr\BinaryOp\BooleanOr( - new Expr\BinaryOp\BooleanAnd( - new Expr\BooleanNot($this->createFunctionCall('is_int', 'foo')), - new Expr\BooleanNot($this->createFunctionCall('is_string', 'foo')) - ), - $this->createFunctionCall('random') - ), - [], - ['$foo' => 'int|string'], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new Expr\BinaryOp\BooleanOr( - new Expr\BooleanNot($this->createFunctionCall('is_int', 'foo')), - new Expr\BooleanNot($this->createFunctionCall('is_string', 'foo')) - ), - $this->createFunctionCall('random') - ), - ['$foo' => '~*NEVER*'], - [], - ], + [ + new PropertyFetch(new Variable('this'), 'foo'), + ['$this->foo' => self::SURE_NOT_FALSEY], + ['$this->foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new PropertyFetch(new Variable('this'), 'foo'), + $this->createFunctionCall('random') + ), + ['$this->foo' => self::SURE_NOT_FALSEY], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + new PropertyFetch(new Variable('this'), 'foo'), + $this->createFunctionCall('random') + ), + [], + ['$this->foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\BooleanNot(new PropertyFetch(new Variable('this'), 'foo')), + ['$this->foo' => self::SURE_NOT_TRUTHY], + ['$this->foo' => self::SURE_NOT_FALSEY], + ], - [ - new Identical( - new Variable('foo'), - new Expr\ConstFetch(new Name('true')) - ), - ['$foo' => 'true & ~' . self::FALSEY_TYPE_DESCRIPTION], - ['$foo' => '~true'], - ], - [ - new Identical( - new Variable('foo'), - new Expr\ConstFetch(new Name('false')) - ), - ['$foo' => 'false & ~' . self::TRUTHY_TYPE_DESCRIPTION], - ['$foo' => '~false'], - ], - [ - new Identical( - $this->createFunctionCall('is_int'), - new Expr\ConstFetch(new Name('true')) - ), - ['is_int($foo)' => 'true', '$foo' => 'int'], - ['is_int($foo)' => '~true', '$foo' => '~int'], - ], - [ - new Identical( - $this->createFunctionCall('is_int'), - new Expr\ConstFetch(new Name('false')) - ), - ['is_int($foo)' => 'false', '$foo' => '~int'], - ['$foo' => 'int', 'is_int($foo)' => '~false'], - ], - [ - new Equal( - $this->createFunctionCall('is_int'), - new Expr\ConstFetch(new Name('true')) - ), - ['$foo' => 'int'], - ['$foo' => '~int'], - ], - [ - new Equal( - $this->createFunctionCall('is_int'), - new Expr\ConstFetch(new Name('false')) - ), - ['$foo' => '~int'], - ['$foo' => 'int'], - ], - [ - new Equal( - new Variable('foo'), - new Expr\ConstFetch(new Name('false')) - ), - ['$foo' => self::SURE_NOT_TRUTHY], - ['$foo' => self::SURE_NOT_FALSEY], - ], - [ - new Equal( - new Variable('foo'), - new Expr\ConstFetch(new Name('null')) - ), - ['$foo' => self::SURE_NOT_TRUTHY], - ['$foo' => self::SURE_NOT_FALSEY], - ], - [ - new Expr\BinaryOp\Identical( - new Variable('foo'), - new Variable('bar') - ), - ['$foo' => 'Bar', '$bar' => 'Bar'], - [], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new String_('Foo')), - ]), - ['$foo' => 'Foo'], - ['$foo' => '~Foo'], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Variable('className')), - ]), - ['$foo' => 'object'], - [], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Expr\ClassConstFetch( - new Name('static'), - 'class' - )), - ]), - ['$foo' => 'static(DateTime)'], - ['$foo' => '~static(DateTime)'], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Variable('classString')), - ]), - ['$foo' => 'object'], - [], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Variable('genericClassString')), - ]), - ['$foo' => 'Bar'], - ['$foo' => '~Bar'], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new String_('Foo')), - new Arg(new Expr\ConstFetch(new Name('true'))), - ]), - ['$foo' => 'class-string|Foo'], - ['$foo' => '~Foo'], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Variable('className')), - new Arg(new Expr\ConstFetch(new Name('true'))), - ]), - ['$foo' => 'class-string|object'], - [], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new String_('Foo')), - new Arg(new Variable('unknown')), - ]), - ['$foo' => 'class-string|Foo'], - ['$foo' => '~Foo'], - ], - [ - new FuncCall(new Name('is_a'), [ - new Arg(new Variable('foo')), - new Arg(new Variable('className')), - new Arg(new Variable('unknown')), - ]), - ['$foo' => 'class-string|object'], - [], - ], - [ - new Expr\Assign( - new Variable('foo'), - new Variable('stringOrNull') - ), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\Assign( - new Variable('foo'), - new Variable('stringOrFalse') - ), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\Assign( - new Variable('foo'), - new Variable('bar') - ), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], - ], - [ - new Expr\Isset_([ - new Variable('stringOrNull'), - new Variable('barOrNull'), - ]), - [ - '$stringOrNull' => '~null', - '$barOrNull' => '~null', - ], - [ - 'isset($stringOrNull, $barOrNull)' => self::SURE_NOT_TRUTHY, - ], - ], - [ - new Expr\BooleanNot(new Expr\Empty_(new Variable('stringOrNull'))), - [ - '$stringOrNull' => '~false|null', - ], - [ - 'empty($stringOrNull)' => self::SURE_NOT_FALSEY, - ], - ], - [ - new Expr\BinaryOp\Identical( - new Variable('foo'), - new LNumber(123) - ), - [ - '$foo' => '123', - 123 => '123', - ], - ['$foo' => '~123'], - ], - [ - new Expr\Empty_(new Variable('array')), - [ - '$array' => '~nonEmpty', - ], - [ - '$array' => 'nonEmpty & ~false|null', - ], - ], - [ - new BooleanNot(new Expr\Empty_(new Variable('array'))), - [ - '$array' => 'nonEmpty & ~false|null', - ], - [ - '$array' => '~nonEmpty', - ], - ], - [ - new FuncCall(new Name('count'), [ - new Arg(new Variable('array')), - ]), - [ - '$array' => 'nonEmpty', - ], - [ - '$array' => '~nonEmpty', - ], - ], - [ - new BooleanNot(new FuncCall(new Name('count'), [ - new Arg(new Variable('array')), - ])), - [ - '$array' => '~nonEmpty', - ], - [ - '$array' => 'nonEmpty', - ], - ], - [ - new Variable('foo'), - [ - '$foo' => self::SURE_NOT_FALSEY, - ], - [ - '$foo' => self::SURE_NOT_TRUTHY, - ], - ], - [ - new Variable('array'), - [ - '$array' => self::SURE_NOT_FALSEY, - ], - [ - '$array' => self::SURE_NOT_TRUTHY, - ], - ], - [ - new Equal( - new Expr\Instanceof_( - new Variable('foo'), - new Variable('className') - ), - new LNumber(1) - ), - ['$foo' => 'object'], - [], - ], - [ - new Equal( - new Expr\Instanceof_( - new Variable('foo'), - new Variable('className') - ), - new LNumber(0) - ), - [], - [ - '$foo' => 'object', - ], - ], - [ - new Expr\Isset_( - [ - new PropertyFetch(new Variable('foo'), new Identifier('bar')), - ] - ), - [ - '$foo' => 'object&hasProperty(bar) & ~null', - '$foo->bar' => '~null', - ], - [ - 'isset($foo->bar)' => self::SURE_NOT_TRUTHY, - ], - ], - [ - new Expr\Isset_( - [ - new Expr\StaticPropertyFetch(new Name('Foo'), new VarLikeIdentifier('bar')), - ] - ), - [ - 'Foo::$bar' => '~null', - ], - [ - 'isset(Foo::$bar)' => self::SURE_NOT_TRUTHY, - ], - ], - [ - new Identical( - new Variable('barOrNull'), - new Expr\ConstFetch(new Name('null')) - ), - [ - '$barOrNull' => 'null', - ], - [ - '$barOrNull' => '~null', - ], - ], - [ - new Identical( - new Expr\Assign( - new Variable('notNullBar'), - new Variable('barOrNull') - ), - new Expr\ConstFetch(new Name('null')) - ), - [ - '$notNullBar' => 'null', - ], - [ - '$notNullBar' => '~null', - ], - ], - [ - new NotIdentical( - new Variable('barOrNull'), - new Expr\ConstFetch(new Name('null')) - ), - [ - '$barOrNull' => '~null', - ], - [ - '$barOrNull' => 'null', - ], - ], - [ - new Expr\BinaryOp\Smaller( - new Variable('n'), - new LNumber(3) - ), - [ - '$n' => 'mixed~int<3, max>|true', - ], - [ - '$n' => 'mixed~int|false|null', - ], - ], - [ - new Expr\BinaryOp\Smaller( - new Variable('n'), - new LNumber(PHP_INT_MIN) - ), - [ - '$n' => 'mixed~int<' . PHP_INT_MIN . ', max>|true', - ], - [ - '$n' => 'mixed~false|null', - ], - ], - [ - new Expr\BinaryOp\Greater( - new Variable('n'), - new LNumber(PHP_INT_MAX) - ), - [ - '$n' => 'mixed~bool|int|null', - ], - [ - '$n' => 'mixed', - ], - ], - [ - new Expr\BinaryOp\SmallerOrEqual( - new Variable('n'), - new LNumber(PHP_INT_MIN) - ), - [ - '$n' => 'mixed~int<' . (PHP_INT_MIN + 1) . ', max>', - ], - [ - '$n' => 'mixed~bool|int|null', - ], - ], - [ - new Expr\BinaryOp\GreaterOrEqual( - new Variable('n'), - new LNumber(PHP_INT_MAX) - ), - [ - '$n' => 'mixed~int|false|null', - ], - [ - '$n' => 'mixed~int<' . PHP_INT_MAX . ', max>|true', - ], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new Expr\BinaryOp\GreaterOrEqual( - new Variable('n'), - new LNumber(3) - ), - new Expr\BinaryOp\SmallerOrEqual( - new Variable('n'), - new LNumber(5) - ) - ), - [ - '$n' => 'mixed~int|int<6, max>|false|null', - ], - [ - '$n' => 'mixed~int<3, 5>|true', - ], - ], - [ - new Expr\BinaryOp\BooleanAnd( - new Expr\Assign( - new Variable('foo'), - new LNumber(1) - ), - new Expr\BinaryOp\SmallerOrEqual( - new Variable('n'), - new LNumber(5) - ) - ), - [ - '$n' => 'mixed~int<6, max>', - '$foo' => self::SURE_NOT_FALSEY, - ], - [], - ], - [ - new NotIdentical( - new Expr\Assign( - new Variable('notNullBar'), - new Variable('barOrNull') - ), - new Expr\ConstFetch(new Name('null')) - ), - [ - '$notNullBar' => '~null', - ], - [ - '$notNullBar' => 'null', - ], - ], - [ - new Identical( - new Variable('barOrFalse'), - new Expr\ConstFetch(new Name('false')) - ), - [ - '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, - ], - [ - '$barOrFalse' => '~false', - ], - ], - [ - new Identical( - new Expr\Assign( - new Variable('notFalseBar'), - new Variable('barOrFalse') - ), - new Expr\ConstFetch(new Name('false')) - ), - [ - '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, - ], - [ - '$notFalseBar' => '~false', - ], - ], - [ - new NotIdentical( - new Variable('barOrFalse'), - new Expr\ConstFetch(new Name('false')) - ), - [ - '$barOrFalse' => '~false', - ], - [ - '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, - ], - ], - [ - new NotIdentical( - new Expr\Assign( - new Variable('notFalseBar'), - new Variable('barOrFalse') - ), - new Expr\ConstFetch(new Name('false')) - ), - [ - '$notFalseBar' => '~false', - ], - [ - '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, - ], - ], - [ - new Expr\Instanceof_( - new Expr\Assign( - new Variable('notFalseBar'), - new Variable('barOrFalse') - ), - new Name('Bar') - ), - [ - '$notFalseBar' => 'Bar', - ], - [ - '$notFalseBar' => '~Bar', - ], - ], - [ - new Expr\BinaryOp\BooleanOr( - new FuncCall(new Name('array_key_exists'), [ - new Arg(new String_('foo')), - new Arg(new Variable('array')), - ]), - new FuncCall(new Name('array_key_exists'), [ - new Arg(new String_('bar')), - new Arg(new Variable('array')), - ]) - ), - [ - '$array' => 'array', - ], - [ - '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', - ], - ], - [ - new BooleanNot(new Expr\BinaryOp\BooleanOr( - new FuncCall(new Name('array_key_exists'), [ - new Arg(new String_('foo')), - new Arg(new Variable('array')), - ]), - new FuncCall(new Name('array_key_exists'), [ - new Arg(new String_('bar')), - new Arg(new Variable('array')), - ]) - )), - [ - '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', - ], - [ - '$array' => 'array', - ], - ], - [ - new FuncCall(new Name('array_key_exists'), [ - new Arg(new String_('foo')), - new Arg(new Variable('array')), - ]), - [ - '$array' => 'array&hasOffset(\'foo\')', - ], - [ - '$array' => '~hasOffset(\'foo\')', - ], - ], - [ - new FuncCall(new Name('is_subclass_of'), [ - new Arg(new Variable('string')), - new Arg(new Variable('stringOrNull')), - ]), - [ - '$string' => 'class-string|object', - ], - [], - ], - [ - new FuncCall(new Name('is_subclass_of'), [ - new Arg(new Variable('string')), - new Arg(new Variable('stringOrNull')), - new Arg(new Expr\ConstFetch(new Name('false'))), - ]), - [ - '$string' => 'object', - ], - [], - ], - ]; - } + [ + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_int'), + $this->createFunctionCall('is_string') + ), + ['$foo' => 'int|string'], + ['$foo' => '~int|string'], + ], + [ + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_int'), + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_string'), + $this->createFunctionCall('is_bool') + ) + ), + ['$foo' => 'bool|int|string'], + ['$foo' => '~bool|int|string'], + ], + [ + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_int', 'foo'), + $this->createFunctionCall('is_string', 'bar') + ), + [], + ['$foo' => '~int', '$bar' => '~string'], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new Expr\BinaryOp\BooleanOr( + $this->createFunctionCall('is_int', 'foo'), + $this->createFunctionCall('is_string', 'foo') + ), + $this->createFunctionCall('random') + ), + ['$foo' => 'int|string'], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + new Expr\BinaryOp\BooleanAnd( + $this->createFunctionCall('is_int', 'foo'), + $this->createFunctionCall('is_string', 'foo') + ), + $this->createFunctionCall('random') + ), + [], + ['$foo' => '~*NEVER*'], + ], + [ + new Expr\BinaryOp\BooleanOr( + new Expr\BinaryOp\BooleanAnd( + $this->createFunctionCall('is_int', 'foo'), + $this->createFunctionCall('is_string', 'bar') + ), + $this->createFunctionCall('random') + ), + [], + [], + ], + [ + new Expr\BinaryOp\BooleanOr( + new Expr\BinaryOp\BooleanAnd( + new Expr\BooleanNot($this->createFunctionCall('is_int', 'foo')), + new Expr\BooleanNot($this->createFunctionCall('is_string', 'foo')) + ), + $this->createFunctionCall('random') + ), + [], + ['$foo' => 'int|string'], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new Expr\BinaryOp\BooleanOr( + new Expr\BooleanNot($this->createFunctionCall('is_int', 'foo')), + new Expr\BooleanNot($this->createFunctionCall('is_string', 'foo')) + ), + $this->createFunctionCall('random') + ), + ['$foo' => '~*NEVER*'], + [], + ], - /** - * @param \PHPStan\Analyser\SpecifiedTypes $specifiedTypes - * @return mixed[] - */ - private function toReadableResult(SpecifiedTypes $specifiedTypes): array - { - $typesDescription = []; + [ + new Identical( + new Variable('foo'), + new Expr\ConstFetch(new Name('true')) + ), + ['$foo' => 'true & ~' . self::FALSEY_TYPE_DESCRIPTION], + ['$foo' => '~true'], + ], + [ + new Identical( + new Variable('foo'), + new Expr\ConstFetch(new Name('false')) + ), + ['$foo' => 'false & ~' . self::TRUTHY_TYPE_DESCRIPTION], + ['$foo' => '~false'], + ], + [ + new Identical( + $this->createFunctionCall('is_int'), + new Expr\ConstFetch(new Name('true')) + ), + ['is_int($foo)' => 'true', '$foo' => 'int'], + ['is_int($foo)' => '~true', '$foo' => '~int'], + ], + [ + new Identical( + $this->createFunctionCall('is_int'), + new Expr\ConstFetch(new Name('false')) + ), + ['is_int($foo)' => 'false', '$foo' => '~int'], + ['$foo' => 'int', 'is_int($foo)' => '~false'], + ], + [ + new Equal( + $this->createFunctionCall('is_int'), + new Expr\ConstFetch(new Name('true')) + ), + ['$foo' => 'int'], + ['$foo' => '~int'], + ], + [ + new Equal( + $this->createFunctionCall('is_int'), + new Expr\ConstFetch(new Name('false')) + ), + ['$foo' => '~int'], + ['$foo' => 'int'], + ], + [ + new Equal( + new Variable('foo'), + new Expr\ConstFetch(new Name('false')) + ), + ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_NOT_FALSEY], + ], + [ + new Equal( + new Variable('foo'), + new Expr\ConstFetch(new Name('null')) + ), + ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_NOT_FALSEY], + ], + [ + new Expr\BinaryOp\Identical( + new Variable('foo'), + new Variable('bar') + ), + ['$foo' => 'Bar', '$bar' => 'Bar'], + [], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new String_('Foo')), + ]), + ['$foo' => 'Foo'], + ['$foo' => '~Foo'], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Variable('className')), + ]), + ['$foo' => 'object'], + [], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Expr\ClassConstFetch( + new Name('static'), + 'class' + )), + ]), + ['$foo' => 'static(DateTime)'], + ['$foo' => '~static(DateTime)'], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Variable('classString')), + ]), + ['$foo' => 'object'], + [], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Variable('genericClassString')), + ]), + ['$foo' => 'Bar'], + ['$foo' => '~Bar'], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new String_('Foo')), + new Arg(new Expr\ConstFetch(new Name('true'))), + ]), + ['$foo' => 'class-string|Foo'], + ['$foo' => '~Foo'], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Variable('className')), + new Arg(new Expr\ConstFetch(new Name('true'))), + ]), + ['$foo' => 'class-string|object'], + [], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new String_('Foo')), + new Arg(new Variable('unknown')), + ]), + ['$foo' => 'class-string|Foo'], + ['$foo' => '~Foo'], + ], + [ + new FuncCall(new Name('is_a'), [ + new Arg(new Variable('foo')), + new Arg(new Variable('className')), + new Arg(new Variable('unknown')), + ]), + ['$foo' => 'class-string|object'], + [], + ], + [ + new Expr\Assign( + new Variable('foo'), + new Variable('stringOrNull') + ), + ['$foo' => self::SURE_NOT_FALSEY], + ['$foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\Assign( + new Variable('foo'), + new Variable('stringOrFalse') + ), + ['$foo' => self::SURE_NOT_FALSEY], + ['$foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\Assign( + new Variable('foo'), + new Variable('bar') + ), + ['$foo' => self::SURE_NOT_FALSEY], + ['$foo' => self::SURE_NOT_TRUTHY], + ], + [ + new Expr\Isset_([ + new Variable('stringOrNull'), + new Variable('barOrNull'), + ]), + [ + '$stringOrNull' => '~null', + '$barOrNull' => '~null', + ], + [ + 'isset($stringOrNull, $barOrNull)' => self::SURE_NOT_TRUTHY, + ], + ], + [ + new Expr\BooleanNot(new Expr\Empty_(new Variable('stringOrNull'))), + [ + '$stringOrNull' => '~false|null', + ], + [ + 'empty($stringOrNull)' => self::SURE_NOT_FALSEY, + ], + ], + [ + new Expr\BinaryOp\Identical( + new Variable('foo'), + new LNumber(123) + ), + [ + '$foo' => '123', + 123 => '123', + ], + ['$foo' => '~123'], + ], + [ + new Expr\Empty_(new Variable('array')), + [ + '$array' => '~nonEmpty', + ], + [ + '$array' => 'nonEmpty & ~false|null', + ], + ], + [ + new BooleanNot(new Expr\Empty_(new Variable('array'))), + [ + '$array' => 'nonEmpty & ~false|null', + ], + [ + '$array' => '~nonEmpty', + ], + ], + [ + new FuncCall(new Name('count'), [ + new Arg(new Variable('array')), + ]), + [ + '$array' => 'nonEmpty', + ], + [ + '$array' => '~nonEmpty', + ], + ], + [ + new BooleanNot(new FuncCall(new Name('count'), [ + new Arg(new Variable('array')), + ])), + [ + '$array' => '~nonEmpty', + ], + [ + '$array' => 'nonEmpty', + ], + ], + [ + new Variable('foo'), + [ + '$foo' => self::SURE_NOT_FALSEY, + ], + [ + '$foo' => self::SURE_NOT_TRUTHY, + ], + ], + [ + new Variable('array'), + [ + '$array' => self::SURE_NOT_FALSEY, + ], + [ + '$array' => self::SURE_NOT_TRUTHY, + ], + ], + [ + new Equal( + new Expr\Instanceof_( + new Variable('foo'), + new Variable('className') + ), + new LNumber(1) + ), + ['$foo' => 'object'], + [], + ], + [ + new Equal( + new Expr\Instanceof_( + new Variable('foo'), + new Variable('className') + ), + new LNumber(0) + ), + [], + [ + '$foo' => 'object', + ], + ], + [ + new Expr\Isset_( + [ + new PropertyFetch(new Variable('foo'), new Identifier('bar')), + ] + ), + [ + '$foo' => 'object&hasProperty(bar) & ~null', + '$foo->bar' => '~null', + ], + [ + 'isset($foo->bar)' => self::SURE_NOT_TRUTHY, + ], + ], + [ + new Expr\Isset_( + [ + new Expr\StaticPropertyFetch(new Name('Foo'), new VarLikeIdentifier('bar')), + ] + ), + [ + 'Foo::$bar' => '~null', + ], + [ + 'isset(Foo::$bar)' => self::SURE_NOT_TRUTHY, + ], + ], + [ + new Identical( + new Variable('barOrNull'), + new Expr\ConstFetch(new Name('null')) + ), + [ + '$barOrNull' => 'null', + ], + [ + '$barOrNull' => '~null', + ], + ], + [ + new Identical( + new Expr\Assign( + new Variable('notNullBar'), + new Variable('barOrNull') + ), + new Expr\ConstFetch(new Name('null')) + ), + [ + '$notNullBar' => 'null', + ], + [ + '$notNullBar' => '~null', + ], + ], + [ + new NotIdentical( + new Variable('barOrNull'), + new Expr\ConstFetch(new Name('null')) + ), + [ + '$barOrNull' => '~null', + ], + [ + '$barOrNull' => 'null', + ], + ], + [ + new Expr\BinaryOp\Smaller( + new Variable('n'), + new LNumber(3) + ), + [ + '$n' => 'mixed~int<3, max>|true', + ], + [ + '$n' => 'mixed~int|false|null', + ], + ], + [ + new Expr\BinaryOp\Smaller( + new Variable('n'), + new LNumber(PHP_INT_MIN) + ), + [ + '$n' => 'mixed~int<' . PHP_INT_MIN . ', max>|true', + ], + [ + '$n' => 'mixed~false|null', + ], + ], + [ + new Expr\BinaryOp\Greater( + new Variable('n'), + new LNumber(PHP_INT_MAX) + ), + [ + '$n' => 'mixed~bool|int|null', + ], + [ + '$n' => 'mixed', + ], + ], + [ + new Expr\BinaryOp\SmallerOrEqual( + new Variable('n'), + new LNumber(PHP_INT_MIN) + ), + [ + '$n' => 'mixed~int<' . (PHP_INT_MIN + 1) . ', max>', + ], + [ + '$n' => 'mixed~bool|int|null', + ], + ], + [ + new Expr\BinaryOp\GreaterOrEqual( + new Variable('n'), + new LNumber(PHP_INT_MAX) + ), + [ + '$n' => 'mixed~int|false|null', + ], + [ + '$n' => 'mixed~int<' . PHP_INT_MAX . ', max>|true', + ], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new Expr\BinaryOp\GreaterOrEqual( + new Variable('n'), + new LNumber(3) + ), + new Expr\BinaryOp\SmallerOrEqual( + new Variable('n'), + new LNumber(5) + ) + ), + [ + '$n' => 'mixed~int|int<6, max>|false|null', + ], + [ + '$n' => 'mixed~int<3, 5>|true', + ], + ], + [ + new Expr\BinaryOp\BooleanAnd( + new Expr\Assign( + new Variable('foo'), + new LNumber(1) + ), + new Expr\BinaryOp\SmallerOrEqual( + new Variable('n'), + new LNumber(5) + ) + ), + [ + '$n' => 'mixed~int<6, max>', + '$foo' => self::SURE_NOT_FALSEY, + ], + [], + ], + [ + new NotIdentical( + new Expr\Assign( + new Variable('notNullBar'), + new Variable('barOrNull') + ), + new Expr\ConstFetch(new Name('null')) + ), + [ + '$notNullBar' => '~null', + ], + [ + '$notNullBar' => 'null', + ], + ], + [ + new Identical( + new Variable('barOrFalse'), + new Expr\ConstFetch(new Name('false')) + ), + [ + '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, + ], + [ + '$barOrFalse' => '~false', + ], + ], + [ + new Identical( + new Expr\Assign( + new Variable('notFalseBar'), + new Variable('barOrFalse') + ), + new Expr\ConstFetch(new Name('false')) + ), + [ + '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, + ], + [ + '$notFalseBar' => '~false', + ], + ], + [ + new NotIdentical( + new Variable('barOrFalse'), + new Expr\ConstFetch(new Name('false')) + ), + [ + '$barOrFalse' => '~false', + ], + [ + '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, + ], + ], + [ + new NotIdentical( + new Expr\Assign( + new Variable('notFalseBar'), + new Variable('barOrFalse') + ), + new Expr\ConstFetch(new Name('false')) + ), + [ + '$notFalseBar' => '~false', + ], + [ + '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, + ], + ], + [ + new Expr\Instanceof_( + new Expr\Assign( + new Variable('notFalseBar'), + new Variable('barOrFalse') + ), + new Name('Bar') + ), + [ + '$notFalseBar' => 'Bar', + ], + [ + '$notFalseBar' => '~Bar', + ], + ], + [ + new Expr\BinaryOp\BooleanOr( + new FuncCall(new Name('array_key_exists'), [ + new Arg(new String_('foo')), + new Arg(new Variable('array')), + ]), + new FuncCall(new Name('array_key_exists'), [ + new Arg(new String_('bar')), + new Arg(new Variable('array')), + ]) + ), + [ + '$array' => 'array', + ], + [ + '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', + ], + ], + [ + new BooleanNot(new Expr\BinaryOp\BooleanOr( + new FuncCall(new Name('array_key_exists'), [ + new Arg(new String_('foo')), + new Arg(new Variable('array')), + ]), + new FuncCall(new Name('array_key_exists'), [ + new Arg(new String_('bar')), + new Arg(new Variable('array')), + ]) + )), + [ + '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', + ], + [ + '$array' => 'array', + ], + ], + [ + new FuncCall(new Name('array_key_exists'), [ + new Arg(new String_('foo')), + new Arg(new Variable('array')), + ]), + [ + '$array' => 'array&hasOffset(\'foo\')', + ], + [ + '$array' => '~hasOffset(\'foo\')', + ], + ], + [ + new FuncCall(new Name('is_subclass_of'), [ + new Arg(new Variable('string')), + new Arg(new Variable('stringOrNull')), + ]), + [ + '$string' => 'class-string|object', + ], + [], + ], + [ + new FuncCall(new Name('is_subclass_of'), [ + new Arg(new Variable('string')), + new Arg(new Variable('stringOrNull')), + new Arg(new Expr\ConstFetch(new Name('false'))), + ]), + [ + '$string' => 'object', + ], + [], + ], + ]; + } - foreach ($specifiedTypes->getSureTypes() as $exprString => [$exprNode, $exprType]) { - $typesDescription[$exprString][] = $exprType->describe(VerbosityLevel::precise()); - } + /** + * @param \PHPStan\Analyser\SpecifiedTypes $specifiedTypes + * @return mixed[] + */ + private function toReadableResult(SpecifiedTypes $specifiedTypes): array + { + $typesDescription = []; - foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$exprNode, $exprType]) { - $typesDescription[$exprString][] = '~' . $exprType->describe(VerbosityLevel::precise()); - } + foreach ($specifiedTypes->getSureTypes() as $exprString => [$exprNode, $exprType]) { + $typesDescription[$exprString][] = $exprType->describe(VerbosityLevel::precise()); + } - $descriptions = []; - foreach ($typesDescription as $exprString => $exprTypes) { - $descriptions[$exprString] = implode(' & ', $exprTypes); - } + foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$exprNode, $exprType]) { + $typesDescription[$exprString][] = '~' . $exprType->describe(VerbosityLevel::precise()); + } - return $descriptions; - } + $descriptions = []; + foreach ($typesDescription as $exprString => $exprTypes) { + $descriptions[$exprString] = implode(' & ', $exprTypes); + } - private function createInstanceOf(string $className, string $variableName = 'foo'): Expr\Instanceof_ - { - return new Expr\Instanceof_(new Variable($variableName), new Name($className)); - } + return $descriptions; + } - private function createFunctionCall(string $functionName, string $variableName = 'foo'): FuncCall - { - return new FuncCall(new Name($functionName), [new Arg(new Variable($variableName))]); - } + private function createInstanceOf(string $className, string $variableName = 'foo'): Expr\Instanceof_ + { + return new Expr\Instanceof_(new Variable($variableName), new Name($className)); + } + private function createFunctionCall(string $functionName, string $variableName = 'foo'): FuncCall + { + return new FuncCall(new Name($functionName), [new Arg(new Variable($variableName))]); + } } diff --git a/tests/PHPStan/Analyser/data/AnonymousClassesWithComments.php b/tests/PHPStan/Analyser/data/AnonymousClassesWithComments.php index 3ee8656661..b5fcdfc3dd 100644 --- a/tests/PHPStan/Analyser/data/AnonymousClassesWithComments.php +++ b/tests/PHPStan/Analyser/data/AnonymousClassesWithComments.php @@ -1,31 +1,25 @@ doBar(); - } - +new class() { + public function doFoo(): void + { + $this->doBar(); + } }; /* Test comment */ -new class () { - - public function doFoo(): void - { - $this->doBar(); - } - +new class() { + public function doFoo(): void + { + $this->doBar(); + } }; /** Test comment */ -new class () { - - public function doFoo(): void - { - $this->doBar(); - } - +new class() { + public function doFoo(): void + { + $this->doBar(); + } }; diff --git a/tests/PHPStan/Analyser/data/ClassWithUnknownParent.php b/tests/PHPStan/Analyser/data/ClassWithUnknownParent.php index f1fe6422c9..d5123e8cde 100644 --- a/tests/PHPStan/Analyser/data/ClassWithUnknownParent.php +++ b/tests/PHPStan/Analyser/data/ClassWithUnknownParent.php @@ -1,6 +1,7 @@ -foo->foo(); - } - + public function doFoo(): void + { + $this->foo->foo(); + } } diff --git a/tests/PHPStan/Analyser/data/Foo-callable.php b/tests/PHPStan/Analyser/data/Foo-callable.php index 99ec3be957..cf93a91378 100644 --- a/tests/PHPStan/Analyser/data/Foo-callable.php +++ b/tests/PHPStan/Analyser/data/Foo-callable.php @@ -6,11 +6,10 @@ */ class Foo { - /** - * @param Foo|(callable(): mixed) $xxx - */ - public function abc($xxx): void - { - - } + /** + * @param Foo|(callable(): mixed) $xxx + */ + public function abc($xxx): void + { + } } diff --git a/tests/PHPStan/Analyser/data/Foo.php b/tests/PHPStan/Analyser/data/Foo.php index 718039b88e..7527d363fc 100644 --- a/tests/PHPStan/Analyser/data/Foo.php +++ b/tests/PHPStan/Analyser/data/Foo.php @@ -1,8 +1,9 @@ -doBar($this); - $bar->test(); - } - + $bar = $foo->doBar($this); + $bar->test(); + } } diff --git a/tests/PHPStan/Analyser/data/anonymous-function.php b/tests/PHPStan/Analyser/data/anonymous-function.php index e4cd17b411..58282345bc 100644 --- a/tests/PHPStan/Analyser/data/anonymous-function.php +++ b/tests/PHPStan/Analyser/data/anonymous-function.php @@ -3,8 +3,8 @@ namespace AnonymousFunction; function () { - $integer = 1; - function (string $str, ...$arr) use ($integer, $bar) { - die; - }; + $integer = 1; + function (string $str, ...$arr) use ($integer, $bar) { + die; + }; }; diff --git a/tests/PHPStan/Analyser/data/array-accessable.php b/tests/PHPStan/Analyser/data/array-accessable.php index e04bd8b48b..b6c87cff5e 100644 --- a/tests/PHPStan/Analyser/data/array-accessable.php +++ b/tests/PHPStan/Analyser/data/array-accessable.php @@ -4,54 +4,45 @@ class Foo implements \ArrayAccess { - - public function __construct() - { - die; - } - - /** - * @return string[] - */ - public function returnArrayOfStrings(): array - { - - } - - /** - * @return mixed - */ - public function returnMixed() - { - - } - - /** - * @return self|int[] - */ - public function returnSelfWithIterableInt(): self - { - - } - - public function offsetExists($offset) - { - - } - - public function offsetGet($offset): int - { - - } - - public function offsetSet($offset, $value) - { - - } - - public function offsetUnset($offset) - { - - } - + public function __construct() + { + die; + } + + /** + * @return string[] + */ + public function returnArrayOfStrings(): array + { + } + + /** + * @return mixed + */ + public function returnMixed() + { + } + + /** + * @return self|int[] + */ + public function returnSelfWithIterableInt(): self + { + } + + public function offsetExists($offset) + { + } + + public function offsetGet($offset): int + { + } + + public function offsetSet($offset, $value) + { + } + + public function offsetUnset($offset) + { + } } diff --git a/tests/PHPStan/Analyser/data/array-destructuring-array-dim-fetch.php b/tests/PHPStan/Analyser/data/array-destructuring-array-dim-fetch.php index d108334387..c21c88bdf1 100644 --- a/tests/PHPStan/Analyser/data/array-destructuring-array-dim-fetch.php +++ b/tests/PHPStan/Analyser/data/array-destructuring-array-dim-fetch.php @@ -1,15 +1,15 @@ $dynamicAssocKey, $stringKey => $dynamicAssocStrings, $mixedKey => $dynamicAssocMixed] = $constantAssocArray; foreach ([$constantAssocArray] as [$fooKey => $dynamicAssocKeyForeach, $stringKey => $dynamicAssocStringsForeach, $mixedKey => $dynamicAssocMixedForeach]) { - } /** @var iterable> $iterableOverStringArrays */ $iterableOverStringArrays = doFoo(); foreach ($iterableOverStringArrays as [$stringFromIterable]) { - } /** @var string $stringWithVarAnnotation */ @@ -79,7 +73,6 @@ function () { /** @var string $stringWithVarAnnotationInForeach */ foreach (doFoo() as [$stringWithVarAnnotationInForeach]) { - } die; diff --git a/tests/PHPStan/Analyser/data/array-functions.php b/tests/PHPStan/Analyser/data/array-functions.php index 752fe4fd07..a504242eaf 100644 --- a/tests/PHPStan/Analyser/data/array-functions.php +++ b/tests/PHPStan/Analyser/data/array-functions.php @@ -4,63 +4,55 @@ $mixedValues = ['abc', 123]; $mappedStrings = array_map(function (): string { - }, $integers); $filteredIntegers = array_filter($integers, function (): bool { - }); $filteredMixed = array_filter($mixedValues, function ($mixedValue): bool { - return is_int($mixedValue); + return is_int($mixedValue); }); $uniquedIntegers = array_unique($integers); $reducedIntegersToString = array_reduce($integers, function (): string { - }); $reducedIntegersToStringWithNull = array_reduce($uniquedIntegers, function (): string { - }); $reducedIntegersToStringAnother = array_reduce($integers, function (): string { - }, 'initial'); $reducedToNull = array_reduce([], function (): string { - }); $reducedToInt = array_reduce([], function (): string { - }, 1); $reducedIntegersToStringWithInt = array_reduce($uniquedIntegers, function (): string { - }, 1); $filledIntegers = array_fill(0, 5, 1); $filledIntegersWithKeys = array_fill_keys([0], 1); $integerKeys = [ - 1 => 'foo', - 2 => new \stdClass(), + 1 => 'foo', + 2 => new \stdClass(), ]; $stringKeys = [ - 'foo' => 'foo', - 'bar' => new \stdClass(), + 'foo' => 'foo', + 'bar' => new \stdClass(), ]; /** @var \stdClass[] $stdClassesWithIsset */ $stdClassesWithIsset = doFoo(); if (rand(0, 1) === 0) { - $stdClassesWithIsset[] = new \stdClass(); + $stdClassesWithIsset[] = new \stdClass(); } if (!isset($stdClassesWithIsset['baz'])) { - return; + return; } $stringOrIntegerKeys = [ - 'foo' => new \stdClass(), - 1 => new \stdClass(), + 'foo' => new \stdClass(), + 1 => new \stdClass(), ]; $constantArrayWithFalseyValues = [null, '', 1]; @@ -75,7 +67,7 @@ $union = ['a' => 1]; if (rand(0, 1) === 1) { - $union['b'] = false; + $union['b'] = false; } /** @var bool $bool */ @@ -107,7 +99,6 @@ $generalIntegersInAnotherArray = doFoo(); $mappedStringKeys = array_map(function (): \stdClass { - }, $generalStringKeys); /** @var callable $callable */ @@ -115,20 +106,19 @@ $mappedStringKeysWithUnknownClosureType = array_map($callable, $generalStringKeys); $mappedWrongArray = array_map(function (): string { - }, 1); $unknownArray = array_map($callable, 1); $conditionalArray = ['foo', 'bar']; $conditionalKeysArray = [ - 'foo' => 1, - 'bar' => 1, + 'foo' => 1, + 'bar' => 1, ]; if (doFoo()) { - $conditionalArray[] = 'baz'; - $conditionalArray[] = 'lorem'; - $conditionalKeysArray['baz'] = 1; - $conditionalKeysArray['lorem'] = 1; + $conditionalArray[] = 'baz'; + $conditionalArray[] = 'lorem'; + $conditionalKeysArray['baz'] = 1; + $conditionalKeysArray['lorem'] = 1; } /** @var int|string $generalIntegerOrString */ @@ -147,9 +137,9 @@ $clonedConditionalArray[(int)$generalIntegerOrString] = $generalIntegerOrString; if (random_int(0, 1)) { - $unionArrays = [1=>1, 2=> '', 'a' => 0]; + $unionArrays = [1=>1, 2=> '', 'a' => 0]; } else { - $unionArrays = ['foo' => 'bar', 'baz' => 'qux']; + $unionArrays = ['foo' => 'bar', 'baz' => 'qux']; } /** @var mixed $mixed */ @@ -172,7 +162,7 @@ $mergedInts = []; foreach ($array as $val) { - $mergedInts = array_merge($mergedInts, $generalIntegers); + $mergedInts = array_merge($mergedInts, $generalIntegers); } $fooArray = ['foo']; diff --git a/tests/PHPStan/Analyser/data/array-key.php b/tests/PHPStan/Analyser/data/array-key.php index a4b7b36370..a3c323b24a 100644 --- a/tests/PHPStan/Analyser/data/array-key.php +++ b/tests/PHPStan/Analyser/data/array-key.php @@ -6,18 +6,15 @@ class Foo { - - /** - * @param array-key $arrayKey - * @param array $arrayWithArrayKey - */ - public function doFoo( - $arrayKey, - array $arrayWithArrayKey - ): void - { - assertType('(int|string)', $arrayKey); - assertType('array', $arrayWithArrayKey); - } - + /** + * @param array-key $arrayKey + * @param array $arrayWithArrayKey + */ + public function doFoo( + $arrayKey, + array $arrayWithArrayKey + ): void { + assertType('(int|string)', $arrayKey); + assertType('array', $arrayWithArrayKey); + } } diff --git a/tests/PHPStan/Analyser/data/array-keys-branches.php b/tests/PHPStan/Analyser/data/array-keys-branches.php index cf600ed176..f21f14910d 100644 --- a/tests/PHPStan/Analyser/data/array-keys-branches.php +++ b/tests/PHPStan/Analyser/data/array-keys-branches.php @@ -1,55 +1,55 @@ 0, - 'j' => 0, - 'k' => 0, - 'l' => 0, - 'm' => 0, - ]; - - /** @var \DateTimeImmutable|null $nullableDateTime */ - $nullableDateTime = doFoo(); - $array['key'] = $nullableDateTime; - - $arrayAppendedInIf = ['foo', 'bar']; - if ($array['key'] === null) { - $array['key'] = new \DateTimeImmutable(); - $arrayAppendedInIf[] = 'baz'; - } - - if ($generalArray['key'] === null) { - $generalArray['key'] = new \DateTimeImmutable(); - } - - foreach ([1, 2] as $x) { - $array['i'] += $x; - $array['k']++; - } - - /** @var int[] $ints */ - $ints = doFoo(); - $arrayAppendedInForeach = ['foo', 'bar']; - $anotherArrayAppendedInForeach = ['foo', 'bar']; - $i = 0; - - $incremented = 0; - $setFromZeroToOne = 0; - foreach ($ints as $x) { - $array['j'] += $x; - $arrayAppendedInForeach[] = 'baz'; - $anotherArrayAppendedInForeach[$i++] = 'baz'; - $incremented++; - $setFromZeroToOne = 1; - } - - $array['l']++; - $array['m'] += 5; - - if (rand(0, 1) === 1) { - $array['n'] = 'str'; - } - - die; + $array = [ + 'i' => 0, + 'j' => 0, + 'k' => 0, + 'l' => 0, + 'm' => 0, + ]; + + /** @var \DateTimeImmutable|null $nullableDateTime */ + $nullableDateTime = doFoo(); + $array['key'] = $nullableDateTime; + + $arrayAppendedInIf = ['foo', 'bar']; + if ($array['key'] === null) { + $array['key'] = new \DateTimeImmutable(); + $arrayAppendedInIf[] = 'baz'; + } + + if ($generalArray['key'] === null) { + $generalArray['key'] = new \DateTimeImmutable(); + } + + foreach ([1, 2] as $x) { + $array['i'] += $x; + $array['k']++; + } + + /** @var int[] $ints */ + $ints = doFoo(); + $arrayAppendedInForeach = ['foo', 'bar']; + $anotherArrayAppendedInForeach = ['foo', 'bar']; + $i = 0; + + $incremented = 0; + $setFromZeroToOne = 0; + foreach ($ints as $x) { + $array['j'] += $x; + $arrayAppendedInForeach[] = 'baz'; + $anotherArrayAppendedInForeach[$i++] = 'baz'; + $incremented++; + $setFromZeroToOne = 1; + } + + $array['l']++; + $array['m'] += 5; + + if (rand(0, 1) === 1) { + $array['n'] = 'str'; + } + + die; }; diff --git a/tests/PHPStan/Analyser/data/array-map-closure.php b/tests/PHPStan/Analyser/data/array-map-closure.php index d09a0d294c..f4d42c539e 100644 --- a/tests/PHPStan/Analyser/data/array-map-closure.php +++ b/tests/PHPStan/Analyser/data/array-map-closure.php @@ -6,35 +6,32 @@ class A { - } class B extends A { - } class C extends A { - } function (): void { - array_map(function ($item) { - assertType(B::class . '|' . C::class, $item); - }, [new B(), new C()]); + array_map(function ($item) { + assertType(B::class . '|' . C::class, $item); + }, [new B(), new C()]); - array_map(function (A $item) { - assertType(B::class . '|' . C::class, $item); - }, [new B(), new C()]); + array_map(function (A $item) { + assertType(B::class . '|' . C::class, $item); + }, [new B(), new C()]); }; function (): void { - array_filter([new B(), new C()], function ($item) { - assertType(B::class . '|' . C::class, $item); - }); + array_filter([new B(), new C()], function ($item) { + assertType(B::class . '|' . C::class, $item); + }); - array_filter([new B(), new C()], function (A $item) { - assertType(B::class . '|' . C::class, $item); - }); + array_filter([new B(), new C()], function (A $item) { + assertType(B::class . '|' . C::class, $item); + }); }; diff --git a/tests/PHPStan/Analyser/data/array-pointer-functions.php b/tests/PHPStan/Analyser/data/array-pointer-functions.php index 6bae9731a0..363a8082a0 100644 --- a/tests/PHPStan/Analyser/data/array-pointer-functions.php +++ b/tests/PHPStan/Analyser/data/array-pointer-functions.php @@ -4,29 +4,27 @@ class Foo { + /** + * @param \stdClass[] $generalArray + * @param mixed $somethingElse + */ + public function doFoo(array $generalArray, $somethingElse) + { + $emptyConstantArray = []; + $constantArray = [ + 'a' => 1, + 'b' => 2, + ]; - /** - * @param \stdClass[] $generalArray - * @param mixed $somethingElse - */ - public function doFoo(array $generalArray, $somethingElse) - { - $emptyConstantArray = []; - $constantArray = [ - 'a' => 1, - 'b' => 2, - ]; - - $conditionalArray = ['foo', 'bar']; - if (doFoo()) { - array_unshift($conditionalArray, 'baz'); - } - - $secondConditionalArray = ['foo', 'bar']; - if (doFoo()) { - $secondConditionalArray[] = 'baz'; - } - die; - } + $conditionalArray = ['foo', 'bar']; + if (doFoo()) { + array_unshift($conditionalArray, 'baz'); + } + $secondConditionalArray = ['foo', 'bar']; + if (doFoo()) { + $secondConditionalArray[] = 'baz'; + } + die; + } } diff --git a/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php b/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php index 8d515c924b..7acd6a09db 100644 --- a/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php +++ b/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php @@ -6,19 +6,17 @@ class Foo { - - /** - * @param array{ - * 'namespace/key': string - * } $slash - * @param array $dollar - */ - public function doFoo(array $slash, array $dollar): void - { - assertType("array('namespace/key' => string)", $slash); - assertType('array string)>', $dollar); - } - + /** + * @param array{ + * 'namespace/key': string + * } $slash + * @param array $dollar + */ + public function doFoo(array $slash, array $dollar): void + { + assertType("array('namespace/key' => string)", $slash); + assertType('array string)>', $dollar); + } } diff --git a/tests/PHPStan/Analyser/data/array-shapes.php b/tests/PHPStan/Analyser/data/array-shapes.php index ed36ebfffa..f3a2130e4a 100644 --- a/tests/PHPStan/Analyser/data/array-shapes.php +++ b/tests/PHPStan/Analyser/data/array-shapes.php @@ -4,19 +4,16 @@ class Foo { - - /** - * @param array{0: string, 1: Foo, foo:Bar, Baz} $one - * @param array{0: string, 1?: Foo, foo?:Bar} $two - * @param array{0?: string, 1?: Foo, foo?:Bar} $three - */ - public function doFoo( - array $one, - array $two, - array $three - ) - { - die; - } - + /** + * @param array{0: string, 1: Foo, foo:Bar, Baz} $one + * @param array{0: string, 1?: Foo, foo?:Bar} $two + * @param array{0?: string, 1?: Foo, foo?:Bar} $three + */ + public function doFoo( + array $one, + array $two, + array $three + ) { + die; + } } diff --git a/tests/PHPStan/Analyser/data/array-slice.php b/tests/PHPStan/Analyser/data/array-slice.php index 291ffbdb9f..6568568b9e 100644 --- a/tests/PHPStan/Analyser/data/array-slice.php +++ b/tests/PHPStan/Analyser/data/array-slice.php @@ -6,33 +6,31 @@ class Foo { + /** + * @param non-empty-array $a + */ + public function nonEmpty(array $a): void + { + assertType('array', array_slice($a, 1)); + } - /** - * @param non-empty-array $a - */ - public function nonEmpty(array $a): void - { - assertType('array', array_slice($a, 1)); - } - - /** - * @param mixed $arr - */ - public function fromMixed($arr): void - { - assertType('array', array_slice($arr, 1, 2)); - } - - /** - * @param array $arr1 - * @param array $arr2 - */ - public function preserveTypes(array $arr1, array $arr2): void - { - assertType('array', array_slice($arr1, 1, 2)); - assertType('array', array_slice($arr1, 1, 2, true)); - assertType('array', array_slice($arr2, 1, 2)); - assertType('array', array_slice($arr2, 1, 2, true)); - } + /** + * @param mixed $arr + */ + public function fromMixed($arr): void + { + assertType('array', array_slice($arr, 1, 2)); + } + /** + * @param array $arr1 + * @param array $arr2 + */ + public function preserveTypes(array $arr1, array $arr2): void + { + assertType('array', array_slice($arr1, 1, 2)); + assertType('array', array_slice($arr1, 1, 2, true)); + assertType('array', array_slice($arr2, 1, 2)); + assertType('array', array_slice($arr2, 1, 2, true)); + } } diff --git a/tests/PHPStan/Analyser/data/array-spread.php b/tests/PHPStan/Analyser/data/array-spread.php index 65de15eaef..e8492344ff 100644 --- a/tests/PHPStan/Analyser/data/array-spread.php +++ b/tests/PHPStan/Analyser/data/array-spread.php @@ -1,29 +1,28 @@ -= 7.4 += 7.4 namespace ArraySpreadOperator; class Foo { + /** + * @param int[] $integersArray + * @param int[] $integersIterable + */ + public function doFoo( + array $integersArray, + iterable $integersIterable + ) { + $integersOne = [1, 2, 3, ...$integersArray]; + $integersTwo = [1, 2, 3, ...$integersIterable]; + $integersThree = [1, 2, ...[3, 4, 5], 6, 7]; - /** - * @param int[] $integersArray - * @param int[] $integersIterable - */ - public function doFoo( - array $integersArray, - iterable $integersIterable - ) - { - $integersOne = [1, 2, 3, ...$integersArray]; - $integersTwo = [1, 2, 3, ...$integersIterable]; - $integersThree = [1, 2, ...[3, 4, 5], 6, 7]; - - $integersFour = array(1, 2, 3, ...$integersArray); - $integersFive = array(1, 2, 3, ...$integersIterable); - $integersSix = array(1, 2, ...[3, 4, 5], 6, 7); - - $integersSeven = array(1, 2, ...array(3, 4, 5), 6, 7); - die; - } + $integersFour = array(1, 2, 3, ...$integersArray); + $integersFive = array(1, 2, 3, ...$integersIterable); + $integersSix = array(1, 2, ...[3, 4, 5], 6, 7); + $integersSeven = array(1, 2, ...array(3, 4, 5), 6, 7); + die; + } } diff --git a/tests/PHPStan/Analyser/data/array-sum.php b/tests/PHPStan/Analyser/data/array-sum.php index 7e2f17fe7e..8f15bd4dc5 100644 --- a/tests/PHPStan/Analyser/data/array-sum.php +++ b/tests/PHPStan/Analyser/data/array-sum.php @@ -9,8 +9,8 @@ */ function foo($integerList) { - $sum = array_sum($integerList); - assertType('int', $sum); + $sum = array_sum($integerList); + assertType('int', $sum); } /** @@ -18,8 +18,8 @@ function foo($integerList) */ function foo2($floatList) { - $sum = array_sum($floatList); - assertType('0|float', $sum); + $sum = array_sum($floatList); + assertType('0|float', $sum); } /** @@ -27,8 +27,8 @@ function foo2($floatList) */ function foo3($floatList) { - $sum = array_sum($floatList); - assertType('float', $sum); + $sum = array_sum($floatList); + assertType('float', $sum); } /** @@ -36,8 +36,8 @@ function foo3($floatList) */ function foo4($list) { - $sum = array_sum($list); - assertType('float|int', $sum); + $sum = array_sum($list); + assertType('float|int', $sum); } /** @@ -45,6 +45,6 @@ function foo4($list) */ function foo5($list) { - $sum = array_sum($list); - assertType('float|int', $sum); + $sum = array_sum($list); + assertType('float|int', $sum); } diff --git a/tests/PHPStan/Analyser/data/array-typehint-without-null-in-phpdoc.php b/tests/PHPStan/Analyser/data/array-typehint-without-null-in-phpdoc.php index aafff14030..cc5944a961 100644 --- a/tests/PHPStan/Analyser/data/array-typehint-without-null-in-phpdoc.php +++ b/tests/PHPStan/Analyser/data/array-typehint-without-null-in-phpdoc.php @@ -6,26 +6,24 @@ class Foo { + /** + * @return string[] + */ + public function doFoo(): ?array + { + return ['foo']; + } - /** - * @return string[] - */ - public function doFoo(): ?array - { - return ['foo']; - } - - public function doBar(): void - { - assertType('array|null', $this->doFoo()); - } - - /** - * @param string[] $a - */ - public function doBaz(?array $a): void - { - assertType('array|null', $a); - } + public function doBar(): void + { + assertType('array|null', $this->doFoo()); + } + /** + * @param string[] $a + */ + public function doBaz(?array $a): void + { + assertType('array|null', $a); + } } diff --git a/tests/PHPStan/Analyser/data/arrow-function-return-type.php b/tests/PHPStan/Analyser/data/arrow-function-return-type.php index ff4055dc3e..30a5a1962d 100644 --- a/tests/PHPStan/Analyser/data/arrow-function-return-type.php +++ b/tests/PHPStan/Analyser/data/arrow-function-return-type.php @@ -5,11 +5,11 @@ use function PHPStan\Testing\assertType; function (int $i): void { - $fn = fn () => $i; - assertType('int', $fn()); + $fn = fn () => $i; + assertType('int', $fn()); }; function (int $i): void { - $fn = fn (): string => $i; - assertType('string', $fn()); + $fn = fn (): string => $i; + assertType('string', $fn()); }; diff --git a/tests/PHPStan/Analyser/data/arrow-functions-inside.php b/tests/PHPStan/Analyser/data/arrow-functions-inside.php index 269e2bf8e7..91152a5a04 100644 --- a/tests/PHPStan/Analyser/data/arrow-functions-inside.php +++ b/tests/PHPStan/Analyser/data/arrow-functions-inside.php @@ -1,13 +1,13 @@ -= 7.4 += 7.4 namespace ArrowFunctionsInside; class Foo { - - public function doFoo(int $i) - { - fn(string $s) => die; - } - + public function doFoo(int $i) + { + fn (string $s) => die; + } } diff --git a/tests/PHPStan/Analyser/data/arrow-functions.php b/tests/PHPStan/Analyser/data/arrow-functions.php index 0a94933360..3c664a1ec8 100644 --- a/tests/PHPStan/Analyser/data/arrow-functions.php +++ b/tests/PHPStan/Analyser/data/arrow-functions.php @@ -1,15 +1,15 @@ -= 7.4 += 7.4 namespace ArrowFunctions; class Foo { - - public function doFoo() - { - $x = fn(string $str): int => 1; - $y = fn(): array => ['a' => 1, 'b' => 2]; - die; - } - + public function doFoo() + { + $x = fn (string $str): int => 1; + $y = fn (): array => ['a' => 1, 'b' => 2]; + die; + } } diff --git a/tests/PHPStan/Analyser/data/assign-nested-arrays.php b/tests/PHPStan/Analyser/data/assign-nested-arrays.php index 08227f4a8e..b2726010b9 100644 --- a/tests/PHPStan/Analyser/data/assign-nested-arrays.php +++ b/tests/PHPStan/Analyser/data/assign-nested-arrays.php @@ -6,28 +6,26 @@ class Foo { + public function doFoo(int $i) + { + $array = []; - public function doFoo(int $i) - { - $array = []; + $array[$i]['bar'] = 1; + $array[$i]['baz'] = 2; - $array[$i]['bar'] = 1; - $array[$i]['baz'] = 2; + assertType('array 1, \'baz\' => 2)>&nonEmpty', $array); + } - assertType('array 1, \'baz\' => 2)>&nonEmpty', $array); - } + public function doBar(int $i, int $j) + { + $array = []; - public function doBar(int $i, int $j) - { - $array = []; + $array[$i][$j]['bar'] = 1; + $array[$i][$j]['baz'] = 2; - $array[$i][$j]['bar'] = 1; - $array[$i][$j]['baz'] = 2; - - echo $array[$i][$j]['bar']; - echo $array[$i][$j]['baz']; - - assertType('array 1, \'baz\' => 2)>&nonEmpty>&nonEmpty', $array); - } + echo $array[$i][$j]['bar']; + echo $array[$i][$j]['baz']; + assertType('array 1, \'baz\' => 2)>&nonEmpty>&nonEmpty', $array); + } } diff --git a/tests/PHPStan/Analyser/data/assignment-in-condition.php b/tests/PHPStan/Analyser/data/assignment-in-condition.php index c099fdfa4c..1f77ca96b7 100644 --- a/tests/PHPStan/Analyser/data/assignment-in-condition.php +++ b/tests/PHPStan/Analyser/data/assignment-in-condition.php @@ -4,18 +4,15 @@ class Foo { + public function doFoo(): ?self + { + } - public function doFoo(): ?self - { - - } - - public function doBar() - { - $foo = new self(); - if (null !== $bar = $foo->doFoo()) { - die; - } - } - + public function doBar() + { + $foo = new self(); + if (null !== $bar = $foo->doFoo()) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/binary.php b/tests/PHPStan/Analyser/data/binary.php index fc19fca14e..7358204387 100644 --- a/tests/PHPStan/Analyser/data/binary.php +++ b/tests/PHPStan/Analyser/data/binary.php @@ -4,193 +4,191 @@ class Foo { + public const INT_CONST = 1; - public const INT_CONST = 1; + public function doFoo(array $generalArray) + { + /** @var float $float */ + $float = doFoo(); - public function doFoo(array $generalArray) - { - /** @var float $float */ - $float = doFoo(); + /** @var int $integer */ + $integer = doFoo(); - /** @var int $integer */ - $integer = doFoo(); + /** @var bool $bool */ + $bool = doFoo(); - /** @var bool $bool */ - $bool = doFoo(); + /** @var string $string */ + $string = doFoo(); - /** @var string $string */ - $string = doFoo(); + $fooString = 'foo'; - $fooString = 'foo'; + /** @var string|null $stringOrNull */ + $stringOrNull = doFoo(); - /** @var string|null $stringOrNull */ - $stringOrNull = doFoo(); + $arrayOfIntegers = [$integer, $integer + 1, $integer + 2]; - $arrayOfIntegers = [$integer, $integer + 1, $integer + 2]; + $foo = new Foo(); - $foo = new Foo(); + $one = 1; - $one = 1; + $array = [1, 2, 3]; - $array = [1, 2, 3]; + reset($array); - reset($array); + /** @var number $number */ + $number = doFoo(); - /** @var number $number */ - $number = doFoo(); + /** @var int|null|bool $otherInteger */ + $otherInteger = doFoo(); - /** @var int|null|bool $otherInteger */ - $otherInteger = doFoo(); + /** @var mixed $mixed */ + $mixed = doFoo(); - /** @var mixed $mixed */ - $mixed = doFoo(); + /** @var int[] $arrayOfUnknownIntegers */ + $arrayOfUnknownIntegers = doFoo(); - /** @var int[] $arrayOfUnknownIntegers */ - $arrayOfUnknownIntegers = doFoo(); + $foobarString = $fooString; + $foobarString[6] = 'b'; + $foobarString[7] = 'a'; + $foobarString[8] = 'r'; - $foobarString = $fooString; - $foobarString[6] = 'b'; - $foobarString[7] = 'a'; - $foobarString[8] = 'r'; + $std = new \stdClass(); - $std = new \stdClass(); + /** @var int[] $arrToPush */ + $arrToPush = doFoo(); + array_push($arrToPush, 'foo', new \stdClass()); - /** @var int[] $arrToPush */ - $arrToPush = doFoo(); - array_push($arrToPush, 'foo', new \stdClass()); + /** @var int[] $arrToPush2 */ + $arrToPush2 = doFoo(); + array_push($arrToPush2, ...['foo', new \stdClass()]); - /** @var int[] $arrToPush2 */ - $arrToPush2 = doFoo(); - array_push($arrToPush2, ...['foo', new \stdClass()]); + $arrToUnshift = ['foo' => new \stdClass(), 5 => 'test']; + array_unshift($arrToUnshift, 'lorem', 5); - $arrToUnshift = ['foo' => new \stdClass(), 5 => 'test']; - array_unshift($arrToUnshift, 'lorem', 5); + /** @var int[] $arrToUnshift2 */ + $arrToUnshift2 = doFoo(); + array_unshift($arrToUnshift2, 'lorem', new \stdClass()); + array_unshift($mixed, 'lorem'); - /** @var int[] $arrToUnshift2 */ - $arrToUnshift2 = doFoo(); - array_unshift($arrToUnshift2, 'lorem', new \stdClass()); - array_unshift($mixed, 'lorem'); + $line = __LINE__; + $dir = __DIR__; + $file = __FILE__; + $namespace = __NAMESPACE__; + $class = __CLASS__; + $method = __METHOD__; + $function = __FUNCTION__; - $line = __LINE__; - $dir = __DIR__; - $file = __FILE__; - $namespace = __NAMESPACE__; - $class = __CLASS__; - $method = __METHOD__; - $function = __FUNCTION__; + $incrementedString = $string; + $incrementedString++; - $incrementedString = $string; - $incrementedString++; + $decrementedString = $string; + $decrementedString--; - $decrementedString = $string; - $decrementedString--; + $incrementedFooString = $fooString; + $incrementedFooString++; - $incrementedFooString = $fooString; - $incrementedFooString++; + $decrementedFooString = $fooString; + $decrementedFooString--; - $decrementedFooString = $fooString; - $decrementedFooString--; + $index = 0; + $preIncArray = []; + $preIncArray[++$index] = $index; + $preIncArray[++$index] = $index; - $index = 0; - $preIncArray = []; - $preIncArray[++$index] = $index; - $preIncArray[++$index] = $index; + $anotherIndex = 0; + $postIncArray = []; + $postIncArray[$anotherIndex++] = $anotherIndex++; + $postIncArray[$anotherIndex++] = $anotherIndex++; - $anotherIndex = 0; - $postIncArray = []; - $postIncArray[$anotherIndex++] = $anotherIndex++; - $postIncArray[$anotherIndex++] = $anotherIndex++; + $anotherPostIncArray = []; + $anotherAnotherIndex = 0; + $anotherPostIncArray[$anotherAnotherIndex++][$anotherAnotherIndex++][$anotherAnotherIndex++] = $anotherAnotherIndex++; + $anotherPostIncArray[$anotherAnotherIndex++][$anotherAnotherIndex++][$anotherAnotherIndex++] = $anotherAnotherIndex++; - $anotherPostIncArray = []; - $anotherAnotherIndex = 0; - $anotherPostIncArray[$anotherAnotherIndex++][$anotherAnotherIndex++][$anotherAnotherIndex++] = $anotherAnotherIndex++; - $anotherPostIncArray[$anotherAnotherIndex++][$anotherAnotherIndex++][$anotherAnotherIndex++] = $anotherAnotherIndex++; + $conditionalArray = [1, 1, 1]; + $conditionalInt = 1; + $conditionalString = 'foo'; + $anotherConditionalString = 'lorem'; + if (doFoo()) { + $conditionalArray[] = 2; + $conditionalArray[] = 3; + $conditionalInt = 2; + $conditionalString = 'bar'; + $anotherConditionalString = 'ipsum'; + } - $conditionalArray = [1, 1, 1]; - $conditionalInt = 1; - $conditionalString = 'foo'; - $anotherConditionalString = 'lorem'; - if (doFoo()) { - $conditionalArray[] = 2; - $conditionalArray[] = 3; - $conditionalInt = 2; - $conditionalString = 'bar'; - $anotherConditionalString = 'ipsum'; - } + $unshiftedConditionalArray = $conditionalArray; + array_unshift($unshiftedConditionalArray, 'lorem', new \stdClass()); - $unshiftedConditionalArray = $conditionalArray; - array_unshift($unshiftedConditionalArray, 'lorem', new \stdClass()); + $arrToShift = [1, 2, 3]; + array_shift($arrToShift); - $arrToShift = [1, 2, 3]; - array_shift($arrToShift); + $arrToPop = [1, 2, 3]; + array_pop($arrToPop); - $arrToPop = [1, 2, 3]; - array_pop($arrToPop); + $coalesceArray = []; + $arrayOfUnknownIntegers[42] ?? $coalesceArray[] = 'username'; + $arrayOfUnknownIntegers[108] ?? $coalesceArray[] = 'password'; - $coalesceArray = []; - $arrayOfUnknownIntegers[42] ?? $coalesceArray[] = 'username'; - $arrayOfUnknownIntegers[108] ?? $coalesceArray[] = 'password'; + $arrayToBeUnset = $array; + unset($arrayToBeUnset[$string]); - $arrayToBeUnset = $array; - unset($arrayToBeUnset[$string]); + $arrayToBeUnset2 = $arrayToBeUnset; + unset($arrayToBeUnset2[$string]); - $arrayToBeUnset2 = $arrayToBeUnset; - unset($arrayToBeUnset2[$string]); + /** @var array $shiftedNonEmptyArray */ + $shiftedNonEmptyArray = doFoo(); - /** @var array $shiftedNonEmptyArray */ - $shiftedNonEmptyArray = doFoo(); + if (count($shiftedNonEmptyArray) === 0) { + return; + } - if (count($shiftedNonEmptyArray) === 0) { - return; - } + array_shift($shiftedNonEmptyArray); - array_shift($shiftedNonEmptyArray); + /** @var array $unshiftedArray */ + $unshiftedArray = doFoo(); + array_unshift($unshiftedArray, 1); - /** @var array $unshiftedArray */ - $unshiftedArray = doFoo(); - array_unshift($unshiftedArray, 1); + /** @var array $poppedNonEmptyArray */ + $poppedNonEmptyArray = doFoo(); + if (count($poppedNonEmptyArray) === 0) { + return; + } + + array_pop($poppedNonEmptyArray); - /** @var array $poppedNonEmptyArray */ - $poppedNonEmptyArray = doFoo(); - if (count($poppedNonEmptyArray) === 0) { - return; - } + /** @var array $pushedArray */ + $pushedArray = doFoo(); + array_push($pushedArray, 1); - array_pop($poppedNonEmptyArray); + $simpleXML = new \SimpleXMLElement(''); + $simpleXMLReturningXML = $simpleXML->asXML(); + if ($simpleXMLReturningXML) { + $xmlString = $simpleXMLReturningXML; + } + + $simpleXMLWritingXML = $simpleXML->asXML('path.xml'); - /** @var array $pushedArray */ - $pushedArray = doFoo(); - array_push($pushedArray, 1); + /** @var string $stringForXpath */ + $stringForXpath = doFoo(); + + $simpleXMLRightXpath = $simpleXML->xpath('/a/b/c'); + $simpleXMLWrongXpath = $simpleXML->xpath('[foo]'); + $simpleXMLUnknownXpath = $simpleXML->xpath($stringForXpath); - $simpleXML = new \SimpleXMLElement(''); - $simpleXMLReturningXML = $simpleXML->asXML(); - if ($simpleXMLReturningXML) { - $xmlString = $simpleXMLReturningXML; - } + $namespacedXML = new \SimpleXMLElement(''); + $namespacedXML->registerXPathNamespace('ns', 'namespace'); + $namespacedXpath = $namespacedXML->xpath('/ns:node'); - $simpleXMLWritingXML = $simpleXML->asXML('path.xml'); + if (rand(0, 1)) { + $maybeDefinedVariable = 'foo'; + } - /** @var string $stringForXpath */ - $stringForXpath = doFoo(); - - $simpleXMLRightXpath = $simpleXML->xpath('/a/b/c'); - $simpleXMLWrongXpath = $simpleXML->xpath('[foo]'); - $simpleXMLUnknownXpath = $simpleXML->xpath($stringForXpath); - - $namespacedXML = new \SimpleXMLElement(''); - $namespacedXML->registerXPathNamespace('ns', 'namespace'); - $namespacedXpath = $namespacedXML->xpath('/ns:node'); - - if (rand(0, 1)) { - $maybeDefinedVariable = 'foo'; - } - - $sumWithStaticConst = static::INT_CONST + 1; - $severalSumWithStaticConst1 = static::INT_CONST + 1 + 1; - $severalSumWithStaticConst2 = 1 + static::INT_CONST + 1; - $severalSumWithStaticConst3 = 1 + 1 + static::INT_CONST; - - die; - } + $sumWithStaticConst = static::INT_CONST + 1; + $severalSumWithStaticConst1 = static::INT_CONST + 1 + 1; + $severalSumWithStaticConst2 = 1 + static::INT_CONST + 1; + $severalSumWithStaticConst3 = 1 + 1 + static::INT_CONST; + die; + } } diff --git a/tests/PHPStan/Analyser/data/bitwise-not.php b/tests/PHPStan/Analyser/data/bitwise-not.php index f7f07c1996..15b1bd123d 100644 --- a/tests/PHPStan/Analyser/data/bitwise-not.php +++ b/tests/PHPStan/Analyser/data/bitwise-not.php @@ -7,11 +7,12 @@ /** * @param string|int $stringOrInt */ -function foo(int $int, string $string, float $float, $stringOrInt) : void{ - assertType('int', ~$int); - assertType('string', ~$string); - assertType('int', ~$float); - assertType('int|string', ~$stringOrInt); - assertType("'" . (~"abc") . "'", ~"abc"); - assertType('int', ~1); //result is dependent on PHP_INT_SIZE +function foo(int $int, string $string, float $float, $stringOrInt): void +{ + assertType('int', ~$int); + assertType('string', ~$string); + assertType('int', ~$float); + assertType('int|string', ~$stringOrInt); + assertType("'" . (~"abc") . "'", ~"abc"); + assertType('int', ~1); //result is dependent on PHP_INT_SIZE } diff --git a/tests/PHPStan/Analyser/data/bug-1014.php b/tests/PHPStan/Analyser/data/bug-1014.php index 9d0f0567b0..37dc41af01 100644 --- a/tests/PHPStan/Analyser/data/bug-1014.php +++ b/tests/PHPStan/Analyser/data/bug-1014.php @@ -1,18 +1,21 @@ -a; - } + /** + * @return \DateTimeInterface|null + */ + public function getA() + { + return $this->a; + } } function (HelloWorld $class): void { - if ($class->getA()) { - assertType(DateTimeInterface::class, $class->getA()); - } + if ($class->getA()) { + assertType(DateTimeInterface::class, $class->getA()); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-1209.php b/tests/PHPStan/Analyser/data/bug-1209.php index fff8d13dff..4aa35d4d28 100644 --- a/tests/PHPStan/Analyser/data/bug-1209.php +++ b/tests/PHPStan/Analyser/data/bug-1209.php @@ -6,29 +6,29 @@ class HelloWorld { - /** - * @param mixed[]|string $value - */ - public function sayHello($value): void - { - $isArray = is_array($value); - if($isArray){ - assertType('array', $value); - } - } + /** + * @param mixed[]|string $value + */ + public function sayHello($value): void + { + $isArray = is_array($value); + if ($isArray) { + assertType('array', $value); + } + } - /** - * @param mixed[]|string $value - */ - public function sayHello2($value): void - { - $isArray = is_array($value); - $value = 123; - assertType('123', $value); - if ($isArray) { - assertType('123', $value); - } + /** + * @param mixed[]|string $value + */ + public function sayHello2($value): void + { + $isArray = is_array($value); + $value = 123; + assertType('123', $value); + if ($isArray) { + assertType('123', $value); + } - assertType('123', $value); - } + assertType('123', $value); + } } diff --git a/tests/PHPStan/Analyser/data/bug-1216.php b/tests/PHPStan/Analyser/data/bug-1216.php index 1ccb7d093d..a3191459d1 100644 --- a/tests/PHPStan/Analyser/data/bug-1216.php +++ b/tests/PHPStan/Analyser/data/bug-1216.php @@ -6,20 +6,20 @@ abstract class Foo { - /** - * @var int - */ - protected $foo; + /** + * @var int + */ + protected $foo; } trait Bar { - /** - * @var int - */ - protected $bar; + /** + * @var int + */ + protected $bar; - protected $untypedBar; + protected $untypedBar; } /** @@ -29,18 +29,16 @@ trait Bar */ class Baz extends Foo { - - public function __construct() - { - assertType('string', $this->foo); - assertType('string', $this->bar); - assertType('string', $this->untypedBar); - } - + public function __construct() + { + assertType('string', $this->foo); + assertType('string', $this->bar); + assertType('string', $this->untypedBar); + } } function (Baz $baz): void { - assertType('string', $baz->foo); - assertType('string', $baz->bar); - assertType('string', $baz->untypedBar); + assertType('string', $baz->foo); + assertType('string', $baz->bar); + assertType('string', $baz->untypedBar); }; diff --git a/tests/PHPStan/Analyser/data/bug-1219.php b/tests/PHPStan/Analyser/data/bug-1219.php index edde8c456a..38ae321984 100644 --- a/tests/PHPStan/Analyser/data/bug-1219.php +++ b/tests/PHPStan/Analyser/data/bug-1219.php @@ -7,23 +7,21 @@ class Foo { + public function test(): int + { + try { + $var = 1; + } catch (\Throwable $e) { + switch ($e->getCode()) { + case 1: + return 0; + default: + return -1; + } + } - function test(): int - { - try { - $var = 1; - } catch (\Throwable $e) { - switch ($e->getCode()) { - case 1: - return 0; - default: - return -1; - } - } - - assertVariableCertainty(TrinaryLogic::createYes(), $var); - - return $var; - } + assertVariableCertainty(TrinaryLogic::createYes(), $var); + return $var; + } } diff --git a/tests/PHPStan/Analyser/data/bug-1233.php b/tests/PHPStan/Analyser/data/bug-1233.php index 7d70679585..7ee7649d39 100644 --- a/tests/PHPStan/Analyser/data/bug-1233.php +++ b/tests/PHPStan/Analyser/data/bug-1233.php @@ -6,23 +6,23 @@ class HelloWorld { - public function toArray($value): array - { - assertType('mixed', $value); - if (is_array($value)) { - assertType('array', $value); - return $value; - } + public function toArray($value): array + { + assertType('mixed', $value); + if (is_array($value)) { + assertType('array', $value); + return $value; + } - assertType('mixed~array', $value); + assertType('mixed~array', $value); - if (is_iterable($value)) { - assertType('Traversable', $value); - return iterator_to_array($value); - } + if (is_iterable($value)) { + assertType('Traversable', $value); + return iterator_to_array($value); + } - assertType('mixed~array', $value); + assertType('mixed~array', $value); - throw new \LogicException(); - } + throw new \LogicException(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-1283.php b/tests/PHPStan/Analyser/data/bug-1283.php index fb1e5a2e0e..0821538518 100644 --- a/tests/PHPStan/Analyser/data/bug-1283.php +++ b/tests/PHPStan/Analyser/data/bug-1283.php @@ -7,21 +7,21 @@ use function PHPStan\Testing\assertVariableCertainty; function (array $levels): void { - foreach ($levels as $level) { - switch ($level) { - case 'all': - continue 2; - case 'some': - $allowedElements = array(1, 3); - break; - case 'one': - $allowedElements = array(1); - break; - default: - throw new \UnexpectedValueException(sprintf('Unsupported level `%s`', $level)); - } + foreach ($levels as $level) { + switch ($level) { + case 'all': + continue 2; + case 'some': + $allowedElements = array(1, 3); + break; + case 'one': + $allowedElements = array(1); + break; + default: + throw new \UnexpectedValueException(sprintf('Unsupported level `%s`', $level)); + } - assertType('array(0 => 1, ?1 => 3)', $allowedElements); - assertVariableCertainty(TrinaryLogic::createYes(), $allowedElements); - } + assertType('array(0 => 1, ?1 => 3)', $allowedElements); + assertVariableCertainty(TrinaryLogic::createYes(), $allowedElements); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-1511.php b/tests/PHPStan/Analyser/data/bug-1511.php index a418374fe5..ba9aa50e06 100644 --- a/tests/PHPStan/Analyser/data/bug-1511.php +++ b/tests/PHPStan/Analyser/data/bug-1511.php @@ -8,42 +8,38 @@ class Response { - private $message; - - /** @throws void */ - public function __construct($message) - { - $this->message = $message; - } + private $message; + /** @throws void */ + public function __construct($message) + { + $this->message = $message; + } } class Serializer { - - public function serialize(): void - { - - } - + public function serialize(): void + { + } } class Controller { - public function prepareResponse($date): Response - { - $serializer = new Serializer(); - $response = null; - try { - $serializer->serialize(); - // do something with response object e.g. serialize some entity - $response = new Response("Success"); - } catch (\Throwable $e) { - $response = new Response("Error"); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $response); - assertType(Response::class, $response); - return $response; - } - } + public function prepareResponse($date): Response + { + $serializer = new Serializer(); + $response = null; + try { + $serializer->serialize(); + // do something with response object e.g. serialize some entity + $response = new Response("Success"); + } catch (\Throwable $e) { + $response = new Response("Error"); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $response); + assertType(Response::class, $response); + return $response; + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-1597.php b/tests/PHPStan/Analyser/data/bug-1597.php index 5023defbb8..015bf02739 100644 --- a/tests/PHPStan/Analyser/data/bug-1597.php +++ b/tests/PHPStan/Analyser/data/bug-1597.php @@ -7,7 +7,7 @@ $date = ''; try { - $date = new \DateTime($date); + $date = new \DateTime($date); } catch (\Exception $e) { - assertType('\'\'', $date); + assertType('\'\'', $date); } diff --git a/tests/PHPStan/Analyser/data/bug-1657.php b/tests/PHPStan/Analyser/data/bug-1657.php index 9be0bbebca..23d75a63ca 100644 --- a/tests/PHPStan/Analyser/data/bug-1657.php +++ b/tests/PHPStan/Analyser/data/bug-1657.php @@ -9,15 +9,14 @@ */ function foo($value) { - assertType('int|string', $value); - try { - assert(is_string($value)); - assertType('string', $value); + assertType('int|string', $value); + try { + assert(is_string($value)); + assertType('string', $value); + } catch (\Throwable $e) { + $value = 'A'; + assertType('\'A\'', $value); + } - } catch (\Throwable $e) { - $value = 'A'; - assertType('\'A\'', $value); - } - - assertType('string', $value); + assertType('string', $value); }; diff --git a/tests/PHPStan/Analyser/data/bug-1670.php b/tests/PHPStan/Analyser/data/bug-1670.php index 15c46f6fde..256a2a042e 100644 --- a/tests/PHPStan/Analyser/data/bug-1670.php +++ b/tests/PHPStan/Analyser/data/bug-1670.php @@ -8,11 +8,11 @@ $object = null; try { - if ($object === null) { - throw new \InvalidArgumentException(); - } + if ($object === null) { + throw new \InvalidArgumentException(); + } - throw new \RuntimeException(); + throw new \RuntimeException(); } catch (\Throwable $e) { - assertType('stdClass|null', $object); + assertType('stdClass|null', $object); } diff --git a/tests/PHPStan/Analyser/data/bug-1801.php b/tests/PHPStan/Analyser/data/bug-1801.php index 5720206bb7..f569fe66f8 100644 --- a/tests/PHPStan/Analyser/data/bug-1801.php +++ b/tests/PHPStan/Analyser/data/bug-1801.php @@ -5,13 +5,14 @@ use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertVariableCertainty; -function demo() : string { - try { - $response = 'OK'; - } catch (\Throwable $e) { - $response = 'ERROR'; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $response); - return $response; - } +function demo(): string +{ + try { + $response = 'OK'; + } catch (\Throwable $e) { + $response = 'ERROR'; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $response); + return $response; + } } diff --git a/tests/PHPStan/Analyser/data/bug-1843.php b/tests/PHPStan/Analyser/data/bug-1843.php index 7e2b1f201b..251a133d30 100644 --- a/tests/PHPStan/Analyser/data/bug-1843.php +++ b/tests/PHPStan/Analyser/data/bug-1843.php @@ -4,21 +4,21 @@ class HelloWorld { - const W = '1'; + public const W = '1'; - const P = [ - self::W => [ - 'A' => '2', - 'B' => '3', - 'C' => '4', - 'D' => '5', - 'E' => '6', - 'F' => '7', - ], - ]; + public const P = [ + self::W => [ + 'A' => '2', + 'B' => '3', + 'C' => '4', + 'D' => '5', + 'E' => '6', + 'F' => '7', + ], + ]; - public function sayHello(): void - { - echo self::P[self::W]['A']; - } + public function sayHello(): void + { + echo self::P[self::W]['A']; + } } diff --git a/tests/PHPStan/Analyser/data/bug-1865.php b/tests/PHPStan/Analyser/data/bug-1865.php index b4e3a39845..a05a4da146 100644 --- a/tests/PHPStan/Analyser/data/bug-1865.php +++ b/tests/PHPStan/Analyser/data/bug-1865.php @@ -5,19 +5,20 @@ use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertVariableCertainty; -function someFunction() { - $a = (bool) random_int(0, 1); +function someFunction() +{ + $a = (bool) random_int(0, 1); - if ($a) { - $key = 'xyz'; - } + if ($a) { + $key = 'xyz'; + } - assertVariableCertainty(TrinaryLogic::createMaybe(), $key); + assertVariableCertainty(TrinaryLogic::createMaybe(), $key); - if ($a) { - assertVariableCertainty(TrinaryLogic::createYes(), $key); - echo $key; - } else { - assertVariableCertainty(TrinaryLogic::createNo(), $key); - } + if ($a) { + assertVariableCertainty(TrinaryLogic::createYes(), $key); + echo $key; + } else { + assertVariableCertainty(TrinaryLogic::createNo(), $key); + } } diff --git a/tests/PHPStan/Analyser/data/bug-1871.php b/tests/PHPStan/Analyser/data/bug-1871.php index 9f8ec3cf10..bfeee96f1f 100644 --- a/tests/PHPStan/Analyser/data/bug-1871.php +++ b/tests/PHPStan/Analyser/data/bug-1871.php @@ -2,16 +2,20 @@ namespace Bug1871; -interface I {} +interface I +{ +} -class A implements I {} +class A implements I +{ +} -function(): void { - $objects = [ - new A() - ]; +function (): void { + $objects = [ + new A() + ]; - foreach($objects as $object) { - var_dump(is_subclass_of($object, '\C')); - } + foreach ($objects as $object) { + var_dump(is_subclass_of($object, '\C')); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-1897.php b/tests/PHPStan/Analyser/data/bug-1897.php index 25b1dcd6f2..bf48db2f94 100644 --- a/tests/PHPStan/Analyser/data/bug-1897.php +++ b/tests/PHPStan/Analyser/data/bug-1897.php @@ -1,4 +1,6 @@ - $this->getArrayOrNull(), - 'b' => $this->getArrayOrNull(), - ]; - assertType('array(\'a\' => array|null, \'b\' => array|null)', $arr); - - $cond = isset($arr['a']) && isset($arr['b']); - assertType('bool', $cond); - } + public function foo(): void + { + $arr = [ + 'a' => $this->getArrayOrNull(), + 'b' => $this->getArrayOrNull(), + ]; + assertType('array(\'a\' => array|null, \'b\' => array|null)', $arr); + $cond = isset($arr['a']) && isset($arr['b']); + assertType('bool', $cond); + } } diff --git a/tests/PHPStan/Analyser/data/bug-1945.php b/tests/PHPStan/Analyser/data/bug-1945.php index 8776a03c52..a19aa345e4 100644 --- a/tests/PHPStan/Analyser/data/bug-1945.php +++ b/tests/PHPStan/Analyser/data/bug-1945.php @@ -7,100 +7,100 @@ use function PHPStan\Testing\assertVariableCertainty; function (): void { - foreach (["a", "b", "c"] as $letter) { - switch ($letter) { - case "b": - $foo = 1; - break; - case "c": - $foo = 2; - break; - default: - continue 2; - } + foreach (["a", "b", "c"] as $letter) { + switch ($letter) { + case "b": + $foo = 1; + break; + case "c": + $foo = 2; + break; + default: + continue 2; + } - assertType('1|2', $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + assertType('1|2', $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function (): void { - foreach (["a", "b", "c"] as $letter) { - switch ($letter) { - case "a": - if (rand(0, 10) === 1) { - continue 2; - } - $foo = 1; - break; - case "b": - if (rand(0, 10) === 1) { - continue 2; - } - $foo = 2; - break; - default: - continue 2; - } + foreach (["a", "b", "c"] as $letter) { + switch ($letter) { + case "a": + if (rand(0, 10) === 1) { + continue 2; + } + $foo = 1; + break; + case "b": + if (rand(0, 10) === 1) { + continue 2; + } + $foo = 2; + break; + default: + continue 2; + } - assertType('1|2', $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + assertType('1|2', $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function (array $docs): void { - foreach ($docs as $doc) { - switch (true) { - case 'bar': - continue 2; - break; - default: - $foo = $doc; - break; - } + foreach ($docs as $doc) { + switch (true) { + case 'bar': + continue 2; + break; + default: + $foo = $doc; + break; + } - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - if (!$foo) { - return; - } - } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + if (!$foo) { + return; + } + } }; function (array $docs): void { - foreach ($docs as $doc) { - switch (true) { - case 'bar': - continue 2; - default: - $foo = $doc; - break; - } + foreach ($docs as $doc) { + switch (true) { + case 'bar': + continue 2; + default: + $foo = $doc; + break; + } - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - if (!$foo) { - return; - } - } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + if (!$foo) { + return; + } + } }; function (array $items): string { - foreach ($items as $item) { - switch ($item) { - case 1: - $string = 'a'; - break; - case 2: - $string = 'b'; - break; - default: - continue 2; - } + foreach ($items as $item) { + switch ($item) { + case 1: + $string = 'a'; + break; + case 2: + $string = 'b'; + break; + default: + continue 2; + } - assertType('\'a\'|\'b\'', $string); - assertVariableCertainty(TrinaryLogic::createYes(), $string); + assertType('\'a\'|\'b\'', $string); + assertVariableCertainty(TrinaryLogic::createYes(), $string); - return 'result: ' . $string; - } + return 'result: ' . $string; + } - return 'ok'; + return 'ok'; }; diff --git a/tests/PHPStan/Analyser/data/bug-2001.php b/tests/PHPStan/Analyser/data/bug-2001.php index 01ff5a4113..d4851c0729 100644 --- a/tests/PHPStan/Analyser/data/bug-2001.php +++ b/tests/PHPStan/Analyser/data/bug-2001.php @@ -6,46 +6,46 @@ class HelloWorld { - public function parseUrl(string $url): string - { - $parsedUrl = parse_url(urldecode($url)); - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + public function parseUrl(string $url): string + { + $parsedUrl = parse_url(urldecode($url)); + assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); - if (array_key_exists('host', $parsedUrl)) { - assertType('array(?\'scheme\' => string, \'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $parsedUrl); - throw new \RuntimeException('Absolute URLs are prohibited for the redirectTo parameter.'); - } + if (array_key_exists('host', $parsedUrl)) { + assertType('array(?\'scheme\' => string, \'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $parsedUrl); + throw new \RuntimeException('Absolute URLs are prohibited for the redirectTo parameter.'); + } - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); - $redirectUrl = $parsedUrl['path']; + $redirectUrl = $parsedUrl['path']; - if (array_key_exists('query', $parsedUrl)) { - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $parsedUrl); - $redirectUrl .= '?' . $parsedUrl['query']; - } + if (array_key_exists('query', $parsedUrl)) { + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $parsedUrl); + $redirectUrl .= '?' . $parsedUrl['query']; + } - if (array_key_exists('fragment', $parsedUrl)) { - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, \'fragment\' => string)', $parsedUrl); - $redirectUrl .= '#' . $parsedUrl['query']; - } + if (array_key_exists('fragment', $parsedUrl)) { + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, \'fragment\' => string)', $parsedUrl); + $redirectUrl .= '#' . $parsedUrl['query']; + } - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); - return $redirectUrl; - } + return $redirectUrl; + } - public function doFoo(int $i) - { - $a = ['a' => $i]; - if (rand(0, 1)) { - $a['b'] = $i; - } + public function doFoo(int $i) + { + $a = ['a' => $i]; + if (rand(0, 1)) { + $a['b'] = $i; + } - if (rand(0,1)) { - $a = ['d' => $i]; - } + if (rand(0, 1)) { + $a = ['d' => $i]; + } - assertType('array(\'a\' => int, ?\'b\' => int)|array(\'d\' => int)', $a); - } + assertType('array(\'a\' => int, ?\'b\' => int)|array(\'d\' => int)', $a); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2003.php b/tests/PHPStan/Analyser/data/bug-2003.php index 125b1a2a16..893b48ade9 100644 --- a/tests/PHPStan/Analyser/data/bug-2003.php +++ b/tests/PHPStan/Analyser/data/bug-2003.php @@ -1,4 +1,6 @@ -getFoos(); - assertType('array', $foos); - $foos[0] = null; - - assertType('null', $foos[0]); - assertType('array&nonEmpty', $foos); - } - - /** @return self[] */ - public function getFooBars(): array - { - return []; - } - - public function doBars(): void - { - $foos = $this->getFooBars(); - assertType('array', $foos); - $foos[0] = null; - - assertType('null', $foos[0]); - assertType('array&nonEmpty', $foos); - } - + public function getFoos(): array + { + return []; + } + + public function doBar(): void + { + $foos = $this->getFoos(); + assertType('array', $foos); + $foos[0] = null; + + assertType('null', $foos[0]); + assertType('array&nonEmpty', $foos); + } + + /** @return self[] */ + public function getFooBars(): array + { + return []; + } + + public function doBars(): void + { + $foos = $this->getFooBars(); + assertType('array', $foos); + $foos[0] = null; + + assertType('null', $foos[0]); + assertType('array&nonEmpty', $foos); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2231.php b/tests/PHPStan/Analyser/data/bug-2231.php index 9afaf3d3f5..a1104b71e2 100644 --- a/tests/PHPStan/Analyser/data/bug-2231.php +++ b/tests/PHPStan/Analyser/data/bug-2231.php @@ -6,34 +6,32 @@ class Foo { - - public function doFoo(?Foo $x): void - { - if ($x !== null && !($x instanceof static)) { - throw new \TypeError('custom error'); - } - - assertType('static(Bug2231\Foo)|null', $x); - } - - public function doBar(?Foo $x): void - { - if ($x !== null && !($x instanceof self)) { - throw new \TypeError('custom error'); - } - - assertType('Bug2231\Foo|null', $x); - } - - public function doBaz($x): void - { - if ($x instanceof self) { - assertType('Bug2231\Foo', $x); - } - - if ($x instanceof static) { - assertType('static(Bug2231\Foo)', $x); - } - } - + public function doFoo(?Foo $x): void + { + if ($x !== null && !($x instanceof static)) { + throw new \TypeError('custom error'); + } + + assertType('static(Bug2231\Foo)|null', $x); + } + + public function doBar(?Foo $x): void + { + if ($x !== null && !($x instanceof self)) { + throw new \TypeError('custom error'); + } + + assertType('Bug2231\Foo|null', $x); + } + + public function doBaz($x): void + { + if ($x instanceof self) { + assertType('Bug2231\Foo', $x); + } + + if ($x instanceof static) { + assertType('static(Bug2231\Foo)', $x); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2232.php b/tests/PHPStan/Analyser/data/bug-2232.php index 3f1613c725..21c3dfaece 100644 --- a/tests/PHPStan/Analyser/data/bug-2232.php +++ b/tests/PHPStan/Analyser/data/bug-2232.php @@ -5,35 +5,35 @@ use function PHPStan\Testing\assertType; function () { - $data = [ - 'a1' => "a", - 'a2' => "b", - 'a3' => "c", - 'a4' => [ - 'name' => "dsfs", - 'version' => "fdsfs", - ], - ]; - - if (rand(0, 1)) { - $data['b1'] = "hello"; - } - - if (rand(0, 1)) { - $data['b2'] = "hello"; - } - - if (rand(0, 1)) { - $data['b3'] = "hello"; - } - - if (rand(0, 1)) { - $data['b4'] = "goodbye"; - } - - if (rand(0, 1)) { - $data['b5'] = "env"; - } - - assertType('array(\'a1\' => \'a\', \'a2\' => \'b\', \'a3\' => \'c\', \'a4\' => array(\'name\' => \'dsfs\', \'version\' => \'fdsfs\'), ?\'b1\' => \'hello\', ?\'b2\' => \'hello\', ?\'b3\' => \'hello\', ?\'b4\' => \'goodbye\', ?\'b5\' => \'env\')', $data); + $data = [ + 'a1' => "a", + 'a2' => "b", + 'a3' => "c", + 'a4' => [ + 'name' => "dsfs", + 'version' => "fdsfs", + ], + ]; + + if (rand(0, 1)) { + $data['b1'] = "hello"; + } + + if (rand(0, 1)) { + $data['b2'] = "hello"; + } + + if (rand(0, 1)) { + $data['b3'] = "hello"; + } + + if (rand(0, 1)) { + $data['b4'] = "goodbye"; + } + + if (rand(0, 1)) { + $data['b5'] = "env"; + } + + assertType('array(\'a1\' => \'a\', \'a2\' => \'b\', \'a3\' => \'c\', \'a4\' => array(\'name\' => \'dsfs\', \'version\' => \'fdsfs\'), ?\'b1\' => \'hello\', ?\'b2\' => \'hello\', ?\'b3\' => \'hello\', ?\'b4\' => \'goodbye\', ?\'b5\' => \'env\')', $data); }; diff --git a/tests/PHPStan/Analyser/data/bug-2288.php b/tests/PHPStan/Analyser/data/bug-2288.php index e0e10a986e..8fc745feed 100644 --- a/tests/PHPStan/Analyser/data/bug-2288.php +++ b/tests/PHPStan/Analyser/data/bug-2288.php @@ -6,19 +6,19 @@ class One { - public function test() :?\DateTimeImmutable - { - return rand(0,1) === 1 ? new \DateTimeImmutable(): null; - } + public function test(): ?\DateTimeImmutable + { + return rand(0, 1) === 1 ? new \DateTimeImmutable() : null; + } } class Two { - public function test(): void - { - $test = new One(); - if ($test->test()) { - assertType(\DateTimeImmutable::class, $test->test()); - } - } + public function test(): void + { + $test = new One(); + if ($test->test()) { + assertType(\DateTimeImmutable::class, $test->test()); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2378.php b/tests/PHPStan/Analyser/data/bug-2378.php index ab58f7a28c..695c678dcf 100644 --- a/tests/PHPStan/Analyser/data/bug-2378.php +++ b/tests/PHPStan/Analyser/data/bug-2378.php @@ -6,18 +6,15 @@ class Foo { + public function doFoo( + $mixed, + int $int, + string $s, + float $f + ): void { + assertType('array(\'a\', \'b\', \'c\', \'d\')', range('a', 'd')); + assertType('array(\'a\', \'c\', \'e\', \'g\', \'i\')', range('a', 'i', 2)); - public function doFoo( - $mixed, - int $int, - string $s, - float $f - ): void - { - assertType('array(\'a\', \'b\', \'c\', \'d\')', range('a', 'd')); - assertType('array(\'a\', \'c\', \'e\', \'g\', \'i\')', range('a', 'i', 2)); - - assertType('array', range($s, $s)); - } - + assertType('array', range($s, $s)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2443.php b/tests/PHPStan/Analyser/data/bug-2443.php index 7ab56ef332..487de33e14 100644 --- a/tests/PHPStan/Analyser/data/bug-2443.php +++ b/tests/PHPStan/Analyser/data/bug-2443.php @@ -7,11 +7,10 @@ /** * @param array $a */ -function (array $a): void -{ - assertType('bool', array_filter($a) !== []); - assertType('bool', [] !== array_filter($a)); +function (array $a): void { + assertType('bool', array_filter($a) !== []); + assertType('bool', [] !== array_filter($a)); - assertType('bool', array_filter($a) === []); - assertType('bool', [] === array_filter($a)); + assertType('bool', array_filter($a) === []); + assertType('bool', [] === array_filter($a)); }; diff --git a/tests/PHPStan/Analyser/data/bug-2539.php b/tests/PHPStan/Analyser/data/bug-2539.php index 34bc26f5a6..dba4cd9096 100644 --- a/tests/PHPStan/Analyser/data/bug-2539.php +++ b/tests/PHPStan/Analyser/data/bug-2539.php @@ -6,28 +6,25 @@ class Foo { - - /** - * @param array $array - * @param non-empty-array $nonEmptyArray - */ - public function doFoo( - array $array, - array $nonEmptyArray - ): void - { - assertType('int|false', current($array)); - assertType('int', current($nonEmptyArray)); - - assertType('false', current([])); - assertType('1|2|3', current([1, 2, 3])); - - $a = []; - if (rand(0, 1)) { - $a[] = 1; - } - - assertType('1|false', current($a)); - } - + /** + * @param array $array + * @param non-empty-array $nonEmptyArray + */ + public function doFoo( + array $array, + array $nonEmptyArray + ): void { + assertType('int|false', current($array)); + assertType('int', current($nonEmptyArray)); + + assertType('false', current([])); + assertType('1|2|3', current([1, 2, 3])); + + $a = []; + if (rand(0, 1)) { + $a[] = 1; + } + + assertType('1|false', current($a)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2549.php b/tests/PHPStan/Analyser/data/bug-2549.php index ad9f6601ea..92f12636ae 100644 --- a/tests/PHPStan/Analyser/data/bug-2549.php +++ b/tests/PHPStan/Analyser/data/bug-2549.php @@ -8,46 +8,45 @@ class Pear { - - public function doFoo(): void - { - $apples = [1, 2, 3]; - - foreach($apples as $apple) { - $pear = null; - - switch(true) { - case true: - $pear = new self(); - - break; - - default: - continue 2; - } - - assertType('Bug2549\Pear', $pear); - } - } - - public function sayHello(): void - { - $array = [1,2,3]; - - foreach($array as $value) { - switch ($value) { - case 1: - $a = 2; - break; - case 2: - $a = 3; - break; - default: - continue 2; - } - - assertType('2|3', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } - } + public function doFoo(): void + { + $apples = [1, 2, 3]; + + foreach ($apples as $apple) { + $pear = null; + + switch (true) { + case true: + $pear = new self(); + + break; + + default: + continue 2; + } + + assertType('Bug2549\Pear', $pear); + } + } + + public function sayHello(): void + { + $array = [1,2,3]; + + foreach ($array as $value) { + switch ($value) { + case 1: + $a = 2; + break; + case 2: + $a = 3; + break; + default: + continue 2; + } + + assertType('2|3', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2600.php b/tests/PHPStan/Analyser/data/bug-2600.php index 07ca71fd77..a8b1b8ef90 100644 --- a/tests/PHPStan/Analyser/data/bug-2600.php +++ b/tests/PHPStan/Analyser/data/bug-2600.php @@ -6,83 +6,92 @@ class Foo { - /** - * @param mixed ...$x - */ - public function doFoo($x = null) { - $args = func_get_args(); - assertType('mixed', $x); - assertType('array', $args); - } + /** + * @param mixed ...$x + */ + public function doFoo($x = null) + { + $args = func_get_args(); + assertType('mixed', $x); + assertType('array', $args); + } - /** - * @param mixed ...$x - */ - public function doBar($x = null) { - assertType('mixed', $x); - } + /** + * @param mixed ...$x + */ + public function doBar($x = null) + { + assertType('mixed', $x); + } - /** - * @param mixed $x - */ - public function doBaz(...$x) { - assertType('array', $x); - } + /** + * @param mixed $x + */ + public function doBaz(...$x) + { + assertType('array', $x); + } - /** - * @param mixed ...$x - */ - public function doLorem(...$x) { - assertType('array', $x); - } + /** + * @param mixed ...$x + */ + public function doLorem(...$x) + { + assertType('array', $x); + } - public function doIpsum($x = null) { - $args = func_get_args(); - assertType('mixed', $x); - assertType('array', $args); - } + public function doIpsum($x = null) + { + $args = func_get_args(); + assertType('mixed', $x); + assertType('array', $args); + } } class Bar { - /** - * @param string ...$x - */ - public function doFoo($x = null) { - $args = func_get_args(); - assertType('string|null', $x); - assertType('array', $args); - } + /** + * @param string ...$x + */ + public function doFoo($x = null) + { + $args = func_get_args(); + assertType('string|null', $x); + assertType('array', $args); + } - /** - * @param string ...$x - */ - public function doBar($x = null) { - assertType('string|null', $x); - } + /** + * @param string ...$x + */ + public function doBar($x = null) + { + assertType('string|null', $x); + } - /** - * @param string $x - */ - public function doBaz(...$x) { - assertType('array', $x); - } + /** + * @param string $x + */ + public function doBaz(...$x) + { + assertType('array', $x); + } - /** - * @param string ...$x - */ - public function doLorem(...$x) { - assertType('array', $x); - } + /** + * @param string ...$x + */ + public function doLorem(...$x) + { + assertType('array', $x); + } } function foo($x, string ...$y): void { - assertType('mixed', $x); - assertType('array', $y); + assertType('mixed', $x); + assertType('array', $y); } function ($x, string ...$y): void { - assertType('mixed', $x); - assertType('array', $y); + assertType('mixed', $x); + assertType('array', $y); }; diff --git a/tests/PHPStan/Analyser/data/bug-2611.php b/tests/PHPStan/Analyser/data/bug-2611.php index 6f9df231e7..54ccb9dfef 100644 --- a/tests/PHPStan/Analyser/data/bug-2611.php +++ b/tests/PHPStan/Analyser/data/bug-2611.php @@ -10,21 +10,21 @@ */ function flatten($collection) { - $stack = [$collection]; - $result = []; + $stack = [$collection]; + $result = []; - while (!empty($stack)) { - $item = \array_shift($stack); - assertType('mixed', $item); + while (!empty($stack)) { + $item = \array_shift($stack); + assertType('mixed', $item); - if (\is_array($item) || $item instanceof \Traversable) { - foreach ($item as $element) { - \array_unshift($stack, $element); - } - } else { - \array_unshift($result, $item); - } - } + if (\is_array($item) || $item instanceof \Traversable) { + foreach ($item as $element) { + \array_unshift($stack, $element); + } + } else { + \array_unshift($result, $item); + } + } - return $result; + return $result; } diff --git a/tests/PHPStan/Analyser/data/bug-2612.php b/tests/PHPStan/Analyser/data/bug-2612.php index 1673410781..95e782846a 100644 --- a/tests/PHPStan/Analyser/data/bug-2612.php +++ b/tests/PHPStan/Analyser/data/bug-2612.php @@ -6,30 +6,25 @@ class TypeFactory { - - /** - * @phpstan-template T - * @phpstan-param T $type - * @phpstan-return T - */ - public static function singleton($type) - { - return $type; - } - + /** + * @phpstan-template T + * @phpstan-param T $type + * @phpstan-return T + */ + public static function singleton($type) + { + return $type; + } } class StringType { - - public static function create(string $value): self - { - $valueType = new static(); - $result = TypeFactory::singleton($valueType); - assertType('static(Bug2612\StringType)', $result); - - return $result; - } - - + public static function create(string $value): self + { + $valueType = new static(); + $result = TypeFactory::singleton($valueType); + assertType('static(Bug2612\StringType)', $result); + + return $result; + } } diff --git a/tests/PHPStan/Analyser/data/bug-2648.php b/tests/PHPStan/Analyser/data/bug-2648.php index 23797170a1..d9f49dc8ac 100644 --- a/tests/PHPStan/Analyser/data/bug-2648.php +++ b/tests/PHPStan/Analyser/data/bug-2648.php @@ -6,46 +6,44 @@ class Foo { - - /** - * @param bool[] $list - */ - public function doFoo(array $list): void - { - if (count($list) > 1) { - assertType('int<2, max>', count($list)); - unset($list['fooo']); - assertType('array', $list); - assertType('int<0, max>', count($list)); - } - } - - /** - * @param bool[] $list - */ - public function doBar(array $list): void - { - if (count($list) > 1) { - assertType('int<2, max>', count($list)); - foreach ($list as $key => $item) { - assertType('0|int<2, max>', count($list)); - if ($item === false) { - unset($list[$key]); - assertType('int<0, max>', count($list)); - } - - assertType('int<0, max>', count($list)); - - if (count($list) === 1) { - assertType('int<1, max>', count($list)); - break; - } - } - - assertType('int<0, max>', count($list)); - } - - assertType('int<0, max>', count($list)); - } - + /** + * @param bool[] $list + */ + public function doFoo(array $list): void + { + if (count($list) > 1) { + assertType('int<2, max>', count($list)); + unset($list['fooo']); + assertType('array', $list); + assertType('int<0, max>', count($list)); + } + } + + /** + * @param bool[] $list + */ + public function doBar(array $list): void + { + if (count($list) > 1) { + assertType('int<2, max>', count($list)); + foreach ($list as $key => $item) { + assertType('0|int<2, max>', count($list)); + if ($item === false) { + unset($list[$key]); + assertType('int<0, max>', count($list)); + } + + assertType('int<0, max>', count($list)); + + if (count($list) === 1) { + assertType('int<1, max>', count($list)); + break; + } + } + + assertType('int<0, max>', count($list)); + } + + assertType('int<0, max>', count($list)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2676.php b/tests/PHPStan/Analyser/data/bug-2676.php index 4daa2b5552..d3adf885b9 100644 --- a/tests/PHPStan/Analyser/data/bug-2676.php +++ b/tests/PHPStan/Analyser/data/bug-2676.php @@ -7,7 +7,6 @@ class BankAccount { - } /** @@ -16,30 +15,29 @@ class BankAccount */ class Wallet { - /** - * @var Collection - * - * @ORM\OneToMany(targetEntity=BankAccount::class, mappedBy="wallet") - * @ORM\OrderBy({"id" = "ASC"}) - */ - private $bankAccountList; - - /** - * @return Collection - */ - public function getBankAccountList(): Collection - { - return $this->bankAccountList; - } + /** + * @var Collection + * + * @ORM\OneToMany(targetEntity=BankAccount::class, mappedBy="wallet") + * @ORM\OrderBy({"id" = "ASC"}) + */ + private $bankAccountList; + + /** + * @return Collection + */ + public function getBankAccountList(): Collection + { + return $this->bankAccountList; + } } -function (Wallet $wallet): void -{ - $bankAccounts = $wallet->getBankAccountList(); - assertType('DoctrineIntersectionTypeIsSupertypeOf\Collection&iterable', $bankAccounts); +function (Wallet $wallet): void { + $bankAccounts = $wallet->getBankAccountList(); + assertType('DoctrineIntersectionTypeIsSupertypeOf\Collection&iterable', $bankAccounts); - foreach ($bankAccounts as $key => $bankAccount) { - assertType('(int|string)', $key); - assertType('Bug2676\BankAccount', $bankAccount); - } + foreach ($bankAccounts as $key => $bankAccount) { + assertType('(int|string)', $key); + assertType('Bug2676\BankAccount', $bankAccount); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-2677.php b/tests/PHPStan/Analyser/data/bug-2677.php index aaf1201b2f..c19dac17a9 100644 --- a/tests/PHPStan/Analyser/data/bug-2677.php +++ b/tests/PHPStan/Analyser/data/bug-2677.php @@ -4,46 +4,77 @@ use function PHPStan\Testing\assertType; -class A {} -class B {} -class C {} -class D {} -class E {} -class F {} -class G {} -class H {} -class I {} -class J {} -class K {} -class L {} -class M {} -class N {} -class O {} -class P {} +class A +{ +} +class B +{ +} +class C +{ +} +class D +{ +} +class E +{ +} +class F +{ +} +class G +{ +} +class H +{ +} +class I +{ +} +class J +{ +} +class K +{ +} +class L +{ +} +class M +{ +} +class N +{ +} +class O +{ +} +class P +{ +} -function () -{ - $classes = [ - A::class, - B::class, - C::class, - D::class, - E::class, - F::class, - G::class, - H::class, - I::class, - J::class, - K::class, - L::class, - M::class, - N::class, - O::class, - P::class, - ]; - assertType('array(\'Bug2677\\\\A\', \'Bug2677\\\\B\', \'Bug2677\\\\C\', \'Bug2677\\\\D\', \'Bug2677\\\\E\', \'Bug2677\\\\F\', \'Bug2677\\\\G\', \'Bug2677\\\\H\', \'Bug2677\\\\I\', \'Bug2677\\\\J\', \'Bug2677\\\\K\', \'Bug2677\\\\L\', \'Bug2677\\\\M\', \'Bug2677\\\\N\', \'Bug2677\\\\O\', \'Bug2677\\\\P\')', $classes); +function () { + $classes = [ + A::class, + B::class, + C::class, + D::class, + E::class, + F::class, + G::class, + H::class, + I::class, + J::class, + K::class, + L::class, + M::class, + N::class, + O::class, + P::class, + ]; + assertType('array(\'Bug2677\\\\A\', \'Bug2677\\\\B\', \'Bug2677\\\\C\', \'Bug2677\\\\D\', \'Bug2677\\\\E\', \'Bug2677\\\\F\', \'Bug2677\\\\G\', \'Bug2677\\\\H\', \'Bug2677\\\\I\', \'Bug2677\\\\J\', \'Bug2677\\\\K\', \'Bug2677\\\\L\', \'Bug2677\\\\M\', \'Bug2677\\\\N\', \'Bug2677\\\\O\', \'Bug2677\\\\P\')', $classes); - foreach ($classes as $class) { - assertType('class-string', $class); - } + foreach ($classes as $class) { + assertType('class-string', $class); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-2733.php b/tests/PHPStan/Analyser/data/bug-2733.php index d40acdc032..a5786fb2b2 100644 --- a/tests/PHPStan/Analyser/data/bug-2733.php +++ b/tests/PHPStan/Analyser/data/bug-2733.php @@ -6,19 +6,17 @@ class Foo { + /** + * @param array{id:int, name:string} $data + */ + public function doSomething(array $data): void + { + foreach (['id', 'name'] as $required) { + if (!isset($data[$required])) { + throw new \Exception(sprintf('Missing data element: %s', $required)); + } + } - /** - * @param array{id:int, name:string} $data - */ - public function doSomething(array $data): void - { - foreach (['id', 'name'] as $required) { - if (!isset($data[$required])) { - throw new \Exception(sprintf('Missing data element: %s', $required)); - } - } - - assertType('array(\'id\' => int, \'name\' => string)', $data); - } - + assertType('array(\'id\' => int, \'name\' => string)', $data); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2740.php b/tests/PHPStan/Analyser/data/bug-2740.php index 2aa2ef14be..eb76abd29a 100644 --- a/tests/PHPStan/Analyser/data/bug-2740.php +++ b/tests/PHPStan/Analyser/data/bug-2740.php @@ -4,9 +4,8 @@ use function PHPStan\Testing\assertType; -function (Member $member): void -{ - foreach ($member as $i) { - assertType('Bug2740\Member', $i); - } +function (Member $member): void { + foreach ($member as $i) { + assertType('Bug2740\Member', $i); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-2750.php b/tests/PHPStan/Analyser/data/bug-2750.php index c48c567e25..a9da4ac8a1 100644 --- a/tests/PHPStan/Analyser/data/bug-2750.php +++ b/tests/PHPStan/Analyser/data/bug-2750.php @@ -5,23 +5,23 @@ use function PHPStan\Testing\assertType; function (array $input) { - \assert(count($input) > 0); - assertType('int<1, max>', count($input)); - array_shift($input); - assertType('int<0, max>', count($input)); + \assert(count($input) > 0); + assertType('int<1, max>', count($input)); + array_shift($input); + assertType('int<0, max>', count($input)); - \assert(count($input) > 0); - assertType('int<1, max>', count($input)); - array_pop($input); - assertType('int<0, max>', count($input)); + \assert(count($input) > 0); + assertType('int<1, max>', count($input)); + array_pop($input); + assertType('int<0, max>', count($input)); - \assert(count($input) > 0); - assertType('int<1, max>', count($input)); - array_unshift($input, 'test'); - assertType('int<1, max>', count($input)); + \assert(count($input) > 0); + assertType('int<1, max>', count($input)); + array_unshift($input, 'test'); + assertType('int<1, max>', count($input)); - \assert(count($input) > 0); - assertType('int<1, max>', count($input)); - array_push($input, 'nope'); - assertType('int<1, max>', count($input)); + \assert(count($input) > 0); + assertType('int<1, max>', count($input)); + array_push($input, 'nope'); + assertType('int<1, max>', count($input)); }; diff --git a/tests/PHPStan/Analyser/data/bug-2816-2.php b/tests/PHPStan/Analyser/data/bug-2816-2.php index b3f5d154a1..df6fd7ec3d 100644 --- a/tests/PHPStan/Analyser/data/bug-2816-2.php +++ b/tests/PHPStan/Analyser/data/bug-2816-2.php @@ -7,29 +7,29 @@ use function PHPStan\Testing\assertVariableCertainty; if (isset($_GET['x'])) { - $a = 1; + $a = 1; } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); assertType('mixed', $a); if (isset($a)) { - echo "hello"; - assertVariableCertainty(TrinaryLogic::createYes(), $a); - assertType('mixed~null', $a); + echo "hello"; + assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertType('mixed~null', $a); } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); assertType('mixed', $a); if (isset($a)) { - echo "hello2"; - assertVariableCertainty(TrinaryLogic::createYes(), $a); - assertType('mixed~null', $a); + echo "hello2"; + assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertType('mixed~null', $a); } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); diff --git a/tests/PHPStan/Analyser/data/bug-2816.php b/tests/PHPStan/Analyser/data/bug-2816.php index 16482bd8f9..a6c52c4b79 100644 --- a/tests/PHPStan/Analyser/data/bug-2816.php +++ b/tests/PHPStan/Analyser/data/bug-2816.php @@ -7,29 +7,29 @@ use function PHPStan\Testing\assertVariableCertainty; if (isset($_GET['x'])) { - $a = 1; + $a = 1; } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); assertType('mixed', $a); if (isset($a) === true) { - echo "hello"; - assertVariableCertainty(TrinaryLogic::createYes(), $a); - assertType('mixed~null', $a); + echo "hello"; + assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertType('mixed~null', $a); } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); assertType('mixed', $a); if (isset($a) === true) { - echo "hello2"; - assertVariableCertainty(TrinaryLogic::createYes(), $a); - assertType('mixed~null', $a); + echo "hello2"; + assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertType('mixed~null', $a); } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); } assertVariableCertainty(TrinaryLogic::createMaybe(), $a); diff --git a/tests/PHPStan/Analyser/data/bug-2822.php b/tests/PHPStan/Analyser/data/bug-2822.php index 2a6d49efdb..e9b4de5356 100644 --- a/tests/PHPStan/Analyser/data/bug-2822.php +++ b/tests/PHPStan/Analyser/data/bug-2822.php @@ -4,40 +4,39 @@ use function PHPStan\Testing\assertType; -$getter = function (string $key) use ($store): int -{ - return $store[$key]; +$getter = function (string $key) use ($store): int { + return $store[$key]; }; function (array $tokens) { - $i = 0; - \assert($tokens[$i] === 1); - assertType('1', $tokens[$i]); - $i++; - assertType('mixed', $tokens[$i]); + $i = 0; + \assert($tokens[$i] === 1); + assertType('1', $tokens[$i]); + $i++; + assertType('mixed', $tokens[$i]); }; function () use ($getter) { - $key = 'foo'; + $key = 'foo'; - if ($getter($key) > 4) { - assertType('int<5, max>', $getter($key)); - $key = 'bar'; - assertType('int', $getter($key)); - } + if ($getter($key) > 4) { + assertType('int<5, max>', $getter($key)); + $key = 'bar'; + assertType('int', $getter($key)); + } }; function (array $tokens) { - $lastContent = 'a'; - - if ($tokens[$lastContent]['code'] === 1 - || $tokens[$lastContent]['code'] === 2 - ) { - assertType('1|2', $tokens[$lastContent]['code']); - $lastContent = 'b'; - assertType('mixed', $tokens[$lastContent]['code']); - if ($tokens[$lastContent]['code'] === 3) { - echo "what?"; - } - } + $lastContent = 'a'; + + if ($tokens[$lastContent]['code'] === 1 + || $tokens[$lastContent]['code'] === 2 + ) { + assertType('1|2', $tokens[$lastContent]['code']); + $lastContent = 'b'; + assertType('mixed', $tokens[$lastContent]['code']); + if ($tokens[$lastContent]['code'] === 3) { + echo "what?"; + } + } }; diff --git a/tests/PHPStan/Analyser/data/bug-2823.php b/tests/PHPStan/Analyser/data/bug-2823.php index 99b4733514..fb38a7c701 100644 --- a/tests/PHPStan/Analyser/data/bug-2823.php +++ b/tests/PHPStan/Analyser/data/bug-2823.php @@ -1,13 +1,13 @@ - $tokens - * @return bool - */ - public function doFoo(array $tokens): bool { - $i = 0; - while (isset($tokens[$i])) { - assertType('int', $i); - if ($tokens[$i]['code'] !== 1) { - assertType('mixed~1', $tokens[$i]['code']); - $i++; - assertType('int', $i); - assertType('mixed', $tokens[$i]['code']); - continue; - } - assertType('1', $tokens[$i]['code']); - $i++; - assertType('int', $i); - assertType('mixed', $tokens[$i]['code']); - if ($tokens[$i]['code'] !== 2) { - assertType('mixed~2', $tokens[$i]['code']); - $i++; - assertType('int', $i); - continue; - } - assertType('2', $tokens[$i]['code']); - return true; - } - return false; - } - + /** + * @param array $tokens + * @return bool + */ + public function doFoo(array $tokens): bool + { + $i = 0; + while (isset($tokens[$i])) { + assertType('int', $i); + if ($tokens[$i]['code'] !== 1) { + assertType('mixed~1', $tokens[$i]['code']); + $i++; + assertType('int', $i); + assertType('mixed', $tokens[$i]['code']); + continue; + } + assertType('1', $tokens[$i]['code']); + $i++; + assertType('int', $i); + assertType('mixed', $tokens[$i]['code']); + if ($tokens[$i]['code'] !== 2) { + assertType('mixed~2', $tokens[$i]['code']); + $i++; + assertType('int', $i); + continue; + } + assertType('2', $tokens[$i]['code']); + return true; + } + return false; + } } diff --git a/tests/PHPStan/Analyser/data/bug-2850.php b/tests/PHPStan/Analyser/data/bug-2850.php index 76666b52d9..f0539107b0 100644 --- a/tests/PHPStan/Analyser/data/bug-2850.php +++ b/tests/PHPStan/Analyser/data/bug-2850.php @@ -6,29 +6,31 @@ class Foo { - public function y(): void {} + public function y(): void + { + } } class Bar { - /** @var Foo|null */ - private $x; + /** @var Foo|null */ + private $x; - public function getFoo(): Foo - { - if ($this->x === null) { - $this->x = new Foo(); - assertType(Foo::class, $this->x); - $this->x->y(); - assertType(Foo::class, $this->x); - $this->y(); - assertType(Foo::class . '|null', $this->x); - } - return $this->x; - } + public function getFoo(): Foo + { + if ($this->x === null) { + $this->x = new Foo(); + assertType(Foo::class, $this->x); + $this->x->y(); + assertType(Foo::class, $this->x); + $this->y(); + assertType(Foo::class . '|null', $this->x); + } + return $this->x; + } - public function y(): void - { - $this->x = null; - } + public function y(): void + { + $this->x = null; + } } diff --git a/tests/PHPStan/Analyser/data/bug-2863.php b/tests/PHPStan/Analyser/data/bug-2863.php index 5f70c4795a..ed67a25375 100644 --- a/tests/PHPStan/Analyser/data/bug-2863.php +++ b/tests/PHPStan/Analyser/data/bug-2863.php @@ -9,7 +9,7 @@ assertType('string', json_last_error_msg()); if (json_last_error() !== JSON_ERROR_NONE || json_last_error_msg() !== 'No error') { - throw new Exception(json_last_error_msg()); + throw new Exception(json_last_error_msg()); } assertType('0', json_last_error()); @@ -21,7 +21,7 @@ assertType('string', json_last_error_msg()); if (json_last_error() !== JSON_ERROR_NONE || json_last_error_msg() !== 'No error') { - throw new Exception(json_last_error_msg()); + throw new Exception(json_last_error_msg()); } assertType('0', json_last_error()); @@ -33,7 +33,7 @@ assertType('string', json_last_error_msg()); if (json_last_error() !== JSON_ERROR_NONE || json_last_error_msg() !== 'No error') { - throw new Exception(json_last_error_msg()); + throw new Exception(json_last_error_msg()); } assertType('0', json_last_error()); diff --git a/tests/PHPStan/Analyser/data/bug-2869.php b/tests/PHPStan/Analyser/data/bug-2869.php index dc6ed20185..9213fa110e 100644 --- a/tests/PHPStan/Analyser/data/bug-2869.php +++ b/tests/PHPStan/Analyser/data/bug-2869.php @@ -6,27 +6,25 @@ class Foo { + /** + * @param array $bar + */ + public function doFoo(array $bar): void + { + if (array_key_exists('foo', $bar)) { + $foobar = isset($bar['foo']); + assertType('bool', $foobar); + } + } - /** - * @param array $bar - */ - public function doFoo(array $bar): void - { - if (array_key_exists('foo', $bar)) { - $foobar = isset($bar['foo']); - assertType('bool' ,$foobar); - } - } - - /** - * @param array $bar - */ - public function doBar(array $bar): void - { - if (array_key_exists('foo', $bar)) { - $foobar = isset($bar['foo']); - assertType('true' ,$foobar); - } - } - + /** + * @param array $bar + */ + public function doBar(array $bar): void + { + if (array_key_exists('foo', $bar)) { + $foobar = isset($bar['foo']); + assertType('true', $foobar); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2899.php b/tests/PHPStan/Analyser/data/bug-2899.php index 2342fb2466..0dba9640ba 100644 --- a/tests/PHPStan/Analyser/data/bug-2899.php +++ b/tests/PHPStan/Analyser/data/bug-2899.php @@ -6,13 +6,11 @@ class Foo { - - public function doFoo(string $s, $mixed) - { - assertType('string&numeric', date('Y')); - assertType('string', date('Y.m.d')); - assertType('string', date($s)); - assertType('string', date($mixed)); - } - + public function doFoo(string $s, $mixed) + { + assertType('string&numeric', date('Y')); + assertType('string', date('Y.m.d')); + assertType('string', date($s)); + assertType('string', date($mixed)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2906.php b/tests/PHPStan/Analyser/data/bug-2906.php index 3c53aa68e4..7d485a7c67 100644 --- a/tests/PHPStan/Analyser/data/bug-2906.php +++ b/tests/PHPStan/Analyser/data/bug-2906.php @@ -2,19 +2,18 @@ namespace Bug2906; - use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; class HelloWorld { - /** - * @param string $thing - * @param mixed $value - */ - public function sayHello($thing, $value): void - { - assertType('false', $thing === null); - assertNativeType('bool', $thing === null); - } + /** + * @param string $thing + * @param mixed $value + */ + public function sayHello($thing, $value): void + { + assertType('false', $thing === null); + assertNativeType('bool', $thing === null); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2927.php b/tests/PHPStan/Analyser/data/bug-2927.php index a7f7811398..86e6aceb44 100644 --- a/tests/PHPStan/Analyser/data/bug-2927.php +++ b/tests/PHPStan/Analyser/data/bug-2927.php @@ -6,30 +6,27 @@ abstract class Event { - } class MyEvent extends Event { - } function (): void { - $reflect = new \ReflectionFunction('test\\handler'); - $paramClass = $reflect->getParameters()[0]->getClass(); - if ($paramClass === null or !$paramClass->isSubclassOf(Event::class)) { - return; - } - - /** @var \ReflectionClass $paramClass */ + $reflect = new \ReflectionFunction('test\\handler'); + $paramClass = $reflect->getParameters()[0]->getClass(); + if ($paramClass === null or !$paramClass->isSubclassOf(Event::class)) { + return; + } - assertType('class-string', $paramClass->getName()); + /** @var \ReflectionClass $paramClass */ - try { - throw new \Exception(); - } catch (\Exception $e) { + assertType('class-string', $paramClass->getName()); - } + try { + throw new \Exception(); + } catch (\Exception $e) { + } - assertType('class-string', $paramClass->getName()); + assertType('class-string', $paramClass->getName()); }; diff --git a/tests/PHPStan/Analyser/data/bug-2945.php b/tests/PHPStan/Analyser/data/bug-2945.php index a680906b1f..90021f77b6 100644 --- a/tests/PHPStan/Analyser/data/bug-2945.php +++ b/tests/PHPStan/Analyser/data/bug-2945.php @@ -5,40 +5,43 @@ use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; -class A{ - /** - * @param \stdClass[] $blocks - * - * @return void - */ - public function doFoo(array $blocks){ - foreach($blocks as $b){ - if(!($b instanceof \stdClass)){ - assertType('*NEVER*', $b); - assertNativeType('mixed~stdClass', $b); - throw new \TypeError(); - } - $pk = new \Exception(); +class A +{ + /** + * @param \stdClass[] $blocks + * + * @return void + */ + public function doFoo(array $blocks) + { + foreach ($blocks as $b) { + if (!($b instanceof \stdClass)) { + assertType('*NEVER*', $b); + assertNativeType('mixed~stdClass', $b); + throw new \TypeError(); + } + $pk = new \Exception(); - $pk->x = $b->x; - } - } + $pk->x = $b->x; + } + } - /** - * @param \stdClass[] $blocks - * - * @return void - */ - public function doBar(array $blocks){ - foreach($blocks as $b){ - if(!($b instanceof \stdClass)){ - assertType('*NEVER*', $b); - assertNativeType('mixed~stdClass', $b); - throw new \TypeError(); - } - $pk = new \Exception(); + /** + * @param \stdClass[] $blocks + * + * @return void + */ + public function doBar(array $blocks) + { + foreach ($blocks as $b) { + if (!($b instanceof \stdClass)) { + assertType('*NEVER*', $b); + assertNativeType('mixed~stdClass', $b); + throw new \TypeError(); + } + $pk = new \Exception(); - $x = $b->x; - } - } + $x = $b->x; + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2954.php b/tests/PHPStan/Analyser/data/bug-2954.php index 73b21239e6..8e3279a63c 100644 --- a/tests/PHPStan/Analyser/data/bug-2954.php +++ b/tests/PHPStan/Analyser/data/bug-2954.php @@ -5,33 +5,41 @@ use function PHPStan\Testing\assertType; function (int $x) { - if ($x === 0) return; - assertType('int|int<1, max>', $x); + if ($x === 0) { + return; + } + assertType('int|int<1, max>', $x); - $x++; - assertType('int', $x); + $x++; + assertType('int', $x); }; function (int $x) { - if ($x === 0) return; - assertType('int|int<1, max>', $x); + if ($x === 0) { + return; + } + assertType('int|int<1, max>', $x); - ++$x; - assertType('int', $x); + ++$x; + assertType('int', $x); }; function (int $x) { - if ($x === 0) return; - assertType('int|int<1, max>', $x); + if ($x === 0) { + return; + } + assertType('int|int<1, max>', $x); - $x--; - assertType('int', $x); + $x--; + assertType('int', $x); }; function (int $x) { - if ($x === 0) return; - assertType('int|int<1, max>', $x); + if ($x === 0) { + return; + } + assertType('int|int<1, max>', $x); - --$x; - assertType('int', $x); + --$x; + assertType('int', $x); }; diff --git a/tests/PHPStan/Analyser/data/bug-2969.php b/tests/PHPStan/Analyser/data/bug-2969.php index a494cd2c03..6467ce3174 100644 --- a/tests/PHPStan/Analyser/data/bug-2969.php +++ b/tests/PHPStan/Analyser/data/bug-2969.php @@ -2,29 +2,28 @@ namespace Bug2969; - use function PHPStan\Testing\assertType; class HelloWorld { - public function methodWithOccasionalUndocumentedException(): void - { - if (time()%2==0) { - throw new \Exception('Go straight to finally'); - } - } + public function methodWithOccasionalUndocumentedException(): void + { + if (time()%2==0) { + throw new \Exception('Go straight to finally'); + } + } - public function execute(): void - { - // ...Upload image to permanent storage + public function execute(): void + { + // ...Upload image to permanent storage - $done = false; - try{ - // call method to save reference to database - $this->methodWithOccasionalUndocumentedException(); - $done = true; - } finally { - assertType('bool', $done); - } - } + $done = false; + try { + // call method to save reference to database + $this->methodWithOccasionalUndocumentedException(); + $done = true; + } finally { + assertType('bool', $done); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2977.php b/tests/PHPStan/Analyser/data/bug-2977.php index 285fff0d1b..ba64f46fce 100644 --- a/tests/PHPStan/Analyser/data/bug-2977.php +++ b/tests/PHPStan/Analyser/data/bug-2977.php @@ -6,75 +6,75 @@ interface MyInterface { - /** - * @return self|null The parent or null if there is none - */ - public function getParent(); + /** + * @return self|null The parent or null if there is none + */ + public function getParent(); - /** - * @return mixed data - * - * @throws \Exception If the form inherits data but has no parent - */ - public function getData(); + /** + * @return mixed data + * + * @throws \Exception If the form inherits data but has no parent + */ + public function getData(); } class My implements MyInterface { - /** - * @var MyInterface|null - */ - private $parent; + /** + * @var MyInterface|null + */ + private $parent; - /** - * @var mixed - */ - private $data; + /** + * @var mixed + */ + private $data; - /** - * {@inheritdoc} - */ - public function getParent() - { - return $this->parent; - } + /** + * {@inheritdoc} + */ + public function getParent() + { + return $this->parent; + } - /** - * {@inheritdoc} - */ - public function getData() - { - return $this->data; - } + /** + * {@inheritdoc} + */ + public function getData() + { + return $this->data; + } } class Bar { - public function baz(MyInterface $my): string - { - $parent = $my->getParent(); - if (!$parent) { - assertType('null', $parent); - return 'ok'; - } + public function baz(MyInterface $my): string + { + $parent = $my->getParent(); + if (!$parent) { + assertType('null', $parent); + return 'ok'; + } - assertType(MyInterface::class, $parent); + assertType(MyInterface::class, $parent); - return $parent->getData(); - } + return $parent->getData(); + } - public function baz2(MyInterface $my): string - { - $parent = $my->getParent(); + public function baz2(MyInterface $my): string + { + $parent = $my->getParent(); - $case = $parent; - if (!$case) { - assertType('null', $parent); - return 'ok'; - } + $case = $parent; + if (!$case) { + assertType('null', $parent); + return 'ok'; + } - assertType(MyInterface::class, $parent); + assertType(MyInterface::class, $parent); - return $parent->getData(); - } + return $parent->getData(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-2980.php b/tests/PHPStan/Analyser/data/bug-2980.php index 6592b4f022..64d8f1e5c0 100644 --- a/tests/PHPStan/Analyser/data/bug-2980.php +++ b/tests/PHPStan/Analyser/data/bug-2980.php @@ -1,34 +1,36 @@ -v(); - - // 1. Direct test in IF statement - Correct - if (is_array($v)) { - array_shift($v); - } - - // 2. Direct test in IF (ternary)Correct - print_r(is_array($v) ? array_shift($v) : 'xyz'); - - // 3. Result of test stored into variable - PHPStan thows an eror - $isArray = is_array($v); - if ($isArray) { - assertType('array', $v); - array_shift($v); - } - - } + public function m(I $impl): void + { + $v = $impl->v(); + + // 1. Direct test in IF statement - Correct + if (is_array($v)) { + array_shift($v); + } + + // 2. Direct test in IF (ternary)Correct + print_r(is_array($v) ? array_shift($v) : 'xyz'); + + // 3. Result of test stored into variable - PHPStan thows an eror + $isArray = is_array($v); + if ($isArray) { + assertType('array', $v); + array_shift($v); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-2997.php b/tests/PHPStan/Analyser/data/bug-2997.php index 0a19cf42f0..c227149e56 100644 --- a/tests/PHPStan/Analyser/data/bug-2997.php +++ b/tests/PHPStan/Analyser/data/bug-2997.php @@ -5,5 +5,5 @@ use function PHPStan\Testing\assertType; function (\SimpleXMLElement $xml): void { - assertType('bool', (bool) $xml->Exists); + assertType('bool', (bool) $xml->Exists); }; diff --git a/tests/PHPStan/Analyser/data/bug-3004.php b/tests/PHPStan/Analyser/data/bug-3004.php index 8ae8147345..cc398a3d2c 100644 --- a/tests/PHPStan/Analyser/data/bug-3004.php +++ b/tests/PHPStan/Analyser/data/bug-3004.php @@ -7,19 +7,19 @@ class HelloWorld { - public function sayHello(): void - { - try { - $a = $this->getA(); - } catch (\InvalidArgumentException $e) { - $a = 2; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } - } + public function sayHello(): void + { + try { + $a = $this->getA(); + } catch (\InvalidArgumentException $e) { + $a = 2; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + } + } - private function getA(): int - { - throw new \DomainException('test'); - } + private function getA(): int + { + throw new \DomainException('test'); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3009.php b/tests/PHPStan/Analyser/data/bug-3009.php index de9f96ca4c..fce055bd9a 100644 --- a/tests/PHPStan/Analyser/data/bug-3009.php +++ b/tests/PHPStan/Analyser/data/bug-3009.php @@ -6,25 +6,23 @@ class HelloWorld { + public function createRedirectRequest(string $redirectUri): ?string + { + $redirectUrlParts = parse_url($redirectUri); + if (false === is_array($redirectUrlParts) || true === array_key_exists('host', $redirectUrlParts)) { + assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $redirectUrlParts); + return null; + } - public function createRedirectRequest(string $redirectUri): ?string - { - $redirectUrlParts = parse_url($redirectUri); - if (false === is_array($redirectUrlParts) || true === array_key_exists('host', $redirectUrlParts)) { - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $redirectUrlParts); - return null; - } + assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + if (true === array_key_exists('query', $redirectUrlParts)) { + assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + $redirectServer['QUERY_STRING'] = $redirectUrlParts['query']; + } - if (true === array_key_exists('query', $redirectUrlParts)) { - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); - $redirectServer['QUERY_STRING'] = $redirectUrlParts['query']; - } - - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); - - return 'foo'; - } + assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + return 'foo'; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3024.php b/tests/PHPStan/Analyser/data/bug-3024.php index cd97fcad70..1fb741804b 100644 --- a/tests/PHPStan/Analyser/data/bug-3024.php +++ b/tests/PHPStan/Analyser/data/bug-3024.php @@ -4,47 +4,52 @@ use function PHPStan\Testing\assertType; -interface A { function getUser() : U; } -interface U { } +interface A +{ + public function getUser(): U; +} +interface U +{ +} class HelloWorld { - /** @param U[]|A[] $arr */ - public function foo($arr) : void - { - foreach($arr as $elt) { - $admin = ($elt instanceof A); - if($elt instanceof A) { - $u = $elt->getUser(); - assertType('Bug3024\A', $elt); - } else { - $u = $elt; - } - } - } + /** @param U[]|A[] $arr */ + public function foo($arr): void + { + foreach ($arr as $elt) { + $admin = ($elt instanceof A); + if ($elt instanceof A) { + $u = $elt->getUser(); + assertType('Bug3024\A', $elt); + } else { + $u = $elt; + } + } + } - /** @param U[]|A[] $arr */ - public function bar($arr) : void - { - foreach($arr as $elt) { - if($admin = ($elt instanceof A)) { - assertType('Bug3024\A', $elt); - } else { - $u = $elt; - } - } - } + /** @param U[]|A[] $arr */ + public function bar($arr): void + { + foreach ($arr as $elt) { + if ($admin = ($elt instanceof A)) { + assertType('Bug3024\A', $elt); + } else { + $u = $elt; + } + } + } - /** @param U[]|A[] $arr */ - public function baz($arr) : void - { - foreach($arr as $elt) { - $admin = ($elt instanceof A); - if($admin) { - assertType('Bug3024\A', $elt); - } else { - $u = $elt; - } - } - } + /** @param U[]|A[] $arr */ + public function baz($arr): void + { + foreach ($arr as $elt) { + $admin = ($elt instanceof A); + if ($admin) { + assertType('Bug3024\A', $elt); + } else { + $u = $elt; + } + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3132.php b/tests/PHPStan/Analyser/data/bug-3132.php index d744dbe9fc..ad97602da6 100644 --- a/tests/PHPStan/Analyser/data/bug-3132.php +++ b/tests/PHPStan/Analyser/data/bug-3132.php @@ -7,42 +7,40 @@ class Foo { + /** + * @param array $objects + * + * @return array + */ + public function filter(array $objects): array + { + return array_filter($objects, static function ($key) { + assertType('string', $key); + }, ARRAY_FILTER_USE_KEY); + } - /** - * @param array $objects - * - * @return array - */ - function filter(array $objects) : array - { - return array_filter($objects, static function ($key) { - assertType('string', $key); - }, ARRAY_FILTER_USE_KEY); - } - - /** - * @param array $objects - * - * @return array - */ - function bar(array $objects) : array - { - return array_filter($objects, static function ($val) { - assertType('object', $val); - }); - } - - /** - * @param array $objects - * - * @return array - */ - function baz(array $objects) : array - { - return array_filter($objects, static function ($val, $key) { - assertType('string', $key); - assertType('object', $val); - }, ARRAY_FILTER_USE_BOTH); - } + /** + * @param array $objects + * + * @return array + */ + public function bar(array $objects): array + { + return array_filter($objects, static function ($val) { + assertType('object', $val); + }); + } + /** + * @param array $objects + * + * @return array + */ + public function baz(array $objects): array + { + return array_filter($objects, static function ($val, $key) { + assertType('string', $key); + assertType('object', $val); + }, ARRAY_FILTER_USE_BOTH); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3133.php b/tests/PHPStan/Analyser/data/bug-3133.php index 002b68dca3..361d11882c 100644 --- a/tests/PHPStan/Analyser/data/bug-3133.php +++ b/tests/PHPStan/Analyser/data/bug-3133.php @@ -6,53 +6,49 @@ class Foo { - - /** - * @param string[]|string $arg - */ - public function doFoo($arg): void - { - if (!is_numeric($arg)) { - assertType('array|string', $arg); - return; - } - - assertType('string&numeric', $arg); - } - - /** - * @param string|bool|float|int|mixed[]|null $arg - */ - public function doBar($arg): void - { - if (\is_numeric($arg)) { - assertType('float|int|(string&numeric)', $arg); - } - } - - /** - * @param numeric $numeric - * @param numeric-string $numericString - */ - public function doBaz( - $numeric, - string $numericString - ) - { - assertType('float|int|(string&numeric)', $numeric); - assertType('string&numeric', $numericString); - } - - /** - * @param numeric-string $numericString - */ - public function doLorem( - string $numericString - ) - { - $a = []; - $a[$numericString] = 'foo'; - assertType('array', $a); - } - + /** + * @param string[]|string $arg + */ + public function doFoo($arg): void + { + if (!is_numeric($arg)) { + assertType('array|string', $arg); + return; + } + + assertType('string&numeric', $arg); + } + + /** + * @param string|bool|float|int|mixed[]|null $arg + */ + public function doBar($arg): void + { + if (\is_numeric($arg)) { + assertType('float|int|(string&numeric)', $arg); + } + } + + /** + * @param numeric $numeric + * @param numeric-string $numericString + */ + public function doBaz( + $numeric, + string $numericString + ) { + assertType('float|int|(string&numeric)', $numeric); + assertType('string&numeric', $numericString); + } + + /** + * @param numeric-string $numericString + */ + public function doLorem( + string $numericString + ) { + $a = []; + $a[$numericString] = 'foo'; + assertType('array', $a); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3134.php b/tests/PHPStan/Analyser/data/bug-3134.php index 43a47c2d7b..e71fdb7b8f 100644 --- a/tests/PHPStan/Analyser/data/bug-3134.php +++ b/tests/PHPStan/Analyser/data/bug-3134.php @@ -6,19 +6,19 @@ class Registry { - /** - * Map of type names and their corresponding flyweight objects. - * - * @var array - */ - private $instances = []; + /** + * Map of type names and their corresponding flyweight objects. + * + * @var array + */ + private $instances = []; - public function get(string $name) : object - { - return $this->instances[$name]; - } + public function get(string $name): object + { + return $this->instances[$name]; + } } function (Registry $r): void { - assertType('bool', $r->get('x') === $r->get('x')); + assertType('bool', $r->get('x') === $r->get('x')); }; diff --git a/tests/PHPStan/Analyser/data/bug-3142.php b/tests/PHPStan/Analyser/data/bug-3142.php index e46fd7fd22..87aa936e88 100644 --- a/tests/PHPStan/Analyser/data/bug-3142.php +++ b/tests/PHPStan/Analyser/data/bug-3142.php @@ -1,4 +1,6 @@ -sayHi()); - assertType('int', $hw->sayHello()); + $hw = new HelloWorld(); + assertType('string', $hw->sayHi()); + assertType('int', $hw->sayHello()); }; interface DecoratorInterface @@ -44,18 +44,18 @@ interface DecoratorInterface class FooDecorator implements DecoratorInterface { - public function getCode(): string - { - return 'FOO'; - } + public function getCode(): string + { + return 'FOO'; + } } trait DecoratorTrait { - public function getDecorator(): DecoratorInterface - { - return new FooDecorator(); - } + public function getDecorator(): DecoratorInterface + { + return new FooDecorator(); + } } /** @@ -63,15 +63,15 @@ public function getDecorator(): DecoratorInterface */ class Dummy { - use DecoratorTrait; + use DecoratorTrait; - public function getLabel(): string - { - return $this->getDecorator()->getCode(); - } + public function getLabel(): string + { + return $this->getDecorator()->getCode(); + } } function () { - $dummy = new Dummy(); - assertType(FooDecorator::class, $dummy->getDecorator()); + $dummy = new Dummy(); + assertType(FooDecorator::class, $dummy->getDecorator()); }; diff --git a/tests/PHPStan/Analyser/data/bug-3190.php b/tests/PHPStan/Analyser/data/bug-3190.php index 7348ca3623..29cec24c03 100644 --- a/tests/PHPStan/Analyser/data/bug-3190.php +++ b/tests/PHPStan/Analyser/data/bug-3190.php @@ -6,89 +6,89 @@ interface Server { - public function isDedicated(): bool; + public function isDedicated(): bool; - public function getSize(): int; + public function getSize(): int; } class Deployer { - public function deploy(object $component): void - { - $dedicated = $component instanceof Server ? $component->isDedicated() : false; - if (!$dedicated) { - return; - } - - assertType(Server::class, $component); - } - - public function deploy2(object $component): void - { - $dedicated = $component instanceof Server && $component->isDedicated(); - if (!$dedicated) { - return; - } - - assertType(Server::class, $component); - } - - public function deploy3(object $component): void - { - $dedicated = $component instanceof Server; - if (!$dedicated) { - return; - } - - assertType(Server::class, $component); - } - - public function deploy4(object $component): void - { - $dedicated = $component instanceof Server ? $component->isDedicated() : rand(0, 1); - if (!$dedicated) { - return; - } - - assertType('object', $component); - } + public function deploy(object $component): void + { + $dedicated = $component instanceof Server ? $component->isDedicated() : false; + if (!$dedicated) { + return; + } + + assertType(Server::class, $component); + } + + public function deploy2(object $component): void + { + $dedicated = $component instanceof Server && $component->isDedicated(); + if (!$dedicated) { + return; + } + + assertType(Server::class, $component); + } + + public function deploy3(object $component): void + { + $dedicated = $component instanceof Server; + if (!$dedicated) { + return; + } + + assertType(Server::class, $component); + } + + public function deploy4(object $component): void + { + $dedicated = $component instanceof Server ? $component->isDedicated() : rand(0, 1); + if (!$dedicated) { + return; + } + + assertType('object', $component); + } } class Deployer2 { - public function deploy(object $component): void - { - $dedicated = $component instanceof Server ? $component->isDedicated() : false; - if ($dedicated) { - assertType(Server::class, $component); - return; - } - } - - public function deploy2(object $component): void - { - $dedicated = $component instanceof Server && $component->isDedicated(); - if ($dedicated) { - assertType(Server::class, $component); - return; - } - } - - public function deploy3(object $component): void - { - $dedicated = $component instanceof Server; - if ($dedicated) { - assertType(Server::class, $component); - return; - } - } - - public function deploy4(object $component): void - { - $dedicated = $component instanceof Server ? $component->isDedicated() : rand(0, 1); - if ($dedicated) { - assertType('object', $component); - return; - } - } + public function deploy(object $component): void + { + $dedicated = $component instanceof Server ? $component->isDedicated() : false; + if ($dedicated) { + assertType(Server::class, $component); + return; + } + } + + public function deploy2(object $component): void + { + $dedicated = $component instanceof Server && $component->isDedicated(); + if ($dedicated) { + assertType(Server::class, $component); + return; + } + } + + public function deploy3(object $component): void + { + $dedicated = $component instanceof Server; + if ($dedicated) { + assertType(Server::class, $component); + return; + } + } + + public function deploy4(object $component): void + { + $dedicated = $component instanceof Server ? $component->isDedicated() : rand(0, 1); + if ($dedicated) { + assertType('object', $component); + return; + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3226.php b/tests/PHPStan/Analyser/data/bug-3226.php index 09b4668057..f53cda2b2b 100644 --- a/tests/PHPStan/Analyser/data/bug-3226.php +++ b/tests/PHPStan/Analyser/data/bug-3226.php @@ -6,31 +6,31 @@ class Foo { - /** - * @var class-string - */ - private $class; + /** + * @var class-string + */ + private $class; - /** - * @param class-string $class - */ - public function __construct(string $class) - { - $this->class = $class; - } + /** + * @param class-string $class + */ + public function __construct(string $class) + { + $this->class = $class; + } - /** - * @return class-string - */ - public function __toString(): string - { - return $this->class; - } + /** + * @return class-string + */ + public function __toString(): string + { + return $this->class; + } } function (Foo $foo): void { - assertType('class-string', $foo->__toString()); - assertType('class-string', (string) $foo); + assertType('class-string', $foo->__toString()); + assertType('class-string', (string) $foo); }; /** @@ -38,35 +38,35 @@ function (Foo $foo): void { */ class Bar { - /** - * @var class-string - */ - private $class; + /** + * @var class-string + */ + private $class; - /** - * @param class-string $class - */ - public function __construct(string $class) - { - $this->class = $class; - } + /** + * @param class-string $class + */ + public function __construct(string $class) + { + $this->class = $class; + } - /** - * @return class-string - */ - public function __toString(): string - { - return $this->class; - } + /** + * @return class-string + */ + public function __toString(): string + { + return $this->class; + } } function (Bar $bar): void { - assertType('class-string', $bar->__toString()); - assertType('class-string', (string) $bar); + assertType('class-string', $bar->__toString()); + assertType('class-string', (string) $bar); }; function (): void { - $bar = new Bar(\Exception::class); - assertType('class-string', $bar->__toString()); - assertType('class-string', (string) $bar); + $bar = new Bar(\Exception::class); + assertType('class-string', $bar->__toString()); + assertType('class-string', (string) $bar); }; diff --git a/tests/PHPStan/Analyser/data/bug-3266.php b/tests/PHPStan/Analyser/data/bug-3266.php index e7e0dc3b2e..69606d4b9c 100644 --- a/tests/PHPStan/Analyser/data/bug-3266.php +++ b/tests/PHPStan/Analyser/data/bug-3266.php @@ -6,27 +6,25 @@ class Foo { + /** + * @phpstan-template TKey + * @phpstan-template TValue + * @phpstan-param array $iterator + * @phpstan-return array + */ + public function iteratorToArray($iterator) + { + assertType('array', $iterator); + $array = []; + foreach ($iterator as $key => $value) { + assertType('TKey (method Bug3266\Foo::iteratorToArray(), argument)', $key); + assertType('TValue (method Bug3266\Foo::iteratorToArray(), argument)', $value); + $array[$key] = $value; + assertType('array&nonEmpty', $array); + } - /** - * @phpstan-template TKey - * @phpstan-template TValue - * @phpstan-param array $iterator - * @phpstan-return array - */ - public function iteratorToArray($iterator) - { - assertType('array', $iterator); - $array = []; - foreach ($iterator as $key => $value) { - assertType('TKey (method Bug3266\Foo::iteratorToArray(), argument)', $key); - assertType('TValue (method Bug3266\Foo::iteratorToArray(), argument)', $value); - $array[$key] = $value; - assertType('array&nonEmpty', $array); - } - - assertType('array', $array); - - return $array; - } + assertType('array', $array); + return $array; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3269.php b/tests/PHPStan/Analyser/data/bug-3269.php index b3cfdbea68..8c6835cb9f 100644 --- a/tests/PHPStan/Analyser/data/bug-3269.php +++ b/tests/PHPStan/Analyser/data/bug-3269.php @@ -6,41 +6,37 @@ class Foo { - - /** - * @param list> $intervalGroups - */ - public static function bar(array $intervalGroups): void - { - $borders = []; - foreach ($intervalGroups as $group) { - foreach ($group as $interval) { - $borders[] = ['version' => $interval['start']->getVersion(), 'operator' => $interval['start']->getOperator(), 'side' =>'start']; - $borders[] = ['version' => $interval['end']->getVersion(), 'operator' => $interval['end']->getOperator(), 'side' =>'end']; - } - } - - assertType('array string, \'operator\' => string, \'side\' => \'end\'|\'start\')>', $borders); - - foreach ($borders as $border) { - assertType('array(\'version\' => string, \'operator\' => string, \'side\' => \'end\'|\'start\')', $border); - assertType('\'end\'|\'start\'', $border['side']); - } - } - + /** + * @param list> $intervalGroups + */ + public static function bar(array $intervalGroups): void + { + $borders = []; + foreach ($intervalGroups as $group) { + foreach ($group as $interval) { + $borders[] = ['version' => $interval['start']->getVersion(), 'operator' => $interval['start']->getOperator(), 'side' =>'start']; + $borders[] = ['version' => $interval['end']->getVersion(), 'operator' => $interval['end']->getOperator(), 'side' =>'end']; + } + } + + assertType('array string, \'operator\' => string, \'side\' => \'end\'|\'start\')>', $borders); + + foreach ($borders as $border) { + assertType('array(\'version\' => string, \'operator\' => string, \'side\' => \'end\'|\'start\')', $border); + assertType('\'end\'|\'start\'', $border['side']); + } + } } class Blah { - - public function getVersion(): string - { - return ''; - } - - public function getOperator(): string - { - return ''; - } - + public function getVersion(): string + { + return ''; + } + + public function getOperator(): string + { + return ''; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3276.php b/tests/PHPStan/Analyser/data/bug-3276.php index dccb79c5fc..db56d23856 100644 --- a/tests/PHPStan/Analyser/data/bug-3276.php +++ b/tests/PHPStan/Analyser/data/bug-3276.php @@ -1,4 +1,6 @@ -= 7.4 += 7.4 namespace Bug3276; @@ -6,23 +8,21 @@ class Foo { + /** + * @param array{name?:string} $settings + */ + public function doFoo(array $settings): void + { + $settings['name'] ??= 'unknown'; + assertType('array(\'name\' => string)', $settings); + } - /** - * @param array{name?:string} $settings - */ - public function doFoo(array $settings): void - { - $settings['name'] ??= 'unknown'; - assertType('array(\'name\' => string)', $settings); - } - - /** - * @param array{name?:string} $settings - */ - public function doBar(array $settings): void - { - $settings['name'] = 'unknown'; - assertType('array(\'name\' => \'unknown\')', $settings); - } - + /** + * @param array{name?:string} $settings + */ + public function doBar(array $settings): void + { + $settings['name'] = 'unknown'; + assertType('array(\'name\' => \'unknown\')', $settings); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3302.php b/tests/PHPStan/Analyser/data/bug-3302.php index dd874417fa..659ee606fa 100644 --- a/tests/PHPStan/Analyser/data/bug-3302.php +++ b/tests/PHPStan/Analyser/data/bug-3302.php @@ -7,20 +7,19 @@ class HelloWorld { - public function sayHello(int $b): int - { - try { - if ($b === 1) { - throw new \Exception(); - } else { - $a = 2; - } - } catch (\Exception $e) { - $a = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - return $a; - } - } - + public function sayHello(int $b): int + { + try { + if ($b === 1) { + throw new \Exception(); + } else { + $a = 2; + } + } catch (\Exception $e) { + $a = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $a); + return $a; + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3309.php b/tests/PHPStan/Analyser/data/bug-3309.php index cce0012e4d..b4780376f5 100644 --- a/tests/PHPStan/Analyser/data/bug-3309.php +++ b/tests/PHPStan/Analyser/data/bug-3309.php @@ -4,15 +4,15 @@ class Player { - const BAR = PHP_INT_MAX; + public const BAR = PHP_INT_MAX; - /** @var int */ - private $experience; + /** @var int */ + private $experience; - public function __construct() - { - if ($this->experience > self::BAR) { - $this->experience = self::BAR; - } - } + public function __construct() + { + if ($this->experience > self::BAR) { + $this->experience = self::BAR; + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3321.php b/tests/PHPStan/Analyser/data/bug-3321.php index a89c14b478..94032b6026 100644 --- a/tests/PHPStan/Analyser/data/bug-3321.php +++ b/tests/PHPStan/Analyser/data/bug-3321.php @@ -5,36 +5,37 @@ use function PHPStan\Testing\assertType; /** @template T */ -interface Container { - /** @return T */ - public function get(); +interface Container +{ + /** @return T */ + public function get(); } class Foo { + /** + * @template T + * @param array> $containers + * @return T + */ + public function unwrap(array $containers) + { + return array_map( + function ($container) { + return $container->get(); + }, + $containers + )[0]; + } - /** - * @template T - * @param array> $containers - * @return T - */ - function unwrap(array $containers) { - return array_map( - function ($container) { - return $container->get(); - }, - $containers - )[0]; - } - - /** - * @param array> $typed_containers - */ - function takesDifferentTypes(array $typed_containers): void { - assertType('int', $this->unwrap($typed_containers)); - } - + /** + * @param array> $typed_containers + */ + public function takesDifferentTypes(array $typed_containers): void + { + assertType('int', $this->unwrap($typed_containers)); + } } /** @@ -47,31 +48,42 @@ interface Facadable /** * @implements Facadable */ -class A implements Facadable {} +class A implements Facadable +{ +} /** * @implements Facadable */ -class B implements Facadable {} +class B implements Facadable +{ +} -abstract class Facade {} -class AFacade extends Facade {} -class BFacade extends Facade {} +abstract class Facade +{ +} +class AFacade extends Facade +{ +} +class BFacade extends Facade +{ +} -class FacadeFactory { - /** - * @template TFacade of Facade - * @param class-string> $class - * @return TFacade - */ - public function create(string $class): Facade - { - // Returns facade for class - } +class FacadeFactory +{ + /** + * @template TFacade of Facade + * @param class-string> $class + * @return TFacade + */ + public function create(string $class): Facade + { + // Returns facade for class + } } function (FacadeFactory $f): void { - $facadeA = $f->create(A::class); - assertType(AFacade::class, $facadeA); + $facadeA = $f->create(A::class); + assertType(AFacade::class, $facadeA); }; diff --git a/tests/PHPStan/Analyser/data/bug-3336.php b/tests/PHPStan/Analyser/data/bug-3336.php index a0f7127237..33509f091a 100644 --- a/tests/PHPStan/Analyser/data/bug-3336.php +++ b/tests/PHPStan/Analyser/data/bug-3336.php @@ -3,8 +3,8 @@ namespace Bug3336; function (array $arr, string $str, $mixed): void { - \PHPStan\Testing\assertType('array', mb_convert_encoding($arr)); - \PHPStan\Testing\assertType('string', mb_convert_encoding($str)); - \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($mixed)); - \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding()); + \PHPStan\Testing\assertType('array', mb_convert_encoding($arr)); + \PHPStan\Testing\assertType('string', mb_convert_encoding($str)); + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($mixed)); + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding()); }; diff --git a/tests/PHPStan/Analyser/data/bug-3351.php b/tests/PHPStan/Analyser/data/bug-3351.php index 83198c9c1d..3eb943983d 100644 --- a/tests/PHPStan/Analyser/data/bug-3351.php +++ b/tests/PHPStan/Analyser/data/bug-3351.php @@ -1,30 +1,32 @@ -combine($a, $b); - assertType('array|false', $c); + $c = $this->combine($a, $b); + assertType('array|false', $c); - assertType('array(\'a\' => 1, \'b\' => 2, \'c\' => 3)', array_combine($a, $b)); - } + assertType('array(\'a\' => 1, \'b\' => 2, \'c\' => 3)', array_combine($a, $b)); + } - /** - * @template TKey - * @template TValue - * @param array $keys - * @param array $values - * - * @return array|false - */ - private function combine(array $keys, array $values) - { - return array_combine($keys, $values); - } + /** + * @template TKey + * @template TValue + * @param array $keys + * @param array $values + * + * @return array|false + */ + private function combine(array $keys, array $values) + { + return array_combine($keys, $values); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3379.php b/tests/PHPStan/Analyser/data/bug-3379.php index e6b0cb5df4..769b375875 100644 --- a/tests/PHPStan/Analyser/data/bug-3379.php +++ b/tests/PHPStan/Analyser/data/bug-3379.php @@ -4,11 +4,9 @@ class Foo { - - const URL = SOME_UNKNOWN_CONST . '/test'; - + public const URL = SOME_UNKNOWN_CONST . '/test'; } function () { - echo Foo::URL; + echo Foo::URL; }; diff --git a/tests/PHPStan/Analyser/data/bug-3382.php b/tests/PHPStan/Analyser/data/bug-3382.php index 3623d2a9cf..41f02744dc 100644 --- a/tests/PHPStan/Analyser/data/bug-3382.php +++ b/tests/PHPStan/Analyser/data/bug-3382.php @@ -5,5 +5,5 @@ use function PHPStan\Testing\assertType; if (ini_get('auto_prepend_file')) { - assertType('string', ini_get('auto_prepend_file')); + assertType('string', ini_get('auto_prepend_file')); } diff --git a/tests/PHPStan/Analyser/data/bug-3405.php b/tests/PHPStan/Analyser/data/bug-3405.php index 39f6b8636b..47a9b92cab 100644 --- a/tests/PHPStan/Analyser/data/bug-3405.php +++ b/tests/PHPStan/Analyser/data/bug-3405.php @@ -4,18 +4,15 @@ class Foo { - - public function doFoo( - string $file = __FILE__, - int $line = __LINE__, - string $class = __CLASS__, - string $dir = __DIR__, - string $namespace = __NAMESPACE__, - string $method = __METHOD__, - string $function = __FUNCTION__, - string $trait = __TRAIT__ - ): void - { - } - + public function doFoo( + string $file = __FILE__, + int $line = __LINE__, + string $class = __CLASS__, + string $dir = __DIR__, + string $namespace = __NAMESPACE__, + string $method = __METHOD__, + string $function = __FUNCTION__, + string $trait = __TRAIT__ + ): void { + } } diff --git a/tests/PHPStan/Analyser/data/bug-3468.php b/tests/PHPStan/Analyser/data/bug-3468.php index b702618756..1ec7174587 100644 --- a/tests/PHPStan/Analyser/data/bug-3468.php +++ b/tests/PHPStan/Analyser/data/bug-3468.php @@ -7,7 +7,7 @@ class NewInterval extends \DateInterval } function (NewInterval $ni): void { - $ni->f = 0.1; + $ni->f = 0.1; }; class NewDocument extends \DOMDocument @@ -15,5 +15,5 @@ class NewDocument extends \DOMDocument } function (NewDocument $nd): void { - $element = $nd->documentElement; + $element = $nd->documentElement; }; diff --git a/tests/PHPStan/Analyser/data/bug-3548.php b/tests/PHPStan/Analyser/data/bug-3548.php index 43d9568dec..6d3ad2e63e 100644 --- a/tests/PHPStan/Analyser/data/bug-3548.php +++ b/tests/PHPStan/Analyser/data/bug-3548.php @@ -6,15 +6,16 @@ class HelloWorld { - /** - * @param int[] $arr - */ - public function shift(array $arr): int { - if (count($arr) === 0) { - throw new \Exception("oops"); - } - $name = array_shift($arr); - assertType('int', $name); - return $name; - } + /** + * @param int[] $arr + */ + public function shift(array $arr): int + { + if (count($arr) === 0) { + throw new \Exception("oops"); + } + $name = array_shift($arr); + assertType('int', $name); + return $name; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3558.php b/tests/PHPStan/Analyser/data/bug-3558.php index 49dd89b06c..62fe8560ab 100644 --- a/tests/PHPStan/Analyser/data/bug-3558.php +++ b/tests/PHPStan/Analyser/data/bug-3558.php @@ -5,29 +5,29 @@ use function PHPStan\Testing\assertType; function (): void { - $idGroups = []; + $idGroups = []; - if(time() > 3){ - $idGroups[] = [1,2]; - $idGroups[] = [1,2]; - $idGroups[] = [1,2]; - } + if (time() > 3) { + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + } - if(count($idGroups) > 0){ - assertType('array(array(1, 2), array(1, 2), array(1, 2))', $idGroups); - } + if (count($idGroups) > 0) { + assertType('array(array(1, 2), array(1, 2), array(1, 2))', $idGroups); + } }; function (): void { - $idGroups = [1]; + $idGroups = [1]; - if(time() > 3){ - $idGroups[] = [1,2]; - $idGroups[] = [1,2]; - $idGroups[] = [1,2]; - } + if (time() > 3) { + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + } - if(count($idGroups) > 1){ - assertType('array(0 => 1, ?1 => array(1, 2), ?2 => array(1, 2), ?3 => array(1, 2))', $idGroups); - } + if (count($idGroups) > 1) { + assertType('array(0 => 1, ?1 => array(1, 2), ?2 => array(1, 2), ?3 => array(1, 2))', $idGroups); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-3617.php b/tests/PHPStan/Analyser/data/bug-3617.php index 2930c4faa8..1bde029535 100644 --- a/tests/PHPStan/Analyser/data/bug-3617.php +++ b/tests/PHPStan/Analyser/data/bug-3617.php @@ -5,20 +5,20 @@ use function PHPStan\Testing\assertType; function (): void { - $var = 'TEST'; - try { - $var = 1; - $var = test(); - } catch (\Throwable $t) { - assertType('1', $var); - } + $var = 'TEST'; + try { + $var = 1; + $var = test(); + } catch (\Throwable $t) { + assertType('1', $var); + } }; function (): void { - $var = 'TEST'; - try { - $var = test(); - } catch (\Throwable $t) { - assertType('\'TEST\'', $var); - } + $var = 'TEST'; + try { + $var = test(); + } catch (\Throwable $t) { + assertType('\'TEST\'', $var); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-3677.php b/tests/PHPStan/Analyser/data/bug-3677.php index 0280d14773..fa664ee835 100644 --- a/tests/PHPStan/Analyser/data/bug-3677.php +++ b/tests/PHPStan/Analyser/data/bug-3677.php @@ -1,4 +1,6 @@ -value = $value; - } + public function __construct(string $value) + { + $this->value = $value; + } - public function getValue(): string - { - return $this->value; - } + public function getValue(): string + { + return $this->value; + } } class HelloWorld { - /** - * @return (Field|null)[] - */ - public function getValue(): array - { - $first = null; - $second = null; - if (isset($_POST['first']) && is_string($_POST['first']) && !empty($_POST['first'])) { - $first = new Field($_POST['first']); - } - if (isset($_POST['second']) && is_string($_POST['second']) && !empty($_POST['second'])) { - $second = new Field($_POST['second']); - } - return [$first, $second]; - } + /** + * @return (Field|null)[] + */ + public function getValue(): array + { + $first = null; + $second = null; + if (isset($_POST['first']) && is_string($_POST['first']) && !empty($_POST['first'])) { + $first = new Field($_POST['first']); + } + if (isset($_POST['second']) && is_string($_POST['second']) && !empty($_POST['second'])) { + $second = new Field($_POST['second']); + } + return [$first, $second]; + } - public function sayHello(): void - { - [$first, $second] = $this->getValue(); - if (!$first && !$second) { - echo 'empty'; - } elseif (!$first) { - assertType(Field::class, $second); - } elseif (!$second) { - assertType(Field::class, $first); - echo $first->getValue(); - } else { - assertType(Field::class, $first); - assertType(Field::class, $second); - echo $first->getValue() . "\n" . $second->getValue(); - } - } + public function sayHello(): void + { + [$first, $second] = $this->getValue(); + if (!$first && !$second) { + echo 'empty'; + } elseif (!$first) { + assertType(Field::class, $second); + } elseif (!$second) { + assertType(Field::class, $first); + echo $first->getValue(); + } else { + assertType(Field::class, $first); + assertType(Field::class, $second); + echo $first->getValue() . "\n" . $second->getValue(); + } + } - public function sayGoodbye(): void - { - [$first, $second] = $this->getValue(); - if ($first || $second) { - // this assert passes but the ternary breaks the next assert - // assertType(Field::class, $first ?: $second); - assertType(Field::class, $first ?? $second); - } - } + public function sayGoodbye(): void + { + [$first, $second] = $this->getValue(); + if ($first || $second) { + // this assert passes but the ternary breaks the next assert + // assertType(Field::class, $first ?: $second); + assertType(Field::class, $first ?? $second); + } + } - public function sayGoodbye2(): void - { - [$first, $second] = $this->getValue(); - if ($first || $second) { - // this assert passes but the ternary breaks the next assert - // assertType(Field::class, $first ? $first : $second); - assertType(Field::class, $first ?? $second); - } - } + public function sayGoodbye2(): void + { + [$first, $second] = $this->getValue(); + if ($first || $second) { + // this assert passes but the ternary breaks the next assert + // assertType(Field::class, $first ? $first : $second); + assertType(Field::class, $first ?? $second); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3686.php b/tests/PHPStan/Analyser/data/bug-3686.php index 586a62a771..49fe6f2876 100644 --- a/tests/PHPStan/Analyser/data/bug-3686.php +++ b/tests/PHPStan/Analyser/data/bug-3686.php @@ -8,32 +8,30 @@ */ function fred($request) { - $keys = ''; - foreach ($request as $index) - { - foreach ($index as $field) - { - if (isset($keys[$field])) - { - $keys[$field] = 0; - } - } - } + $keys = ''; + foreach ($request as $index) { + foreach ($index as $field) { + if (isset($keys[$field])) { + $keys[$field] = 0; + } + } + } } /** * @param int[][] $a */ -function replaceStringWithZero(array $a) : string { - $keys = 'agfsdafsafdrew1231414'; +function replaceStringWithZero(array $a): string +{ + $keys = 'agfsdafsafdrew1231414'; - foreach ($a as $b) { - foreach ($b as $c) { - if (isset($keys[$c])) { - $keys[$c] = "0"; - } - } - } + foreach ($a as $b) { + foreach ($b as $c) { + if (isset($keys[$c])) { + $keys[$c] = "0"; + } + } + } - return $keys; + return $keys; } diff --git a/tests/PHPStan/Analyser/data/bug-3710.php b/tests/PHPStan/Analyser/data/bug-3710.php index 171d7b9065..637f66fdbd 100644 --- a/tests/PHPStan/Analyser/data/bug-3710.php +++ b/tests/PHPStan/Analyser/data/bug-3710.php @@ -4,33 +4,36 @@ use function PHPStan\Testing\assertType; -class Person { - public function aMethod(): void {} +class Person +{ + public function aMethod(): void + { + } } -class PersonCreationFailedException extends \Exception {} +class PersonCreationFailedException extends \Exception +{ +} class Foo { - - /** - * @throws PersonCreationFailedException - */ - function createPersonButCouldFail(): Person - { - throw new PersonCreationFailedException(); - } - - public function doFoo() - { - $person = null; - try { - $person = $this->createPersonButCouldFail(); - } finally { - assertType('Bug3710\Person|null', $person); - } - - assertType('Bug3710\Person', $person); - } - + /** + * @throws PersonCreationFailedException + */ + public function createPersonButCouldFail(): Person + { + throw new PersonCreationFailedException(); + } + + public function doFoo() + { + $person = null; + try { + $person = $this->createPersonButCouldFail(); + } finally { + assertType('Bug3710\Person|null', $person); + } + + assertType('Bug3710\Person', $person); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3760.php b/tests/PHPStan/Analyser/data/bug-3760.php index dd81edde82..b4bc3c0b2c 100644 --- a/tests/PHPStan/Analyser/data/bug-3760.php +++ b/tests/PHPStan/Analyser/data/bug-3760.php @@ -1,4 +1,6 @@ -allowsCovariance = (bool)$allowsCovariance; - - assertType('bool', $allowsCovariance); - assertNativeType('mixed', $allowsCovariance); - assertType('bool', $allowsContravariance); - assertNativeType('mixed', $allowsContravariance); - $this->allowsContravariance = (bool)$allowsContravariance; - - assertType('bool', $allowsCovariance); - assertNativeType('mixed', $allowsCovariance); - assertType('bool', $allowsContravariance); - assertNativeType('mixed', $allowsContravariance); - } + /** + * Whether the type allows covariant matches + * + * @var bool + */ + public $allowsCovariance; + + /** + * Whether the type allows contravariant matches + * + * @var bool + */ + public $allowsContravariance; + + /** + * @param bool $allowsCovariance + * @param bool $allowsContravariance + */ + protected function __construct($allowsCovariance, $allowsContravariance) + { + assertType('bool', $allowsCovariance); + assertNativeType('mixed', $allowsCovariance); + assertType('bool', $allowsContravariance); + assertNativeType('mixed', $allowsContravariance); + $this->allowsCovariance = (bool)$allowsCovariance; + + assertType('bool', $allowsCovariance); + assertNativeType('mixed', $allowsCovariance); + assertType('bool', $allowsContravariance); + assertNativeType('mixed', $allowsContravariance); + $this->allowsContravariance = (bool)$allowsContravariance; + + assertType('bool', $allowsCovariance); + assertNativeType('mixed', $allowsCovariance); + assertType('bool', $allowsContravariance); + assertNativeType('mixed', $allowsContravariance); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3798.php b/tests/PHPStan/Analyser/data/bug-3798.php index b3aa28897c..27591c9ce6 100644 --- a/tests/PHPStan/Analyser/data/bug-3798.php +++ b/tests/PHPStan/Analyser/data/bug-3798.php @@ -1,10 +1,13 @@ -status = $status; - $this->body = $body; - } - - public function status(): int { return $this->status; } - public function body(): string { return $this->body; } +class Response +{ + /** @var int */ + private $status; + + /** @var string */ + private $body; + + public function __construct(int $status, string $body) + { + $this->status = $status; + $this->body = $body; + } + + public function status(): int + { + return $this->status; + } + public function body(): string + { + return $this->body; + } } class HelloWorld { - public function sayHello(Response $response): string - { - $body = $response->body(); - $status = $response->status(); - - if ($status !== 200) { - throw new \LogicException('NOOOO' . $body); - } - - try { - $payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR); - unset($body); - } catch (\JsonException $e) { - assertVariableCertainty(TrinaryLogic::createYes(), $body); - } - - return $payload['thing'] ?? 'default'; - } + public function sayHello(Response $response): string + { + $body = $response->body(); + $status = $response->status(); + + if ($status !== 200) { + throw new \LogicException('NOOOO' . $body); + } + + try { + $payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR); + unset($body); + } catch (\JsonException $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $body); + } + + return $payload['thing'] ?? 'default'; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3866.php b/tests/PHPStan/Analyser/data/bug-3866.php index 945f60656b..672916e921 100644 --- a/tests/PHPStan/Analyser/data/bug-3866.php +++ b/tests/PHPStan/Analyser/data/bug-3866.php @@ -9,42 +9,41 @@ abstract class PHPStanBug { - public function test(): void - { - /** @var Set $set */ - $set = new Set(); - foreach ($this->a() as $item) { - $set->add(\get_class($item)); - } - foreach ($this->b() as $item) { - $set->add(\get_class($item)); - } - foreach ($this->c() as $item) { - $set->add($item); - } - assertType('Ds\Set', $set); - $set->sort(); - } + public function test(): void + { + /** @var Set $set */ + $set = new Set(); + foreach ($this->a() as $item) { + $set->add(\get_class($item)); + } + foreach ($this->b() as $item) { + $set->add(\get_class($item)); + } + foreach ($this->c() as $item) { + $set->add($item); + } + assertType('Ds\Set', $set); + $set->sort(); + } - /** - * @return Iterator - */ - abstract public function a(): Iterator; + /** + * @return Iterator + */ + abstract public function a(): Iterator; - /** - * @return Iterator - */ - private function b(): Iterator - { - yield new DateTimeImmutable(); - } + /** + * @return Iterator + */ + private function b(): Iterator + { + yield new DateTimeImmutable(); + } - /** - * @return Iterator> - */ - private function c(): Iterator - { - yield DateTimeImmutable::class; - } + /** + * @return Iterator> + */ + private function c(): Iterator + { + yield DateTimeImmutable::class; + } } - diff --git a/tests/PHPStan/Analyser/data/bug-3875.php b/tests/PHPStan/Analyser/data/bug-3875.php index 64d7b6fecf..060c179efa 100644 --- a/tests/PHPStan/Analyser/data/bug-3875.php +++ b/tests/PHPStan/Analyser/data/bug-3875.php @@ -6,14 +6,14 @@ function foo(): void { - $queue = ['foo']; - $list = []; - do { - $current = array_pop($queue); - assertType('\'foo\'', $current); - if ($current === null) { - break; - } - $list[] = $current; - } while ($queue); + $queue = ['foo']; + $list = []; + do { + $current = array_pop($queue); + assertType('\'foo\'', $current); + if ($current === null) { + break; + } + $list[] = $current; + } while ($queue); } diff --git a/tests/PHPStan/Analyser/data/bug-3880.php b/tests/PHPStan/Analyser/data/bug-3880.php index e71c1c1388..6c9da5fcab 100644 --- a/tests/PHPStan/Analyser/data/bug-3880.php +++ b/tests/PHPStan/Analyser/data/bug-3880.php @@ -5,8 +5,8 @@ use function PHPStan\Testing\assertType; function ($value): void { - $num = (float) $value; - if ((!is_numeric($value) && !is_bool($value)) || $num > 9223372036854775807 || $num < -9223372036854775808) { - assertType('mixed', $value); - } + $num = (float) $value; + if ((!is_numeric($value) && !is_bool($value)) || $num > 9223372036854775807 || $num < -9223372036854775808) { + assertType('mixed', $value); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-3909.php b/tests/PHPStan/Analyser/data/bug-3909.php index 3dd72bcf2f..bc6cb8526c 100644 --- a/tests/PHPStan/Analyser/data/bug-3909.php +++ b/tests/PHPStan/Analyser/data/bug-3909.php @@ -1,7 +1,8 @@ &nonEmpty', $lengths); + } - public function sayHello(): void - { - $lengths = [0]; - foreach ([1] as $row) { - $lengths[] = self::getInt(); - } - assertType('array&nonEmpty', $lengths); - } - - public static function getInt(): int - { - return 5; - } - + public static function getInt(): int + { + return 5; + } } diff --git a/tests/PHPStan/Analyser/data/bug-3922-integration.php b/tests/PHPStan/Analyser/data/bug-3922-integration.php index 4aa4238644..1b01a30de0 100644 --- a/tests/PHPStan/Analyser/data/bug-3922-integration.php +++ b/tests/PHPStan/Analyser/data/bug-3922-integration.php @@ -8,12 +8,12 @@ */ interface QueryHandlerInterface { - /** - * @param TQuery $query - * - * @return TResult - */ - public function handle(QueryInterface $query); + /** + * @param TQuery $query + * + * @return TResult + */ + public function handle(QueryInterface $query); } /** @@ -39,30 +39,39 @@ final class FooQueryResult */ final class FooQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query): FooQueryResult - { - return new FooQueryResult(); - } + public function handle(QueryInterface $query): FooQueryResult + { + return new FooQueryResult(); + } } -interface BasePackage {} +interface BasePackage +{ +} -interface InnerPackage extends BasePackage {} +interface InnerPackage extends BasePackage +{ +} /** * @template TInnerPackage of InnerPackage */ -interface GenericPackage extends BasePackage { - /** @return TInnerPackage */ - public function unwrap() : InnerPackage; +interface GenericPackage extends BasePackage +{ + /** @return TInnerPackage */ + public function unwrap(): InnerPackage; } -interface SomeInnerPackage extends InnerPackage {} +interface SomeInnerPackage extends InnerPackage +{ +} /** * @extends GenericPackage */ -interface SomePackage extends GenericPackage {} +interface SomePackage extends GenericPackage +{ +} /** * @template TInnerPackage of InnerPackage @@ -70,8 +79,9 @@ interface SomePackage extends GenericPackage {} * @param TGenericPackage $package * @return TInnerPackage */ -function unwrapGeneric(GenericPackage $package) { - return $package->unwrap(); +function unwrapGeneric(GenericPackage $package) +{ + return $package->unwrap(); } /** @@ -80,9 +90,10 @@ function unwrapGeneric(GenericPackage $package) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithDirectUnwrap(string $class) { - $package = new $class(); - return $package->unwrap(); +function loadWithDirectUnwrap(string $class) +{ + $package = new $class(); + return $package->unwrap(); } /** @@ -91,11 +102,12 @@ function loadWithDirectUnwrap(string $class) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithIndirectUnwrap(string $class) { - $package = new $class(); - return unwrapGeneric($package); +function loadWithIndirectUnwrap(string $class) +{ + $package = new $class(); + return unwrapGeneric($package); } function (): void { - loadWithDirectUnwrap(SomePackage::class); + loadWithDirectUnwrap(SomePackage::class); }; diff --git a/tests/PHPStan/Analyser/data/bug-3922.php b/tests/PHPStan/Analyser/data/bug-3922.php index b65312a447..f82f604510 100644 --- a/tests/PHPStan/Analyser/data/bug-3922.php +++ b/tests/PHPStan/Analyser/data/bug-3922.php @@ -10,12 +10,12 @@ */ interface QueryHandlerInterface { - /** - * @param TQuery $query - * - * @return TResult - */ - public function handle(QueryInterface $query); + /** + * @param TQuery $query + * + * @return TResult + */ + public function handle(QueryInterface $query); } /** @@ -52,13 +52,13 @@ final class BarQueryResult */ final class FooQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query) - { - return new FooQueryResult(); - } + public function handle(QueryInterface $query) + { + return new FooQueryResult(); + } } function (FooQueryHandler $h): void { - assertType(FooQueryResult::class, $h->handle(new FooQuery())); - assertType(FooQueryResult::class, $h->handle(new BarQuery())); + assertType(FooQueryResult::class, $h->handle(new FooQuery())); + assertType(FooQueryResult::class, $h->handle(new BarQuery())); }; diff --git a/tests/PHPStan/Analyser/data/bug-3961-php8.php b/tests/PHPStan/Analyser/data/bug-3961-php8.php index 2fb86e13e4..e698f81dd8 100644 --- a/tests/PHPStan/Analyser/data/bug-3961-php8.php +++ b/tests/PHPStan/Analyser/data/bug-3961-php8.php @@ -6,16 +6,14 @@ class Foo { - - public function doFoo(string $v, string $d, $m): void - { - assertType('array&nonEmpty', explode('.', $v)); - assertType('*NEVER*', explode('', $v)); - assertType('array', explode('.', $v, -2)); - assertType('array&nonEmpty', explode('.', $v, 0)); - assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('array', explode($d, $v)); - assertType('array', explode($m, $v)); - } - + public function doFoo(string $v, string $d, $m): void + { + assertType('array&nonEmpty', explode('.', $v)); + assertType('*NEVER*', explode('', $v)); + assertType('array', explode('.', $v, -2)); + assertType('array&nonEmpty', explode('.', $v, 0)); + assertType('array&nonEmpty', explode('.', $v, 1)); + assertType('array', explode($d, $v)); + assertType('array', explode($m, $v)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3961.php b/tests/PHPStan/Analyser/data/bug-3961.php index 5666786e33..86afc99f00 100644 --- a/tests/PHPStan/Analyser/data/bug-3961.php +++ b/tests/PHPStan/Analyser/data/bug-3961.php @@ -6,16 +6,14 @@ class Foo { - - public function doFoo(string $v, string $d, $m): void - { - assertType('array&nonEmpty', explode('.', $v)); - assertType('false', explode('', $v)); - assertType('array', explode('.', $v, -2)); - assertType('array&nonEmpty', explode('.', $v, 0)); - assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('array|false', explode($d, $v)); - assertType('(array|false)', explode($m, $v)); - } - + public function doFoo(string $v, string $d, $m): void + { + assertType('array&nonEmpty', explode('.', $v)); + assertType('false', explode('', $v)); + assertType('array', explode('.', $v, -2)); + assertType('array&nonEmpty', explode('.', $v, 0)); + assertType('array&nonEmpty', explode('.', $v, 1)); + assertType('array|false', explode($d, $v)); + assertType('(array|false)', explode($m, $v)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3985.php b/tests/PHPStan/Analyser/data/bug-3985.php index 038f75dafc..4196d98eda 100644 --- a/tests/PHPStan/Analyser/data/bug-3985.php +++ b/tests/PHPStan/Analyser/data/bug-3985.php @@ -7,19 +7,19 @@ class Foo { - public function doFoo(array $array): void - { - foreach ($array as $val) { - if (isset($foo[1])) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } - } - } + public function doFoo(array $array): void + { + foreach ($array as $val) { + if (isset($foo[1])) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } + } + } - public function doBar(): void - { - if (isset($foo[1])) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } - } + public function doBar(): void + { + if (isset($foo[1])) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3986.php b/tests/PHPStan/Analyser/data/bug-3986.php index 50165d02a6..cbeadb3895 100644 --- a/tests/PHPStan/Analyser/data/bug-3986.php +++ b/tests/PHPStan/Analyser/data/bug-3986.php @@ -4,22 +4,23 @@ use function PHPStan\Testing\assertType; -interface Boo { - public function nullable(): ?int; +interface Boo +{ + public function nullable(): ?int; } class HelloWorld { - public function sayHello(Boo $value): void - { - $result = $value->nullable(); - $isNotNull = $result !== null; + public function sayHello(Boo $value): void + { + $result = $value->nullable(); + $isNotNull = $result !== null; - if ($isNotNull) { - assertType('int', $result); - } - if ($result !== null) { - assertType('int', $result); - } - } + if ($isNotNull) { + assertType('int', $result); + } + if ($result !== null) { + assertType('int', $result); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-3990.php b/tests/PHPStan/Analyser/data/bug-3990.php index 102745dd68..e9bee82972 100644 --- a/tests/PHPStan/Analyser/data/bug-3990.php +++ b/tests/PHPStan/Analyser/data/bug-3990.php @@ -7,15 +7,15 @@ function doFoo(array $config): void { - extract($config); + extract($config); - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (isset($a)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } + if (isset($a)) { + assertVariableCertainty(TrinaryLogic::createYes(), $a); + } else { + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + } - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); } diff --git a/tests/PHPStan/Analyser/data/bug-3991.php b/tests/PHPStan/Analyser/data/bug-3991.php index 0a801da541..9266c0b282 100644 --- a/tests/PHPStan/Analyser/data/bug-3991.php +++ b/tests/PHPStan/Analyser/data/bug-3991.php @@ -7,25 +7,24 @@ class Foo { - /** - * @param \stdClass|array|null $config - * - * @return \stdClass - */ - public static function email($config = null) - { - assertNativeType('mixed', $config); - assertType('array|stdClass|null', $config); - if (empty($config)) - { - assertNativeType('mixed', $config); - assertType('array|stdClass|null', $config); - $config = new \stdClass(); - } elseif (! (is_array($config) || $config instanceof \stdClass)) { - assertNativeType('mixed~array|stdClass|false|null', $config); - assertType('*NEVER*', $config); - } + /** + * @param \stdClass|array|null $config + * + * @return \stdClass + */ + public static function email($config = null) + { + assertNativeType('mixed', $config); + assertType('array|stdClass|null', $config); + if (empty($config)) { + assertNativeType('mixed', $config); + assertType('array|stdClass|null', $config); + $config = new \stdClass(); + } elseif (! (is_array($config) || $config instanceof \stdClass)) { + assertNativeType('mixed~array|stdClass|false|null', $config); + assertType('*NEVER*', $config); + } - return new \stdClass($config); - } + return new \stdClass($config); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3993.php b/tests/PHPStan/Analyser/data/bug-3993.php index 4c106dbab8..1e8f1686bd 100644 --- a/tests/PHPStan/Analyser/data/bug-3993.php +++ b/tests/PHPStan/Analyser/data/bug-3993.php @@ -6,19 +6,17 @@ class Foo { + public function doFoo($arguments) + { + if (!isset($arguments) || count($arguments) === 0) { + return; + } - public function doFoo($arguments) - { - if (!isset($arguments) || count($arguments) === 0) { - return; - } + assertType('mixed~null', $arguments); - assertType('mixed~null', $arguments); - - array_shift($arguments); - - assertType('mixed~null', $arguments); - assertType('int<0, max>', count($arguments)); - } + array_shift($arguments); + assertType('mixed~null', $arguments); + assertType('int<0, max>', count($arguments)); + } } diff --git a/tests/PHPStan/Analyser/data/bug-3997.php b/tests/PHPStan/Analyser/data/bug-3997.php index 850f2b7112..029c993453 100644 --- a/tests/PHPStan/Analyser/data/bug-3997.php +++ b/tests/PHPStan/Analyser/data/bug-3997.php @@ -5,11 +5,11 @@ use function PHPStan\Testing\assertType; function (\Countable $c): void { - assertType('int<0, max>', $c->count()); - assertType('int<0, max>', count($c)); + assertType('int<0, max>', $c->count()); + assertType('int<0, max>', count($c)); }; function (\ArrayIterator $i): void { - assertType('int<0, max>', $i->count()); - assertType('int<0, max>', count($i)); + assertType('int<0, max>', $i->count()); + assertType('int<0, max>', count($i)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4016.php b/tests/PHPStan/Analyser/data/bug-4016.php index 8582c3e5e6..e8cd1b9de6 100644 --- a/tests/PHPStan/Analyser/data/bug-4016.php +++ b/tests/PHPStan/Analyser/data/bug-4016.php @@ -6,31 +6,29 @@ class Foo { - - /** - * @param array $a - */ - public function doFoo(array $a): void - { - assertType('array', $a); - $a[] = 2; - assertType('array&nonEmpty', $a); - - unset($a[0]); - assertType('array', $a); - } - - /** - * @param array $a - */ - public function doBar(array $a): void - { - assertType('array', $a); - $a[1] = 2; - assertType('array&nonEmpty', $a); - - unset($a[1]); - assertType('array', $a); - } - + /** + * @param array $a + */ + public function doFoo(array $a): void + { + assertType('array', $a); + $a[] = 2; + assertType('array&nonEmpty', $a); + + unset($a[0]); + assertType('array', $a); + } + + /** + * @param array $a + */ + public function doBar(array $a): void + { + assertType('array', $a); + $a[1] = 2; + assertType('array&nonEmpty', $a); + + unset($a[1]); + assertType('array', $a); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4091.php b/tests/PHPStan/Analyser/data/bug-4091.php index 0361c4eb4e..6fc41dbe9d 100644 --- a/tests/PHPStan/Analyser/data/bug-4091.php +++ b/tests/PHPStan/Analyser/data/bug-4091.php @@ -4,7 +4,7 @@ use function PHPStan\Testing\assertType; -if (mt_rand(0,10) > 3) { - echo 'Fizz'; - assertType('int', mt_rand(0,10)); +if (mt_rand(0, 10) > 3) { + echo 'Fizz'; + assertType('int', mt_rand(0, 10)); } diff --git a/tests/PHPStan/Analyser/data/bug-4097.php b/tests/PHPStan/Analyser/data/bug-4097.php index cd27b2f0f7..bcd3a1dce3 100644 --- a/tests/PHPStan/Analyser/data/bug-4097.php +++ b/tests/PHPStan/Analyser/data/bug-4097.php @@ -2,32 +2,38 @@ namespace Bug4097; -class Snapshot {} -class Fu {} -class Bar {} +class Snapshot +{ +} +class Fu +{ +} +class Bar +{ +} /** * @template T */ class SnapshotRepository { - /** - * @return \Traversable - */ - public function findAllSnapshots(): \Traversable - { - yield from \array_map( - \Closure::fromCallable([$this, 'buildSnapshot']), - [] - ); - } + /** + * @return \Traversable + */ + public function findAllSnapshots(): \Traversable + { + yield from \array_map( + \Closure::fromCallable([$this, 'buildSnapshot']), + [] + ); + } - /** - * @param Fu|Bar $entity - * @phpstan-param T $entity - */ - public function buildSnapshot($entity): Snapshot - { - return new Snapshot(); - } + /** + * @param Fu|Bar $entity + * @phpstan-param T $entity + */ + public function buildSnapshot($entity): Snapshot + { + return new Snapshot(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4099.php b/tests/PHPStan/Analyser/data/bug-4099.php index 571dfb3476..13108c5dfb 100644 --- a/tests/PHPStan/Analyser/data/bug-4099.php +++ b/tests/PHPStan/Analyser/data/bug-4099.php @@ -7,35 +7,33 @@ class Foo { - - /** - * @param array{key: array{inner: mixed}} $arr - */ - function arrayHint(array $arr): void - { - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); - assertNativeType('array', $arr); - - if (!array_key_exists('key', $arr)) { - assertType('*NEVER*', $arr); - assertNativeType('array', $arr); - throw new \Exception('no key "key" found.'); - } - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); - assertNativeType('array&hasOffset(\'key\')', $arr); - assertType('array(\'inner\' => mixed)', $arr['key']); - assertNativeType('mixed', $arr['key']); - - if (!array_key_exists('inner', $arr['key'])) { - assertType('array(\'key\' => *NEVER*)', $arr); - //assertNativeType('array(\'key\' => mixed)', $arr); - assertType('*NEVER*', $arr['key']); - //assertNativeType('mixed', $arr['key']); - throw new \Exception('need key.inner'); - } - - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); - assertNativeType('array(\'key\' => array(\'inner\' => mixed))', $arr); - } - + /** + * @param array{key: array{inner: mixed}} $arr + */ + public function arrayHint(array $arr): void + { + assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertNativeType('array', $arr); + + if (!array_key_exists('key', $arr)) { + assertType('*NEVER*', $arr); + assertNativeType('array', $arr); + throw new \Exception('no key "key" found.'); + } + assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertNativeType('array&hasOffset(\'key\')', $arr); + assertType('array(\'inner\' => mixed)', $arr['key']); + assertNativeType('mixed', $arr['key']); + + if (!array_key_exists('inner', $arr['key'])) { + assertType('array(\'key\' => *NEVER*)', $arr); + //assertNativeType('array(\'key\' => mixed)', $arr); + assertType('*NEVER*', $arr['key']); + //assertNativeType('mixed', $arr['key']); + throw new \Exception('need key.inner'); + } + + assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertNativeType('array(\'key\' => array(\'inner\' => mixed))', $arr); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4177.php b/tests/PHPStan/Analyser/data/bug-4177.php index 6620561b23..3a8b7c263d 100644 --- a/tests/PHPStan/Analyser/data/bug-4177.php +++ b/tests/PHPStan/Analyser/data/bug-4177.php @@ -6,28 +6,28 @@ class Dto { - /** @var int */ - private $unixtimestamp = 12345678; + /** @var int */ + private $unixtimestamp = 12345678; - public function getPeriodFrom(): ?int - { - return rand(0,1) ? $this->unixtimestamp : null; - } + public function getPeriodFrom(): ?int + { + return rand(0, 1) ? $this->unixtimestamp : null; + } - public function getPeriodTo(): ?int - { - return rand(0,1) ? $this->unixtimestamp : null; - } + public function getPeriodTo(): ?int + { + return rand(0, 1) ? $this->unixtimestamp : null; + } } function (Dto $request): void { - if ($request->getPeriodFrom() || $request->getPeriodTo()) { - if ($request->getPeriodFrom()) { - assertType('int|int<1, max>', $request->getPeriodFrom()); - } + if ($request->getPeriodFrom() || $request->getPeriodTo()) { + if ($request->getPeriodFrom()) { + assertType('int|int<1, max>', $request->getPeriodFrom()); + } - if ($request->getPeriodTo() !== null) { - assertType('int', $request->getPeriodTo()); - } - } + if ($request->getPeriodTo() !== null) { + assertType('int', $request->getPeriodTo()); + } + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4188.php b/tests/PHPStan/Analyser/data/bug-4188.php index 07a44f9458..e12f2208e6 100644 --- a/tests/PHPStan/Analyser/data/bug-4188.php +++ b/tests/PHPStan/Analyser/data/bug-4188.php @@ -1,40 +1,48 @@ -= 7.4 += 7.4 namespace Bug4188; -interface A {} -interface B {} +interface A +{ +} +interface B +{ +} use function PHPStan\Testing\assertType; class Test { - /** @param array $data */ - public function set(array $data): void - { - $filtered = array_filter( - $data, - function ($param): bool { - return $param instanceof B; - }, - ); - assertType('array', $filtered); - - $this->onlyB($filtered); - } - - /** @param array $data */ - public function setShort(array $data): void - { - $filtered = array_filter( - $data, - fn($param): bool => $param instanceof B, - ); - assertType('array', $filtered); - - $this->onlyB($filtered); - } - - /** @param B[] $data */ - public function onlyB(array $data): void {} + /** @param array $data */ + public function set(array $data): void + { + $filtered = array_filter( + $data, + function ($param): bool { + return $param instanceof B; + }, + ); + assertType('array', $filtered); + + $this->onlyB($filtered); + } + + /** @param array $data */ + public function setShort(array $data): void + { + $filtered = array_filter( + $data, + fn ($param): bool => $param instanceof B, + ); + assertType('array', $filtered); + + $this->onlyB($filtered); + } + + /** @param B[] $data */ + public function onlyB(array $data): void + { + } } diff --git a/tests/PHPStan/Analyser/data/bug-4190.php b/tests/PHPStan/Analyser/data/bug-4190.php index 449f43aaef..abac465f4d 100644 --- a/tests/PHPStan/Analyser/data/bug-4190.php +++ b/tests/PHPStan/Analyser/data/bug-4190.php @@ -4,13 +4,12 @@ use function PHPStan\Testing\assertType; -function (): string -{ - if (random_int(0, 10) <= 5) { - return 'first try'; - } +function (): string { + if (random_int(0, 10) <= 5) { + return 'first try'; + } - assertType('bool', random_int(0, 10) <= 5); + assertType('bool', random_int(0, 10) <= 5); - return 'foo'; + return 'foo'; }; diff --git a/tests/PHPStan/Analyser/data/bug-4205.php b/tests/PHPStan/Analyser/data/bug-4205.php index c65178fe91..762c3d875e 100644 --- a/tests/PHPStan/Analyser/data/bug-4205.php +++ b/tests/PHPStan/Analyser/data/bug-4205.php @@ -5,6 +5,7 @@ use function PHPStan\Testing\assertType; function () { - $result = set_error_handler(function() {}, E_ALL); - assertType('(callable(): mixed)|null', $result); + $result = set_error_handler(function () { + }, E_ALL); + assertType('(callable(): mixed)|null', $result); }; diff --git a/tests/PHPStan/Analyser/data/bug-4206.php b/tests/PHPStan/Analyser/data/bug-4206.php index c535107685..287007160e 100644 --- a/tests/PHPStan/Analyser/data/bug-4206.php +++ b/tests/PHPStan/Analyser/data/bug-4206.php @@ -6,18 +6,16 @@ class Foo { - - public const ONE = 1; - public const TWO = 2; - + public const ONE = 1; + public const TWO = 2; } function (int $i): void { - if ($i === Foo::ONE) { - assertType('1', $i); - } + if ($i === Foo::ONE) { + assertType('1', $i); + } - if ($i === Foo::TWO) { - assertType('2', $i); - } + if ($i === Foo::TWO) { + assertType('2', $i); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4207.php b/tests/PHPStan/Analyser/data/bug-4207.php index e8a1de5f02..a2b5f40792 100644 --- a/tests/PHPStan/Analyser/data/bug-4207.php +++ b/tests/PHPStan/Analyser/data/bug-4207.php @@ -5,5 +5,5 @@ use function PHPStan\Testing\assertType; function (): void { - assertType('array&nonEmpty', range(1, 10000)); + assertType('array&nonEmpty', range(1, 10000)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4209-2.php b/tests/PHPStan/Analyser/data/bug-4209-2.php index 9c93ab8f44..9a27d673ce 100644 --- a/tests/PHPStan/Analyser/data/bug-4209-2.php +++ b/tests/PHPStan/Analyser/data/bug-4209-2.php @@ -6,17 +6,21 @@ class Customer { - public function getName(): string { return 'customer'; } + public function getName(): string + { + return 'customer'; + } } /** * @template T */ -interface Link { - /** - * @return T - */ - public function getItem(); +interface Link +{ + /** + * @return T + */ + public function getItem(); } /** @@ -24,32 +28,34 @@ public function getItem(); */ class CustomerLink implements Link { - /** - * @var Customer - */ - public $item; + /** + * @var Customer + */ + public $item; - /** - * @param Customer $item - */ - public function __construct($item) { - $this->item = $item; - } + /** + * @param Customer $item + */ + public function __construct($item) + { + $this->item = $item; + } - /** - * @return Customer - */ - public function getItem() - { - return $this->item; - } + /** + * @return Customer + */ + public function getItem() + { + return $this->item; + } } /** * @return CustomerLink[] */ -function get_links(): array { - return [new CustomerLink(new Customer())]; +function get_links(): array +{ + return [new CustomerLink(new Customer())]; } /** @@ -57,13 +63,15 @@ function get_links(): array { * @param Link[] $links * @return T */ -function process_customers(array $links) { - // no-op +function process_customers(array $links) +{ + // no-op } -class Runner { - public function run(): void - { - assertType('Bug4209Two\Customer', process_customers(get_links())); - } +class Runner +{ + public function run(): void + { + assertType('Bug4209Two\Customer', process_customers(get_links())); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4209.php b/tests/PHPStan/Analyser/data/bug-4209.php index 007c033bde..54422af91e 100644 --- a/tests/PHPStan/Analyser/data/bug-4209.php +++ b/tests/PHPStan/Analyser/data/bug-4209.php @@ -7,30 +7,36 @@ /** * @template T */ -class Link { - /** - * @var T - */ - public $item; - - /** - * @param T $item - */ - public function __construct($item) { - $this->item = $item; - } +class Link +{ + /** + * @var T + */ + public $item; + + /** + * @param T $item + */ + public function __construct($item) + { + $this->item = $item; + } } class Customer { - public function getName(): string { return 'customer'; } + public function getName(): string + { + return 'customer'; + } } /** * @return Link[] */ -function get_links(): array { - return [new Link(new Customer())]; +function get_links(): array +{ + return [new Link(new Customer())]; } /** @@ -38,13 +44,15 @@ function get_links(): array { * @param Link[] $links * @return T */ -function process_customers(array $links) { - // no-op +function process_customers(array $links) +{ + // no-op } -class Runner { - public function run(): void - { - assertType('Bug4209\Customer', process_customers(get_links())); - } +class Runner +{ + public function run(): void + { + assertType('Bug4209\Customer', process_customers(get_links())); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4213.php b/tests/PHPStan/Analyser/data/bug-4213.php index bc9742be9a..75d0b0075e 100644 --- a/tests/PHPStan/Analyser/data/bug-4213.php +++ b/tests/PHPStan/Analyser/data/bug-4213.php @@ -6,50 +6,54 @@ abstract class BaseEnum { - /** @var string */ - private $value; + /** @var string */ + private $value; - final private function __construct(string $value) - { - $this->value = $value; - } - /** - * @return static - */ - public static function get(string $value): self { - return new static($value); - } + final private function __construct(string $value) + { + $this->value = $value; + } + /** + * @return static + */ + public static function get(string $value): self + { + return new static($value); + } } final class Enum extends BaseEnum { } -final class Entity { - public function setEnums(Enum ...$enums): void { - } - /** - * @param Enum[] $enums - */ - public function setEnumsWithoutSplat(array $enums): void { - } +final class Entity +{ + public function setEnums(Enum ...$enums): void + { + } + /** + * @param Enum[] $enums + */ + public function setEnumsWithoutSplat(array $enums): void + { + } } function (): void { - assertType('Bug4213\Enum', Enum::get('test')); - assertType('array(Bug4213\Enum)', array_map([Enum::class, 'get'], ['test'])); + assertType('Bug4213\Enum', Enum::get('test')); + assertType('array(Bug4213\Enum)', array_map([Enum::class, 'get'], ['test'])); }; class Foo { - /** - * @return static - */ - public static function create() : Foo - { - return new static(); - } + /** + * @return static + */ + public static function create(): Foo + { + return new static(); + } } @@ -58,8 +62,8 @@ class Bar extends Foo } function (): void { - $cbFoo = [Foo::class, 'create']; - $cbBar = [Bar::class, 'create']; - assertType('Bug4213\Foo', $cbFoo()); - assertType('Bug4213\Bar', $cbBar()); + $cbFoo = [Foo::class, 'create']; + $cbBar = [Bar::class, 'create']; + assertType('Bug4213\Foo', $cbFoo()); + assertType('Bug4213\Bar', $cbBar()); }; diff --git a/tests/PHPStan/Analyser/data/bug-4215.php b/tests/PHPStan/Analyser/data/bug-4215.php index 7fd5aaf719..fc17fd3151 100644 --- a/tests/PHPStan/Analyser/data/bug-4215.php +++ b/tests/PHPStan/Analyser/data/bug-4215.php @@ -6,18 +6,18 @@ class Foo { + /** + * @param int|null $a + * @param array $b + */ + public function test(int $a = null, array $b = null): void + { + if ($a === null && $b === null) { + return; + } - /** - * @param int|null $a - * @param array $b - */ - function test(int $a = null, array $b = null): void - { - if ($a === null && $b === null) return; - - if ($b === null) { - assertType('int', $a); - } - } - + if ($b === null) { + assertType('int', $a); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4231.php b/tests/PHPStan/Analyser/data/bug-4231.php index 73db62990f..b8aac9629a 100644 --- a/tests/PHPStan/Analyser/data/bug-4231.php +++ b/tests/PHPStan/Analyser/data/bug-4231.php @@ -5,23 +5,23 @@ use function PHPStan\Testing\assertType; function (): void { - $property = new \ReflectionProperty($this, 'data'); + $property = new \ReflectionProperty($this, 'data'); - if (is_null($property->getValue($this))) { - assertType('null', $property->getValue($this)); - $property->setValue($this, 'Some value which is not null'); - assertType('mixed', $property->getValue($this)); - } + if (is_null($property->getValue($this))) { + assertType('null', $property->getValue($this)); + $property->setValue($this, 'Some value which is not null'); + assertType('mixed', $property->getValue($this)); + } }; function (): void { - $property = new \ReflectionProperty($this, 'data'); + $property = new \ReflectionProperty($this, 'data'); - if (is_null($property->getValue($this))) { - assertType('null', $property->getValue($this)); - $property->setValue($this, 'Some value which is not null'); + if (is_null($property->getValue($this))) { + assertType('null', $property->getValue($this)); + $property->setValue($this, 'Some value which is not null'); - $value = $property->getValue($this); - assertType('mixed', $value); - } + $value = $property->getValue($this); + assertType('mixed', $value); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4247.php b/tests/PHPStan/Analyser/data/bug-4247.php index a96c7d71cc..14ac228296 100644 --- a/tests/PHPStan/Analyser/data/bug-4247.php +++ b/tests/PHPStan/Analyser/data/bug-4247.php @@ -6,25 +6,24 @@ class HelloWorld { - /** - * @return static - */ - public function singleton() { - assertType('static(Bug4247\HelloWorld)', SingletonLib::init(static::class)); - assertType('Bug4247\HelloWorld', SingletonLib::init(self::class)); - } + /** + * @return static + */ + public function singleton() + { + assertType('static(Bug4247\HelloWorld)', SingletonLib::init(static::class)); + assertType('Bug4247\HelloWorld', SingletonLib::init(self::class)); + } } final class SingletonLib { - - /** - * @template TInit - * @param class-string $classname - * @return TInit - */ - public static function init($classname) { - - } - + /** + * @template TInit + * @param class-string $classname + * @return TInit + */ + public static function init($classname) + { + } } diff --git a/tests/PHPStan/Analyser/data/bug-4267.php b/tests/PHPStan/Analyser/data/bug-4267.php index be533bafcd..34da598522 100644 --- a/tests/PHPStan/Analyser/data/bug-4267.php +++ b/tests/PHPStan/Analyser/data/bug-4267.php @@ -9,44 +9,43 @@ */ class Model1 implements \IteratorAggregate { - - public function getIterator(): iterable - { - throw new \Exception('not implemented'); - } + public function getIterator(): iterable + { + throw new \Exception('not implemented'); + } } class HelloWorld1 extends Model1 { - /** @var int */ - public $x = 5; + /** @var int */ + public $x = 5; } function (): void { - foreach (new HelloWorld1() as $h) { - assertType(HelloWorld1::class, $h); - } + foreach (new HelloWorld1() as $h) { + assertType(HelloWorld1::class, $h); + } }; class Model2 implements \IteratorAggregate { - /** - * @return iterable - */ - public function getIterator(): iterable - { - throw new \Exception('not implemented'); - } + /** + * @return iterable + */ + public function getIterator(): iterable + { + throw new \Exception('not implemented'); + } } class HelloWorld2 extends Model2 { - /** @var int */ - public $x = 5; + /** @var int */ + public $x = 5; } function (): void { - foreach (new HelloWorld2() as $h) { - assertType(HelloWorld2::class, $h); - } + foreach (new HelloWorld2() as $h) { + assertType(HelloWorld2::class, $h); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4287.php b/tests/PHPStan/Analyser/data/bug-4287.php index 646a313d49..c6d0486d40 100644 --- a/tests/PHPStan/Analyser/data/bug-4287.php +++ b/tests/PHPStan/Analyser/data/bug-4287.php @@ -7,29 +7,29 @@ /** * @param \ArrayObject $obj */ -function(\ArrayObject $obj): void { - if (count($obj) === 0) { - return; - } +function (\ArrayObject $obj): void { + if (count($obj) === 0) { + return; + } - assertType('int<1, max>', count($obj)); + assertType('int<1, max>', count($obj)); - $obj->offsetUnset(0); + $obj->offsetUnset(0); - assertType('int<0, max>', count($obj)); + assertType('int<0, max>', count($obj)); }; /** * @param \ArrayObject $obj */ -function(\ArrayObject $obj): void { - if (count($obj) === 0) { - return; - } +function (\ArrayObject $obj): void { + if (count($obj) === 0) { + return; + } - assertType('int<1, max>', count($obj)); + assertType('int<1, max>', count($obj)); - unset($obj[0]); + unset($obj[0]); - assertType('int<0, max>', count($obj)); + assertType('int<0, max>', count($obj)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4288.php b/tests/PHPStan/Analyser/data/bug-4288.php index 10c2318731..69e7021d7c 100644 --- a/tests/PHPStan/Analyser/data/bug-4288.php +++ b/tests/PHPStan/Analyser/data/bug-4288.php @@ -4,25 +4,23 @@ trait PaginationTrait { + /** @var int */ + private $test = self::DEFAULT_SIZE; - /** @var int */ - private $test = self::DEFAULT_SIZE; - - private function paginate(int $size = self::DEFAULT_SIZE): void - { - echo $size; - } + private function paginate(int $size = self::DEFAULT_SIZE): void + { + echo $size; + } } class MyClass { - use PaginationTrait; + use PaginationTrait; - const DEFAULT_SIZE = 10; + public const DEFAULT_SIZE = 10; - public function test(): void - { - $this->paginate(); - } + public function test(): void + { + $this->paginate(); + } } - diff --git a/tests/PHPStan/Analyser/data/bug-4300.php b/tests/PHPStan/Analyser/data/bug-4300.php index 46895b0456..2f07942307 100644 --- a/tests/PHPStan/Analyser/data/bug-4300.php +++ b/tests/PHPStan/Analyser/data/bug-4300.php @@ -1,18 +1,18 @@ - count($column2) ? 2 : 1; - - return $column; - } + $column = count($column1) > count($column2) ? 2 : 1; + return $column; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4326.php b/tests/PHPStan/Analyser/data/bug-4326.php index 7291d66b35..47e96cbf8e 100644 --- a/tests/PHPStan/Analyser/data/bug-4326.php +++ b/tests/PHPStan/Analyser/data/bug-4326.php @@ -6,29 +6,29 @@ class HelloWorld { - public function sayHello(?string $a, ?string $b): string - { - if (null === $a && null === $b) { - return 'no $a, no $b'; - } - assertType('string|null', $a); - assertType('string|null', $b); - if (null === $a) { - assertType('null', $a); - assertType('string', $b); - return $b; - } + public function sayHello(?string $a, ?string $b): string + { + if (null === $a && null === $b) { + return 'no $a, no $b'; + } + assertType('string|null', $a); + assertType('string|null', $b); + if (null === $a) { + assertType('null', $a); + assertType('string', $b); + return $b; + } - assertType('string', $a); - assertType('string|null', $b); + assertType('string', $a); + assertType('string|null', $b); - if (null === $b) { - return $a; - } + if (null === $b) { + return $a; + } - assertType('string', $a); - assertType('string', $b); + assertType('string', $a); + assertType('string', $b); - return $a . ' - ' . $b; - } + return $a . ' - ' . $b; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4339.php b/tests/PHPStan/Analyser/data/bug-4339.php index d5f4dcefd3..896097e66f 100644 --- a/tests/PHPStan/Analyser/data/bug-4339.php +++ b/tests/PHPStan/Analyser/data/bug-4339.php @@ -1,15 +1,17 @@ -= 7.4 += 7.4 namespace Bug4339; use function PHPStan\Testing\assertType; function (?string $v) { - assertType('string', $v ?? '-'); - fn (?string $value): string => assertType('string', $value ?? '-'); - fn (?string $value): void => assertType('string|null', $value); + assertType('string', $v ?? '-'); + fn (?string $value): string => assertType('string', $value ?? '-'); + fn (?string $value): void => assertType('string|null', $value); - $f = fn (?string $value): string => $value ?? '-'; + $f = fn (?string $value): string => $value ?? '-'; - assertType('string', $f($v)); + assertType('string', $f($v)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4343.php b/tests/PHPStan/Analyser/data/bug-4343.php index cbc0271df3..716bac87cf 100644 --- a/tests/PHPStan/Analyser/data/bug-4343.php +++ b/tests/PHPStan/Analyser/data/bug-4343.php @@ -6,11 +6,11 @@ use function PHPStan\Testing\assertVariableCertainty; function (array $a) { - if (count($a) > 0) { - $test = new \stdClass(); - } + if (count($a) > 0) { + $test = new \stdClass(); + } - foreach ($a as $my) { - assertVariableCertainty(TrinaryLogic::createYes(), $test); - } + foreach ($a as $my) { + assertVariableCertainty(TrinaryLogic::createYes(), $test); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4351.php b/tests/PHPStan/Analyser/data/bug-4351.php index 4cfd2fbe25..a46e465890 100644 --- a/tests/PHPStan/Analyser/data/bug-4351.php +++ b/tests/PHPStan/Analyser/data/bug-4351.php @@ -6,66 +6,62 @@ class Thing { - public function doSomething(): void - { - } + public function doSomething(): void + { + } } class ParentC { - /** @var Thing|null */ - protected $thing; + /** @var Thing|null */ + protected $thing; - protected function __construct() - { - $this->thing = null; - } + protected function __construct() + { + $this->thing = null; + } } class HelloWorld extends ParentC { - public function __construct(Thing $thing) - { - assertType('Bug4351\Thing|null', $this->thing); - $this->thing = $thing; - assertType('Bug4351\Thing', $this->thing); - - parent::__construct(); - assertType('Bug4351\Thing|null', $this->thing); - } - - public function doFoo(Thing $thing) - { - assertType('Bug4351\Thing|null', $this->thing); - $this->thing = $thing; - assertType('Bug4351\Thing', $this->thing); - - UnrelatedClass::doFoo(); - assertType('Bug4351\Thing', $this->thing); - } - - public function doBar(Thing $thing) - { - assertType('Bug4351\Thing|null', $this->thing); - $this->thing = $thing; - assertType('Bug4351\Thing', $this->thing); - - UnrelatedClass::doStaticFoo(); - assertType('Bug4351\Thing', $this->thing); - } + public function __construct(Thing $thing) + { + assertType('Bug4351\Thing|null', $this->thing); + $this->thing = $thing; + assertType('Bug4351\Thing', $this->thing); + + parent::__construct(); + assertType('Bug4351\Thing|null', $this->thing); + } + + public function doFoo(Thing $thing) + { + assertType('Bug4351\Thing|null', $this->thing); + $this->thing = $thing; + assertType('Bug4351\Thing', $this->thing); + + UnrelatedClass::doFoo(); + assertType('Bug4351\Thing', $this->thing); + } + + public function doBar(Thing $thing) + { + assertType('Bug4351\Thing|null', $this->thing); + $this->thing = $thing; + assertType('Bug4351\Thing', $this->thing); + + UnrelatedClass::doStaticFoo(); + assertType('Bug4351\Thing', $this->thing); + } } class UnrelatedClass { + public function doFoo(): void + { + } - public function doFoo(): void - { - - } - - public static function doStaticFoo(): void - { - - } - + public static function doStaticFoo(): void + { + } } diff --git a/tests/PHPStan/Analyser/data/bug-4398.php b/tests/PHPStan/Analyser/data/bug-4398.php index 23bab3eaa2..7c026b8376 100644 --- a/tests/PHPStan/Analyser/data/bug-4398.php +++ b/tests/PHPStan/Analyser/data/bug-4398.php @@ -5,14 +5,14 @@ use function PHPStan\Testing\assertType; function (array $meters): void { - assertType('array', $meters); - if (empty($meters)) { - throw new \Exception('NO_METERS_FOUND'); - } + assertType('array', $meters); + if (empty($meters)) { + throw new \Exception('NO_METERS_FOUND'); + } - assertType('array&nonEmpty', $meters); - assertType('array', array_reverse()); - assertType('array&nonEmpty', array_reverse($meters)); - assertType('array&nonEmpty', array_keys($meters)); - assertType('array&nonEmpty', array_values($meters)); + assertType('array&nonEmpty', $meters); + assertType('array', array_reverse()); + assertType('array&nonEmpty', array_reverse($meters)); + assertType('array&nonEmpty', array_keys($meters)); + assertType('array&nonEmpty', array_values($meters)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4415.php b/tests/PHPStan/Analyser/data/bug-4415.php index 338401f86a..d75cf6bc2c 100644 --- a/tests/PHPStan/Analyser/data/bug-4415.php +++ b/tests/PHPStan/Analyser/data/bug-4415.php @@ -9,17 +9,14 @@ */ class Foo implements \IteratorAggregate { - - public function getIterator(): \Iterator - { - - } - + public function getIterator(): \Iterator + { + } } function (Foo $foo): void { - foreach ($foo as $k => $v) { - assertType('int', $k); - assertType('string', $v); - } + foreach ($foo as $k => $v) { + assertType('int', $k); + assertType('string', $v); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4423.php b/tests/PHPStan/Analyser/data/bug-4423.php index d136dc4d6f..19406573fe 100644 --- a/tests/PHPStan/Analyser/data/bug-4423.php +++ b/tests/PHPStan/Analyser/data/bug-4423.php @@ -1,4 +1,6 @@ - $bar * @method Bar doBar() */ -trait Foo { - - /** @var Bar */ - public $baz; +trait Foo +{ + /** @var Bar */ + public $baz; - /** @param K $k */ - public function doFoo($k) - { - assertType('T (class Bug4423\Child, argument)', $k); - //assertType('Bug4423\Bar', $this->bar); - assertType('Bug4423\Bar', $this->baz); - //assertType('Bug4423\Bar', $this->doBar()); - assertType('Bug4423\Bar', $this->doBaz()); - } - - /** @return Bar */ - public function doBaz() - { - - } + /** @param K $k */ + public function doFoo($k) + { + assertType('T (class Bug4423\Child, argument)', $k); + //assertType('Bug4423\Bar', $this->bar); + assertType('Bug4423\Bar', $this->baz); + //assertType('Bug4423\Bar', $this->doBar()); + assertType('Bug4423\Bar', $this->doBaz()); + } + /** @return Bar */ + public function doBaz() + { + } } /** * @template T * @template K */ -class Base { - +class Base +{ } /** * @template T * @extends Base */ -class Child extends Base { - /** @phpstan-use Foo */ - use Foo; +class Child extends Base +{ + /** @phpstan-use Foo */ + use Foo; } function (Child $child): void { - /** @var Child $child */ - assertType('Bug4423\Child', $child); - //assertType('Bug4423\Bar', $child->bar); - assertType('Bug4423\Bar', $child->baz); - //assertType('Bug4423\Bar', $child->doBar()); - assertType('Bug4423\Bar', $child->doBaz()); + /** @var Child $child */ + assertType('Bug4423\Child', $child); + //assertType('Bug4423\Bar', $child->bar); + assertType('Bug4423\Bar', $child->baz); + //assertType('Bug4423\Bar', $child->doBar()); + assertType('Bug4423\Bar', $child->doBaz()); }; diff --git a/tests/PHPStan/Analyser/data/bug-4434.php b/tests/PHPStan/Analyser/data/bug-4434.php index b60b751ed1..255448779f 100644 --- a/tests/PHPStan/Analyser/data/bug-4434.php +++ b/tests/PHPStan/Analyser/data/bug-4434.php @@ -7,36 +7,36 @@ class HelloWorld { - public function testSendEmailToLog(): void - { - foreach ([1] as $emailFile) { - assertType('int', PHP_MAJOR_VERSION); - assertType('int', \PHP_MAJOR_VERSION); - if (PHP_MAJOR_VERSION === 7) { - assertType('int', PHP_MAJOR_VERSION); - assertType('int', \PHP_MAJOR_VERSION); - } else { - assertType('int|int<8, max>', PHP_MAJOR_VERSION); - assertType('int|int<8, max>', \PHP_MAJOR_VERSION); - } - } - } + public function testSendEmailToLog(): void + { + foreach ([1] as $emailFile) { + assertType('int', PHP_MAJOR_VERSION); + assertType('int', \PHP_MAJOR_VERSION); + if (PHP_MAJOR_VERSION === 7) { + assertType('int', PHP_MAJOR_VERSION); + assertType('int', \PHP_MAJOR_VERSION); + } else { + assertType('int|int<8, max>', PHP_MAJOR_VERSION); + assertType('int|int<8, max>', \PHP_MAJOR_VERSION); + } + } + } } class HelloWorld2 { - public function testSendEmailToLog(): void - { - foreach ([1] as $emailFile) { - assertType('int', PHP_MAJOR_VERSION); - assertType('int', \PHP_MAJOR_VERSION); - if (PHP_MAJOR_VERSION === 100) { - assertType('int', PHP_MAJOR_VERSION); - assertType('int', \PHP_MAJOR_VERSION); - } else { - assertType('int|int<101, max>', PHP_MAJOR_VERSION); - assertType('int|int<101, max>', \PHP_MAJOR_VERSION); - } - } - } + public function testSendEmailToLog(): void + { + foreach ([1] as $emailFile) { + assertType('int', PHP_MAJOR_VERSION); + assertType('int', \PHP_MAJOR_VERSION); + if (PHP_MAJOR_VERSION === 100) { + assertType('int', PHP_MAJOR_VERSION); + assertType('int', \PHP_MAJOR_VERSION); + } else { + assertType('int|int<101, max>', PHP_MAJOR_VERSION); + assertType('int|int<101, max>', \PHP_MAJOR_VERSION); + } + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4436.php b/tests/PHPStan/Analyser/data/bug-4436.php index e442f55ca3..ff6652baf2 100644 --- a/tests/PHPStan/Analyser/data/bug-4436.php +++ b/tests/PHPStan/Analyser/data/bug-4436.php @@ -10,22 +10,22 @@ class Bar class Foo { - /** @var \SplObjectStorage */ - private $storage; + /** @var \SplObjectStorage */ + private $storage; - public function __construct() - { - $this->storage = new \SplObjectStorage(); - } + public function __construct() + { + $this->storage = new \SplObjectStorage(); + } - public function add(Bar $bar, string $value): void - { - $this->storage[$bar] = $value; - } + public function add(Bar $bar, string $value): void + { + $this->storage[$bar] = $value; + } - public function get(Bar $bar): string - { - assertType('string', $this->storage[$bar]); - return $this->storage[$bar]; - } + public function get(Bar $bar): string + { + assertType('string', $this->storage[$bar]); + return $this->storage[$bar]; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4498.php b/tests/PHPStan/Analyser/data/bug-4498.php index 19e878c763..2f36b9e9bd 100644 --- a/tests/PHPStan/Analyser/data/bug-4498.php +++ b/tests/PHPStan/Analyser/data/bug-4498.php @@ -6,45 +6,43 @@ class Foo { - - /** - * @param iterable $iterable - * - * @return iterable - * - * @template TKey - * @template TValue - */ - public function fcn(iterable $iterable): iterable - { - if ($iterable instanceof \Traversable) { - assertType('iterable&Traversable', $iterable); - return $iterable; - } - - assertType('array', $iterable); - - return $iterable; - } - - /** - * @param iterable $iterable - * - * @return iterable - * - * @template TKey - * @template TValue - */ - public function bar(iterable $iterable): iterable - { - if (is_array($iterable)) { - assertType('array', $iterable); - return $iterable; - } - - assertType('Traversable', $iterable); - - return $iterable; - } - + /** + * @param iterable $iterable + * + * @return iterable + * + * @template TKey + * @template TValue + */ + public function fcn(iterable $iterable): iterable + { + if ($iterable instanceof \Traversable) { + assertType('iterable&Traversable', $iterable); + return $iterable; + } + + assertType('array', $iterable); + + return $iterable; + } + + /** + * @param iterable $iterable + * + * @return iterable + * + * @template TKey + * @template TValue + */ + public function bar(iterable $iterable): iterable + { + if (is_array($iterable)) { + assertType('array', $iterable); + return $iterable; + } + + assertType('Traversable', $iterable); + + return $iterable; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4500.php b/tests/PHPStan/Analyser/data/bug-4500.php index 2aff665833..b1cd276fab 100644 --- a/tests/PHPStan/Analyser/data/bug-4500.php +++ b/tests/PHPStan/Analyser/data/bug-4500.php @@ -6,134 +6,132 @@ class Foo { - - public function doFirst(): void - { - global $foo; - assertType('mixed', $foo); - } - - public function doFoo(): void - { - /** @var int */ - global $foo; - assertType('int', $foo); - } - - public function doBar(): void - { - /** @var int $foo */ - global $foo; - assertType('int', $foo); - } - - public function doBaz(): void - { - /** @var int */ - global $foo, $bar; - assertType('mixed', $foo); - assertType('mixed', $bar); - } - - public function doLorem(): void - { - /** @var int $foo */ - global $foo, $bar; - assertType('int', $foo); - assertType('mixed', $bar); - - $baz = 'foo'; - - /** @var int $baz */ - global $lorem; - assertType('mixed', $lorem); - assertType('\'foo\'', $baz); - } - - public function doIpsum(): void - { - /** - * @var int $foo - * @var string $bar - */ - global $foo, $bar; - - assertType('int', $foo); - assertType('string', $bar); - } - - public function doDolor(): void - { - /** @var int $baz */ - global $lorem; - - assertType('mixed', $lorem); - assertType('*ERROR*', $baz); - } - - public function doSit(): void - { - /** @var array $modelPropertyParameter */ - $modelPropertyParameter = doFoo(); - - /** @var int $parameterIndex */ - /** @var \stdClass $modelType */ - [$parameterIndex, $modelType] = $modelPropertyParameter; - - assertType('int', $parameterIndex); - assertType('stdClass', $modelType); - } - - public function doAmet(array $slots): void - { - /** @var \stdClass[] $itemSlots */ - /** @var \stdClass[] $slots */ - $itemSlots = []; - - assertType('array', $itemSlots); - assertType('array', $slots); - } - - public function listDestructuring(): void - { - /** @var int $test */ - [[$test]] = doFoo(); - assertType('int', $test); - } - - public function listDestructuring2(): void - { - /** @var int $test */ - [$test] = doFoo(); - assertType('int', $test); - } - - public function listDestructuringForeach(): void - { - /** @var int $value */ - foreach (doFoo() as [[$value]]) { - assertType('int', $value); - } - } - - public function listDestructuringForeach2(): void - { - /** @var int $value */ - foreach (doFoo() as [$value]) { - assertType('int', $value); - } - } - - public function doConseteur(): void - { - /** - * @var int $foo - * @var string $bar - */ - [$foo, $bar] = doFoo(); - - assertType('int', $foo); - assertType('string', $bar); - } - + public function doFirst(): void + { + global $foo; + assertType('mixed', $foo); + } + + public function doFoo(): void + { + /** @var int */ + global $foo; + assertType('int', $foo); + } + + public function doBar(): void + { + /** @var int $foo */ + global $foo; + assertType('int', $foo); + } + + public function doBaz(): void + { + /** @var int */ + global $foo, $bar; + assertType('mixed', $foo); + assertType('mixed', $bar); + } + + public function doLorem(): void + { + /** @var int $foo */ + global $foo, $bar; + assertType('int', $foo); + assertType('mixed', $bar); + + $baz = 'foo'; + + /** @var int $baz */ + global $lorem; + assertType('mixed', $lorem); + assertType('\'foo\'', $baz); + } + + public function doIpsum(): void + { + /** + * @var int $foo + * @var string $bar + */ + global $foo, $bar; + + assertType('int', $foo); + assertType('string', $bar); + } + + public function doDolor(): void + { + /** @var int $baz */ + global $lorem; + + assertType('mixed', $lorem); + assertType('*ERROR*', $baz); + } + + public function doSit(): void + { + /** @var array $modelPropertyParameter */ + $modelPropertyParameter = doFoo(); + + /** @var int $parameterIndex */ + /** @var \stdClass $modelType */ + [$parameterIndex, $modelType] = $modelPropertyParameter; + + assertType('int', $parameterIndex); + assertType('stdClass', $modelType); + } + + public function doAmet(array $slots): void + { + /** @var \stdClass[] $itemSlots */ + /** @var \stdClass[] $slots */ + $itemSlots = []; + + assertType('array', $itemSlots); + assertType('array', $slots); + } + + public function listDestructuring(): void + { + /** @var int $test */ + [[$test]] = doFoo(); + assertType('int', $test); + } + + public function listDestructuring2(): void + { + /** @var int $test */ + [$test] = doFoo(); + assertType('int', $test); + } + + public function listDestructuringForeach(): void + { + /** @var int $value */ + foreach (doFoo() as [[$value]]) { + assertType('int', $value); + } + } + + public function listDestructuringForeach2(): void + { + /** @var int $value */ + foreach (doFoo() as [$value]) { + assertType('int', $value); + } + } + + public function doConseteur(): void + { + /** + * @var int $foo + * @var string $bar + */ + [$foo, $bar] = doFoo(); + + assertType('int', $foo); + assertType('string', $bar); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4504.php b/tests/PHPStan/Analyser/data/bug-4504.php index 50ba802671..0a3c1925c9 100644 --- a/tests/PHPStan/Analyser/data/bug-4504.php +++ b/tests/PHPStan/Analyser/data/bug-4504.php @@ -6,16 +6,13 @@ class Foo { + public function sayHello($models): void + { + /** @var \Iterator $models */ + foreach ($models as $k => $v) { + assertType('Bug4504TypeInference\A', $v); + } - public function sayHello($models): void - { - /** @var \Iterator $models */ - foreach ($models as $k => $v) { - assertType('Bug4504TypeInference\A', $v); - } - - assertType('array()|Iterator', $models); - } - + assertType('array()|Iterator', $models); + } } - diff --git a/tests/PHPStan/Analyser/data/bug-4513.php b/tests/PHPStan/Analyser/data/bug-4513.php index 23604269f4..89b660f52f 100644 --- a/tests/PHPStan/Analyser/data/bug-4513.php +++ b/tests/PHPStan/Analyser/data/bug-4513.php @@ -4,24 +4,20 @@ class Foo { - - use BarTrait; - + use BarTrait; } trait BarTrait { - - public function doFoo(): void - { - // @phpstan-ignore-next-line - echo 'foo'; - } - - public function doBar(): void - { - // @phpstan-ignore-next-line - echo []; - } - + public function doFoo(): void + { + // @phpstan-ignore-next-line + echo 'foo'; + } + + public function doBar(): void + { + // @phpstan-ignore-next-line + echo []; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4538.php b/tests/PHPStan/Analyser/data/bug-4538.php index ebdbec8e48..65702c132e 100644 --- a/tests/PHPStan/Analyser/data/bug-4538.php +++ b/tests/PHPStan/Analyser/data/bug-4538.php @@ -6,12 +6,12 @@ class Foo { - /** - * @param string $index - */ - public function bar(string $index): void - { - assertType('string|false', getenv($index)); - assertType('array', getenv()); - } + /** + * @param string $index + */ + public function bar(string $index): void + { + assertType('string|false', getenv($index)); + assertType('array', getenv()); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4545.php b/tests/PHPStan/Analyser/data/bug-4545.php index a7162e9f79..49f84415f1 100644 --- a/tests/PHPStan/Analyser/data/bug-4545.php +++ b/tests/PHPStan/Analyser/data/bug-4545.php @@ -10,34 +10,31 @@ class Foo { + /** + * Returns keys which either exist only in one of the maps or exist in both but their associated values are not equal. + * + * @template TKey of Hashable + * @template TValue1 + * @template TValue2 + * + * @param Map $firstMap + * @param Map $secondMap + * @param Closure(TValue1, TValue2): bool $comparator + * + * @return Set + */ + public function compareMaps(Map $firstMap, Map $secondMap, Closure $comparator): Set + { + $firstMapKeys = $firstMap->keys(); + $secondMapKeys = $secondMap->keys(); + $keys = $firstMapKeys->xor($secondMapKeys); + $intersect = $firstMapKeys->intersect($secondMapKeys); + foreach ($intersect as $key) { + assertType('TValue1 (method Bug4545\Foo::compareMaps(), argument)', $firstMap->get($key)); + assertType('TValue2 (method Bug4545\Foo::compareMaps(), argument)', $secondMap->get($key)); + assertType('int|TValue2 (method Bug4545\Foo::compareMaps(), argument)', $secondMap->get($key, 1)); + } - /** - * Returns keys which either exist only in one of the maps or exist in both but their associated values are not equal. - * - * @template TKey of Hashable - * @template TValue1 - * @template TValue2 - * - * @param Map $firstMap - * @param Map $secondMap - * @param Closure(TValue1, TValue2): bool $comparator - * - * @return Set - */ - function compareMaps(Map $firstMap, Map $secondMap, Closure $comparator): Set - { - $firstMapKeys = $firstMap->keys(); - $secondMapKeys = $secondMap->keys(); - $keys = $firstMapKeys->xor($secondMapKeys); - $intersect = $firstMapKeys->intersect($secondMapKeys); - foreach ($intersect as $key) { - assertType('TValue1 (method Bug4545\Foo::compareMaps(), argument)', $firstMap->get($key)); - assertType('TValue2 (method Bug4545\Foo::compareMaps(), argument)', $secondMap->get($key)); - assertType('int|TValue2 (method Bug4545\Foo::compareMaps(), argument)', $secondMap->get($key, 1)); - } - - return $keys; - } - + return $keys; + } } - diff --git a/tests/PHPStan/Analyser/data/bug-4557.php b/tests/PHPStan/Analyser/data/bug-4557.php index 5418cc932a..f5d134c77d 100644 --- a/tests/PHPStan/Analyser/data/bug-4557.php +++ b/tests/PHPStan/Analyser/data/bug-4557.php @@ -18,16 +18,14 @@ interface MockObject class Foo { - - /** - * @template T - * @param class-string $class - * @return T&MockObject - */ - public function createMock($class) - { - } - + /** + * @template T + * @param class-string $class + * @return T&MockObject + */ + public function createMock($class) + { + } } /** @@ -35,32 +33,27 @@ public function createMock($class) */ class Bar extends Foo { - - public function doBar(): void - { - $mock = $this->createMock(\stdClass::class); - assertType('Bug4557\\MockObject&stdClass', $mock); - } - - /** @return T */ - public function doBaz() - { - - } - + public function doBar(): void + { + $mock = $this->createMock(\stdClass::class); + assertType('Bug4557\\MockObject&stdClass', $mock); + } + + /** @return T */ + public function doBaz() + { + } } class Baz { - - /** - * @param Bar $barLorem - * @param Bar $barIpsum - */ - public function doFoo(Bar $barLorem, Bar $barIpsum): void - { - assertType('Bug4557\\Lorem', $barLorem->doBaz()); - assertType('Bug4557\\Ipsum', $barIpsum->doBaz()); - } - + /** + * @param Bar $barLorem + * @param Bar $barIpsum + */ + public function doFoo(Bar $barLorem, Bar $barIpsum): void + { + assertType('Bug4557\\Lorem', $barLorem->doBaz()); + assertType('Bug4557\\Ipsum', $barIpsum->doBaz()); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4558.php b/tests/PHPStan/Analyser/data/bug-4558.php index 89b250cc0b..04c554324e 100644 --- a/tests/PHPStan/Analyser/data/bug-4558.php +++ b/tests/PHPStan/Analyser/data/bug-4558.php @@ -7,44 +7,44 @@ class HelloWorld { - /** - * @var DateTime[] - */ - private $suggestions = []; - - public function sayHello(): ?DateTime - { - while (count($this->suggestions) > 0) { - assertType('array&nonEmpty', $this->suggestions); - assertType('int<1, max>', count($this->suggestions)); - $try = array_shift($this->suggestions); - - assertType('array', $this->suggestions); - assertType('int<0, max>', count($this->suggestions)); - - if (rand(0, 1)) { - return $try; - } - - assertType('array', $this->suggestions); - assertType('int<0, max>', count($this->suggestions)); - - // we might be out of suggested days, so load some more - if (count($this->suggestions) === 0) { - assertType('array()', $this->suggestions); - assertType('0', count($this->suggestions)); - $this->createSuggestions(); - } - - assertType('array', $this->suggestions); - assertType('int<0, max>', count($this->suggestions)); - } - - return null; - } - - private function createSuggestions(): void - { - $this->suggestions[] = new DateTime; - } + /** + * @var DateTime[] + */ + private $suggestions = []; + + public function sayHello(): ?DateTime + { + while (count($this->suggestions) > 0) { + assertType('array&nonEmpty', $this->suggestions); + assertType('int<1, max>', count($this->suggestions)); + $try = array_shift($this->suggestions); + + assertType('array', $this->suggestions); + assertType('int<0, max>', count($this->suggestions)); + + if (rand(0, 1)) { + return $try; + } + + assertType('array', $this->suggestions); + assertType('int<0, max>', count($this->suggestions)); + + // we might be out of suggested days, so load some more + if (count($this->suggestions) === 0) { + assertType('array()', $this->suggestions); + assertType('0', count($this->suggestions)); + $this->createSuggestions(); + } + + assertType('array', $this->suggestions); + assertType('int<0, max>', count($this->suggestions)); + } + + return null; + } + + private function createSuggestions(): void + { + $this->suggestions[] = new DateTime(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4573.php b/tests/PHPStan/Analyser/data/bug-4573.php index 92fb321de5..a66c68cb53 100644 --- a/tests/PHPStan/Analyser/data/bug-4573.php +++ b/tests/PHPStan/Analyser/data/bug-4573.php @@ -6,35 +6,30 @@ class Bar { - - public function doFoo(): void - { - - } - + public function doFoo(): void + { + } } class Foo { - - /** - * @param string|Bar $stringOrObject - */ - public function doFoo($stringOrObject): void - { - if (is_callable([$stringOrObject, 'doFoo'])) { - assertType('Bug4573\Bar|class-string', $stringOrObject); - } - } - - /** - * @param string|Bar $stringOrObject - */ - public function doBar($stringOrObject): void - { - if (method_exists($stringOrObject, 'doFoo')) { - assertType('Bug4573\Bar|class-string', $stringOrObject); - } - } - + /** + * @param string|Bar $stringOrObject + */ + public function doFoo($stringOrObject): void + { + if (is_callable([$stringOrObject, 'doFoo'])) { + assertType('Bug4573\Bar|class-string', $stringOrObject); + } + } + + /** + * @param string|Bar $stringOrObject + */ + public function doBar($stringOrObject): void + { + if (method_exists($stringOrObject, 'doFoo')) { + assertType('Bug4573\Bar|class-string', $stringOrObject); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4577.php b/tests/PHPStan/Analyser/data/bug-4577.php index edfefca302..a88894c6d0 100644 --- a/tests/PHPStan/Analyser/data/bug-4577.php +++ b/tests/PHPStan/Analyser/data/bug-4577.php @@ -6,13 +6,11 @@ class Test { - - public function test(\ReflectionClass $refClass): void - { - if ($refClass->isSubclassOf(Test::class)) { - $instance = $refClass->newInstance(); - assertType(Test::class, $instance); - } - } - + public function test(\ReflectionClass $refClass): void + { + if ($refClass->isSubclassOf(Test::class)) { + $instance = $refClass->newInstance(); + assertType(Test::class, $instance); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4579.php b/tests/PHPStan/Analyser/data/bug-4579.php index bcaa6bb812..d68e739aa3 100644 --- a/tests/PHPStan/Analyser/data/bug-4579.php +++ b/tests/PHPStan/Analyser/data/bug-4579.php @@ -5,18 +5,18 @@ use function PHPStan\Testing\assertType; function (string $class): void { - $foo = new $class(); - assertType('mixed~string', $foo); - if (method_exists($foo, 'doFoo')) { - assertType('object&hasMethod(doFoo)', $foo); - } + $foo = new $class(); + assertType('mixed~string', $foo); + if (method_exists($foo, 'doFoo')) { + assertType('object&hasMethod(doFoo)', $foo); + } }; function (): void { - $s = \stdClass::class; - if (rand(0, 1)) { - $s = \Exception::class; - } + $s = \stdClass::class; + if (rand(0, 1)) { + $s = \Exception::class; + } - assertType('Exception|stdClass', new $s()); + assertType('Exception|stdClass', new $s()); }; diff --git a/tests/PHPStan/Analyser/data/bug-4587.php b/tests/PHPStan/Analyser/data/bug-4587.php index 880aef8d47..4a18ed461f 100644 --- a/tests/PHPStan/Analyser/data/bug-4587.php +++ b/tests/PHPStan/Analyser/data/bug-4587.php @@ -6,32 +6,32 @@ class HelloWorld { - public function a(): void - { - /** @var list $results */ - $results = []; - - $type = array_map(static function (array $result): array { - assertType('array(\'a\' => int)', $result); - return $result; - }, $results); - - assertType('array int)>', $type); - } - - public function b(): void - { - /** @var list $results */ - $results = []; - - $type = array_map(static function (array $result): array { - assertType('array(\'a\' => int)', $result); - $result['a'] = (string) $result['a']; - assertType('array(\'a\' => string&numeric)', $result); - - return $result; - }, $results); - - assertType('array string&numeric)>', $type); - } + public function a(): void + { + /** @var list $results */ + $results = []; + + $type = array_map(static function (array $result): array { + assertType('array(\'a\' => int)', $result); + return $result; + }, $results); + + assertType('array int)>', $type); + } + + public function b(): void + { + /** @var list $results */ + $results = []; + + $type = array_map(static function (array $result): array { + assertType('array(\'a\' => int)', $result); + $result['a'] = (string) $result['a']; + assertType('array(\'a\' => string&numeric)', $result); + + return $result; + }, $results); + + assertType('array string&numeric)>', $type); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4588.php b/tests/PHPStan/Analyser/data/bug-4588.php index ca46ae1d2a..f21ea5df53 100644 --- a/tests/PHPStan/Analyser/data/bug-4588.php +++ b/tests/PHPStan/Analyser/data/bug-4588.php @@ -4,23 +4,29 @@ use function PHPStan\Testing\assertType; -class c { - private $b; - public function __construct(?b $b ) { - $this->b = $b; - } - public function getB(): ?b { - - return $this->b; - } +class c +{ + private $b; + public function __construct(?b $b) + { + $this->b = $b; + } + public function getB(): ?b + { + return $this->b; + } } -class b{ - public function callB():bool {return true;} +class b +{ + public function callB(): bool + { + return true; + } } function (c $c): void { - if ($c->getB()) { - assertType(b::class, $c->getB()); - } + if ($c->getB()) { + assertType(b::class, $c->getB()); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4642.php b/tests/PHPStan/Analyser/data/bug-4642.php index 7281959f1f..5cf4b64d27 100644 --- a/tests/PHPStan/Analyser/data/bug-4642.php +++ b/tests/PHPStan/Analyser/data/bug-4642.php @@ -4,25 +4,34 @@ use function PHPStan\Testing\assertType; -interface IEntity {} +interface IEntity +{ +} /** @template E of IEntity */ -interface IRepository {} +interface IRepository +{ +} -interface I { - /** - * Returns repository by repository class. - * @template E of IEntity - * @template T of IRepository - * @phpstan-param class-string $className - * @phpstan-return T - */ - function getRepository(string $className): IRepository; +interface I +{ + /** + * Returns repository by repository class. + * @template E of IEntity + * @template T of IRepository + * @phpstan-param class-string $className + * @phpstan-return T + */ + public function getRepository(string $className): IRepository; } -class User implements IEntity {} +class User implements IEntity +{ +} /** @implements IRepository */ -class UsersRepository implements IRepository {} +class UsersRepository implements IRepository +{ +} function (I $model): void { - assertType(UsersRepository::class, $model->getRepository(UsersRepository::class)); + assertType(UsersRepository::class, $model->getRepository(UsersRepository::class)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4650.php b/tests/PHPStan/Analyser/data/bug-4650.php index f378375869..44d1519e5c 100644 --- a/tests/PHPStan/Analyser/data/bug-4650.php +++ b/tests/PHPStan/Analyser/data/bug-4650.php @@ -7,21 +7,20 @@ class Foo { + /** + * @phpstan-param non-empty-array $idx + */ + public function doFoo(array $idx): void + { + assertType('array&nonEmpty', $idx); + assertNativeType('array', $idx); - /** - * @phpstan-param non-empty-array $idx - */ - function doFoo(array $idx): void { - assertType('array&nonEmpty', $idx); - assertNativeType('array', $idx); - - assertType('array()', []); - assertNativeType('array()', []); - - assertType('false', $idx === []); - assertNativeType('bool', $idx === []); - assertType('true', $idx !== []); - assertNativeType('bool', $idx !== []); - } + assertType('array()', []); + assertNativeType('array()', []); + assertType('false', $idx === []); + assertNativeType('bool', $idx === []); + assertType('true', $idx !== []); + assertNativeType('bool', $idx !== []); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4657.php b/tests/PHPStan/Analyser/data/bug-4657.php index 3cc65c9ed9..71cb0b41d5 100644 --- a/tests/PHPStan/Analyser/data/bug-4657.php +++ b/tests/PHPStan/Analyser/data/bug-4657.php @@ -7,16 +7,16 @@ use function PHPStan\Testing\assertNativeType; function (): void { - $value = null; - $other = null; - $callback = function () use (&$value, &$other) : void { - $value = new DateTime(); - }; - $callback(); + $value = null; + $other = null; + $callback = function () use (&$value, &$other): void { + $value = new DateTime(); + }; + $callback(); - assertType('DateTime|null', $value); - assertNativeType('DateTime|null', $value); + assertType('DateTime|null', $value); + assertNativeType('DateTime|null', $value); - assertType('null', $other); - assertNativeType('null', $other); + assertType('null', $other); + assertNativeType('null', $other); }; diff --git a/tests/PHPStan/Analyser/data/bug-4695.php b/tests/PHPStan/Analyser/data/bug-4695.php index e025d3f8a0..3c17b1436f 100644 --- a/tests/PHPStan/Analyser/data/bug-4695.php +++ b/tests/PHPStan/Analyser/data/bug-4695.php @@ -6,62 +6,63 @@ class Foo { + /** + * @param int|null $u + * @param int|null $a + * @return array + */ + public function foo($u=null, $a=null) + { + if (is_null($u) && is_null($a)) { + return [0,0]; + } + if ($u) { + $a = $u; + } elseif ($a) { + $u = $a; + } + assertType('int|null', $u); + assertType('int|null', $a); + return [$u, $a]; + } - /** - * @param int|null $u - * @param int|null $a - * @return array - */ - function foo($u=null, $a=null){ - if (is_null($u) && is_null($a)){ - return [0,0]; - } - if ($u){ - $a = $u; - }else if ($a){ - $u = $a; - } - assertType('int|null', $u); - assertType('int|null', $a); - return [$u, $a]; - } - - /** - * @param int|null $u - * @param int|null $a - * @return array - */ - function bar($u=null, $a=null){ - if (!$u && !$a){ - return [0,0]; - } - if ($u){ - $a = $u; - }else if ($a){ - $u = $a; - } - assertType('int|int<1, max>', $u); - assertType('int|int<1, max>', $a); - return [$u, $a]; - } - - /** - * @param int|null $u - * @param int|null $a - * @return array - */ - function baz($u=null, $a=null){ - if (is_null($u) && is_null($a)){ - return [0,0]; - } - if ($u !== null){ - $a = $u; - }else if ($a !== null){ - $u = $a; - } - assertType('int', $u); - assertType('int', $a); - return [$u, $a]; - } + /** + * @param int|null $u + * @param int|null $a + * @return array + */ + public function bar($u=null, $a=null) + { + if (!$u && !$a) { + return [0,0]; + } + if ($u) { + $a = $u; + } elseif ($a) { + $u = $a; + } + assertType('int|int<1, max>', $u); + assertType('int|int<1, max>', $a); + return [$u, $a]; + } + /** + * @param int|null $u + * @param int|null $a + * @return array + */ + public function baz($u=null, $a=null) + { + if (is_null($u) && is_null($a)) { + return [0,0]; + } + if ($u !== null) { + $a = $u; + } elseif ($a !== null) { + $u = $a; + } + assertType('int', $u); + assertType('int', $a); + return [$u, $a]; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4700.php b/tests/PHPStan/Analyser/data/bug-4700.php index bf73f23a09..18451eb9cc 100644 --- a/tests/PHPStan/Analyser/data/bug-4700.php +++ b/tests/PHPStan/Analyser/data/bug-4700.php @@ -4,46 +4,66 @@ use function PHPStan\Testing\assertType; -function(array $array, int $count): void { - if ($count < 1) { - return; - } - - assertType('int<1, max>', $count); - - $a = []; - if (isset($array['a'])) $a[] = $array['a']; - if (isset($array['b'])) $a[] = $array['b']; - if (isset($array['c'])) $a[] = $array['c']; - if (isset($array['d'])) $a[] = $array['d']; - if (isset($array['e'])) $a[] = $array['e']; - if (count($a) >= $count) { - assertType('1|2|3|4|5', count($a)); - assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); - } else { - assertType('0|1|2|3|4|5', count($a)); - assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); - } +function (array $array, int $count): void { + if ($count < 1) { + return; + } + + assertType('int<1, max>', $count); + + $a = []; + if (isset($array['a'])) { + $a[] = $array['a']; + } + if (isset($array['b'])) { + $a[] = $array['b']; + } + if (isset($array['c'])) { + $a[] = $array['c']; + } + if (isset($array['d'])) { + $a[] = $array['d']; + } + if (isset($array['e'])) { + $a[] = $array['e']; + } + if (count($a) >= $count) { + assertType('1|2|3|4|5', count($a)); + assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + } else { + assertType('0|1|2|3|4|5', count($a)); + assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + } }; -function(array $array, int $count): void { - if ($count < 1) { - return; - } - - assertType('int<1, max>', $count); - - $a = []; - if (isset($array['a'])) $a[] = $array['a']; - if (isset($array['b'])) $a[] = $array['b']; - if (isset($array['c'])) $a[] = $array['c']; - if (isset($array['d'])) $a[] = $array['d']; - if (isset($array['e'])) $a[] = $array['e']; - if (count($a) > $count) { - assertType('2|3|4|5', count($a)); - assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); - } else { - assertType('0|1|2|3|4|5', count($a)); - assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); - } +function (array $array, int $count): void { + if ($count < 1) { + return; + } + + assertType('int<1, max>', $count); + + $a = []; + if (isset($array['a'])) { + $a[] = $array['a']; + } + if (isset($array['b'])) { + $a[] = $array['b']; + } + if (isset($array['c'])) { + $a[] = $array['c']; + } + if (isset($array['d'])) { + $a[] = $array['d']; + } + if (isset($array['e'])) { + $a[] = $array['e']; + } + if (count($a) > $count) { + assertType('2|3|4|5', count($a)); + assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + } else { + assertType('0|1|2|3|4|5', count($a)); + assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-4707.php b/tests/PHPStan/Analyser/data/bug-4707.php index 309becc933..322df6ee87 100644 --- a/tests/PHPStan/Analyser/data/bug-4707.php +++ b/tests/PHPStan/Analyser/data/bug-4707.php @@ -6,50 +6,40 @@ interface Foo { - - /** @param static $p */ - public function doFoo($p): void; - + /** @param static $p */ + public function doFoo($p): void; } class Bar implements Foo { - - /** @param static $p */ - public function doFoo($p): void - { - assertType('static(Bug4707TypeInference\Bar)', $p); - } - + /** @param static $p */ + public function doFoo($p): void + { + assertType('static(Bug4707TypeInference\Bar)', $p); + } } class Bar2 implements Foo { - - public function doFoo($p): void - { - assertType('static(Bug4707TypeInference\Bar2)', $p); - } - + public function doFoo($p): void + { + assertType('static(Bug4707TypeInference\Bar2)', $p); + } } final class Bar3 implements Foo { - - /** @param static $p */ - public function doFoo($p): void - { - assertType('Bug4707TypeInference\Bar3', $p); - } - + /** @param static $p */ + public function doFoo($p): void + { + assertType('Bug4707TypeInference\Bar3', $p); + } } final class Bar4 implements Foo { - - public function doFoo($p): void - { - assertType('Bug4707TypeInference\Bar4', $p); - } - + public function doFoo($p): void + { + assertType('Bug4707TypeInference\Bar4', $p); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4713.php b/tests/PHPStan/Analyser/data/bug-4713.php index c1c519429b..28e22fec58 100644 --- a/tests/PHPStan/Analyser/data/bug-4713.php +++ b/tests/PHPStan/Analyser/data/bug-4713.php @@ -4,12 +4,12 @@ class Service { - public static function createInstance(string $class = self::class): Service - { - return new $class(); - } + public static function createInstance(string $class = self::class): Service + { + return new $class(); + } } function (): void { - $service = Service::createInstance(); + $service = Service::createInstance(); }; diff --git a/tests/PHPStan/Analyser/data/bug-4714.php b/tests/PHPStan/Analyser/data/bug-4714.php index 3185b4a0d9..5040222653 100644 --- a/tests/PHPStan/Analyser/data/bug-4714.php +++ b/tests/PHPStan/Analyser/data/bug-4714.php @@ -7,18 +7,18 @@ class Foo { - public function bar(\PDO $database): void - { - $statement = $database->prepare('SELECT `col` FROM `foo` WHERE `param` = :param'); - $statement->bindParam(':param', $param); + public function bar(\PDO $database): void + { + $statement = $database->prepare('SELECT `col` FROM `foo` WHERE `param` = :param'); + $statement->bindParam(':param', $param); - assertVariableCertainty(TrinaryLogic::createYes(), $param); + assertVariableCertainty(TrinaryLogic::createYes(), $param); - $param = 1; + $param = 1; - $statement->execute(); - $statement->bindColumn('col', $col); + $statement->execute(); + $statement->bindColumn('col', $col); - assertVariableCertainty(TrinaryLogic::createYes(), $col); - } + assertVariableCertainty(TrinaryLogic::createYes(), $col); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4715.php b/tests/PHPStan/Analyser/data/bug-4715.php index 14ea1ff1f2..09aba12cdd 100644 --- a/tests/PHPStan/Analyser/data/bug-4715.php +++ b/tests/PHPStan/Analyser/data/bug-4715.php @@ -7,7 +7,9 @@ * @phpstan-template T * @template-extends \IteratorAggregate */ -interface Collection extends \IteratorAggregate {} +interface Collection extends \IteratorAggregate +{ +} /** * @phpstan-template TKey @@ -16,34 +18,36 @@ interface Collection extends \IteratorAggregate {} */ class ArrayCollection implements Collection { - /** - * {@inheritDoc} - */ - public function getIterator() - { - return new \ArrayIterator([]); - } + /** + * {@inheritDoc} + */ + public function getIterator() + { + return new \ArrayIterator([]); + } } -class Administration {} +class Administration +{ +} class Company { - /** - * @var Collection|Administration[] - */ - protected Collection $administrations; + /** + * @var Collection|Administration[] + */ + protected Collection $administrations; - public function __construct() - { - $this->administrations = new ArrayCollection(); - } + public function __construct() + { + $this->administrations = new ArrayCollection(); + } - /** - * @return Collection|Administration[] - */ - public function getAdministrations() : Collection - { - return $this->administrations; - } + /** + * @return Collection|Administration[] + */ + public function getAdministrations(): Collection + { + return $this->administrations; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4725.php b/tests/PHPStan/Analyser/data/bug-4725.php index e69631b578..1cdfb35ad8 100644 --- a/tests/PHPStan/Analyser/data/bug-4725.php +++ b/tests/PHPStan/Analyser/data/bug-4725.php @@ -4,9 +4,13 @@ use function PHPStan\Testing\assertType; -class Application_Model_Ada extends Clx_Model_Abstract {} +class Application_Model_Ada extends Clx_Model_Abstract +{ +} -abstract class Clx_Model_Abstract {} +abstract class Clx_Model_Abstract +{ +} /** * @template T of Clx_Model_Abstract @@ -21,18 +25,17 @@ abstract class Clx_Model_Mapper_Abstract */ class Clx_Paginator_Adapter_Mapper { - /** - * @phpstan-param Clx_Model_Mapper_Abstract $mapper - */ - public function __construct(Clx_Model_Mapper_Abstract $mapper) - { - } - - /** @return T */ - public function getT() - { - - } + /** + * @phpstan-param Clx_Model_Mapper_Abstract $mapper + */ + public function __construct(Clx_Model_Mapper_Abstract $mapper) + { + } + + /** @return T */ + public function getT() + { + } } /** @@ -41,10 +44,10 @@ public function getT() */ class ClxProductNet_Model_Mapper_Ada extends Clx_Model_Mapper_Abstract { - - public function x() { - $map = new Clx_Paginator_Adapter_Mapper($this); - assertType('Bug4725\Clx_Paginator_Adapter_Mapper', $map); - assertType('T of Bug4725\Application_Model_Ada (class Bug4725\ClxProductNet_Model_Mapper_Ada, argument)', $map->getT()); - } + public function x() + { + $map = new Clx_Paginator_Adapter_Mapper($this); + assertType('Bug4725\Clx_Paginator_Adapter_Mapper', $map); + assertType('T of Bug4725\Application_Model_Ada (class Bug4725\ClxProductNet_Model_Mapper_Ada, argument)', $map->getT()); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4733.php b/tests/PHPStan/Analyser/data/bug-4733.php index 39961cc464..663592e709 100644 --- a/tests/PHPStan/Analyser/data/bug-4733.php +++ b/tests/PHPStan/Analyser/data/bug-4733.php @@ -6,63 +6,63 @@ class HelloWorld { - public function getDescription(?\DateTimeImmutable $start, ?string $someObject): void - { - if ($start === null && $someObject === null) { - return; - } + public function getDescription(?\DateTimeImmutable $start, ?string $someObject): void + { + if ($start === null && $someObject === null) { + return; + } - // $start !== null || $someObject !== null + // $start !== null || $someObject !== null - if ($start !== null) { - return; - } + if ($start !== null) { + return; + } - // $start === null therefore $someObject !== null + // $start === null therefore $someObject !== null - assertType('string', $someObject); - } + assertType('string', $someObject); + } - public function getDescription2(?\DateTimeImmutable $start, ?string $someObject): void - { - if ($start !== null || $someObject !== null) { - if ($start !== null) { - return; - } + public function getDescription2(?\DateTimeImmutable $start, ?string $someObject): void + { + if ($start !== null || $someObject !== null) { + if ($start !== null) { + return; + } - // $start === null therefore $someObject !== null + // $start === null therefore $someObject !== null - assertType('string', $someObject); - } - } + assertType('string', $someObject); + } + } - public function getDescription3(?\DateTimeImmutable $start, ?string $someObject): void - { - if ($start === null && $someObject === null) { - return; - } + public function getDescription3(?\DateTimeImmutable $start, ?string $someObject): void + { + if ($start === null && $someObject === null) { + return; + } - // $start !== null || $someObject !== null + // $start !== null || $someObject !== null - if ($someObject !== null) { - return; - } + if ($someObject !== null) { + return; + } - // $someObject === null therefore $start cannot be null + // $someObject === null therefore $start cannot be null - assertType('DateTimeImmutable', $start); - } + assertType('DateTimeImmutable', $start); + } - public function getDescription4(?\DateTimeImmutable $start, ?string $someObject): void - { - if ($start !== null || $someObject !== null) { - if ($someObject !== null) { - return; - } + public function getDescription4(?\DateTimeImmutable $start, ?string $someObject): void + { + if ($start !== null || $someObject !== null) { + if ($someObject !== null) { + return; + } - // $someObject === null therefore $start cannot be null + // $someObject === null therefore $start cannot be null - assertType('DateTimeImmutable', $start); - } - } + assertType('DateTimeImmutable', $start); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4734.php b/tests/PHPStan/Analyser/data/bug-4734.php index 6b231e0f9b..c339ff526a 100644 --- a/tests/PHPStan/Analyser/data/bug-4734.php +++ b/tests/PHPStan/Analyser/data/bug-4734.php @@ -6,39 +6,39 @@ class Foo { - /** - * @var bool - */ - private static $httpMethodParameterOverride = true; - - /** - * @var bool - */ - private $httpMethodParameterOverride2 = true; + /** + * @var bool + */ + private static $httpMethodParameterOverride = true; + + /** + * @var bool + */ + private $httpMethodParameterOverride2 = true; } class Bar { - public function test(): void - { - $disableHttpMethodParameterOverride = \Closure::bind(static function (): void { - static::$httpMethodParameterOverride = false; - }, new Foo(), Foo::class); - $disableHttpMethodParameterOverride(); - - $disableHttpMethodParameterOverride2 = \Closure::bind(function (): void { - $this->httpMethodParameterOverride2 = false; - }, new Foo(), Foo::class); - $disableHttpMethodParameterOverride2(); - - $disableHttpMethodParameterOverride3 = \Closure::bind(function (): void { - static::$httpMethodParameterOverride3 = false; - }, new Foo(), Foo::class); - $disableHttpMethodParameterOverride3(); - - $disableHttpMethodParameterOverride4 = \Closure::bind(function (): void { - $this->httpMethodParameterOverride4 = false; - }, new Foo(), Foo::class); - $disableHttpMethodParameterOverride4(); - } + public function test(): void + { + $disableHttpMethodParameterOverride = \Closure::bind(static function (): void { + static::$httpMethodParameterOverride = false; + }, new Foo(), Foo::class); + $disableHttpMethodParameterOverride(); + + $disableHttpMethodParameterOverride2 = \Closure::bind(function (): void { + $this->httpMethodParameterOverride2 = false; + }, new Foo(), Foo::class); + $disableHttpMethodParameterOverride2(); + + $disableHttpMethodParameterOverride3 = \Closure::bind(function (): void { + static::$httpMethodParameterOverride3 = false; + }, new Foo(), Foo::class); + $disableHttpMethodParameterOverride3(); + + $disableHttpMethodParameterOverride4 = \Closure::bind(function (): void { + $this->httpMethodParameterOverride4 = false; + }, new Foo(), Foo::class); + $disableHttpMethodParameterOverride4(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4757.php b/tests/PHPStan/Analyser/data/bug-4757.php index 9b620ea29e..6a8c86c917 100644 --- a/tests/PHPStan/Analyser/data/bug-4757.php +++ b/tests/PHPStan/Analyser/data/bug-4757.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace Bug4757; @@ -6,364 +8,358 @@ class HelloWorld { - public function sayHello(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo()) { - assertType(Reservation::class, $oldReservation); - assertType('true', $oldReservation->isFoo()); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - } - - public function sayHello2(?Reservation $oldReservation): void - { - if (!$oldReservation?->isFoo()) { - assertType(Reservation::class . '|null', $oldReservation); - assertType('bool', $oldReservation->isFoo()); - return; - } - - assertType(Reservation::class, $oldReservation); - assertType('true', $oldReservation->isFoo()); - } - - public function sayHello3(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo() === true) { - assertType(Reservation::class, $oldReservation); - assertType('true', $oldReservation->isFoo()); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - assertType('bool', $oldReservation->isFoo()); - } - - public function sayHello4(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo() === false) { - assertType(Reservation::class , $oldReservation); - assertType('false', $oldReservation->isFoo()); - return; - } - - //assertType(Reservation::class . '|null', $oldReservation); - assertType('bool', $oldReservation->isFoo()); - } - - public function sayHello5(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo() === null) { - assertType(Reservation::class . '|null', $oldReservation); - return; - } - - assertType(Reservation::class, $oldReservation); - } - - public function sayHello6(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo() !== null) { - assertType(Reservation::class, $oldReservation); - assertType('bool', $oldReservation->isFoo()); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - assertType('bool', $oldReservation->isFoo()); - } - - public function sayHelloPure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFoo()) { - assertType(Reservation::class, $oldReservation); - assertType('true', $oldReservation->isFoo()); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - } - - public function sayHelloImpure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFooImpure()) { - assertType(Reservation::class, $oldReservation); - assertType('bool', $oldReservation->isFooImpure()); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - } - - public function sayHello2Impure(?Reservation $oldReservation): void - { - if (!$oldReservation?->isFooImpure()) { - assertType(Reservation::class . '|null', $oldReservation); - return; - } - - assertType(Reservation::class, $oldReservation); - } - - public function sayHello3Impure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFooImpure() === true) { - assertType(Reservation::class, $oldReservation); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - } - - public function sayHello4Impure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFooImpure() === false) { - assertType(Reservation::class , $oldReservation); - return; - } - - //assertType(Reservation::class . '|null', $oldReservation); - } - - public function sayHello5Impure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFooImpure() === null) { - assertType(Reservation::class . '|null', $oldReservation); - return; - } - - assertType(Reservation::class, $oldReservation); - } - - public function sayHello6Impure(?Reservation $oldReservation): void - { - if ($oldReservation?->isFooImpure() !== null) { - assertType(Reservation::class, $oldReservation); - return; - } - - assertType(Reservation::class . '|null', $oldReservation); - } + public function sayHello(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo()) { + assertType(Reservation::class, $oldReservation); + assertType('true', $oldReservation->isFoo()); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + } + + public function sayHello2(?Reservation $oldReservation): void + { + if (!$oldReservation?->isFoo()) { + assertType(Reservation::class . '|null', $oldReservation); + assertType('bool', $oldReservation->isFoo()); + return; + } + + assertType(Reservation::class, $oldReservation); + assertType('true', $oldReservation->isFoo()); + } + + public function sayHello3(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo() === true) { + assertType(Reservation::class, $oldReservation); + assertType('true', $oldReservation->isFoo()); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + assertType('bool', $oldReservation->isFoo()); + } + + public function sayHello4(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo() === false) { + assertType(Reservation::class, $oldReservation); + assertType('false', $oldReservation->isFoo()); + return; + } + + //assertType(Reservation::class . '|null', $oldReservation); + assertType('bool', $oldReservation->isFoo()); + } + + public function sayHello5(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo() === null) { + assertType(Reservation::class . '|null', $oldReservation); + return; + } + + assertType(Reservation::class, $oldReservation); + } + + public function sayHello6(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo() !== null) { + assertType(Reservation::class, $oldReservation); + assertType('bool', $oldReservation->isFoo()); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + assertType('bool', $oldReservation->isFoo()); + } + + public function sayHelloPure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFoo()) { + assertType(Reservation::class, $oldReservation); + assertType('true', $oldReservation->isFoo()); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + } + + public function sayHelloImpure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFooImpure()) { + assertType(Reservation::class, $oldReservation); + assertType('bool', $oldReservation->isFooImpure()); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + } + + public function sayHello2Impure(?Reservation $oldReservation): void + { + if (!$oldReservation?->isFooImpure()) { + assertType(Reservation::class . '|null', $oldReservation); + return; + } + + assertType(Reservation::class, $oldReservation); + } + + public function sayHello3Impure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFooImpure() === true) { + assertType(Reservation::class, $oldReservation); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + } + + public function sayHello4Impure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFooImpure() === false) { + assertType(Reservation::class, $oldReservation); + return; + } + + //assertType(Reservation::class . '|null', $oldReservation); + } + + public function sayHello5Impure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFooImpure() === null) { + assertType(Reservation::class . '|null', $oldReservation); + return; + } + + assertType(Reservation::class, $oldReservation); + } + + public function sayHello6Impure(?Reservation $oldReservation): void + { + if ($oldReservation?->isFooImpure() !== null) { + assertType(Reservation::class, $oldReservation); + return; + } + + assertType(Reservation::class . '|null', $oldReservation); + } } -interface Reservation { - public function isFoo(): bool; +interface Reservation +{ + public function isFoo(): bool; - /** @phpstan-impure */ - public function isFooImpure(): bool; + /** @phpstan-impure */ + public function isFooImpure(): bool; } interface Bar { - public function get(): ?int; + public function get(): ?int; - /** @phpstan-impure */ - public function getImpure(): ?int; + /** @phpstan-impure */ + public function getImpure(): ?int; } class Foo { - - public function getBarOrNull(): ?Bar - { - return null; - } - - public function doFoo(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->get() === null) { - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->get()); - //assertType('null', $barOrNull?->get()); - return; - } - - assertType(Bar::class, $barOrNull); - assertType('int', $barOrNull->get()); - assertType('int', $barOrNull?->get()); - } - - public function doFooImpire(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->getImpure() === null) { - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - assertType('int|null', $barOrNull?->getImpure()); - return; - } - - assertType(Bar::class, $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - assertType('int|null', $barOrNull?->getImpure()); - } - - public function doFoo2(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->get() !== null) { - assertType(Bar::class, $barOrNull); - assertType('int', $barOrNull->get()); - assertType('int', $barOrNull?->get()); - return; - } - - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->get()); - } - - public function doFoo2Impure(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->getImpure() !== null) { - assertType(Bar::class, $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - assertType('int|null', $barOrNull?->getImpure()); - return; - } - - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - assertType('int|null', $barOrNull?->getImpure()); - } - - public function doFoo3(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->get()) { - assertType(Bar::class, $barOrNull); - assertType('int|int<1, max>', $barOrNull->get()); - return; - } - - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->get()); - } - - public function doFoo3Impure(Bar $b): void - { - $barOrNull = $this->getBarOrNull(); - if ($barOrNull?->getImpure()) { - assertType(Bar::class, $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - return; - } - - assertType(Bar::class . '|null', $barOrNull); - assertType('int|null', $barOrNull->getImpure()); - } - + public function getBarOrNull(): ?Bar + { + return null; + } + + public function doFoo(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->get() === null) { + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->get()); + //assertType('null', $barOrNull?->get()); + return; + } + + assertType(Bar::class, $barOrNull); + assertType('int', $barOrNull->get()); + assertType('int', $barOrNull?->get()); + } + + public function doFooImpire(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->getImpure() === null) { + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + assertType('int|null', $barOrNull?->getImpure()); + return; + } + + assertType(Bar::class, $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + assertType('int|null', $barOrNull?->getImpure()); + } + + public function doFoo2(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->get() !== null) { + assertType(Bar::class, $barOrNull); + assertType('int', $barOrNull->get()); + assertType('int', $barOrNull?->get()); + return; + } + + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->get()); + } + + public function doFoo2Impure(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->getImpure() !== null) { + assertType(Bar::class, $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + assertType('int|null', $barOrNull?->getImpure()); + return; + } + + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + assertType('int|null', $barOrNull?->getImpure()); + } + + public function doFoo3(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->get()) { + assertType(Bar::class, $barOrNull); + assertType('int|int<1, max>', $barOrNull->get()); + return; + } + + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->get()); + } + + public function doFoo3Impure(Bar $b): void + { + $barOrNull = $this->getBarOrNull(); + if ($barOrNull?->getImpure()) { + assertType(Bar::class, $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + return; + } + + assertType(Bar::class . '|null', $barOrNull); + assertType('int|null', $barOrNull->getImpure()); + } } class Chain { - - /** @var int */ - private $baz; - - /** @var self|null */ - private $selfOrNull; - - /** @var self */ - private $self; - - public function find(): ?self - { - - } - - public function get(): self - { - - } - - /** @phpstan-impure */ - public function findImpure(): ?self - { - - } - - public function doFoo(): void - { - assertType('int', $this->baz); - assertType('int|null', $this->find()?->baz); - assertType('int|null', $this->findImpure()?->baz); - } - - public function doBar(): void - { - if ($this->selfOrNull?->find()?->baz !== null) { - assertType(self::class, $this->selfOrNull); - assertType(self::class, $this->selfOrNull->find()); - } - } - - public function doBar2(): void - { - if ($this->selfOrNull?->find()?->get()->baz !== null) { - assertType(self::class, $this->selfOrNull); - assertType(self::class, $this->selfOrNull->find()); - } - } - - public function doBar3(): void - { - if ($this->selfOrNull?->find()?->get()->find() !== null) { - assertType(self::class, $this->selfOrNull); - assertType(self::class, $this->selfOrNull->find()); - } - } - - public function doBaz(): void - { - if ($this->selfOrNull?->findImpure()->baz !== null) { - assertType(self::class, $this->selfOrNull); - assertType(self::class . '|null', $this->selfOrNull->findImpure()); - } - } - - public function doBaz2(): void - { - if ($this->selfOrNull?->self->baz !== null) { - assertType(self::class, $this->selfOrNull); - } - } - - public function doBaz3(): void - { - if ($this->selfOrNull?->findImpure()->baz === 1) { - assertType(self::class, $this->selfOrNull); - assertType(self::class . '|null', $this->selfOrNull->findImpure()); - } - } - - public function doBaz4(): void - { - if ($this->selfOrNull?->find()?->get()->baz === 1) { - assertType(self::class, $this->selfOrNull); - assertType(self::class, $this->selfOrNull->find()); - } - } - - public function doVariable(): void - { - $foo = $this->selfOrNull; - if ($foo?->get()->selfOrNull !== null) { - assertType(self::class, $foo); - assertType(self::class, $foo->get()->selfOrNull); - } - } - - public function doLorem(): void - { - if ($this->find()?->find()?->find() !== null) { - assertType(self::class, $this->find()); - assertType(self::class, $this->find()->find()); - assertType(self::class, $this->find()->find()->find()); - } - } - + /** @var int */ + private $baz; + + /** @var self|null */ + private $selfOrNull; + + /** @var self */ + private $self; + + public function find(): ?self + { + } + + public function get(): self + { + } + + /** @phpstan-impure */ + public function findImpure(): ?self + { + } + + public function doFoo(): void + { + assertType('int', $this->baz); + assertType('int|null', $this->find()?->baz); + assertType('int|null', $this->findImpure()?->baz); + } + + public function doBar(): void + { + if ($this->selfOrNull?->find()?->baz !== null) { + assertType(self::class, $this->selfOrNull); + assertType(self::class, $this->selfOrNull->find()); + } + } + + public function doBar2(): void + { + if ($this->selfOrNull?->find()?->get()->baz !== null) { + assertType(self::class, $this->selfOrNull); + assertType(self::class, $this->selfOrNull->find()); + } + } + + public function doBar3(): void + { + if ($this->selfOrNull?->find()?->get()->find() !== null) { + assertType(self::class, $this->selfOrNull); + assertType(self::class, $this->selfOrNull->find()); + } + } + + public function doBaz(): void + { + if ($this->selfOrNull?->findImpure()->baz !== null) { + assertType(self::class, $this->selfOrNull); + assertType(self::class . '|null', $this->selfOrNull->findImpure()); + } + } + + public function doBaz2(): void + { + if ($this->selfOrNull?->self->baz !== null) { + assertType(self::class, $this->selfOrNull); + } + } + + public function doBaz3(): void + { + if ($this->selfOrNull?->findImpure()->baz === 1) { + assertType(self::class, $this->selfOrNull); + assertType(self::class . '|null', $this->selfOrNull->findImpure()); + } + } + + public function doBaz4(): void + { + if ($this->selfOrNull?->find()?->get()->baz === 1) { + assertType(self::class, $this->selfOrNull); + assertType(self::class, $this->selfOrNull->find()); + } + } + + public function doVariable(): void + { + $foo = $this->selfOrNull; + if ($foo?->get()->selfOrNull !== null) { + assertType(self::class, $foo); + assertType(self::class, $foo->get()->selfOrNull); + } + } + + public function doLorem(): void + { + if ($this->find()?->find()?->find() !== null) { + assertType(self::class, $this->find()); + assertType(self::class, $this->find()->find()); + assertType(self::class, $this->find()->find()->find()); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4803.php b/tests/PHPStan/Analyser/data/bug-4803.php index df9ee683bc..97303c3089 100644 --- a/tests/PHPStan/Analyser/data/bug-4803.php +++ b/tests/PHPStan/Analyser/data/bug-4803.php @@ -7,73 +7,73 @@ /** * @template T of object */ -interface Proxy {} +interface Proxy +{ +} class Foo { + /** + * @template T of object + * @param Proxy|T $proxyOrObject + * @return T + */ + public function doFoo($proxyOrObject) + { + assertType('Bug4803\Proxy|T of object (method Bug4803\Foo::doFoo(), argument)', $proxyOrObject); + } - /** - * @template T of object - * @param Proxy|T $proxyOrObject - * @return T - */ - public function doFoo($proxyOrObject) - { - assertType('Bug4803\Proxy|T of object (method Bug4803\Foo::doFoo(), argument)', $proxyOrObject); - } - - /** @param Proxy<\stdClass> $proxy */ - public function doBar($proxy): void - { - assertType('stdClass', $this->doFoo($proxy)); - } - - /** @param \stdClass $std */ - public function doBaz($std): void - { - assertType('stdClass', $this->doFoo($std)); - } + /** @param Proxy<\stdClass> $proxy */ + public function doBar($proxy): void + { + assertType('stdClass', $this->doFoo($proxy)); + } - /** @param Proxy<\stdClass>|\stdClass $proxyOrStd */ - public function doLorem($proxyOrStd): void - { - assertType('stdClass', $this->doFoo($proxyOrStd)); - } + /** @param \stdClass $std */ + public function doBaz($std): void + { + assertType('stdClass', $this->doFoo($std)); + } + /** @param Proxy<\stdClass>|\stdClass $proxyOrStd */ + public function doLorem($proxyOrStd): void + { + assertType('stdClass', $this->doFoo($proxyOrStd)); + } } interface ProxyClassResolver { - /** - * @template T of object - * @param class-string>|class-string $className - * @return class-string - */ - public function resolveClassName(string $className): string; + /** + * @template T of object + * @param class-string>|class-string $className + * @return class-string + */ + public function resolveClassName(string $className): string; } final class Client { - /** @var ProxyClassResolver */ - private $proxyClassResolver; + /** @var ProxyClassResolver */ + private $proxyClassResolver; - public function __construct(ProxyClassResolver $proxyClassResolver) - { - $this->proxyClassResolver = $proxyClassResolver; - } + public function __construct(ProxyClassResolver $proxyClassResolver) + { + $this->proxyClassResolver = $proxyClassResolver; + } - /** - * @template T of object - * @param class-string>|class-string $className - * @return class-string - */ - public function getRealClass(string $className): string - { - assertType('class-string>|class-string', $className); + /** + * @template T of object + * @param class-string>|class-string $className + * @return class-string + */ + public function getRealClass(string $className): string + { + assertType('class-string>|class-string', $className); - $result = $this->proxyClassResolver->resolveClassName($className); - assertType('class-string', $result); + $result = $this->proxyClassResolver->resolveClassName($className); + assertType('class-string', $result); - return $result; - } + return $result; + } } diff --git a/tests/PHPStan/Analyser/data/bug-4814.php b/tests/PHPStan/Analyser/data/bug-4814.php index 4e8607a793..020977f44b 100644 --- a/tests/PHPStan/Analyser/data/bug-4814.php +++ b/tests/PHPStan/Analyser/data/bug-4814.php @@ -4,37 +4,44 @@ use function PHPStan\Testing\assertType; -class Request { - public function __construct(string $endpoint) { echo $endpoint; } +class Request +{ + public function __construct(string $endpoint) + { + echo $endpoint; + } } -class Response { - /** @return mixed */ - public function getBody() { return '{"json"}'; } +class Response +{ + /** @return mixed */ + public function getBody() + { + return '{"json"}'; + } } class Foo { - - /** @throws \Throwable */ - public function sendRequest(Request $req) : Response { - return new Response(); - } - - public function doFoo() - { - $body = null; - $decodedResponseBody = []; - try { - $request = new Request('endpoint'); - - $response = $this->sendRequest($request); - $body = (string) $response->getBody(); - $decodedResponseBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); - } catch (\Throwable $exception) { - assertType('string|null', $body); - assertType('array()', $decodedResponseBody); - } - } - + /** @throws \Throwable */ + public function sendRequest(Request $req): Response + { + return new Response(); + } + + public function doFoo() + { + $body = null; + $decodedResponseBody = []; + try { + $request = new Request('endpoint'); + + $response = $this->sendRequest($request); + $body = (string) $response->getBody(); + $decodedResponseBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); + } catch (\Throwable $exception) { + assertType('string|null', $body); + assertType('array()', $decodedResponseBody); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4816.php b/tests/PHPStan/Analyser/data/bug-4816.php index 12935f7e44..64d0945b60 100644 --- a/tests/PHPStan/Analyser/data/bug-4816.php +++ b/tests/PHPStan/Analyser/data/bug-4816.php @@ -5,43 +5,43 @@ use function PHPStan\Testing\assertType; function (): void { - if (is_dir('foo')) { - assertType('true', is_dir('foo')); - assertType('bool', is_dir('bar')); + if (is_dir('foo')) { + assertType('true', is_dir('foo')); + assertType('bool', is_dir('bar')); - clearstatcache(); + clearstatcache(); - assertType('bool', is_dir('foo')); - assertType('bool', is_dir('bar')); - } + assertType('bool', is_dir('foo')); + assertType('bool', is_dir('bar')); + } }; function (): void { - if (!is_dir('foo')) { - assertType('bool', is_dir('foo')); - assertType('bool', is_dir('bar')); - } + if (!is_dir('foo')) { + assertType('bool', is_dir('foo')); + assertType('bool', is_dir('bar')); + } }; function (): void { - if (!is_dir('foo')) { - return; - } + if (!is_dir('foo')) { + return; + } - assertType('true', is_dir('foo')); - assertType('bool', is_dir('bar')); + assertType('true', is_dir('foo')); + assertType('bool', is_dir('bar')); - clearstatcache(); + clearstatcache(); - assertType('bool', is_dir('foo')); - assertType('bool', is_dir('bar')); + assertType('bool', is_dir('foo')); + assertType('bool', is_dir('bar')); }; function (): void { - if (is_dir('foo')) { - return; - } + if (is_dir('foo')) { + return; + } - assertType('bool', is_dir('foo')); - assertType('bool', is_dir('bar')); + assertType('bool', is_dir('foo')); + assertType('bool', is_dir('bar')); }; diff --git a/tests/PHPStan/Analyser/data/bug-4820.php b/tests/PHPStan/Analyser/data/bug-4820.php index b921075071..7f317380d6 100644 --- a/tests/PHPStan/Analyser/data/bug-4820.php +++ b/tests/PHPStan/Analyser/data/bug-4820.php @@ -6,36 +6,33 @@ use function PHPStan\Testing\assertType; use function PHPStan\Testing\assertVariableCertainty; -class Param { - - /** @var bool */ - public $foo; +class Param +{ + /** @var bool */ + public $foo; } class HelloWorld { - public function sayHello(Param $param): void - { - - try { - $result = call_user_func([$this, 'mayThrow']); - if ($param->foo) { - $this->mayThrow(); - } - - } catch (\Throwable $e) { - assertType('bool', $param->foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $result); - throw $e; - } - } - - /** - * @throws \RuntimeException - */ - private function mayThrow(): void - { - throw new \RuntimeException(); - } - + public function sayHello(Param $param): void + { + try { + $result = call_user_func([$this, 'mayThrow']); + if ($param->foo) { + $this->mayThrow(); + } + } catch (\Throwable $e) { + assertType('bool', $param->foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $result); + throw $e; + } + } + + /** + * @throws \RuntimeException + */ + private function mayThrow(): void + { + throw new \RuntimeException(); + } } diff --git a/tests/PHPStan/Analyser/data/bug-4821.php b/tests/PHPStan/Analyser/data/bug-4821.php index 9e9fbfa774..7edd1c7620 100644 --- a/tests/PHPStan/Analyser/data/bug-4821.php +++ b/tests/PHPStan/Analyser/data/bug-4821.php @@ -7,16 +7,16 @@ class HelloWorld { - public function sayHello(): void - { - try { - $object = new HelloWorld(); - $method = new \ReflectionMethod($object, 'nonExisting'); - $method->invoke($object); - return; - } catch (\ReflectionException $e) { - assertVariableCertainty(TrinaryLogic::createYes(), $object); - assertVariableCertainty(TrinaryLogic::createMaybe(), $method); - } - } + public function sayHello(): void + { + try { + $object = new HelloWorld(); + $method = new \ReflectionMethod($object, 'nonExisting'); + $method->invoke($object); + return; + } catch (\ReflectionException $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $object); + assertVariableCertainty(TrinaryLogic::createMaybe(), $method); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4822.php b/tests/PHPStan/Analyser/data/bug-4822.php index 2f730eaa45..f4015ffcb7 100644 --- a/tests/PHPStan/Analyser/data/bug-4822.php +++ b/tests/PHPStan/Analyser/data/bug-4822.php @@ -7,27 +7,25 @@ class Foo { - - /** - * @throws \Exception - */ - function save(): void { - - } - - function doFoo() - { - $soapClient = new \SoapClient('https://example.com/?wsdl'); - - try { - $response = $soapClient->test(); - - if (is_array($response)) { - $this->save(); - } - } catch (\Exception $e) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $response); - } - } - + /** + * @throws \Exception + */ + public function save(): void + { + } + + public function doFoo() + { + $soapClient = new \SoapClient('https://example.com/?wsdl'); + + try { + $response = $soapClient->test(); + + if (is_array($response)) { + $this->save(); + } + } catch (\Exception $e) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $response); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4838.php b/tests/PHPStan/Analyser/data/bug-4838.php index 19f8022517..c2d4562f15 100644 --- a/tests/PHPStan/Analyser/data/bug-4838.php +++ b/tests/PHPStan/Analyser/data/bug-4838.php @@ -7,18 +7,16 @@ class Foo { + public function doIt(): void + { + try { + $handle = tmpfile(); - public function doIt(): void - { - try { - $handle = tmpfile(); - - if (rand(1,10) > 5) { - throw new \LogicException(); - } - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $handle); - } - } - + if (rand(1, 10) > 5) { + throw new \LogicException(); + } + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $handle); + } + } } diff --git a/tests/PHPStan/Analyser/data/bug-4879.php b/tests/PHPStan/Analyser/data/bug-4879.php index 1c6c9536c4..87c53b9aaf 100644 --- a/tests/PHPStan/Analyser/data/bug-4879.php +++ b/tests/PHPStan/Analyser/data/bug-4879.php @@ -7,38 +7,38 @@ class HelloWorld { - public function sayHello(bool $bool1): void - { - try { - if ($bool1) { - throw new \Exception(); - } - - $var = 'foo'; - - $this->test(); - } catch (\Throwable $ex) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $var); - } - } - - public function sayHello2(bool $bool1): void - { - try { - if ($bool1) { - throw new \Exception(); - } - - $var = 'foo'; - - $this->test(); - } catch (\Exception $ex) { - assertVariableCertainty(TrinaryLogic::createNo(), $var); - } - } - - public function test(): void - { - return; - } + public function sayHello(bool $bool1): void + { + try { + if ($bool1) { + throw new \Exception(); + } + + $var = 'foo'; + + $this->test(); + } catch (\Throwable $ex) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $var); + } + } + + public function sayHello2(bool $bool1): void + { + try { + if ($bool1) { + throw new \Exception(); + } + + $var = 'foo'; + + $this->test(); + } catch (\Exception $ex) { + assertVariableCertainty(TrinaryLogic::createNo(), $var); + } + } + + public function test(): void + { + return; + } } diff --git a/tests/PHPStan/Analyser/data/bug-505.php b/tests/PHPStan/Analyser/data/bug-505.php index ad21de9364..a5beba383a 100644 --- a/tests/PHPStan/Analyser/data/bug-505.php +++ b/tests/PHPStan/Analyser/data/bug-505.php @@ -6,24 +6,24 @@ class Test { - public function foo(int $x): string - { - try { - $x = $this->resolve($x); - } catch (\Throwable $e) { - assertType('int', $x); - } + public function foo(int $x): string + { + try { + $x = $this->resolve($x); + } catch (\Throwable $e) { + assertType('int', $x); + } - return 'bar'; - } + return 'bar'; + } - private function resolve(int $x) : string - { - return 'ok'; - } + private function resolve(int $x): string + { + return 'ok'; + } - private function handleError(int $x) : string - { - return 'error'; - } + private function handleError(int $x): string + { + return 'error'; + } } diff --git a/tests/PHPStan/Analyser/data/bug-560.php b/tests/PHPStan/Analyser/data/bug-560.php index 93733a7e9e..862a55c526 100644 --- a/tests/PHPStan/Analyser/data/bug-560.php +++ b/tests/PHPStan/Analyser/data/bug-560.php @@ -10,13 +10,13 @@ assertType('mixed', $city); if ($city ?? false) { - assertVariableCertainty(TrinaryLogic::createYes(), $city); - assertType('mixed~null', $city); + assertVariableCertainty(TrinaryLogic::createYes(), $city); + assertType('mixed~null', $city); } function (?string $s): void { - if ($s ?? false) { - assertVariableCertainty(TrinaryLogic::createYes(), $s); - assertType('string' ,$s); - } + if ($s ?? false) { + assertVariableCertainty(TrinaryLogic::createYes(), $s); + assertType('string', $s); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-651.php b/tests/PHPStan/Analyser/data/bug-651.php index 7a00030f04..a989ea3f88 100644 --- a/tests/PHPStan/Analyser/data/bug-651.php +++ b/tests/PHPStan/Analyser/data/bug-651.php @@ -7,21 +7,21 @@ use function PHPStan\Testing\assertVariableCertainty; function (): void { - foreach (['foo', 'bar'] as $loopValue) { - switch ($loopValue) { - case 'foo': - continue 2; + foreach (['foo', 'bar'] as $loopValue) { + switch ($loopValue) { + case 'foo': + continue 2; - case 'bar': - $variableDefinedWithinForeach = 23; - break; + case 'bar': + $variableDefinedWithinForeach = 23; + break; - default: - throw new \LogicException(); - } + default: + throw new \LogicException(); + } - assertType('23', $variableDefinedWithinForeach); - assertVariableCertainty(TrinaryLogic::createYes(), $variableDefinedWithinForeach); - echo $variableDefinedWithinForeach; - } + assertType('23', $variableDefinedWithinForeach); + assertVariableCertainty(TrinaryLogic::createYes(), $variableDefinedWithinForeach); + echo $variableDefinedWithinForeach; + } }; diff --git a/tests/PHPStan/Analyser/data/bug-778.php b/tests/PHPStan/Analyser/data/bug-778.php index 0d561b2836..7ea4a5dc25 100644 --- a/tests/PHPStan/Analyser/data/bug-778.php +++ b/tests/PHPStan/Analyser/data/bug-778.php @@ -8,105 +8,98 @@ class Foo { - /** - * @return resource - */ - public function baz() - { - throw new \Exception(); - } - - public function bar(): void - { - try { - $handle = $this->baz(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $handle); - } - } - - public function lorem(): void - { - try { - $foo = foo(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } - - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } - - public function lorem2(): void - { - try { - $foo = foo(); - } finally { - $foo = 1; - } - - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } - - public function lorem3(): void - { - try { - $foo = foo(); - } catch (\Exception $e) { - $foo = 1; - } - - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } - - public function lorem4(): void - { - try { - $foo = foo(); - } catch (\Exception $e) { - - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } - - public function ipsum(): void - { - try { - doFoo(); - } catch (\InvalidArgumentException $e) { - - } catch (\RuntimeException $e) { - - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $e); - assertType('InvalidArgumentException|RuntimeException', $e); - } - - public function dolor(): void - { - try { - doFoo(); - } catch (\InvalidArgumentException $e) { - - } catch (\RuntimeException $e) { - - } finally { - - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $e); - assertType('InvalidArgumentException|RuntimeException', $e); - } - - public function sit(): void - { - try { - $var = 1; - } catch (\Exception $e) { - - } - - assertVariableCertainty(TrinaryLogic::createNo(), $e); - } + /** + * @return resource + */ + public function baz() + { + throw new \Exception(); + } + + public function bar(): void + { + try { + $handle = $this->baz(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $handle); + } + } + + public function lorem(): void + { + try { + $foo = foo(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + + public function lorem2(): void + { + try { + $foo = foo(); + } finally { + $foo = 1; + } + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + + public function lorem3(): void + { + try { + $foo = foo(); + } catch (\Exception $e) { + $foo = 1; + } + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + + public function lorem4(): void + { + try { + $foo = foo(); + } catch (\Exception $e) { + } + + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } + + public function ipsum(): void + { + try { + doFoo(); + } catch (\InvalidArgumentException $e) { + } catch (\RuntimeException $e) { + } + + assertVariableCertainty(TrinaryLogic::createMaybe(), $e); + assertType('InvalidArgumentException|RuntimeException', $e); + } + + public function dolor(): void + { + try { + doFoo(); + } catch (\InvalidArgumentException $e) { + } catch (\RuntimeException $e) { + } finally { + } + + assertVariableCertainty(TrinaryLogic::createMaybe(), $e); + assertType('InvalidArgumentException|RuntimeException', $e); + } + + public function sit(): void + { + try { + $var = 1; + } catch (\Exception $e) { + } + + assertVariableCertainty(TrinaryLogic::createNo(), $e); + } } diff --git a/tests/PHPStan/Analyser/data/bug-801.php b/tests/PHPStan/Analyser/data/bug-801.php index e349376162..a167f13c16 100644 --- a/tests/PHPStan/Analyser/data/bug-801.php +++ b/tests/PHPStan/Analyser/data/bug-801.php @@ -6,21 +6,21 @@ class HelloWorld { - public function getDateTime(): ?\DateTime - { - return (bool) rand(0, 1) ? new \DateTime('now') : null; - } + public function getDateTime(): ?\DateTime + { + return (bool) rand(0, 1) ? new \DateTime('now') : null; + } } function (): void { - $hello = new HelloWorld(); - $dt = $hello->getDateTime(); + $hello = new HelloWorld(); + $dt = $hello->getDateTime(); - $condition = null !== $dt; + $condition = null !== $dt; - if ($condition) { - assertType('DateTime', $dt); - } else { - assertType('null', $dt); - } + if ($condition) { + assertType('DateTime', $dt); + } else { + assertType('null', $dt); + } }; diff --git a/tests/PHPStan/Analyser/data/bug-987.php b/tests/PHPStan/Analyser/data/bug-987.php index c168ef6224..7e80c15936 100644 --- a/tests/PHPStan/Analyser/data/bug-987.php +++ b/tests/PHPStan/Analyser/data/bug-987.php @@ -6,48 +6,46 @@ class HelloWorld { - public function sayHello(?int $a, ?int $b): void - { - - if ($a === null && $b === null) { - throw new \LogicException(); - } - - assertType('int', $a !== null ? $a : $b); - } - - /** - * @return string[]|null - */ - public function arrOrNull(): ?array - { - return []; - } - - /** - * @return string[] - */ - public function otherGetArray(): array - { - return []; - } - - /** - * @return string[] - */ - function hello(self $class, bool $boolOption): array - { - $myArray = $class->arrOrNull(); - if ($boolOption && $myArray === null) { - throw new \Exception(''); - } - if (!$boolOption) { - $myArray = $class->otherGetArray(); - } - - assertType('array', $myArray); - - return $myArray; - } - + public function sayHello(?int $a, ?int $b): void + { + if ($a === null && $b === null) { + throw new \LogicException(); + } + + assertType('int', $a !== null ? $a : $b); + } + + /** + * @return string[]|null + */ + public function arrOrNull(): ?array + { + return []; + } + + /** + * @return string[] + */ + public function otherGetArray(): array + { + return []; + } + + /** + * @return string[] + */ + public function hello(self $class, bool $boolOption): array + { + $myArray = $class->arrOrNull(); + if ($boolOption && $myArray === null) { + throw new \Exception(''); + } + if (!$boolOption) { + $myArray = $class->otherGetArray(); + } + + assertType('array', $myArray); + + return $myArray; + } } diff --git a/tests/PHPStan/Analyser/data/bug-empty-array.php b/tests/PHPStan/Analyser/data/bug-empty-array.php index c50df67513..1e8135a795 100644 --- a/tests/PHPStan/Analyser/data/bug-empty-array.php +++ b/tests/PHPStan/Analyser/data/bug-empty-array.php @@ -6,38 +6,36 @@ class Foo { - - /** @var string[] */ - private $comments; - - public function doFoo(): void - { - assertType('array', $this->comments); - $this->comments = []; - assertType('array()', $this->comments); - if ($this->comments === []) { - assertType('array()', $this->comments); - return; - } else { - assertType('*NEVER*', $this->comments); - } - - assertType('*NEVER*', $this->comments); - } - - public function doBar(): void - { - assertType('array', $this->comments); - $this->comments = []; - assertType('array()', $this->comments); - if ([] === $this->comments) { - assertType('array()', $this->comments); - return; - } else { - assertType('*NEVER*', $this->comments); - } - - assertType('*NEVER*', $this->comments); - } - + /** @var string[] */ + private $comments; + + public function doFoo(): void + { + assertType('array', $this->comments); + $this->comments = []; + assertType('array()', $this->comments); + if ($this->comments === []) { + assertType('array()', $this->comments); + return; + } else { + assertType('*NEVER*', $this->comments); + } + + assertType('*NEVER*', $this->comments); + } + + public function doBar(): void + { + assertType('array', $this->comments); + $this->comments = []; + assertType('array()', $this->comments); + if ([] === $this->comments) { + assertType('array()', $this->comments); + return; + } else { + assertType('*NEVER*', $this->comments); + } + + assertType('*NEVER*', $this->comments); + } } diff --git a/tests/PHPStan/Analyser/data/bug-pr-339.php b/tests/PHPStan/Analyser/data/bug-pr-339.php index 01e3822e96..4c06b6ccde 100644 --- a/tests/PHPStan/Analyser/data/bug-pr-339.php +++ b/tests/PHPStan/Analyser/data/bug-pr-339.php @@ -12,22 +12,22 @@ assertType('mixed', $c); if ($a || $c) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - assertVariableCertainty(TrinaryLogic::createMaybe(), $c); - assertType('mixed', $a); - assertType('mixed', $c); - if ($a) { - assertType("mixed~0|0.0|''|'0'|array()|false|null", $a); - assertType('mixed', $c); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } + assertVariableCertainty(TrinaryLogic::createMaybe(), $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $c); + assertType('mixed', $a); + assertType('mixed', $c); + if ($a) { + assertType("mixed~0|0.0|''|'0'|array()|false|null", $a); + assertType('mixed', $c); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + } - if ($c) { - assertType('mixed', $a); - assertType("mixed~0|0.0|''|'0'|array()|false|null", $c); - assertVariableCertainty(TrinaryLogic::createYes(), $c); - } + if ($c) { + assertType('mixed', $a); + assertType("mixed~0|0.0|''|'0'|array()|false|null", $c); + assertVariableCertainty(TrinaryLogic::createYes(), $c); + } } else { - assertType("0|0.0|''|'0'|array()|false|null", $a); - assertType("0|0.0|''|'0'|array()|false|null", $c); + assertType("0|0.0|''|'0'|array()|false|null", $a); + assertType("0|0.0|''|'0'|array()|false|null", $c); } diff --git a/tests/PHPStan/Analyser/data/bug2574.php b/tests/PHPStan/Analyser/data/bug2574.php index 711a4077a4..87cb10077e 100644 --- a/tests/PHPStan/Analyser/data/bug2574.php +++ b/tests/PHPStan/Analyser/data/bug2574.php @@ -4,14 +4,17 @@ use function PHPStan\Testing\assertType; -abstract class Model { - /** @return static */ - public function newInstance() { - return new static(); - } +abstract class Model +{ + /** @return static */ + public function newInstance() + { + return new static(); + } } -class Model1 extends Model { +class Model1 extends Model +{ } /** @@ -19,13 +22,15 @@ class Model1 extends Model { * @param T $m * @return T */ -function foo(Model $m) : Model { - assertType('T of Analyser\Bug2574\Model (function Analyser\Bug2574\foo(), argument)', $m); - $instance = $m->newInstance(); - assertType('T of Analyser\Bug2574\Model (function Analyser\Bug2574\foo(), argument)', $m); - return $instance; +function foo(Model $m): Model +{ + assertType('T of Analyser\Bug2574\Model (function Analyser\Bug2574\foo(), argument)', $m); + $instance = $m->newInstance(); + assertType('T of Analyser\Bug2574\Model (function Analyser\Bug2574\foo(), argument)', $m); + return $instance; } -function test(): void { - assertType('Analyser\Bug2574\Model1', foo(new Model1())); +function test(): void +{ + assertType('Analyser\Bug2574\Model1', foo(new Model1())); } diff --git a/tests/PHPStan/Analyser/data/bug2577.php b/tests/PHPStan/Analyser/data/bug2577.php index 843d242984..cf67a2233d 100644 --- a/tests/PHPStan/Analyser/data/bug2577.php +++ b/tests/PHPStan/Analyser/data/bug2577.php @@ -4,9 +4,15 @@ use function PHPStan\Testing\assertType; -class A {} -class A1 extends A {} -class A2 extends A {} +class A +{ +} +class A1 extends A +{ +} +class A2 extends A +{ +} /** * @template T of A @@ -15,18 +21,20 @@ class A2 extends A {} * @param T $t2 * @return T */ -function echoOneOrOther(\Closure $t1, A $t2) { - echo get_class($t1()); - echo get_class($t2); - throw new \Exception(); +function echoOneOrOther(\Closure $t1, A $t2) +{ + echo get_class($t1()); + echo get_class($t2); + throw new \Exception(); } -function test(): void { - $result = echoOneOrOther( - function () : A1 { - return new A1; - }, - new A2 - ); - assertType('Analyser\Bug2577\A1|Analyser\Bug2577\A2', $result); +function test(): void +{ + $result = echoOneOrOther( + function (): A1 { + return new A1(); + }, + new A2() + ); + assertType('Analyser\Bug2577\A1|Analyser\Bug2577\A2', $result); } diff --git a/tests/PHPStan/Analyser/data/callables.php b/tests/PHPStan/Analyser/data/callables.php index 144196f5c2..5085879ec9 100644 --- a/tests/PHPStan/Analyser/data/callables.php +++ b/tests/PHPStan/Analyser/data/callables.php @@ -4,27 +4,22 @@ class Foo { - - public function doFoo(): float - { - $closure = function (): string { - - }; - $foo = $this; - $arrayWithStaticMethod = ['Callables\\Foo', 'doBar']; - $stringWithStaticMethod = 'Callables\\Foo::doFoo'; - $arrayWithInstanceMethod = [$this, 'doFoo']; - die; - } - - public function doBar(): Bar - { - - } - - public function __invoke(): int - { - - } - + public function doFoo(): float + { + $closure = function (): string { + }; + $foo = $this; + $arrayWithStaticMethod = ['Callables\\Foo', 'doBar']; + $stringWithStaticMethod = 'Callables\\Foo::doFoo'; + $arrayWithInstanceMethod = [$this, 'doFoo']; + die; + } + + public function doBar(): Bar + { + } + + public function __invoke(): int + { + } } diff --git a/tests/PHPStan/Analyser/data/calling-multiple-classes-per-file.php b/tests/PHPStan/Analyser/data/calling-multiple-classes-per-file.php index 4f37590e6d..b3522d2b7a 100644 --- a/tests/PHPStan/Analyser/data/calling-multiple-classes-per-file.php +++ b/tests/PHPStan/Analyser/data/calling-multiple-classes-per-file.php @@ -3,7 +3,7 @@ namespace CallingMultipleClasses; function () { - $foo = new \MultipleClasses\Foo(); - $bar = new \MultipleClasses\Bar(); - die; + $foo = new \MultipleClasses\Foo(); + $bar = new \MultipleClasses\Bar(); + die; }; diff --git a/tests/PHPStan/Analyser/data/case-insensitive-phpdocs.php b/tests/PHPStan/Analyser/data/case-insensitive-phpdocs.php index ede4a06a84..d096f23121 100644 --- a/tests/PHPStan/Analyser/data/case-insensitive-phpdocs.php +++ b/tests/PHPStan/Analyser/data/case-insensitive-phpdocs.php @@ -7,16 +7,14 @@ class Test { + /** @var bar */ + private $bar; - /** @var bar */ - private $bar; - - /** @var lOREM */ - private $lorem; - - public function doFoo() - { - die; - } + /** @var lOREM */ + private $lorem; + public function doFoo() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/cast-to-numeric-string.php b/tests/PHPStan/Analyser/data/cast-to-numeric-string.php index e9e2259cb7..3c7f65a56f 100644 --- a/tests/PHPStan/Analyser/data/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/data/cast-to-numeric-string.php @@ -12,15 +12,16 @@ * @param negative-int $negative * @param 1 $constantInt */ -function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('string&numeric', (string)$a); - assertType('string&numeric', (string)$b); - assertType('string&numeric', (string)$numeric); - assertType('string&numeric', (string)$numeric2); - assertType('string&numeric', (string)$number); - assertType('string&numeric', (string)$positive); - assertType('string&numeric', (string)$negative); - assertType("'1'", (string)$constantInt); +function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void +{ + assertType('string&numeric', (string)$a); + assertType('string&numeric', (string)$b); + assertType('string&numeric', (string)$numeric); + assertType('string&numeric', (string)$numeric2); + assertType('string&numeric', (string)$number); + assertType('string&numeric', (string)$positive); + assertType('string&numeric', (string)$negative); + assertType("'1'", (string)$constantInt); } /** @@ -31,31 +32,33 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param negative-int $negative * @param 1 $constantInt */ -function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('string&numeric', '' . $a); - assertType('string&numeric', '' . $b); - assertType('string&numeric', '' . $numeric); - assertType('string&numeric', '' . $numeric2); - assertType('string&numeric', '' . $number); - assertType('string&numeric', '' . $positive); - assertType('string&numeric', '' . $negative); - assertType("'1'", '' . $constantInt); +function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void +{ + assertType('string&numeric', '' . $a); + assertType('string&numeric', '' . $b); + assertType('string&numeric', '' . $numeric); + assertType('string&numeric', '' . $numeric2); + assertType('string&numeric', '' . $number); + assertType('string&numeric', '' . $positive); + assertType('string&numeric', '' . $negative); + assertType("'1'", '' . $constantInt); - assertType('string&numeric', $a . ''); - assertType('string&numeric', $b . ''); - assertType('string&numeric', $numeric . ''); - assertType('string&numeric', $numeric2 . ''); - assertType('string&numeric', $number . ''); - assertType('string&numeric', $positive . ''); - assertType('string&numeric', $negative . ''); - assertType("'1'", $constantInt . ''); + assertType('string&numeric', $a . ''); + assertType('string&numeric', $b . ''); + assertType('string&numeric', $numeric . ''); + assertType('string&numeric', $numeric2 . ''); + assertType('string&numeric', $number . ''); + assertType('string&numeric', $positive . ''); + assertType('string&numeric', $negative . ''); + assertType("'1'", $constantInt . ''); } -function concatAssignEmptyString(int $i, float $f) { - $i .= ''; - assertType('string&numeric', $i); +function concatAssignEmptyString(int $i, float $f) +{ + $i .= ''; + assertType('string&numeric', $i); - $s = ''; - $s .= $f; - assertType('string&numeric', $s); + $s = ''; + $s .= $f; + assertType('string&numeric', $s); } diff --git a/tests/PHPStan/Analyser/data/cast-unset.php b/tests/PHPStan/Analyser/data/cast-unset.php index 701bd9834c..b43a55adf1 100644 --- a/tests/PHPStan/Analyser/data/cast-unset.php +++ b/tests/PHPStan/Analyser/data/cast-unset.php @@ -4,11 +4,9 @@ class Foo { - - public function doFoo() - { - $castedNull = (unset) foo(); - die; - } - + public function doFoo() + { + $castedNull = (unset) foo(); + die; + } } diff --git a/tests/PHPStan/Analyser/data/casts.php b/tests/PHPStan/Analyser/data/casts.php index a8c722431a..e807ff4249 100644 --- a/tests/PHPStan/Analyser/data/casts.php +++ b/tests/PHPStan/Analyser/data/casts.php @@ -4,49 +4,45 @@ class Bar { - - /** @var self */ - private $barProperty; - + /** @var self */ + private $barProperty; } class Foo extends Bar { - - /** @var self */ - private $foo; - - /** @var int */ - private $int; - - /** @var int */ - protected $protectedInt; - - /** @var int */ - public $publicInt; - - /** - * @param string $str - * @param iterable $iterable - */ - public function doFoo(string $str, iterable $iterable) - { - $castedInteger = (int) foo(); - $castedBoolean = (bool) foo(); - $castedFloat = (float) foo(); - $castedString = (string) foo(); - $castedArray = (array) foo(); - $castedObject = (object) foo(); - $foo = new self(); - $castedFoo = (object) $foo; - - /** @var self|array $arrayOrObject */ - $arrayOrObject = foo(); - $castedArrayOrObject = (object) $arrayOrObject; - - /** @var bool $bool */ - $bool = doFoo(); - die; - } - + /** @var self */ + private $foo; + + /** @var int */ + private $int; + + /** @var int */ + protected $protectedInt; + + /** @var int */ + public $publicInt; + + /** + * @param string $str + * @param iterable $iterable + */ + public function doFoo(string $str, iterable $iterable) + { + $castedInteger = (int) foo(); + $castedBoolean = (bool) foo(); + $castedFloat = (float) foo(); + $castedString = (string) foo(); + $castedArray = (array) foo(); + $castedObject = (object) foo(); + $foo = new self(); + $castedFoo = (object) $foo; + + /** @var self|array $arrayOrObject */ + $arrayOrObject = foo(); + $castedArrayOrObject = (object) $arrayOrObject; + + /** @var bool $bool */ + $bool = doFoo(); + die; + } } diff --git a/tests/PHPStan/Analyser/data/catch-specified-variable.php b/tests/PHPStan/Analyser/data/catch-specified-variable.php index dde9dab7d2..1709ecc1b8 100644 --- a/tests/PHPStan/Analyser/data/catch-specified-variable.php +++ b/tests/PHPStan/Analyser/data/catch-specified-variable.php @@ -4,19 +4,18 @@ class FooException extends \Exception { - } function () { - /** @var string|null $foo */ - $foo = doFoo(); - if ($foo !== null) { - return; - } + /** @var string|null $foo */ + $foo = doFoo(); + if ($foo !== null) { + return; + } - try { - maybeThrows(); - } catch (FooException $foo) { - die; - } + try { + maybeThrows(); + } catch (FooException $foo) { + die; + } }; diff --git a/tests/PHPStan/Analyser/data/catch-union.php b/tests/PHPStan/Analyser/data/catch-union.php index 9fa8811949..523ac454f3 100644 --- a/tests/PHPStan/Analyser/data/catch-union.php +++ b/tests/PHPStan/Analyser/data/catch-union.php @@ -4,18 +4,16 @@ class FooException extends \Exception { - } class BarException extends \Exception { - } function () { - try { - maybeThrows(); - } catch (FooException | BarException $e) { - die; - } + try { + maybeThrows(); + } catch (FooException | BarException $e) { + die; + } }; diff --git a/tests/PHPStan/Analyser/data/catch-without-variable.php b/tests/PHPStan/Analyser/data/catch-without-variable.php index 11b4ef80bd..a0fe83690a 100644 --- a/tests/PHPStan/Analyser/data/catch-without-variable.php +++ b/tests/PHPStan/Analyser/data/catch-without-variable.php @@ -6,14 +6,11 @@ class Foo { - - public function doFoo(): void - { - try { - - } catch (\FooException) { - assertType('*ERROR*', $e); - } - } - + public function doFoo(): void + { + try { + } catch (\FooException) { + assertType('*ERROR*', $e); + } + } } diff --git a/tests/PHPStan/Analyser/data/class-constant-on-expr.php b/tests/PHPStan/Analyser/data/class-constant-on-expr.php index 2aeb08ae5b..38d852a10b 100644 --- a/tests/PHPStan/Analyser/data/class-constant-on-expr.php +++ b/tests/PHPStan/Analyser/data/class-constant-on-expr.php @@ -6,18 +6,15 @@ class Foo { - - public function doFoo( - \stdClass $std, - string $string, - ?\stdClass $stdOrNull, - ?string $stringOrNull - ): void - { - assertType('class-string', $std::class); - assertType('*ERROR*', $string::class); - assertType('class-string', $stdOrNull::class); - assertType('*ERROR*', $stringOrNull::class); - } - + public function doFoo( + \stdClass $std, + string $string, + ?\stdClass $stdOrNull, + ?string $stringOrNull + ): void { + assertType('class-string', $std::class); + assertType('*ERROR*', $string::class); + assertType('class-string', $stdOrNull::class); + assertType('*ERROR*', $stringOrNull::class); + } } diff --git a/tests/PHPStan/Analyser/data/class-exists.php b/tests/PHPStan/Analyser/data/class-exists.php index 65ccf58456..ad604e16c3 100644 --- a/tests/PHPStan/Analyser/data/class-exists.php +++ b/tests/PHPStan/Analyser/data/class-exists.php @@ -4,13 +4,11 @@ class Foo { - - public function doFoo(): void - { - $className = '\PHPStan\GitHubIssue2359'; - if (class_exists($className)) { - var_dump(new $className()); - } - } - + public function doFoo(): void + { + $className = '\PHPStan\GitHubIssue2359'; + if (class_exists($className)) { + var_dump(new $className()); + } + } } diff --git a/tests/PHPStan/Analyser/data/class.php b/tests/PHPStan/Analyser/data/class.php index 0cefc1f4ab..bf3f179b71 100644 --- a/tests/PHPStan/Analyser/data/class.php +++ b/tests/PHPStan/Analyser/data/class.php @@ -4,35 +4,31 @@ class InvalidArgumentException extends \Exception { - } class Foo { - - public function doFoo($baz) - { - static $staticVariable = []; - /** @var string $staticVariableWithPhpDocType */ - static $staticVariableWithPhpDocType = 'foo'; - /** - * @var int $staticVariableWithPhpDocType2 - * @var float $staticVariableWithPhpDocType3 - */ - static $staticVariableWithPhpDocType2 = 100, $staticVariableWithPhpDocType3 = 3.141; - try { - foo(); - } catch (InvalidArgumentException $exception) { - $lorem = 1; - foreach ($arr as $i => $val) { - die; - } - } - } - - public function doBar() - { - - } - + public function doFoo($baz) + { + static $staticVariable = []; + /** @var string $staticVariableWithPhpDocType */ + static $staticVariableWithPhpDocType = 'foo'; + /** + * @var int $staticVariableWithPhpDocType2 + * @var float $staticVariableWithPhpDocType3 + */ + static $staticVariableWithPhpDocType2 = 100, $staticVariableWithPhpDocType3 = 3.141; + try { + foo(); + } catch (InvalidArgumentException $exception) { + $lorem = 1; + foreach ($arr as $i => $val) { + die; + } + } + } + + public function doBar() + { + } } diff --git a/tests/PHPStan/Analyser/data/classPhpDocs.php b/tests/PHPStan/Analyser/data/classPhpDocs.php index 0d447a3d49..3dced3122d 100644 --- a/tests/PHPStan/Analyser/data/classPhpDocs.php +++ b/tests/PHPStan/Analyser/data/classPhpDocs.php @@ -15,9 +15,13 @@ */ class Foo { - public function __call($name, $arguments){} + public function __call($name, $arguments) + { + } - public static function __callStatic($name, $arguments){} + public static function __callStatic($name, $arguments) + { + } public function doFoo() { @@ -41,4 +45,3 @@ public function doFoo() assertType('int', static::overrodeStaticMethod()); } } - diff --git a/tests/PHPStan/Analyser/data/clear-stat-cache.php b/tests/PHPStan/Analyser/data/clear-stat-cache.php index 48686cf61d..7e677a4450 100644 --- a/tests/PHPStan/Analyser/data/clear-stat-cache.php +++ b/tests/PHPStan/Analyser/data/clear-stat-cache.php @@ -6,57 +6,57 @@ use function PHPStan\Testing\assertType; function (string $a, string $b, bool $c): string { - if (is_file($a) && $c) { - assertType('true', is_file($a)); - assertType('bool', is_file($b)); - assertType('true', $c); - clearstatcache(); - assertType('bool', is_file($a)); - assertType('bool', is_file($b)); - assertType('true', $c); - } + if (is_file($a) && $c) { + assertType('true', is_file($a)); + assertType('bool', is_file($b)); + assertType('true', $c); + clearstatcache(); + assertType('bool', is_file($a)); + assertType('bool', is_file($b)); + assertType('true', $c); + } }; function (string $a, string $b, bool $c): string { - if (\is_file($a) && $c) { - //assertType('true', is_file($a)); - assertType('bool', is_file($b)); - assertType('true', $c); - clearstatcache(); - assertType('bool', is_file($a)); - assertType('bool', is_file($b)); - assertType('true', $c); - } + if (\is_file($a) && $c) { + //assertType('true', is_file($a)); + assertType('bool', is_file($b)); + assertType('true', $c); + clearstatcache(); + assertType('bool', is_file($a)); + assertType('bool', is_file($b)); + assertType('true', $c); + } }; function (string $a, string $b, bool $c): string { - if (is_file($a) && $c) { - //assertType('true', \is_file($a)); - assertType('bool', \is_file($b)); - assertType('true', $c); - clearstatcache(); - assertType('bool', \is_file($a)); - assertType('bool', \is_file($b)); - assertType('true', $c); - } + if (is_file($a) && $c) { + //assertType('true', \is_file($a)); + assertType('bool', \is_file($b)); + assertType('true', $c); + clearstatcache(); + assertType('bool', \is_file($a)); + assertType('bool', \is_file($b)); + assertType('true', $c); + } }; function (string $a, string $b, bool $c): string { - if (\is_file($a) && $c) { - assertType('true', \is_file($a)); - assertType('bool', \is_file($b)); - assertType('true', $c); - clearstatcache(); - assertType('bool', \is_file($a)); - assertType('bool', \is_file($b)); - assertType('true', $c); - } + if (\is_file($a) && $c) { + assertType('true', \is_file($a)); + assertType('bool', \is_file($b)); + assertType('true', $c); + clearstatcache(); + assertType('bool', \is_file($a)); + assertType('bool', \is_file($b)); + assertType('true', $c); + } }; function (): void { - if (file_exists('foo')) { - assertType('true', file_exists('foo')); - unlink('foo'); - assertType('bool', file_exists('foo')); - } + if (file_exists('foo')) { + assertType('true', file_exists('foo')); + unlink('foo'); + assertType('bool', file_exists('foo')); + } }; diff --git a/tests/PHPStan/Analyser/data/clone.php b/tests/PHPStan/Analyser/data/clone.php index e12fa9b351..c2da5c7a70 100644 --- a/tests/PHPStan/Analyser/data/clone.php +++ b/tests/PHPStan/Analyser/data/clone.php @@ -4,11 +4,10 @@ class Foo { - } function () { - $fooObject = new Foo(); + $fooObject = new Foo(); - die; + die; }; diff --git a/tests/PHPStan/Analyser/data/closure-inferred-typehint.php b/tests/PHPStan/Analyser/data/closure-inferred-typehint.php index 40e5024d3d..7820ce65ec 100644 --- a/tests/PHPStan/Analyser/data/closure-inferred-typehint.php +++ b/tests/PHPStan/Analyser/data/closure-inferred-typehint.php @@ -4,31 +4,27 @@ class Foo { - - public function doFoo() - { - $this->doBar(function ($foo, $bar) { - die; - }); - $this->doBaz(function ($foo, $bar) { - die; - }); - } - - /** - * @param \Closure(\DateTime|\stdClass): void $closure - */ - private function doBar(\Closure $closure) - { - - } - - /** - * @param callable(\DateTime|\stdClass): void $closure - */ - private function doBaz(callable $closure) - { - - } - + public function doFoo() + { + $this->doBar(function ($foo, $bar) { + die; + }); + $this->doBaz(function ($foo, $bar) { + die; + }); + } + + /** + * @param \Closure(\DateTime|\stdClass): void $closure + */ + private function doBar(\Closure $closure) + { + } + + /** + * @param callable(\DateTime|\stdClass): void $closure + */ + private function doBaz(callable $closure) + { + } } diff --git a/tests/PHPStan/Analyser/data/closure-passed-by-reference-in-call.php b/tests/PHPStan/Analyser/data/closure-passed-by-reference-in-call.php index b261218955..778b556bc4 100644 --- a/tests/PHPStan/Analyser/data/closure-passed-by-reference-in-call.php +++ b/tests/PHPStan/Analyser/data/closure-passed-by-reference-in-call.php @@ -4,17 +4,15 @@ class Foo { + public function doFoo(\Closure $closure): int + { + return 5; + } - public function doFoo(\Closure $closure): int - { - return 5; - } - - public function doBar() - { - $five = $this->doFoo(function () use (&$five) { - die; - }); - } - + public function doBar() + { + $five = $this->doFoo(function () use (&$five) { + die; + }); + } } diff --git a/tests/PHPStan/Analyser/data/closure-passed-by-reference-return.php b/tests/PHPStan/Analyser/data/closure-passed-by-reference-return.php index eb631207d9..d5b78eea71 100644 --- a/tests/PHPStan/Analyser/data/closure-passed-by-reference-return.php +++ b/tests/PHPStan/Analyser/data/closure-passed-by-reference-return.php @@ -3,18 +3,18 @@ namespace ClosurePassedByReference; function () { - $fooOrNull = null; - 'beforeCallback'; - $callback = function () use (&$fooOrNull): void { - 'inCallbackBeforeAssign'; - if ($fooOrNull === null) { - $fooOrNull = new Foo(); - } + $fooOrNull = null; + 'beforeCallback'; + $callback = function () use (&$fooOrNull): void { + 'inCallbackBeforeAssign'; + if ($fooOrNull === null) { + $fooOrNull = new Foo(); + } - 'inCallbackAfterAssign'; + 'inCallbackAfterAssign'; - return $fooOrNull; - }; + return $fooOrNull; + }; - 'afterCallback'; + 'afterCallback'; }; diff --git a/tests/PHPStan/Analyser/data/closure-passed-by-reference.php b/tests/PHPStan/Analyser/data/closure-passed-by-reference.php index 06857980d2..8e86f441c3 100644 --- a/tests/PHPStan/Analyser/data/closure-passed-by-reference.php +++ b/tests/PHPStan/Analyser/data/closure-passed-by-reference.php @@ -3,32 +3,31 @@ namespace ClosurePassedByReference; function () { + $progressStarted = false; + $anotherVariable = false; + $incrementedInside = 1; + $fooOrNull = null; + 'beforeCallback'; + $callback = function () use (&$progressStarted, $anotherVariable, &$untouchedPassedByRef, &$incrementedInside, &$fooOrNull): void { + 'inCallbackBeforeAssign'; + if (doFoo()) { + $progressStarted = 1; + return; + } + if (!$progressStarted) { + $progressStarted = true; + } + if (!$anotherVariable) { + $anotherVariable = true; + } + if ($fooOrNull === null) { + $fooOrNull = new Foo(); + } - $progressStarted = false; - $anotherVariable = false; - $incrementedInside = 1; - $fooOrNull = null; - 'beforeCallback'; - $callback = function () use (&$progressStarted, $anotherVariable, &$untouchedPassedByRef, &$incrementedInside, &$fooOrNull): void { - 'inCallbackBeforeAssign'; - if (doFoo()) { - $progressStarted = 1; - return; - } - if (!$progressStarted) { - $progressStarted = true; - } - if (!$anotherVariable) { - $anotherVariable = true; - } - if ($fooOrNull === null) { - $fooOrNull = new Foo(); - } + $incrementedInside++; - $incrementedInside++; + 'inCallbackAfterAssign'; + }; - 'inCallbackAfterAssign'; - }; - - 'afterCallback'; + 'afterCallback'; }; diff --git a/tests/PHPStan/Analyser/data/closure-return-type-extensions.php b/tests/PHPStan/Analyser/data/closure-return-type-extensions.php index c171fcffb5..1229fb91fb 100644 --- a/tests/PHPStan/Analyser/data/closure-return-type-extensions.php +++ b/tests/PHPStan/Analyser/data/closure-return-type-extensions.php @@ -4,17 +4,19 @@ use function PHPStan\Testing\assertType; -$predicate = function (object $thing): bool { return true; }; +$predicate = function (object $thing): bool { + return true; +}; $closure = \Closure::fromCallable($predicate); assertType('Closure(object): true', $closure); -$newThis = new class {}; +$newThis = new class() {}; $boundClosure = $closure->bindTo($newThis); assertType('Closure(object): true', $boundClosure); $staticallyBoundClosure = \Closure::bind($closure, $newThis); assertType('Closure(object): true', $staticallyBoundClosure); -$returnType = $closure->call($newThis, new class {}); +$returnType = $closure->call($newThis, new class() {}); assertType('true', $returnType); diff --git a/tests/PHPStan/Analyser/data/closure-return-type.php b/tests/PHPStan/Analyser/data/closure-return-type.php index f8574295aa..657a6e1a7a 100644 --- a/tests/PHPStan/Analyser/data/closure-return-type.php +++ b/tests/PHPStan/Analyser/data/closure-return-type.php @@ -6,154 +6,151 @@ class Foo { - - public function doFoo(int $i): void - { - $f = function () { - - }; - assertType('void', $f()); - - $f = function () { - return; - }; - assertType('void', $f()); - - $f = function () { - return 1; - }; - assertType('1', $f()); - - $f = function (): array { - return ['foo' => 'bar']; - }; - assertType('array(\'foo\' => \'bar\')', $f()); - - $f = function (string $s) { - return $s; - }; - assertType('string', $f('foo')); - - $f = function () use ($i) { - return $i; - }; - assertType('int', $f()); - - $f = function () use ($i) { - if (rand(0, 1)) { - return $i; - } - - return null; - }; - assertType('int|null', $f()); - - $f = function () use ($i) { - if (rand(0, 1)) { - return $i; - } - - return; - }; - assertType('int|null', $f()); - - $f = function () { - yield 1; - return 2; - }; - assertType('Generator', $f()); - - $g = function () use ($f) { - yield from $f(); - }; - assertType('Generator', $g()); - - $h = function (): \Generator { - yield 1; - return 2; - }; - assertType('Generator', $h()); - } - - public function doBar(): void - { - $f = function () { - if (rand(0, 1)) { - return 1; - } - - function () { - return 'foo'; - }; - - $c = new class() { - public function doFoo() { - return 2.0; - } - }; - - return 2; - }; - - assertType('1|2', $f()); - } - - /** - * @return never - */ - public function returnNever(): void - { - - } - - public function doBaz(): void - { - $f = function() { - $this->returnNever(); - }; - assertType('*NEVER*', $f()); - - $f = function(): void { - $this->returnNever(); - }; - assertType('*NEVER*', $f()); - - $f = function() { - if (rand(0, 1)) { - return; - } - - $this->returnNever(); - }; - assertType('void', $f()); - - $f = function(array $a) { - foreach ($a as $v) { - continue; - } - - $this->returnNever(); - }; - assertType('*NEVER*', $f([])); - - $f = function(array $a) { - foreach ($a as $v) { - $this->returnNever(); - } - }; - assertType('void', $f([])); - - $f = function() { - foreach ([1, 2, 3] as $v) { - $this->returnNever(); - } - }; - assertType('*NEVER*', $f()); - - $f = function (): \stdClass { - throw new \Exception(); - }; - assertType('stdClass', $f()); - } - + public function doFoo(int $i): void + { + $f = function () { + }; + assertType('void', $f()); + + $f = function () { + return; + }; + assertType('void', $f()); + + $f = function () { + return 1; + }; + assertType('1', $f()); + + $f = function (): array { + return ['foo' => 'bar']; + }; + assertType('array(\'foo\' => \'bar\')', $f()); + + $f = function (string $s) { + return $s; + }; + assertType('string', $f('foo')); + + $f = function () use ($i) { + return $i; + }; + assertType('int', $f()); + + $f = function () use ($i) { + if (rand(0, 1)) { + return $i; + } + + return null; + }; + assertType('int|null', $f()); + + $f = function () use ($i) { + if (rand(0, 1)) { + return $i; + } + + return; + }; + assertType('int|null', $f()); + + $f = function () { + yield 1; + return 2; + }; + assertType('Generator', $f()); + + $g = function () use ($f) { + yield from $f(); + }; + assertType('Generator', $g()); + + $h = function (): \Generator { + yield 1; + return 2; + }; + assertType('Generator', $h()); + } + + public function doBar(): void + { + $f = function () { + if (rand(0, 1)) { + return 1; + } + + function () { + return 'foo'; + }; + + $c = new class() { + public function doFoo() + { + return 2.0; + } + }; + + return 2; + }; + + assertType('1|2', $f()); + } + + /** + * @return never + */ + public function returnNever(): void + { + } + + public function doBaz(): void + { + $f = function () { + $this->returnNever(); + }; + assertType('*NEVER*', $f()); + + $f = function (): void { + $this->returnNever(); + }; + assertType('*NEVER*', $f()); + + $f = function () { + if (rand(0, 1)) { + return; + } + + $this->returnNever(); + }; + assertType('void', $f()); + + $f = function (array $a) { + foreach ($a as $v) { + continue; + } + + $this->returnNever(); + }; + assertType('*NEVER*', $f([])); + + $f = function (array $a) { + foreach ($a as $v) { + $this->returnNever(); + } + }; + assertType('void', $f([])); + + $f = function () { + foreach ([1, 2, 3] as $v) { + $this->returnNever(); + } + }; + assertType('*NEVER*', $f()); + + $f = function (): \stdClass { + throw new \Exception(); + }; + assertType('stdClass', $f()); + } } diff --git a/tests/PHPStan/Analyser/data/coalesce-assign.php b/tests/PHPStan/Analyser/data/coalesce-assign.php index fd700ec914..e962baae99 100644 --- a/tests/PHPStan/Analyser/data/coalesce-assign.php +++ b/tests/PHPStan/Analyser/data/coalesce-assign.php @@ -1,37 +1,36 @@ -= 7.4 += 7.4 namespace CoalesceAssign; class Foo { + public function doFoo( + string $string, + ?string $nullableString + ) { + $emptyArray = []; + $arrayWithFoo = ['foo' => 'foo']; + $arrayWithMaybeFoo = []; + if (rand(0, 1)) { + $arrayWithMaybeFoo['foo'] = 'foo'; + } - public function doFoo( - string $string, - ?string $nullableString - ) - { - $emptyArray = []; - $arrayWithFoo = ['foo' => 'foo']; - $arrayWithMaybeFoo = []; - if (rand(0, 1)) { - $arrayWithMaybeFoo['foo'] = 'foo'; - } - - $arrayAfterAssignment = []; - $arrayAfterAssignment['foo'] ??= 'foo'; - - $arrayWithFooAfterAssignment = ['foo' => 'foo']; - $arrayWithFooAfterAssignment['foo'] ??= 'bar'; + $arrayAfterAssignment = []; + $arrayAfterAssignment['foo'] ??= 'foo'; - $nonexistentVariableAfterAssignment ??= 'foo'; + $arrayWithFooAfterAssignment = ['foo' => 'foo']; + $arrayWithFooAfterAssignment['foo'] ??= 'bar'; - if (rand(0, 1)) { - $maybeNonexistentVariableAfterAssignment = 'foo'; - } + $nonexistentVariableAfterAssignment ??= 'foo'; - $maybeNonexistentVariableAfterAssignment ??= 'bar'; + if (rand(0, 1)) { + $maybeNonexistentVariableAfterAssignment = 'foo'; + } - die; - } + $maybeNonexistentVariableAfterAssignment ??= 'bar'; + die; + } } diff --git a/tests/PHPStan/Analyser/data/combine-types.php b/tests/PHPStan/Analyser/data/combine-types.php index 5d3ee2ecf1..027ace67e5 100644 --- a/tests/PHPStan/Analyser/data/combine-types.php +++ b/tests/PHPStan/Analyser/data/combine-types.php @@ -5,18 +5,16 @@ /** @var string[] $arr */ $arr = doFoo(); foreach ($arr as $foo) { - $x = $foo; + $x = $foo; } $y = null; if (doFoo()) { - } else { - if (doBar()) { - - } else { - $y = 1; - } + if (doBar()) { + } else { + $y = 1; + } } die; diff --git a/tests/PHPStan/Analyser/data/compact.php b/tests/PHPStan/Analyser/data/compact.php index 4c29db6781..1fba7cf306 100644 --- a/tests/PHPStan/Analyser/data/compact.php +++ b/tests/PHPStan/Analyser/data/compact.php @@ -7,16 +7,16 @@ assertType('array(?\'bar\' => mixed)', compact(['foo' => 'bar'])); function (string $dolor): void { - $foo = 'bar'; - $bar = 'baz'; - if (rand(0, 1)) { - $lorem = 'ipsum'; - } - assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\')', compact('foo', ['bar'])); - assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\', ?\'lorem\' => \'ipsum\')', compact([['foo']], 'bar', 'lorem')); + $foo = 'bar'; + $bar = 'baz'; + if (rand(0, 1)) { + $lorem = 'ipsum'; + } + assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\')', compact('foo', ['bar'])); + assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\', ?\'lorem\' => \'ipsum\')', compact([['foo']], 'bar', 'lorem')); - assertType('array', compact($dolor)); - assertType('array', compact([$dolor])); + assertType('array', compact($dolor)); + assertType('array', compact([$dolor])); - assertType('array()', compact([])); + assertType('array()', compact([])); }; diff --git a/tests/PHPStan/Analyser/data/comparison-operators.php b/tests/PHPStan/Analyser/data/comparison-operators.php index 6f5761498c..be406420da 100644 --- a/tests/PHPStan/Analyser/data/comparison-operators.php +++ b/tests/PHPStan/Analyser/data/comparison-operators.php @@ -6,354 +6,352 @@ class ComparisonOperators { - public function null(): void - { - assertType('false', -1 < null); - assertType('false', 0 < null); - assertType('false', 1 < null); - assertType('false', true < null); - assertType('false', false < null); - assertType('false', '1' < null); - assertType('true', 0 <= null); - assertType('false', '0' <= null); + public function null(): void + { + assertType('false', -1 < null); + assertType('false', 0 < null); + assertType('false', 1 < null); + assertType('false', true < null); + assertType('false', false < null); + assertType('false', '1' < null); + assertType('true', 0 <= null); + assertType('false', '0' <= null); - assertType('true', null < -1); - assertType('false', null < 0); - assertType('true', null < 1); - assertType('true', null < true); - assertType('false', null < false); - assertType('true', null < '1'); - assertType('true', null <= '0'); - } + assertType('true', null < -1); + assertType('false', null < 0); + assertType('true', null < 1); + assertType('true', null < true); + assertType('false', null < false); + assertType('true', null < '1'); + assertType('true', null <= '0'); + } - public function bool(): void - { - assertType('true', true > false); - assertType('true', true >= false); - assertType('false', true < false); - assertType('false', true <= false); - assertType('false', false > true); - assertType('false', false >= true); - assertType('true', false < true); - assertType('true', false <= true); - } + public function bool(): void + { + assertType('true', true > false); + assertType('true', true >= false); + assertType('false', true < false); + assertType('false', true <= false); + assertType('false', false > true); + assertType('false', false >= true); + assertType('true', false < true); + assertType('true', false <= true); + } - public function string(): void - { - assertType('false', 'foo' < 'bar'); - assertType('false', 'foo' <= 'bar'); - assertType('true', 'foo' > 'bar'); - assertType('true', 'foo' >= 'bar'); - } + public function string(): void + { + assertType('false', 'foo' < 'bar'); + assertType('false', 'foo' <= 'bar'); + assertType('true', 'foo' > 'bar'); + assertType('true', 'foo' >= 'bar'); + } - public function float(): void - { - assertType('true', 1.9 > 1); - assertType('true', '1.9' > 1); + public function float(): void + { + assertType('true', 1.9 > 1); + assertType('true', '1.9' > 1); - assertType('false', 1.9 > 2.1); - assertType('true', 1.9 > 1.5); - assertType('true', 1.9 < 2.1); - assertType('false', 1.9 < 1.5); - } + assertType('false', 1.9 > 2.1); + assertType('true', 1.9 > 1.5); + assertType('true', 1.9 < 2.1); + assertType('false', 1.9 < 1.5); + } - public function unions(int $a, int $b): void - { - if (($a === 17 || $a === 42) && ($b === 3 || $b === 7)) { - assertType('false', $a < $b); - assertType('true', $a > $b); - assertType('false', $a <= $b); - assertType('true', $a >= $b); - } - if (($a === 11 || $a === 42) && ($b === 3 || $b === 11)) { - assertType('false', $a < $b); - assertType('bool', $a > $b); - assertType('bool', $a <= $b); - assertType('true', $a >= $b); - } - } + public function unions(int $a, int $b): void + { + if (($a === 17 || $a === 42) && ($b === 3 || $b === 7)) { + assertType('false', $a < $b); + assertType('true', $a > $b); + assertType('false', $a <= $b); + assertType('true', $a >= $b); + } + if (($a === 11 || $a === 42) && ($b === 3 || $b === 11)) { + assertType('false', $a < $b); + assertType('bool', $a > $b); + assertType('bool', $a <= $b); + assertType('true', $a >= $b); + } + } - public function ranges(int $a, int $b): void - { - if ($a >= 10 && $a <= 20) { - if ($b >= 30 && $b <= 40) { - assertType('true', $a < $b); - assertType('false', $a > $b); - assertType('true', $a <= $b); - assertType('false', $a >= $b); - } - } - if ($a >= 10 && $a <= 25) { - if ($b >= 25 && $b <= 40) { - assertType('bool', $a < $b); - assertType('false', $a > $b); - assertType('true', $a <= $b); - assertType('bool', $a >= $b); - } - } - } + public function ranges(int $a, int $b): void + { + if ($a >= 10 && $a <= 20) { + if ($b >= 30 && $b <= 40) { + assertType('true', $a < $b); + assertType('false', $a > $b); + assertType('true', $a <= $b); + assertType('false', $a >= $b); + } + } + if ($a >= 10 && $a <= 25) { + if ($b >= 25 && $b <= 40) { + assertType('bool', $a < $b); + assertType('false', $a > $b); + assertType('true', $a <= $b); + assertType('bool', $a >= $b); + } + } + } } class ComparisonOperatorsInTypeSpecifier { + public function null(?int $i, ?float $f, ?string $s, ?bool $b): void + { + if ($i > null) { + assertType('int|int<1, max>', $i); + } + if ($i >= null) { + assertType('int|null', $i); + } + if ($i < null) { + assertType('*NEVER*', $i); + } + if ($i <= null) { + assertType('0|null', $i); + } - public function null(?int $i, ?float $f, ?string $s, ?bool $b): void - { - if ($i > null) { - assertType('int|int<1, max>', $i); - } - if ($i >= null) { - assertType('int|null', $i); - } - if ($i < null) { - assertType('*NEVER*', $i); - } - if ($i <= null) { - assertType('0|null', $i); - } + if ($f > null) { + assertType('float', $f); + } + if ($f >= null) { + assertType('float|null', $f); + } + if ($f < null) { + assertType('*NEVER*', $f); + } + if ($f <= null) { + assertType('0.0|null', $f); + } - if ($f > null) { - assertType('float', $f); - } - if ($f >= null) { - assertType('float|null', $f); - } - if ($f < null) { - assertType('*NEVER*', $f); - } - if ($f <= null) { - assertType('0.0|null', $f); - } + if ($s > null) { + assertType('string', $s); + } + if ($s >= null) { + assertType('string|null', $s); + } + if ($s < null) { + assertType('*NEVER*', $s); + } + if ($s <= null) { + assertType('\'\'|null', $s); + } - if ($s > null) { - assertType('string', $s); - } - if ($s >= null) { - assertType('string|null', $s); - } - if ($s < null) { - assertType('*NEVER*', $s); - } - if ($s <= null) { - assertType('\'\'|null', $s); - } + if ($b > null) { + assertType('true', $b); + } + if ($b >= null) { + assertType('bool|null', $b); + } + if ($b < null) { + assertType('*NEVER*', $b); + } + if ($b <= null) { + assertType('false|null', $b); + } + } - if ($b > null) { - assertType('true', $b); - } - if ($b >= null) { - assertType('bool|null', $b); - } - if ($b < null) { - assertType('*NEVER*', $b); - } - if ($b <= null) { - assertType('false|null', $b); - } - } + public function bool(?bool $b): void + { + if ($b > false) { + assertType('true', $b); + } + if ($b >= false) { + assertType('bool|null', $b); + } + if ($b < false) { + assertType('*NEVER*', $b); + } + if ($b <= false) { + assertType('false|null', $b); + } - public function bool(?bool $b): void - { - if ($b > false) { - assertType('true', $b); - } - if ($b >= false) { - assertType('bool|null', $b); - } - if ($b < false) { - assertType('*NEVER*', $b); - } - if ($b <= false) { - assertType('false|null', $b); - } + if ($b > true) { + assertType('*NEVER*', $b); + } + if ($b >= true) { + assertType('true', $b); + } + if ($b < true) { + assertType('false|null', $b); + } + if ($b <= true) { + assertType('bool|null', $b); + } + } - if ($b > true) { - assertType('*NEVER*', $b); - } - if ($b >= true) { - assertType('true', $b); - } - if ($b < true) { - assertType('false|null', $b); - } - if ($b <= true) { - assertType('bool|null', $b); - } - } + public function string(?string $s): void + { + if ($s < '') { + assertType('*NEVER*', $s); + } + if ($s <= '') { + assertType('string|null', $s); // Would be nice to have ''|null + } + } - public function string(?string $s): void - { - if ($s < '') { - assertType('*NEVER*', $s); - } - if ($s <= '') { - assertType('string|null', $s); // Would be nice to have ''|null - } - } + public function intPositive10(?int $i, ?float $f): void + { + if ($i > 10) { + assertType('int<11, max>', $i); + } + if ($i >= 10) { + assertType('int<10, max>', $i); + } + if ($i < 10) { + assertType('int|null', $i); + } + if ($i <= 10) { + assertType('int|null', $i); + } - public function intPositive10(?int $i, ?float $f): void - { - if ($i > 10) { - assertType('int<11, max>', $i); - } - if ($i >= 10) { - assertType('int<10, max>', $i); - } - if ($i < 10) { - assertType('int|null', $i); - } - if ($i <= 10) { - assertType('int|null', $i); - } + if ($f > 10) { + assertType('float', $f); + } + if ($f >= 10) { + assertType('float', $f); + } + if ($f < 10) { + assertType('float|null', $f); + } + if ($f <= 10) { + assertType('float|null', $f); + } + } - if ($f > 10) { - assertType('float', $f); - } - if ($f >= 10) { - assertType('float', $f); - } - if ($f < 10) { - assertType('float|null', $f); - } - if ($f <= 10) { - assertType('float|null', $f); - } - } + public function intNegative10(?int $i, ?float $f): void + { + if ($i > -10) { + assertType('int<-9, max>', $i); + } + if ($i >= -10) { + assertType('int<-10, max>', $i); + } + if ($i < -10) { + assertType('int|null', $i); + } + if ($i <= -10) { + assertType('int|null', $i); + } - public function intNegative10(?int $i, ?float $f): void - { - if ($i > -10) { - assertType('int<-9, max>', $i); - } - if ($i >= -10) { - assertType('int<-10, max>', $i); - } - if ($i < -10) { - assertType('int|null', $i); - } - if ($i <= -10) { - assertType('int|null', $i); - } + if ($f > -10) { + assertType('float', $f); + } + if ($f >= -10) { + assertType('float', $f); + } + if ($f < -10) { + assertType('float|null', $f); + } + if ($f <= -10) { + assertType('float|null', $f); + } + } - if ($f > -10) { - assertType('float', $f); - } - if ($f >= -10) { - assertType('float', $f); - } - if ($f < -10) { - assertType('float|null', $f); - } - if ($f <= -10) { - assertType('float|null', $f); - } - } + public function intZero(?int $i, ?float $f): void + { + if ($i > 0) { + assertType('int<1, max>', $i); + } + if ($i >= 0) { + assertType('int<0, max>|null', $i); + } + if ($i < 0) { + assertType('int', $i); + } + if ($i <= 0) { + assertType('int|null', $i); + } - public function intZero(?int $i, ?float $f): void - { - if ($i > 0) { - assertType('int<1, max>', $i); - } - if ($i >= 0) { - assertType('int<0, max>|null', $i); - } - if ($i < 0) { - assertType('int', $i); - } - if ($i <= 0) { - assertType('int|null', $i); - } + if ($f > 0) { + assertType('float', $f); + } + if ($f >= 0) { + assertType('float|null', $f); + } + if ($f < 0) { + assertType('float', $f); + } + if ($f <= 0) { + assertType('float|null', $f); + } + } - if ($f > 0) { - assertType('float', $f); - } - if ($f >= 0) { - assertType('float|null', $f); - } - if ($f < 0) { - assertType('float', $f); - } - if ($f <= 0) { - assertType('float|null', $f); - } - } + public function float10(?int $i): void + { + if ($i > 10.0) { + assertType('int<11, max>', $i); + } + if ($i >= 10.0) { + assertType('int<10, max>', $i); + } + if ($i < 10.0) { + assertType('int|null', $i); + } + if ($i <= 10.0) { + assertType('int|null', $i); + } - public function float10(?int $i): void - { - if ($i > 10.0) { - assertType('int<11, max>', $i); - } - if ($i >= 10.0) { - assertType('int<10, max>', $i); - } - if ($i < 10.0) { - assertType('int|null', $i); - } - if ($i <= 10.0) { - assertType('int|null', $i); - } + if ($i > 10.1) { + assertType('int<11, max>', $i); + } + if ($i >= 10.1) { + assertType('int<11, max>', $i); + } + if ($i < 10.1) { + assertType('int|null', $i); + } + if ($i <= 10.1) { + assertType('int|null', $i); + } + } - if ($i > 10.1) { - assertType('int<11, max>', $i); - } - if ($i >= 10.1) { - assertType('int<11, max>', $i); - } - if ($i < 10.1) { - assertType('int|null', $i); - } - if ($i <= 10.1) { - assertType('int|null', $i); - } - } + public function floatZero(?int $i): void + { + if ($i > 0.0) { + assertType('int<1, max>', $i); + } + if ($i >= 0.0) { + assertType('int<0, max>|null', $i); + } + if ($i < 0.0) { + assertType('int', $i); + } + if ($i <= 0.0) { + assertType('int|null', $i); + } + } - public function floatZero(?int $i): void - { - if ($i > 0.0) { - assertType('int<1, max>', $i); - } - if ($i >= 0.0) { - assertType('int<0, max>|null', $i); - } - if ($i < 0.0) { - assertType('int', $i); - } - if ($i <= 0.0) { - assertType('int|null', $i); - } - } - - public function ranges(int $a, ?int $b): void - { - if ($a >= 17 && $a <= 42) { - if ($b < $a) { - assertType('int|null', $b); - } - if ($b <= $a) { - assertType('int|null', $b); - } - if ($b > $a) { - assertType('int<18, max>', $b); - } - if ($b >= $a) { - assertType('int<17, max>', $b); - } - } - - if ($a >= -17 && $a <= 42) { - if ($b < $a) { - assertType('int|null', $b); - } - if ($b <= $a) { - assertType('int|null', $b); - } - if ($b > $a) { - assertType('int<-16, max>', $b); - } - if ($b >= $a) { - assertType('int<-17, max>|null', $b); - } - } - } + public function ranges(int $a, ?int $b): void + { + if ($a >= 17 && $a <= 42) { + if ($b < $a) { + assertType('int|null', $b); + } + if ($b <= $a) { + assertType('int|null', $b); + } + if ($b > $a) { + assertType('int<18, max>', $b); + } + if ($b >= $a) { + assertType('int<17, max>', $b); + } + } + if ($a >= -17 && $a <= 42) { + if ($b < $a) { + assertType('int|null', $b); + } + if ($b <= $a) { + assertType('int|null', $b); + } + if ($b > $a) { + assertType('int<-16, max>', $b); + } + if ($b >= $a) { + assertType('int<-17, max>|null', $b); + } + } + } } diff --git a/tests/PHPStan/Analyser/data/complex-generics-example.php b/tests/PHPStan/Analyser/data/complex-generics-example.php index 3c18573f45..63a9ac3e42 100644 --- a/tests/PHPStan/Analyser/data/complex-generics-example.php +++ b/tests/PHPStan/Analyser/data/complex-generics-example.php @@ -1,4 +1,6 @@ - $experiment - * @return TVariant - */ - public function getVariant(ExperimentInterface $experiment): VariantInterface; + /** + * @template TVariant of VariantInterface + * @param ExperimentInterface $experiment + * @return TVariant + */ + public function getVariant(ExperimentInterface $experiment): VariantInterface; } /** @@ -38,15 +40,15 @@ class SomeVariant implements VariantInterface class SomeClass { - private $variantRetriever; + private $variantRetriever; - public function __construct(VariantRetrieverInterface $variantRetriever) - { - $this->variantRetriever = $variantRetriever; - } + public function __construct(VariantRetrieverInterface $variantRetriever) + { + $this->variantRetriever = $variantRetriever; + } - public function someFunction(): void - { - assertType('ComplexGenericsExample\SomeVariant', $this->variantRetriever->getVariant(new SomeExperiment())); - } + public function someFunction(): void + { + assertType('ComplexGenericsExample\SomeVariant', $this->variantRetriever->getVariant(new SomeExperiment())); + } } diff --git a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php index e13d22e3a0..4611ce85ae 100644 --- a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php +++ b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php @@ -8,23 +8,21 @@ class Foo { + public function doFoo(array $a): void + { + foreach ($a as $val) { + $foo = 1; + } - public function doFoo(array $a): void - { - foreach ($a as $val) { - $foo = 1; - } - - assertType('array', $a); - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - - if (count($a) > 0) { - assertType('array&nonEmpty', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } else { - assertType('array()', $a); - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } - } + assertType('array', $a); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + if (count($a) > 0) { + assertType('array&nonEmpty', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } else { + assertType('array()', $a); + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } + } } diff --git a/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php b/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php index 317c91c7b9..7f6edd8c3b 100644 --- a/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php +++ b/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php @@ -7,42 +7,39 @@ class Foo { + public const SOME_CONSTANT = 1; + public const SOME_OTHER_CONSTANT = 2; - public const SOME_CONSTANT = 1; - public const SOME_OTHER_CONSTANT = 2; - - /** - * @param 'foo'|'bar' $one - * @param self::SOME_* $two - * @param self::SOME_OTHER_CONSTANT $three - * @param \ConstExprPhpDocType\Foo::SOME_CONSTANT $four - * @param Rec::LEAVES_ONLY $five - * @param 1.0 $six - * @param 234 $seven - * @param self::SOME_OTHER_* $eight - * @param self::* $nine - */ - public function doFoo( - $one, - $two, - $three, - $four, - $five, - $six, - $seven, - $eight, - $nine - ) - { - assertType("'bar'|'foo'", $one); - assertType('1|2', $two); - assertType('2', $three); - assertType('1', $four); - assertType('0', $five); - assertType('1.0', $six); - assertType('234', $seven); - assertType('2', $eight); - assertType('1|2', $nine); - } - + /** + * @param 'foo'|'bar' $one + * @param self::SOME_* $two + * @param self::SOME_OTHER_CONSTANT $three + * @param \ConstExprPhpDocType\Foo::SOME_CONSTANT $four + * @param Rec::LEAVES_ONLY $five + * @param 1.0 $six + * @param 234 $seven + * @param self::SOME_OTHER_* $eight + * @param self::* $nine + */ + public function doFoo( + $one, + $two, + $three, + $four, + $five, + $six, + $seven, + $eight, + $nine + ) { + assertType("'bar'|'foo'", $one); + assertType('1|2', $two); + assertType('2', $three); + assertType('1', $four); + assertType('0', $five); + assertType('1.0', $six); + assertType('234', $seven); + assertType('2', $eight); + assertType('1|2', $nine); + } } diff --git a/tests/PHPStan/Analyser/data/const-in-functions-namespaced.php b/tests/PHPStan/Analyser/data/const-in-functions-namespaced.php index 55e9ebbe09..1a76b8de44 100644 --- a/tests/PHPStan/Analyser/data/const-in-functions-namespaced.php +++ b/tests/PHPStan/Analyser/data/const-in-functions-namespaced.php @@ -16,29 +16,29 @@ assertType('\'bar\'', \ConstInFunctions\ANOTHER_NAME); if (rand(0, 1)) { - define('CONDITIONAL', true); + define('CONDITIONAL', true); } else { - define('CONDITIONAL', false); + define('CONDITIONAL', false); } assertType('bool', CONDITIONAL); assertType('bool', \CONDITIONAL); function () { - assertType('\'resized_images\'', TABLE_NAME); - assertType('\'foo\'', \ANOTHER_NAME); - assertType('\'bar\'', ANOTHER_NAME); - assertType('\'resized_images\'', \ConstInFunctions\TABLE_NAME); - assertType('\'bar\'', \ConstInFunctions\ANOTHER_NAME); - - if (CONDITIONAL) { - assertType('true', CONDITIONAL); - assertType('true', \CONDITIONAL); - } else { - assertType('false', CONDITIONAL); - assertType('false', \CONDITIONAL); - } - - assertType('bool', CONDITIONAL); - assertType('bool', \CONDITIONAL); + assertType('\'resized_images\'', TABLE_NAME); + assertType('\'foo\'', \ANOTHER_NAME); + assertType('\'bar\'', ANOTHER_NAME); + assertType('\'resized_images\'', \ConstInFunctions\TABLE_NAME); + assertType('\'bar\'', \ConstInFunctions\ANOTHER_NAME); + + if (CONDITIONAL) { + assertType('true', CONDITIONAL); + assertType('true', \CONDITIONAL); + } else { + assertType('false', CONDITIONAL); + assertType('false', \CONDITIONAL); + } + + assertType('bool', CONDITIONAL); + assertType('bool', \CONDITIONAL); }; diff --git a/tests/PHPStan/Analyser/data/const-in-functions.php b/tests/PHPStan/Analyser/data/const-in-functions.php index ab1ddaf8b0..f96984a382 100644 --- a/tests/PHPStan/Analyser/data/const-in-functions.php +++ b/tests/PHPStan/Analyser/data/const-in-functions.php @@ -11,8 +11,8 @@ assertType('\'foo\'', \ANOTHER_NAME); function () { - assertType('\'resized_images\'', TABLE_NAME); - assertType('\'resized_images\'', \TABLE_NAME); - assertType('\'foo\'', ANOTHER_NAME); - assertType('\'foo\'', \ANOTHER_NAME); + assertType('\'resized_images\'', TABLE_NAME); + assertType('\'resized_images\'', \TABLE_NAME); + assertType('\'foo\'', ANOTHER_NAME); + assertType('\'foo\'', \ANOTHER_NAME); }; diff --git a/tests/PHPStan/Analyser/data/constant-types-duplicate-condition.php b/tests/PHPStan/Analyser/data/constant-types-duplicate-condition.php index f0953e4890..b08c7685d1 100644 --- a/tests/PHPStan/Analyser/data/constant-types-duplicate-condition.php +++ b/tests/PHPStan/Analyser/data/constant-types-duplicate-condition.php @@ -4,29 +4,27 @@ class Foo { + public function doFoo(int $a, int $b) + { + $c = 0; - public function doFoo(int $a, int $b) - { - $c = 0; + if ($c === $a && $c === $b) { + 'inCondition'; + return +1; + } - if ($c === $a && $c === $b) { - 'inCondition'; - return +1; - } + 'afterFirst'; - 'afterFirst'; + if ($c === $a) { + return -1; + } - if ($c === $a) { - return -1; - } + 'afterSecond'; - 'afterSecond'; - - if ($c === $b) { - return +1; - } - - 'afterThird'; - } + if ($c === $b) { + return +1; + } + 'afterThird'; + } } diff --git a/tests/PHPStan/Analyser/data/constantTypes.php b/tests/PHPStan/Analyser/data/constantTypes.php index 4564972348..a860cafd2f 100644 --- a/tests/PHPStan/Analyser/data/constantTypes.php +++ b/tests/PHPStan/Analyser/data/constantTypes.php @@ -4,115 +4,113 @@ class Foo { - - /** @var int */ - private $intProperty; - - /** @var int */ - private static $staticIntProperty; - - /** @var int */ - private $anotherIntProperty; - - /** @var int */ - private static $anotherStaticIntProperty; - - public function doFoo() - { - $postIncrement = 1; - $postIncrement++; - - $preIncrement = 1; - ++$preIncrement; - - $postDecrement = 5; - $postDecrement--; - - $preDecrement = 5; - --$preDecrement; - - $literalArray = [ - 'a' => 1, - 'b' => 5, - 'c' => 1, - 'd' => 5, - ]; - $literalArray['a']++; - $literalArray['b']--; - ++$literalArray['c']; - --$literalArray['d']; - - $nullIncremented = null; - $nullDecremented = null; - $nullIncremented++; - $nullDecremented--; - - $incrementInIf = 1; - $anotherIncrementInIf = 1; - $valueOverwrittenInIf = 1; - $anotherValueOverwrittenInIf = 10; - $appendingToArrayInBranches = []; - - $this->anotherIntProperty = 1; - self::$anotherStaticIntProperty = 1; - if (doFoo()) { - $incrementInIf++; - $anotherIncrementInIf++; - $valueOverwrittenInIf = 2; - $anotherValueOverwrittenInIf /= 2; - $appendingToArrayInBranches[] = 1; - $appendingToArrayInBranches[] = 2; - $this->anotherIntProperty++; - self::$anotherStaticIntProperty++; - } elseif (doBar()) { - $incrementInIf++; - $incrementInIf++; - $anotherIncrementInIf++; - $anotherIncrementInIf++; - } else { - $anotherIncrementInIf++; - } - - $incrementInForLoop = 1; - $valueOverwrittenInForLoop = 1; - $arrayOverwrittenInForLoop = [ - 'a' => 1, - 'b' => 'foo', - ]; - - $this->intProperty = 1; - self::$staticIntProperty = 1; - for ($i = 0; $i < 10; $i++) { - $incrementInForLoop++; - $valueOverwrittenInForLoop = 2; - $arrayOverwrittenInForLoop['a']++; - $arrayOverwrittenInForLoop['b'] = 'bar'; - $this->intProperty++; - self::$staticIntProperty++; - } - - $intProperty = $this->intProperty; - $staticIntProperty = self::$staticIntProperty; - $anotherIntProperty = $this->anotherIntProperty; - $anotherStaticIntProperty = self::$anotherStaticIntProperty; - - $variableIncrementedInClosurePassedByReference = 0; - $anotherVariableIncrementedInClosure = 0; - $yetAnotherVariableInClosurePassedByReference = 0; - function () use (&$variableIncrementedInClosurePassedByReference, $anotherVariableIncrementedInClosure, &$yetAnotherVariableInClosurePassedByReference) { - $variableIncrementedInClosurePassedByReference++; - $anotherVariableIncrementedInClosure++; - $yetAnotherVariableInClosurePassedByReference = 1; - }; - - $variableIncrementedInFinally = 0; - try { - doFoo(); - } finally { - $variableIncrementedInFinally++; - } - - die; - } - + /** @var int */ + private $intProperty; + + /** @var int */ + private static $staticIntProperty; + + /** @var int */ + private $anotherIntProperty; + + /** @var int */ + private static $anotherStaticIntProperty; + + public function doFoo() + { + $postIncrement = 1; + $postIncrement++; + + $preIncrement = 1; + ++$preIncrement; + + $postDecrement = 5; + $postDecrement--; + + $preDecrement = 5; + --$preDecrement; + + $literalArray = [ + 'a' => 1, + 'b' => 5, + 'c' => 1, + 'd' => 5, + ]; + $literalArray['a']++; + $literalArray['b']--; + ++$literalArray['c']; + --$literalArray['d']; + + $nullIncremented = null; + $nullDecremented = null; + $nullIncremented++; + $nullDecremented--; + + $incrementInIf = 1; + $anotherIncrementInIf = 1; + $valueOverwrittenInIf = 1; + $anotherValueOverwrittenInIf = 10; + $appendingToArrayInBranches = []; + + $this->anotherIntProperty = 1; + self::$anotherStaticIntProperty = 1; + if (doFoo()) { + $incrementInIf++; + $anotherIncrementInIf++; + $valueOverwrittenInIf = 2; + $anotherValueOverwrittenInIf /= 2; + $appendingToArrayInBranches[] = 1; + $appendingToArrayInBranches[] = 2; + $this->anotherIntProperty++; + self::$anotherStaticIntProperty++; + } elseif (doBar()) { + $incrementInIf++; + $incrementInIf++; + $anotherIncrementInIf++; + $anotherIncrementInIf++; + } else { + $anotherIncrementInIf++; + } + + $incrementInForLoop = 1; + $valueOverwrittenInForLoop = 1; + $arrayOverwrittenInForLoop = [ + 'a' => 1, + 'b' => 'foo', + ]; + + $this->intProperty = 1; + self::$staticIntProperty = 1; + for ($i = 0; $i < 10; $i++) { + $incrementInForLoop++; + $valueOverwrittenInForLoop = 2; + $arrayOverwrittenInForLoop['a']++; + $arrayOverwrittenInForLoop['b'] = 'bar'; + $this->intProperty++; + self::$staticIntProperty++; + } + + $intProperty = $this->intProperty; + $staticIntProperty = self::$staticIntProperty; + $anotherIntProperty = $this->anotherIntProperty; + $anotherStaticIntProperty = self::$anotherStaticIntProperty; + + $variableIncrementedInClosurePassedByReference = 0; + $anotherVariableIncrementedInClosure = 0; + $yetAnotherVariableInClosurePassedByReference = 0; + function () use (&$variableIncrementedInClosurePassedByReference, $anotherVariableIncrementedInClosure, &$yetAnotherVariableInClosurePassedByReference) { + $variableIncrementedInClosurePassedByReference++; + $anotherVariableIncrementedInClosure++; + $yetAnotherVariableInClosurePassedByReference = 1; + }; + + $variableIncrementedInFinally = 0; + try { + doFoo(); + } finally { + $variableIncrementedInFinally++; + } + + die; + } } diff --git a/tests/PHPStan/Analyser/data/constants.php b/tests/PHPStan/Analyser/data/constants.php index 1c5074b234..2500822ce1 100644 --- a/tests/PHPStan/Analyser/data/constants.php +++ b/tests/PHPStan/Analyser/data/constants.php @@ -7,5 +7,5 @@ define('BAR_CONSTANT', 'bar'); if (defined('BAZ_CONSTANT')) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/count-type.php b/tests/PHPStan/Analyser/data/count-type.php index 5f99ef117e..e613dcc06c 100644 --- a/tests/PHPStan/Analyser/data/count-type.php +++ b/tests/PHPStan/Analyser/data/count-type.php @@ -6,15 +6,12 @@ class Foo { - - /** - * @param non-empty-array $nonEmpty - */ - public function doFoo( - array $nonEmpty - ) - { - assertType('int<1, max>', count($nonEmpty)); - } - + /** + * @param non-empty-array $nonEmpty + */ + public function doFoo( + array $nonEmpty + ) { + assertType('int<1, max>', count($nonEmpty)); + } } diff --git a/tests/PHPStan/Analyser/data/custom-function-in-signature-map.php b/tests/PHPStan/Analyser/data/custom-function-in-signature-map.php index f79aea7dd3..d6ad4bc33f 100644 --- a/tests/PHPStan/Analyser/data/custom-function-in-signature-map.php +++ b/tests/PHPStan/Analyser/data/custom-function-in-signature-map.php @@ -2,5 +2,4 @@ function bcompiler_write_file(): void { - } diff --git a/tests/PHPStan/Analyser/data/declaration-warning.php b/tests/PHPStan/Analyser/data/declaration-warning.php index e8ef0765b1..1252679c9a 100644 --- a/tests/PHPStan/Analyser/data/declaration-warning.php +++ b/tests/PHPStan/Analyser/data/declaration-warning.php @@ -8,20 +8,14 @@ class Foo { - - public function doFoo(): void - { - - } - + public function doFoo(): void + { + } } class Bar extends Foo { - - public function doFoo(int $i): void - { - - } - + public function doFoo(int $i): void + { + } } diff --git a/tests/PHPStan/Analyser/data/declareStrictTypes.php b/tests/PHPStan/Analyser/data/declareStrictTypes.php index 5fdb1ac881..e4a963e32a 100644 --- a/tests/PHPStan/Analyser/data/declareStrictTypes.php +++ b/tests/PHPStan/Analyser/data/declareStrictTypes.php @@ -1,3 +1,5 @@ - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - fn () => assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + fn () => $b ? assertVariableCertainty(TrinaryLogic::createYes(), $foo) : assertVariableCertainty(TrinaryLogic::createNo(), $foo); - fn () => $b ? assertVariableCertainty(TrinaryLogic::createYes(), $foo) : assertVariableCertainty(TrinaryLogic::createNo(), $foo); + fn ($b) => $b ? assertVariableCertainty(TrinaryLogic::createMaybe(), $foo) : assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - fn ($b) => $b ? assertVariableCertainty(TrinaryLogic::createMaybe(), $foo) : assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - - fn ($foo) => $b ? assertVariableCertainty(TrinaryLogic::createYes(), $foo) : assertVariableCertainty(TrinaryLogic::createYes(), $foo); - - fn ($foo) => assertVariableCertainty(TrinaryLogic::createYes(), $foo); + fn ($foo) => $b ? assertVariableCertainty(TrinaryLogic::createYes(), $foo) : assertVariableCertainty(TrinaryLogic::createYes(), $foo); + fn ($foo) => assertVariableCertainty(TrinaryLogic::createYes(), $foo); }; diff --git a/tests/PHPStan/Analyser/data/dependent-variables-type-guard-same-as-type.php b/tests/PHPStan/Analyser/data/dependent-variables-type-guard-same-as-type.php index 2a58aa0bdb..175ff91cfe 100644 --- a/tests/PHPStan/Analyser/data/dependent-variables-type-guard-same-as-type.php +++ b/tests/PHPStan/Analyser/data/dependent-variables-type-guard-same-as-type.php @@ -8,70 +8,68 @@ class Foo { + /** + * @return \Generator|mixed[]|null + */ + public function getArrayOrNull(): ?iterable + { + return null; + } - /** - * @return \Generator|mixed[]|null - */ - public function getArrayOrNull(): ?iterable - { - return null; - } + public function doFoo(): void + { + $associationData = $this->getArrayOrNull(); + if ($associationData === null) { + } else { + $itemsCounter = 0; + assertType('0', $itemsCounter); + assertType('Generator&iterable', $associationData); + foreach ($associationData as $row) { + $itemsCounter++; + assertType('int', $itemsCounter); + } - public function doFoo(): void - { - $associationData = $this->getArrayOrNull(); - if ($associationData === null) { - } else { - $itemsCounter = 0; - assertType('0', $itemsCounter); - assertType('Generator&iterable', $associationData); - foreach ($associationData as $row) { - $itemsCounter++; - assertType('int', $itemsCounter); - } + assertType('Generator&iterable', $associationData); - assertType('Generator&iterable', $associationData); + assertType('int', $itemsCounter); + } + } - assertType('int', $itemsCounter); - } - } + public function doBar(float $f, bool $b): void + { + if ($f !== 1.0) { + $foo = 'test'; + } - public function doBar(float $f, bool $b): void - { - if ($f !== 1.0) { - $foo = 'test'; - } + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + if ($f !== 1.0) { + assertType('float', $f); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); // could be Yes, but float type is not subtractable + } else { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); // could be No, but float type is not subtractable + } - if ($f !== 1.0) { - assertType('float', $f); - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); // could be Yes, but float type is not subtractable - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); // could be No, but float type is not subtractable - } + if ($f !== 2.0) { + assertType('float', $f); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } else { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } - if ($f !== 2.0) { - assertType('float', $f); - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + if ($f !== 1.0) { + assertType('float', $f); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } else { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } - if ($f !== 1.0) { - assertType('float', $f); - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } - - if ($b) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + if ($b) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } else { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } } diff --git a/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php index 498a37a7de..11b130167e 100644 --- a/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php +++ b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php @@ -6,71 +6,66 @@ class Foo { - - public function doFoo() - { - function (): void { - if (rand(0, 1)) { - assertType('int', rand(0, 1)); - } - }; - - function (): void { - if (rand(0, 1) === 0) { - assertType('int', rand(0, 1)); - } - }; - function (): void { - assertType('\'foo\'|int|int<1, max>', rand(0, 1) ?: 'foo'); - assertType('\'foo\'|int', rand(0, 1) ? rand(0, 1) : 'foo'); - }; - } - - public function doBar(): bool - { - - } - - /** @phpstan-pure */ - public function doBaz(): bool - { - - } - - /** @phpstan-impure */ - public function doLorem(): bool - { - - } - - public function doIpsum() - { - if ($this->doBar() === true) { - assertType('true', $this->doBar()); - } - - if ($this->doBaz() === true) { - assertType('true', $this->doBaz()); - } - - if ($this->doLorem() === true) { - assertType('bool', $this->doLorem()); - } - } - - public function doDolor() - { - if ($this->doBar()) { - assertType('true', $this->doBar()); - } - - if ($this->doBaz()) { - assertType('true', $this->doBaz()); - } - - if ($this->doLorem()) { - assertType('bool', $this->doLorem()); - } - } - + public function doFoo() + { + function (): void { + if (rand(0, 1)) { + assertType('int', rand(0, 1)); + } + }; + + function (): void { + if (rand(0, 1) === 0) { + assertType('int', rand(0, 1)); + } + }; + function (): void { + assertType('\'foo\'|int|int<1, max>', rand(0, 1) ?: 'foo'); + assertType('\'foo\'|int', rand(0, 1) ? rand(0, 1) : 'foo'); + }; + } + + public function doBar(): bool + { + } + + /** @phpstan-pure */ + public function doBaz(): bool + { + } + + /** @phpstan-impure */ + public function doLorem(): bool + { + } + + public function doIpsum() + { + if ($this->doBar() === true) { + assertType('true', $this->doBar()); + } + + if ($this->doBaz() === true) { + assertType('true', $this->doBaz()); + } + + if ($this->doLorem() === true) { + assertType('bool', $this->doLorem()); + } + } + + public function doDolor() + { + if ($this->doBar()) { + assertType('true', $this->doBar()); + } + + if ($this->doBaz()) { + assertType('true', $this->doBaz()); + } + + if ($this->doLorem()) { + assertType('bool', $this->doLorem()); + } + } } diff --git a/tests/PHPStan/Analyser/data/do-while-loop-variables.php b/tests/PHPStan/Analyser/data/do-while-loop-variables.php index b6b33d60ce..753c7754ac 100644 --- a/tests/PHPStan/Analyser/data/do-while-loop-variables.php +++ b/tests/PHPStan/Analyser/data/do-while-loop-variables.php @@ -3,53 +3,53 @@ namespace LoopVariables; function () { - $foo = null; - $i = 0; - $nullableVal = null; - $falseOrObject = false; - $anotherFalseOrObject = false; - do { - 'begin'; - $foo = new Foo(); - 'afterAssign'; - - if ($nullableVal === null) { - 'nullableValIf'; - $nullableVal = 1; - } else { - $nullableVal *= 10; - 'nullableValElse'; - } - - if ($anotherFalseOrObject === false) { - $anotherFalseOrObject = new Foo(); - } - - if (doFoo()) { - break; - } - - if ($falseOrObject === false) { - $falseOrObject = new Foo(); - } - - if (something()) { - $foo = new Bar(); - break; - } - if (something()) { - $foo = new Baz(); - return; - } - if (something()) { - $foo = new Lorem(); - continue; - } - - $i++; - - 'end'; - } while (doFoo() && $i++ < 10); - - 'afterLoop'; + $foo = null; + $i = 0; + $nullableVal = null; + $falseOrObject = false; + $anotherFalseOrObject = false; + do { + 'begin'; + $foo = new Foo(); + 'afterAssign'; + + if ($nullableVal === null) { + 'nullableValIf'; + $nullableVal = 1; + } else { + $nullableVal *= 10; + 'nullableValElse'; + } + + if ($anotherFalseOrObject === false) { + $anotherFalseOrObject = new Foo(); + } + + if (doFoo()) { + break; + } + + if ($falseOrObject === false) { + $falseOrObject = new Foo(); + } + + if (something()) { + $foo = new Bar(); + break; + } + if (something()) { + $foo = new Baz(); + return; + } + if (something()) { + $foo = new Lorem(); + continue; + } + + $i++; + + 'end'; + } while (doFoo() && $i++ < 10); + + 'afterLoop'; }; diff --git a/tests/PHPStan/Analyser/data/dynamic-constant.php b/tests/PHPStan/Analyser/data/dynamic-constant.php index 2219d7698c..3c640da977 100644 --- a/tests/PHPStan/Analyser/data/dynamic-constant.php +++ b/tests/PHPStan/Analyser/data/dynamic-constant.php @@ -7,17 +7,17 @@ class DynamicConstantClass { - const DYNAMIC_CONSTANT_IN_CLASS = 'abcdef'; - const PURE_CONSTANT_IN_CLASS = 'abc123def'; + public const DYNAMIC_CONSTANT_IN_CLASS = 'abcdef'; + public const PURE_CONSTANT_IN_CLASS = 'abc123def'; } class NoDynamicConstantClass { - // constant name is same as in DynamicConstantClass, just to test - const DYNAMIC_CONSTANT_IN_CLASS = 'xyz'; + // constant name is same as in DynamicConstantClass, just to test + public const DYNAMIC_CONSTANT_IN_CLASS = 'xyz'; - private function rip() - { - die; - } + private function rip() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php index 2c35369686..7fb6ab81ba 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php @@ -4,26 +4,21 @@ interface Collection extends \Traversable { - - public function getSelf(); - + public function getSelf(); } class Foo { - - public function getSelf() - { - - } - - /** - * @param Collection|Foo[] $collection - * @param Collection|Foo $collectionOrFoo - */ - public function doFoo($collection, $collectionOrFoo) - { - die; - } - + public function getSelf() + { + } + + /** + * @param Collection|Foo[] $collection + * @param Collection|Foo $collectionOrFoo + */ + public function doFoo($collection, $collectionOrFoo) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php index e32d36e329..76b3d01193 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php @@ -4,67 +4,54 @@ class EntityManager { - - public function getByPrimary(string $className, int $id): Entity - { - return new $className(); - } - - public static function createManagerForEntity(string $className): self - { - - } - + public function getByPrimary(string $className, int $id): Entity + { + return new $className(); + } + + public static function createManagerForEntity(string $className): self + { + } } class InheritedEntityManager extends EntityManager { - } class ComponentContainer implements \ArrayAccess { + public function offsetExists($offset) + { + } - public function offsetExists($offset) - { - - } - - public function offsetGet($offset): Entity - { - - } + public function offsetGet($offset): Entity + { + } - public function offsetSet($offset, $value) - { - - } - - public function offsetUnset($offset) - { - - } + public function offsetSet($offset, $value) + { + } + public function offsetUnset($offset) + { + } } class Foo { - - public function __construct() - { - } - - public function doFoo() - { - $em = new EntityManager(); - $iem = new InheritedEntityManager(); - $container = new ComponentContainer(); - die; - } - + public function __construct() + { + } + + public function doFoo() + { + $em = new EntityManager(); + $iem = new InheritedEntityManager(); + $container = new ComponentContainer(); + die; + } } class FooWithoutConstructor { - } diff --git a/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php b/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php index 9d4aaf48a3..21ee8c9adf 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php @@ -15,109 +15,103 @@ class MethodThrowTypeExtension implements DynamicMethodThrowTypeExtension { - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'throwOrNot'; - } - - public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type - { - if (count($methodCall->args) < 1) { - return $methodReflection->getThrowType(); - } - - $argType = $scope->getType($methodCall->args[0]->value); - if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { - return $methodReflection->getThrowType(); - } - - return null; - } - + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'throwOrNot'; + } + + public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->args) < 1) { + return $methodReflection->getThrowType(); + } + + $argType = $scope->getType($methodCall->args[0]->value); + if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { + return $methodReflection->getThrowType(); + } + + return null; + } } class StaticMethodThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'staticThrowOrNot'; - } - - public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type - { - if (count($methodCall->args) < 1) { - return $methodReflection->getThrowType(); - } - - $argType = $scope->getType($methodCall->args[0]->value); - if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { - return $methodReflection->getThrowType(); - } - - return null; - } - + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getDeclaringClass()->getName() === Foo::class && $methodReflection->getName() === 'staticThrowOrNot'; + } + + public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->args) < 1) { + return $methodReflection->getThrowType(); + } + + $argType = $scope->getType($methodCall->args[0]->value); + if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { + return $methodReflection->getThrowType(); + } + + return null; + } } class Foo { - - /** @throws \Exception */ - public function throwOrNot(bool $need): int - { - if ($need) { - throw new \Exception(); - } - - return 1; - } - - /** @throws \Exception */ - public static function staticThrowOrNot(bool $need): int - { - if ($need) { - throw new \Exception(); - } - - return 1; - } - - public function doFoo1() - { - try { - $result = $this->throwOrNot(true); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $result); - } - } - - public function doFoo2() - { - try { - $result = $this->throwOrNot(false); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $result); - } - } - - public function doFoo3() - { - try { - $result = self::staticThrowOrNot(true); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $result); - } - } - - public function doFoo4() - { - try { - $result = self::staticThrowOrNot(false); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $result); - } - } - + /** @throws \Exception */ + public function throwOrNot(bool $need): int + { + if ($need) { + throw new \Exception(); + } + + return 1; + } + + /** @throws \Exception */ + public static function staticThrowOrNot(bool $need): int + { + if ($need) { + throw new \Exception(); + } + + return 1; + } + + public function doFoo1() + { + try { + $result = $this->throwOrNot(true); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result); + } + } + + public function doFoo2() + { + try { + $result = $this->throwOrNot(false); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $result); + } + } + + public function doFoo3() + { + try { + $result = self::staticThrowOrNot(true); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result); + } + } + + public function doFoo4() + { + try { + $result = self::staticThrowOrNot(false); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $result); + } + } } diff --git a/tests/PHPStan/Analyser/data/early-termination-defined.php b/tests/PHPStan/Analyser/data/early-termination-defined.php index d1367f1c2b..1013062851 100644 --- a/tests/PHPStan/Analyser/data/early-termination-defined.php +++ b/tests/PHPStan/Analyser/data/early-termination-defined.php @@ -4,41 +4,40 @@ class Foo { - public static function doBar() - { - throw new \Exception(); - } - - public function doFoo() - { - throw new \Exception(); - } - - /** - * @return no-return - */ - public static function doBarPhpDoc() - { - throw new \Exception(); - } - - /** - * @return never-return - */ - public function doFooPhpDoc() - { - throw new \Exception(); - } + public static function doBar() + { + throw new \Exception(); + } + + public function doFoo() + { + throw new \Exception(); + } + + /** + * @return no-return + */ + public static function doBarPhpDoc() + { + throw new \Exception(); + } + + /** + * @return never-return + */ + public function doFooPhpDoc() + { + throw new \Exception(); + } } class Bar extends Foo { - } function baz() { - throw new \Exception(); + throw new \Exception(); } /** @@ -46,7 +45,7 @@ function baz() */ function bazPhpDoc() { - throw new \Exception(); + throw new \Exception(); } /** @@ -54,5 +53,5 @@ function bazPhpDoc() */ function bazPhpDoc2() { - throw new \Exception(); + throw new \Exception(); } diff --git a/tests/PHPStan/Analyser/data/early-termination-phpdoc.php b/tests/PHPStan/Analyser/data/early-termination-phpdoc.php index bb013d05be..92d4652236 100644 --- a/tests/PHPStan/Analyser/data/early-termination-phpdoc.php +++ b/tests/PHPStan/Analyser/data/early-termination-phpdoc.php @@ -5,42 +5,42 @@ use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertVariableCertainty; -function(): void { - if (rand(0, 1)) { - Foo::doBarPhpDoc(); - } else { - $a = 1; - } - - assertVariableCertainty(TrinaryLogic::createYes(), $a); +function (): void { + if (rand(0, 1)) { + Foo::doBarPhpDoc(); + } else { + $a = 1; + } + + assertVariableCertainty(TrinaryLogic::createYes(), $a); }; -function(): void { - if (rand(0, 1)) { - (new Foo)->doFooPhpDoc(); - } else { - $a = 1; - } +function (): void { + if (rand(0, 1)) { + (new Foo())->doFooPhpDoc(); + } else { + $a = 1; + } - assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); }; -function(): void { - if (rand(0, 1)) { - bazPhpDoc(); - } else { - $a = 1; - } +function (): void { + if (rand(0, 1)) { + bazPhpDoc(); + } else { + $a = 1; + } - assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); }; -function(): void { - if (rand(0, 1)) { - bazPhpDoc2(); - } else { - $a = 1; - } +function (): void { + if (rand(0, 1)) { + bazPhpDoc2(); + } else { + $a = 1; + } - assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); }; diff --git a/tests/PHPStan/Analyser/data/early-termination.php b/tests/PHPStan/Analyser/data/early-termination.php index 4abe0fcd3a..d3bba0c499 100644 --- a/tests/PHPStan/Analyser/data/early-termination.php +++ b/tests/PHPStan/Analyser/data/early-termination.php @@ -3,20 +3,20 @@ namespace EarlyTermination; function () { - $something = rand(0, 10); - if ($something % 2 === 0) { - $var = true; - } else { - $foo = new Bar(); + $something = rand(0, 10); + if ($something % 2 === 0) { + $var = true; + } else { + $foo = new Bar(); - if ($something <= 5) { - Bar::doBar(); - } elseif ($something <= 7) { - $foo->doFoo(); - } else { - baz(); - } - } + if ($something <= 5) { + Bar::doBar(); + } elseif ($something <= 7) { + $foo->doFoo(); + } else { + baz(); + } + } - die; + die; }; diff --git a/tests/PHPStan/Analyser/data/evaluation-order.php b/tests/PHPStan/Analyser/data/evaluation-order.php index 9550698023..3e6e336261 100644 --- a/tests/PHPStan/Analyser/data/evaluation-order.php +++ b/tests/PHPStan/Analyser/data/evaluation-order.php @@ -1,12 +1,12 @@ $a */ - public function mapGet(Map $a) : void - { - assertType('int', $a->get(1)); - } - - /** @param Map $a */ - public function mapGetWithDefaultValue(Map $a) : void - { - assertType('int|null', $a->get(1, null)); - } - - /** @param Map $a */ - public function mapGetUnionType(Map $a) : void - { - assertType('int|string', $a->get(1)); - } - - /** @param Map $a */ - public function mapGetUnionTypeWithDefaultValue(Map $a) : void - { - assertType('int|string|null', $a->get(1, null)); - } - - /** @param Map $a */ - public function mapRemoveUnionType(Map $a) : void - { - assertType('int|string', $a->remove(1)); - } - - /** @param Map $a */ - public function mapRemoveUnionTypeWithDefaultValue(Map $a) : void - { - assertType('int|string|null', $a->remove(1, null)); - } - - public function mapMerge() : void - { - $a = new Map([1 => new A()]); - - assertType('Ds\Map', $a->merge(['a' => new B()])); - } - - public function mapUnion() : void - { - $a = new Map([1 => new A()]); - $b = new Map(['a' => new B()]); - - assertType('Ds\Map', $a->union($b)); - } - - public function mapXor() : void - { - $a = new Map([1 => new A()]); - $b = new Map(['a' => new B()]); - - assertType('Ds\Map', $a->xor($b)); - } - - public function setMerge() : void - { - $a = new Set([new A()]); - - assertType('Ds\Set', $a->merge([new B()])); - } - - public function setUnion() : void - { - $a = new Set([new A()]); - $b = new Set([new B()]); - - assertType('Ds\Set', $a->union($b)); - } - - public function setXor() : void - { - $a = new Set([new A()]); - $b = new Set([new B()]); - - assertType('Ds\Set', $a->xor($b)); - } + /** @param Map $a */ + public function mapGet(Map $a): void + { + assertType('int', $a->get(1)); + } + + /** @param Map $a */ + public function mapGetWithDefaultValue(Map $a): void + { + assertType('int|null', $a->get(1, null)); + } + + /** @param Map $a */ + public function mapGetUnionType(Map $a): void + { + assertType('int|string', $a->get(1)); + } + + /** @param Map $a */ + public function mapGetUnionTypeWithDefaultValue(Map $a): void + { + assertType('int|string|null', $a->get(1, null)); + } + + /** @param Map $a */ + public function mapRemoveUnionType(Map $a): void + { + assertType('int|string', $a->remove(1)); + } + + /** @param Map $a */ + public function mapRemoveUnionTypeWithDefaultValue(Map $a): void + { + assertType('int|string|null', $a->remove(1, null)); + } + + public function mapMerge(): void + { + $a = new Map([1 => new A()]); + + assertType('Ds\Map', $a->merge(['a' => new B()])); + } + + public function mapUnion(): void + { + $a = new Map([1 => new A()]); + $b = new Map(['a' => new B()]); + + assertType('Ds\Map', $a->union($b)); + } + + public function mapXor(): void + { + $a = new Map([1 => new A()]); + $b = new Map(['a' => new B()]); + + assertType('Ds\Map', $a->xor($b)); + } + + public function setMerge(): void + { + $a = new Set([new A()]); + + assertType('Ds\Set', $a->merge([new B()])); + } + + public function setUnion(): void + { + $a = new Set([new A()]); + $b = new Set([new B()]); + + assertType('Ds\Set', $a->union($b)); + } + + public function setXor(): void + { + $a = new Set([new A()]); + $b = new Set([new B()]); + + assertType('Ds\Set', $a->xor($b)); + } } /** @@ -105,13 +105,11 @@ public function setXor() : void */ abstract class Bar implements \IteratorAggregate, \Ds\Collection { - - public function doFoo() - { - foreach ($this as $key => $val) { - assertType('int', $key); - assertType('int', $val); - } - } - + public function doFoo() + { + foreach ($this as $key => $val) { + assertType('int', $key); + assertType('int', $val); + } + } } diff --git a/tests/PHPStan/Analyser/data/extending-known-class-with-check.php b/tests/PHPStan/Analyser/data/extending-known-class-with-check.php index 1f4a1bfa55..206ea5f077 100644 --- a/tests/PHPStan/Analyser/data/extending-known-class-with-check.php +++ b/tests/PHPStan/Analyser/data/extending-known-class-with-check.php @@ -4,8 +4,10 @@ if (class_exists(Bar::class)) { class Foo extends Bar - {} + { + } } else { class Foo extends \Exception - {} + { + } } diff --git a/tests/PHPStan/Analyser/data/extending-unknown-class.php b/tests/PHPStan/Analyser/data/extending-unknown-class.php index 1a7b686fb1..c578cf7756 100644 --- a/tests/PHPStan/Analyser/data/extending-unknown-class.php +++ b/tests/PHPStan/Analyser/data/extending-unknown-class.php @@ -4,5 +4,4 @@ class Foo extends Bar { - } diff --git a/tests/PHPStan/Analyser/data/extends-pdo-statement.php b/tests/PHPStan/Analyser/data/extends-pdo-statement.php index 238bc7b6c6..e0aaf1d7e0 100644 --- a/tests/PHPStan/Analyser/data/extends-pdo-statement.php +++ b/tests/PHPStan/Analyser/data/extends-pdo-statement.php @@ -4,90 +4,80 @@ class CrashOne extends \PDOStatement { - /** - * @param int $fetchMode - * @param string|object $arg2 - * @param mixed[] $arg3 - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool - { - return true; - } + /** + * @param int $fetchMode + * @param string|object $arg2 + * @param mixed[] $arg3 + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool + { + return true; + } } class CrashTwo extends \PDOStatement { - - /** - * @param int $mode - * @param mixed $params - * @return bool - */ - public function setFetchMode($mode, $params = null) - { - return true; - } - + /** + * @param int $mode + * @param mixed $params + * @return bool + */ + public function setFetchMode($mode, $params = null) + { + return true; + } } class CrashThree extends \PDOStatement { - - /** - * @param int $fetch_column - * @param int $colno - * @return bool - */ - public function setFetchMode($fetch_column, $colno = null) - { - return true; - } - + /** + * @param int $fetch_column + * @param int $colno + * @return bool + */ + public function setFetchMode($fetch_column, $colno = null) + { + return true; + } } class CrashFour extends \PDOStatement { - - /** - * @param int $fetch_class - * @param string $classname - * @param mixed[] $ctorargs - * @return bool - */ - public function setFetchMode($fetch_class, $classname = null, $ctorargs = null) - { - return true; - } - + /** + * @param int $fetch_class + * @param string $classname + * @param mixed[] $ctorargs + * @return bool + */ + public function setFetchMode($fetch_class, $classname = null, $ctorargs = null) + { + return true; + } } class CrashFive extends \PDOStatement { - - /** - * @param int $fetch_class - * @param string|object $classname - * @param mixed[] $ctorargs - * @return bool - */ - public function setFetchMode($fetch_class, $classname = null, $ctorargs = null) - { - return true; - } - + /** + * @param int $fetch_class + * @param string|object $classname + * @param mixed[] $ctorargs + * @return bool + */ + public function setFetchMode($fetch_class, $classname = null, $ctorargs = null) + { + return true; + } } class CrashSix extends \PDOStatement { - - /** - * @param int $fetch_into - * @param object $object - * @return bool - */ - public function setFetchMode($fetch_into, $object = null) - { - return true; - } - + /** + * @param int $fetch_into + * @param object $object + * @return bool + */ + public function setFetchMode($fetch_into, $object = null) + { + return true; + } } diff --git a/tests/PHPStan/Analyser/data/extra-int-types.php b/tests/PHPStan/Analyser/data/extra-int-types.php index ddf2fb868b..ac41425aae 100644 --- a/tests/PHPStan/Analyser/data/extra-int-types.php +++ b/tests/PHPStan/Analyser/data/extra-int-types.php @@ -6,21 +6,18 @@ class Foo { - - /** - * @param positive-int $positiveInt - * @param negative-int $negativeInt - */ - public function doFoo( - int $positiveInt, - int $negativeInt, - string $str - ): void - { - assertType('int<1, max>', $positiveInt); - assertType('int', $negativeInt); - assertType('false', strpos('u', $str) === -1); - assertType('true', strpos('u', $str) !== -1); - } - + /** + * @param positive-int $positiveInt + * @param negative-int $negativeInt + */ + public function doFoo( + int $positiveInt, + int $negativeInt, + string $str + ): void { + assertType('int<1, max>', $positiveInt); + assertType('int', $negativeInt); + assertType('false', strpos('u', $str) === -1); + assertType('true', strpos('u', $str) !== -1); + } } diff --git a/tests/PHPStan/Analyser/data/filterVar.php b/tests/PHPStan/Analyser/data/filterVar.php index a66c0fe1f5..a84c17de58 100644 --- a/tests/PHPStan/Analyser/data/filterVar.php +++ b/tests/PHPStan/Analyser/data/filterVar.php @@ -1,13 +1,15 @@ - $val) { + assertType('int|string', $val); + if ($key === 'foo') { + assertType('int', $val); + } else { + assertType('string', $val); + } - /** - * @param array{foo: int, bar: string} $a - */ - public function doFoo(array $a): void - { - foreach ($a as $key => $val) { - assertType('int|string', $val); - if ($key === 'foo') { - assertType('int', $val); - } else { - assertType('string', $val); - } - - if ($key === 'bar') { - assertType('string', $val); - } else { - assertType('int', $val); - } - } - } - + if ($key === 'bar') { + assertType('string', $val); + } else { + assertType('int', $val); + } + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach-loop-variables.php b/tests/PHPStan/Analyser/data/foreach-loop-variables.php index 56d2fdd400..5268f93693 100644 --- a/tests/PHPStan/Analyser/data/foreach-loop-variables.php +++ b/tests/PHPStan/Analyser/data/foreach-loop-variables.php @@ -4,85 +4,82 @@ class ForeachFoo { - - /** @var int[] */ - private $property = []; - - public function doFoo(string $s) - { - $foo = null; - $key = null; - $val = null; - $nullableVal = null; - - $this->property = []; - - $integers = []; - $i = 0; - $iterableArray = []; - if (rand(0, 1) === 0) { - $iterableArray = [1, 2, 3]; - } - $falseOrObject = false; - foreach ($iterableArray as $key => $val) { - 'begin'; - $foo = new Foo(); - 'afterAssign'; - - if ($nullableVal === null) { - 'nullableValIf'; - $nullableVal = 1; - } else { - $nullableVal *= 10; - 'nullableValElse'; - } - - if ($falseOrObject === false) { - $falseOrObject = new Foo(); - } - - $foo && $i++; - - $nullableInt = $val; - if (rand(0, 1) === 1) { - $nullableInt = null; - } - - if (something()) { - $foo = new Bar(); - break; - } - if (something()) { - $foo = new Baz(); - return; - } - if (something()) { - $foo = new Lorem(); - continue; - } - - if ($nullableInt === null) { - continue; - } - - if (isset($this->property[$s])) { - continue; - } - - $this->property[$s] = $val; - - $integers[] = $nullableInt; - - 'end'; - } - - $emptyForeachKey = null; - $emptyForeachVal = null; - foreach ($iterableArray as $emptyForeachKey => $emptyForeachVal) { - - } - - 'afterLoop'; - } - + /** @var int[] */ + private $property = []; + + public function doFoo(string $s) + { + $foo = null; + $key = null; + $val = null; + $nullableVal = null; + + $this->property = []; + + $integers = []; + $i = 0; + $iterableArray = []; + if (rand(0, 1) === 0) { + $iterableArray = [1, 2, 3]; + } + $falseOrObject = false; + foreach ($iterableArray as $key => $val) { + 'begin'; + $foo = new Foo(); + 'afterAssign'; + + if ($nullableVal === null) { + 'nullableValIf'; + $nullableVal = 1; + } else { + $nullableVal *= 10; + 'nullableValElse'; + } + + if ($falseOrObject === false) { + $falseOrObject = new Foo(); + } + + $foo && $i++; + + $nullableInt = $val; + if (rand(0, 1) === 1) { + $nullableInt = null; + } + + if (something()) { + $foo = new Bar(); + break; + } + if (something()) { + $foo = new Baz(); + return; + } + if (something()) { + $foo = new Lorem(); + continue; + } + + if ($nullableInt === null) { + continue; + } + + if (isset($this->property[$s])) { + continue; + } + + $this->property[$s] = $val; + + $integers[] = $nullableInt; + + 'end'; + } + + $emptyForeachKey = null; + $emptyForeachVal = null; + foreach ($iterableArray as $emptyForeachKey => $emptyForeachVal) { + } + + 'afterLoop'; + } } diff --git a/tests/PHPStan/Analyser/data/foreach/array-object-type.php b/tests/PHPStan/Analyser/data/foreach/array-object-type.php index b943fd20a6..5972a3f09c 100644 --- a/tests/PHPStan/Analyser/data/foreach/array-object-type.php +++ b/tests/PHPStan/Analyser/data/foreach/array-object-type.php @@ -6,18 +6,16 @@ class Test { + public const ARRAY_CONSTANT = [0, 1, 2, 3]; + public const MIXED_CONSTANT = [0, 'foo']; - const ARRAY_CONSTANT = [0, 1, 2, 3]; - const MIXED_CONSTANT = [0, 'foo']; - - public function doFoo() - { - /** @var Foo[] $foos */ - $foos = foos(); - - foreach ($foos as $foo) { - die; - } - } + public function doFoo() + { + /** @var Foo[] $foos */ + $foos = foos(); + foreach ($foos as $foo) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-complex-value-type.php b/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-complex-value-type.php index 1d5f8df694..6a6a185f03 100644 --- a/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-complex-value-type.php +++ b/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-complex-value-type.php @@ -4,15 +4,13 @@ class Foo { - - /** - * @param iterable $list - */ - public function doFoo(iterable $list) - { - foreach ($list as $value) { - die; - } - } - + /** + * @param iterable $list + */ + public function doFoo(iterable $list) + { + foreach ($list as $value) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-specified-key-type.php b/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-specified-key-type.php index 3999ca159e..210f9b6071 100644 --- a/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-specified-key-type.php +++ b/tests/PHPStan/Analyser/data/foreach/foreach-iterable-with-specified-key-type.php @@ -4,15 +4,13 @@ class Foo { - - /** - * @param iterable $list - */ - public function doFoo(iterable $list) - { - foreach ($list as $key => $value) { - die; - } - } - + /** + * @param iterable $list + */ + public function doFoo(iterable $list) + { + foreach ($list as $key => $value) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach/foreach-with-complex-value-type.php b/tests/PHPStan/Analyser/data/foreach/foreach-with-complex-value-type.php index 75a863d800..29441d3f41 100644 --- a/tests/PHPStan/Analyser/data/foreach/foreach-with-complex-value-type.php +++ b/tests/PHPStan/Analyser/data/foreach/foreach-with-complex-value-type.php @@ -4,15 +4,13 @@ class Foo { - - /** - * @param (float|self)[] $list - */ - public function doFoo(array $list) - { - foreach ($list as $value) { - die; - } - } - + /** + * @param (float|self)[] $list + */ + public function doFoo(array $list) + { + foreach ($list as $value) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach/foreach-with-specified-key-type.php b/tests/PHPStan/Analyser/data/foreach/foreach-with-specified-key-type.php index fbd291a4c3..9ed3047c04 100644 --- a/tests/PHPStan/Analyser/data/foreach/foreach-with-specified-key-type.php +++ b/tests/PHPStan/Analyser/data/foreach/foreach-with-specified-key-type.php @@ -4,15 +4,13 @@ class Foo { - - /** - * @param array $list - */ - public function doFoo(array $list) - { - foreach ($list as $key => $value) { - die; - } - } - + /** + * @param array $list + */ + public function doFoo(array $list) + { + foreach ($list as $key => $value) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/foreach/integer-type.php b/tests/PHPStan/Analyser/data/foreach/integer-type.php index 610c43b0be..bf96750993 100644 --- a/tests/PHPStan/Analyser/data/foreach/integer-type.php +++ b/tests/PHPStan/Analyser/data/foreach/integer-type.php @@ -6,5 +6,5 @@ $integers = foos(); foreach ($integers as $integer) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/nested-object-type.php b/tests/PHPStan/Analyser/data/foreach/nested-object-type.php index f8f9508c87..a814996d33 100644 --- a/tests/PHPStan/Analyser/data/foreach/nested-object-type.php +++ b/tests/PHPStan/Analyser/data/foreach/nested-object-type.php @@ -6,7 +6,7 @@ $fooses = foos(); foreach ($fooses as $foos) { - foreach ($foos as $foo) { - die; - } + foreach ($foos as $foo) { + die; + } } diff --git a/tests/PHPStan/Analyser/data/foreach/object-type.php b/tests/PHPStan/Analyser/data/foreach/object-type.php index 429cad1a10..5f1a0fc1d6 100644 --- a/tests/PHPStan/Analyser/data/foreach/object-type.php +++ b/tests/PHPStan/Analyser/data/foreach/object-type.php @@ -4,48 +4,40 @@ interface MyKey { - } interface MyValue { - } interface MyIterator extends \Iterator { + public function key(): MyKey; - public function key(): MyKey; - - public function current(): MyValue; - + public function current(): MyValue; } interface MyIteratorAggregate extends \IteratorAggregate { - - public function getIterator(): MyIterator; - + public function getIterator(): MyIterator; } interface MyIteratorAggregateRecursive extends \IteratorAggregate { - - public function getIterator(): MyIteratorAggregateRecursive; - + public function getIterator(): MyIteratorAggregateRecursive; } function test(MyIterator $iterator, MyIteratorAggregate $iteratorAggregate, MyIteratorAggregateRecursive $iteratorAggregateRecursive) { - foreach ($iterator as $keyFromIterator => $valueFromIterator) { - 'insideFirstForeach'; - } + foreach ($iterator as $keyFromIterator => $valueFromIterator) { + 'insideFirstForeach'; + } - foreach ($iteratorAggregate as $keyFromAggregate => $valueFromAggregate) { - 'insideSecondForeach'; - } + foreach ($iteratorAggregate as $keyFromAggregate => $valueFromAggregate) { + 'insideSecondForeach'; + } - foreach ($iteratorAggregateRecursive as $keyFromRecursiveAggregate => $valueFromRecursiveAggregate) { - 'insideThirdForeach'; - } + foreach ($iteratorAggregateRecursive as $keyFromRecursiveAggregate => $valueFromRecursiveAggregate) { + 'insideThirdForeach'; + } } diff --git a/tests/PHPStan/Analyser/data/foreach/reusing-specified-variable.php b/tests/PHPStan/Analyser/data/foreach/reusing-specified-variable.php index f12683e21c..ffa442b573 100644 --- a/tests/PHPStan/Analyser/data/foreach/reusing-specified-variable.php +++ b/tests/PHPStan/Analyser/data/foreach/reusing-specified-variable.php @@ -5,9 +5,9 @@ /** @var string|null $business */ $business = doFoo(); if ($business !== null) { - return; + return; } foreach ([1, 2, 3] as $business) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-key.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-key.php index 32710246d0..b6eec4690a 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-key.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-key.php @@ -7,5 +7,5 @@ /** @var int $key */ foreach ($values as $key => $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable-2.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable-2.php index 88c7e831c5..4c8fe73fd6 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable-2.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable-2.php @@ -7,5 +7,5 @@ /** @var bool */ foreach ($values as $key => $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable.php index c64cb8e681..d1bf7bf711 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-no-variable.php @@ -7,5 +7,5 @@ /** @var bool */ foreach ($values as $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-first.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-first.php index 1b64093dcf..ed97889e49 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-first.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-first.php @@ -7,5 +7,5 @@ /** @var $value callable */ foreach ($values as $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-second.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-second.php index 6ab2eed064..48018a346e 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-second.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-second.php @@ -7,5 +7,5 @@ /** @var \stdClass $value */ foreach ($values as $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-with-reference.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-with-reference.php index fe454a139c..aebf34e914 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-with-reference.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-variable-with-reference.php @@ -7,5 +7,5 @@ /** @var string $value */ foreach ($values as &$value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/foreach/type-in-comment-wrong-variable.php b/tests/PHPStan/Analyser/data/foreach/type-in-comment-wrong-variable.php index 4922cb1d18..8b1d59227c 100644 --- a/tests/PHPStan/Analyser/data/foreach/type-in-comment-wrong-variable.php +++ b/tests/PHPStan/Analyser/data/foreach/type-in-comment-wrong-variable.php @@ -7,5 +7,5 @@ /** @var int $wrongValue */ foreach ($values as $value) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/func-call.php b/tests/PHPStan/Analyser/data/func-call.php index 4812d9c571..7161dc4e7d 100644 --- a/tests/PHPStan/Analyser/data/func-call.php +++ b/tests/PHPStan/Analyser/data/func-call.php @@ -1,3 +1,3 @@ doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } } diff --git a/tests/PHPStan/Analyser/data/functionPhpDocs-psalmPrefix.php b/tests/PHPStan/Analyser/data/functionPhpDocs-psalmPrefix.php index 60a0f87076..019d1e9845 100644 --- a/tests/PHPStan/Analyser/data/functionPhpDocs-psalmPrefix.php +++ b/tests/PHPStan/Analyser/data/functionPhpDocs-psalmPrefix.php @@ -39,41 +39,40 @@ * @psalm-return Foo */ function doFooPsalmPrefix( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null -) -{ - $fooFunctionResult = doFoo(); + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null +) { + $fooFunctionResult = doFoo(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } } diff --git a/tests/PHPStan/Analyser/data/functionPhpDocs.php b/tests/PHPStan/Analyser/data/functionPhpDocs.php index 5b3c5feaf6..e8b6e38098 100644 --- a/tests/PHPStan/Analyser/data/functionPhpDocs.php +++ b/tests/PHPStan/Analyser/data/functionPhpDocs.php @@ -39,47 +39,45 @@ * @return Foo */ function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null -) -{ - $fooFunctionResult = doFoo(); - $barFunctionResult = doBar(); + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null +) { + $fooFunctionResult = doFoo(); + $barFunctionResult = doBar(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } } function doBar(): Bar { - } diff --git a/tests/PHPStan/Analyser/data/functions.php b/tests/PHPStan/Analyser/data/functions.php index 173a4cd5f5..0d1cff0a5b 100644 --- a/tests/PHPStan/Analyser/data/functions.php +++ b/tests/PHPStan/Analyser/data/functions.php @@ -8,7 +8,7 @@ $strtotimeNow = strtotime('now'); $strtotimeInvalid = strtotime('4 qm'); -$strtotimeUnknown = strtotime(doFoo() ? 'now': '4 qm'); +$strtotimeUnknown = strtotime(doFoo() ? 'now' : '4 qm'); $strtotimeUnknown2 = strtotime($undefined); $strtotimeCrash = strtotime(); diff --git a/tests/PHPStan/Analyser/data/generalize-scope-recursive.php b/tests/PHPStan/Analyser/data/generalize-scope-recursive.php index bc85f45ce2..60d6396f68 100644 --- a/tests/PHPStan/Analyser/data/generalize-scope-recursive.php +++ b/tests/PHPStan/Analyser/data/generalize-scope-recursive.php @@ -4,25 +4,23 @@ class Foo { + public function doFoo(array $array, array $values) + { + $data = []; + foreach ($array as $val) { + foreach ($values as $val2) { + $data['foo'] = array_merge($data, $this->doBar()); + } + } - public function doFoo(array $array, array $values) - { - $data = []; - foreach ($array as $val) { - foreach ($values as $val2) { - $data['foo'] = array_merge($data, $this->doBar()); - } - } - - die; - } - - /** - * @return string[][]|int[][] - */ - private function doBar(): array - { - return []; - } + die; + } + /** + * @return string[][]|int[][] + */ + private function doBar(): array + { + return []; + } } diff --git a/tests/PHPStan/Analyser/data/generalize-scope.php b/tests/PHPStan/Analyser/data/generalize-scope.php index cc273feae5..3ce849dc9b 100644 --- a/tests/PHPStan/Analyser/data/generalize-scope.php +++ b/tests/PHPStan/Analyser/data/generalize-scope.php @@ -4,39 +4,37 @@ class Foo { - - /** @var mixed[] */ - private $vals = []; - - public function doFoo(): string - { - $statistics = []; - foreach ($this->vals as $val) { - $key = $val['key']; - $model = preg_replace('~[^\\-A-Z\\\\_]+.*$~i', '', $val['key']); - - if (!isset($statistics[$model][$key])) { - $statistics[$model][$key] = [ - 'saveCount' => 0, - 'removeCount' => 0, - 'loadCount' => 0, - 'hitCount' => 0, - ]; - } - - if (rand(0, 1)) { - $statistics[$model][$key]['saveCount']++; - } elseif (rand(0, 1)) { - $statistics[$model][$key]['removeCount']++; - } else { - $statistics[$model][$key]['loadCount']++; - if ($val['hit']) { - $statistics[$model][$key]['hitCount']++; - } - } - } - - die; - } - + /** @var mixed[] */ + private $vals = []; + + public function doFoo(): string + { + $statistics = []; + foreach ($this->vals as $val) { + $key = $val['key']; + $model = preg_replace('~[^\\-A-Z\\\\_]+.*$~i', '', $val['key']); + + if (!isset($statistics[$model][$key])) { + $statistics[$model][$key] = [ + 'saveCount' => 0, + 'removeCount' => 0, + 'loadCount' => 0, + 'hitCount' => 0, + ]; + } + + if (rand(0, 1)) { + $statistics[$model][$key]['saveCount']++; + } elseif (rand(0, 1)) { + $statistics[$model][$key]['removeCount']++; + } else { + $statistics[$model][$key]['loadCount']++; + if ($val['hit']) { + $statistics[$model][$key]['hitCount']++; + } + } + } + + die; + } } diff --git a/tests/PHPStan/Analyser/data/generic-class-string.php b/tests/PHPStan/Analyser/data/generic-class-string.php index f7cec7de62..e059fb931a 100644 --- a/tests/PHPStan/Analyser/data/generic-class-string.php +++ b/tests/PHPStan/Analyser/data/generic-class-string.php @@ -6,118 +6,124 @@ class C { - public static function f(): int { - return 0; - } + public static function f(): int + { + return 0; + } } /** * @param mixed $a */ -function testMixed($a) { - assertType('mixed~string', new $a()); - - if (is_subclass_of($a, 'DateTimeInterface')) { - assertType('class-string|DateTimeInterface', $a); - assertType('DateTimeInterface', new $a()); - } - - if (is_subclass_of($a, 'DateTimeInterface') || is_subclass_of($a, 'stdClass')) { - assertType('class-string|class-string|DateTimeInterface|stdClass', $a); - assertType('DateTimeInterface|stdClass', new $a()); - } - - if (is_subclass_of($a, C::class)) { - assertType('int', $a::f()); - } +function testMixed($a) +{ + assertType('mixed~string', new $a()); + + if (is_subclass_of($a, 'DateTimeInterface')) { + assertType('class-string|DateTimeInterface', $a); + assertType('DateTimeInterface', new $a()); + } + + if (is_subclass_of($a, 'DateTimeInterface') || is_subclass_of($a, 'stdClass')) { + assertType('class-string|class-string|DateTimeInterface|stdClass', $a); + assertType('DateTimeInterface|stdClass', new $a()); + } + + if (is_subclass_of($a, C::class)) { + assertType('int', $a::f()); + } } /** * @param object $a */ -function testObject($a) { - assertType('mixed~string', new $a()); +function testObject($a) +{ + assertType('mixed~string', new $a()); - if (is_subclass_of($a, 'DateTimeInterface')) { - assertType('DateTimeInterface', $a); - } + if (is_subclass_of($a, 'DateTimeInterface')) { + assertType('DateTimeInterface', $a); + } } /** * @param string $a */ -function testString($a) { - assertType('mixed~string', new $a()); +function testString($a) +{ + assertType('mixed~string', new $a()); - if (is_subclass_of($a, 'DateTimeInterface')) { - assertType('class-string', $a); - assertType('DateTimeInterface', new $a()); - } + if (is_subclass_of($a, 'DateTimeInterface')) { + assertType('class-string', $a); + assertType('DateTimeInterface', new $a()); + } - if (is_subclass_of($a, C::class)) { - assertType('int', $a::f()); - } + if (is_subclass_of($a, C::class)) { + assertType('int', $a::f()); + } } /** * @param string|object $a */ -function testStringObject($a) { - assertType('mixed~string', new $a()); +function testStringObject($a) +{ + assertType('mixed~string', new $a()); - if (is_subclass_of($a, 'DateTimeInterface')) { - assertType('class-string|DateTimeInterface', $a); - assertType('DateTimeInterface', new $a()); - } + if (is_subclass_of($a, 'DateTimeInterface')) { + assertType('class-string|DateTimeInterface', $a); + assertType('DateTimeInterface', new $a()); + } - if (is_subclass_of($a, C::class)) { - assertType('int', $a::f()); - } + if (is_subclass_of($a, C::class)) { + assertType('int', $a::f()); + } } /** * @param class-string<\DateTimeInterface> $a */ -function testClassString($a) { - assertType('DateTimeInterface', new $a()); +function testClassString($a) +{ + assertType('DateTimeInterface', new $a()); - if (is_subclass_of($a, 'DateTime')) { - assertType('class-string', $a); - assertType('DateTime', new $a()); - } + if (is_subclass_of($a, 'DateTime')) { + assertType('class-string', $a); + assertType('DateTime', new $a()); + } } function testClassExists(string $str) { - assertType('string', $str); - if (class_exists($str)) { - assertType('class-string', $str); - assertType('mixed~string', new $str()); - } - - $existentClass = \stdClass::class; - if (class_exists($existentClass)) { - assertType('\'stdClass\'', $existentClass); - } - - $nonexistentClass = 'NonexistentClass'; - if (class_exists($nonexistentClass)) { - assertType('\'NonexistentClass\'', $nonexistentClass); - } + assertType('string', $str); + if (class_exists($str)) { + assertType('class-string', $str); + assertType('mixed~string', new $str()); + } + + $existentClass = \stdClass::class; + if (class_exists($existentClass)) { + assertType('\'stdClass\'', $existentClass); + } + + $nonexistentClass = 'NonexistentClass'; + if (class_exists($nonexistentClass)) { + assertType('\'NonexistentClass\'', $nonexistentClass); + } } function testInterfaceExists(string $str) { - assertType('string', $str); - if (interface_exists($str)) { - assertType('class-string', $str); - } + assertType('string', $str); + if (interface_exists($str)) { + assertType('class-string', $str); + } } function testTraitExists(string $str) { - assertType('string', $str); - if (trait_exists($str)) { - assertType('class-string', $str); - } + assertType('string', $str); + if (trait_exists($str)) { + assertType('class-string', $str); + } } diff --git a/tests/PHPStan/Analyser/data/generic-parent.php b/tests/PHPStan/Analyser/data/generic-parent.php index 0d41c95d0a..a3451caf8a 100644 --- a/tests/PHPStan/Analyser/data/generic-parent.php +++ b/tests/PHPStan/Analyser/data/generic-parent.php @@ -6,12 +6,10 @@ interface Animal { - } interface Dog extends Animal { - } /** @@ -19,40 +17,41 @@ interface Dog extends Animal */ class Foo { - - /** @return T */ - public function getAnimal(): Animal - { - - } - + /** @return T */ + public function getAnimal(): Animal + { + } } /** @extends Foo */ class Bar extends Foo { - - public function doFoo() - { - assertType(Dog::class, parent::getAnimal()); - assertType(Dog::class, Foo::getAnimal()); - } - + public function doFoo() + { + assertType(Dog::class, parent::getAnimal()); + assertType(Dog::class, Foo::getAnimal()); + } } -class E {} +class E +{ +} /** * @template T of E */ -class R { - - /** @return T */ - function ret() { return $this->e; } // nonsense, to silence missing return - - function test(): void { - assertType('T of GenericParent\E (class GenericParent\R, argument)', self::ret()); - assertType('T of GenericParent\E (class GenericParent\R, argument)', $this->ret()); - assertType('T of GenericParent\E (class GenericParent\R, argument)', static::ret()); - } +class R +{ + /** @return T */ + public function ret() + { + return $this->e; + } // nonsense, to silence missing return + + public function test(): void + { + assertType('T of GenericParent\E (class GenericParent\R, argument)', self::ret()); + assertType('T of GenericParent\E (class GenericParent\R, argument)', $this->ret()); + assertType('T of GenericParent\E (class GenericParent\R, argument)', static::ret()); + } } diff --git a/tests/PHPStan/Analyser/data/generic-traits.php b/tests/PHPStan/Analyser/data/generic-traits.php index c40d45c4d4..911dd4738f 100644 --- a/tests/PHPStan/Analyser/data/generic-traits.php +++ b/tests/PHPStan/Analyser/data/generic-traits.php @@ -6,221 +6,190 @@ trait FooTrait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('GenericTraits\T', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('GenericTraits\T', $t); + } } /** @template T */ class Foo { + use FooTrait; - use FooTrait; - - public function doBar(): void - { - assertType('GenericTraits\T', $this->doFoo(1)); - } - + public function doBar(): void + { + assertType('GenericTraits\T', $this->doFoo(1)); + } } /** @template T of object */ trait BarTrait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('object', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('object', $t); + } } /** @template T */ class Bar { + use BarTrait; - use BarTrait; - - public function doBar(): void - { - assertType('object', $this->doFoo()); - } - + public function doBar(): void + { + assertType('object', $this->doFoo()); + } } /** @template T of object */ trait Bar2Trait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('object', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('object', $t); + } } /** @template U */ class Bar2 { + use Bar2Trait; - use Bar2Trait; - - public function doBar(): void - { - assertType('object', $this->doFoo()); - } - + public function doBar(): void + { + assertType('object', $this->doFoo()); + } } /** @template T of object */ trait Bar3Trait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('stdClass', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('stdClass', $t); + } } class Bar3 { + /** @use Bar3Trait<\stdClass> */ + use Bar3Trait; - /** @use Bar3Trait<\stdClass> */ - use Bar3Trait; - - public function doBar(): void - { - assertType('stdClass', $this->doFoo()); - } - + public function doBar(): void + { + assertType('stdClass', $this->doFoo()); + } } /** @template T of object */ trait Bar4Trait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('U (class GenericTraits\Bar4, argument)', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('U (class GenericTraits\Bar4, argument)', $t); + } } /** @template U */ class Bar4 { + /** @use Bar4Trait */ + use Bar4Trait; - /** @use Bar4Trait */ - use Bar4Trait; - - public function doBar(): void - { - assertType('U (class GenericTraits\Bar4, argument)', $this->doFoo()); - } - + public function doBar(): void + { + assertType('U (class GenericTraits\Bar4, argument)', $this->doFoo()); + } } /** @template T of object */ trait Bar5Trait { - - /** - * @param T $t - * @return T - */ - public function doFoo($t) - { - assertType('T (class GenericTraits\Bar5, argument)', $t); - } - + /** + * @param T $t + * @return T + */ + public function doFoo($t) + { + assertType('T (class GenericTraits\Bar5, argument)', $t); + } } /** @template T */ class Bar5 { - - /** @use Bar5Trait */ - use Bar5Trait; - - public function doBar(): void - { - assertType('T (class GenericTraits\Bar5, argument)', $this->doFoo()); - } - - // sanity checks below (is T supposed to be an argument? yes) - - /** - * @param T $t - */ - public function doBaz($t) - { - assertType('T (class GenericTraits\Bar5, argument)', $t); - } - - /** - * @return T - */ - public function returnT() - { - - } - - public function doLorem() - { - assertType('T (class GenericTraits\Bar5, argument)', $this->returnT()); - } - + /** @use Bar5Trait */ + use Bar5Trait; + + public function doBar(): void + { + assertType('T (class GenericTraits\Bar5, argument)', $this->doFoo()); + } + + // sanity checks below (is T supposed to be an argument? yes) + + /** + * @param T $t + */ + public function doBaz($t) + { + assertType('T (class GenericTraits\Bar5, argument)', $t); + } + + /** + * @return T + */ + public function returnT() + { + } + + public function doLorem() + { + assertType('T (class GenericTraits\Bar5, argument)', $this->returnT()); + } } /** @template T */ trait Bar6Trait { - - /** @param T $t */ - public function doFoo($t) - { - assertType('int', $t); - } - + /** @param T $t */ + public function doFoo($t) + { + assertType('int', $t); + } } /** @template U */ trait Bar7Trait { - - /** @use Bar6Trait */ - use Bar6Trait; - + /** @use Bar6Trait */ + use Bar6Trait; } class Bar7 { - - /** @use Bar7Trait */ - use Bar7Trait; - + /** @use Bar7Trait */ + use Bar7Trait; } diff --git a/tests/PHPStan/Analyser/data/generic-unions.php b/tests/PHPStan/Analyser/data/generic-unions.php index 7ebc5bb89b..41c1a92f3d 100644 --- a/tests/PHPStan/Analyser/data/generic-unions.php +++ b/tests/PHPStan/Analyser/data/generic-unions.php @@ -6,58 +6,55 @@ class Foo { - - /** - * @template T - * @param T|null $p - * @return T - */ - public function doFoo($p) - { - if ($p === null) { - throw new \Exception(); - } - - return $p; - } - - /** - * @template T - * @param T $p - * @return T - */ - public function doBar($p) - { - return $p; - } - - /** - * @template T - * @param T|int|float $p - * @return T - */ - public function doBaz($p) - { - return $p; - } - - /** - * @param int|string $stringOrInt - */ - public function foo( - ?string $nullableString, - $stringOrInt - ): void - { - assertType('string', $this->doFoo($nullableString)); - assertType('int|string', $this->doFoo($stringOrInt)); - - assertType('string|null', $this->doBar($nullableString)); - - assertType('int', $this->doBaz(1)); - assertType('string', $this->doBaz('foo')); - assertType('float', $this->doBaz(1.2)); - assertType('string', $this->doBaz($stringOrInt)); - } - + /** + * @template T + * @param T|null $p + * @return T + */ + public function doFoo($p) + { + if ($p === null) { + throw new \Exception(); + } + + return $p; + } + + /** + * @template T + * @param T $p + * @return T + */ + public function doBar($p) + { + return $p; + } + + /** + * @template T + * @param T|int|float $p + * @return T + */ + public function doBaz($p) + { + return $p; + } + + /** + * @param int|string $stringOrInt + */ + public function foo( + ?string $nullableString, + $stringOrInt + ): void { + assertType('string', $this->doFoo($nullableString)); + assertType('int|string', $this->doFoo($stringOrInt)); + + assertType('string|null', $this->doBar($nullableString)); + + assertType('int', $this->doBaz(1)); + assertType('string', $this->doBaz('foo')); + assertType('float', $this->doBaz(1.2)); + assertType('string', $this->doBaz($stringOrInt)); + } } diff --git a/tests/PHPStan/Analyser/data/generics-reduce-types-first.php b/tests/PHPStan/Analyser/data/generics-reduce-types-first.php index 4efa90e846..99489dfa8b 100644 --- a/tests/PHPStan/Analyser/data/generics-reduce-types-first.php +++ b/tests/PHPStan/Analyser/data/generics-reduce-types-first.php @@ -8,28 +8,25 @@ class Foo { - - /** - * @template T - * @param T|string $p - * @return T - */ - public function doFoo($p) - { - - } - - /** - * @param string[]|string $a - * @param string|string[] $b - * @param string[] $c - */ - public function doBar($a, $b, $c, string $d) - { - assertType('array', $this->doFoo($a)); - assertType('array', $this->doFoo($b)); - assertType('array', $this->doFoo($c)); - assertType('string', $this->doFoo($d)); - } - + /** + * @template T + * @param T|string $p + * @return T + */ + public function doFoo($p) + { + } + + /** + * @param string[]|string $a + * @param string|string[] $b + * @param string[] $c + */ + public function doBar($a, $b, $c, string $d) + { + assertType('array', $this->doFoo($a)); + assertType('array', $this->doFoo($b)); + assertType('array', $this->doFoo($c)); + assertType('string', $this->doFoo($d)); + } } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index bce58c6370..120d1f2723 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -16,8 +16,8 @@ */ function a($a) { - assertType('T (function PHPStan\Generics\FunctionsAssertType\a(), argument)', $a); - return $a; + assertType('T (function PHPStan\Generics\FunctionsAssertType\a(), argument)', $a); + return $a; } /** @@ -27,10 +27,10 @@ function a($a) */ function testA($int, $intFloat, $mixed) { - assertType('int', a($int)); - assertType('float|int', a($intFloat)); - assertType('DateTime', a(new \DateTime())); - assertType('mixed', a($mixed)); + assertType('int', a($int)); + assertType('float|int', a($intFloat)); + assertType('DateTime', a(new \DateTime())); + assertType('mixed', a($mixed)); } /** @@ -40,9 +40,9 @@ function testA($int, $intFloat, $mixed) */ function b($a) { - assertType('T of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\b(), argument)', $a); - assertType('T of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\b(), argument)', b($a)); - return $a; + assertType('T of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\b(), argument)', $a); + assertType('T of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\b(), argument)', b($a)); + return $a; } /** @@ -50,9 +50,9 @@ function b($a) */ function assertTypeTest($dateTimeInterface) { - assertType('DateTime', b(new \DateTime())); - assertType('DateTimeImmutable', b(new \DateTimeImmutable())); - assertType('DateTimeInterface', b($dateTimeInterface)); + assertType('DateTime', b(new \DateTime())); + assertType('DateTimeImmutable', b(new \DateTimeImmutable())); + assertType('DateTimeInterface', b($dateTimeInterface)); } /** @@ -63,7 +63,7 @@ function assertTypeTest($dateTimeInterface) */ function c($a) { - return $a; + return $a; } /** @@ -71,7 +71,7 @@ function c($a) */ function testC($arrayOfString) { - assertType('array', c($arrayOfString)); + assertType('array', c($arrayOfString)); } /** @@ -82,7 +82,7 @@ function testC($arrayOfString) */ function d($a, $b) { - return $a; + return $a; } /** @@ -92,12 +92,12 @@ function d($a, $b) */ function testD($int, $float, $intFloat) { - assertType('int', d($int, $int)); - assertType('float|int', d($int, $float)); - assertType('DateTime|int', d($int, new \DateTime())); - assertType('DateTime|float|int', d($intFloat, new \DateTime())); - assertType('array()|DateTime', d([], new \DateTime())); - assertType('array(\'blabla\' => string)|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); + assertType('int', d($int, $int)); + assertType('float|int', d($int, $float)); + assertType('DateTime|int', d($int, new \DateTime())); + assertType('DateTime|float|int', d($intFloat, new \DateTime())); + assertType('array()|DateTime', d([], new \DateTime())); + assertType('array(\'blabla\' => string)|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); } /** @@ -107,7 +107,7 @@ function testD($int, $float, $intFloat) */ function e($a) { - throw new \Exception(); + throw new \Exception(); } /** @@ -115,7 +115,7 @@ function e($a) */ function testE($int) { - assertType('int', e([[$int]])); + assertType('int', e([[$int]])); } /** @@ -129,16 +129,16 @@ function testE($int) */ function f($a, $b) { - $result = []; - assertType('array', $a); - assertType('callable(A (function PHPStan\Generics\FunctionsAssertType\f(), argument)): B (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $b); - foreach ($a as $k => $v) { - assertType('A (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $v); - $newV = $b($v); - assertType('B (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $newV); - $result[$k] = $newV; - } - return $result; + $result = []; + assertType('array', $a); + assertType('callable(A (function PHPStan\Generics\FunctionsAssertType\f(), argument)): B (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $b); + foreach ($a as $k => $v) { + assertType('A (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $v); + $newV = $b($v); + assertType('B (function PHPStan\Generics\FunctionsAssertType\f(), argument)', $newV); + $result[$k] = $newV; + } + return $result; } /** @@ -147,18 +147,18 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('array', f($arrayOfInt, function (int $a): string { - return (string)$a; - })); - assertType('array', f($arrayOfInt, function ($a): string { - return (string)$a; - })); - assertType('array', f($arrayOfInt, function ($a) { - return $a; - })); - assertType('array', f($arrayOfInt, $callableOrNull)); - assertType('array', f($arrayOfInt, null)); - assertType('array', f($arrayOfInt, '')); + assertType('array', f($arrayOfInt, function (int $a): string { + return (string)$a; + })); + assertType('array', f($arrayOfInt, function ($a): string { + return (string)$a; + })); + assertType('array', f($arrayOfInt, function ($a) { + return $a; + })); + assertType('array', f($arrayOfInt, $callableOrNull)); + assertType('array', f($arrayOfInt, null)); + assertType('array', f($arrayOfInt, '')); } /** @@ -168,7 +168,7 @@ function testF($arrayOfInt, $callableOrNull) */ function g($a) { - return [$a]; + return [$a]; } /** @@ -176,26 +176,25 @@ function g($a) */ function testG($int) { - assertType('array', g($int)); + assertType('array', g($int)); } class Foo { + /** @var static */ + public static $staticProp; - /** @var static */ - public static $staticProp; + /** @return static */ + public static function returnsStatic() + { + return new static(); + } - /** @return static */ - public static function returnsStatic() - { - return new static(); - } - - /** @return static */ - public function instanceReturnsStatic() - { - return new static(); - } + /** @return static */ + public function instanceReturnsStatic() + { + return new static(); + } } /** @@ -204,8 +203,8 @@ public function instanceReturnsStatic() */ function testReturnsStatic($foo) { - assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\testReturnsStatic(), argument)', $foo::returnsStatic()); - assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\testReturnsStatic(), argument)', $foo->instanceReturnsStatic()); + assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\testReturnsStatic(), argument)', $foo::returnsStatic()); + assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\testReturnsStatic(), argument)', $foo->instanceReturnsStatic()); } /** @@ -213,12 +212,12 @@ function testReturnsStatic($foo) */ function testArrayMap(array $listOfIntegers) { - $strings = array_map(function ($int): string { - assertType('int', $int); + $strings = array_map(function ($int): string { + assertType('int', $int); - return (string) $int; - }, $listOfIntegers); - assertType('array', $strings); + return (string) $int; + }, $listOfIntegers); + assertType('array', $strings); } /** @@ -226,12 +225,12 @@ function testArrayMap(array $listOfIntegers) */ function testArrayFilter(array $listOfIntegers) { - $integers = array_filter($listOfIntegers, function ($int): bool { - assertType('int', $int); + $integers = array_filter($listOfIntegers, function ($int): bool { + assertType('int', $int); - return true; - }); - assertType('array', $integers); + return true; + }); + assertType('array', $integers); } /** @@ -242,11 +241,11 @@ function testArrayFilter(array $listOfIntegers) */ function iterableToArray($it) { - $ret = []; - foreach ($it as $k => $v) { - $ret[$k] = $v; - } - return $ret; + $ret = []; + foreach ($it as $k => $v) { + $ret[$k] = $v; + } + return $ret; } /** @@ -254,7 +253,7 @@ function iterableToArray($it) */ function testIterable(iterable $it) { - assertType('array', iterableToArray($it)); + assertType('array', iterableToArray($it)); } /** @@ -265,14 +264,14 @@ function testIterable(iterable $it) */ function constantArray($a): array { - return [$a['a'], $a['b']]; + return [$a['a'], $a['b']]; } function testConstantArray(int $int, string $str) { - [$a, $b] = constantArray(['a' => $int, 'b' => $str, 'c' => 1]); - assertType('int', $a); - assertType('string', $b); + [$a, $b] = constantArray(['a' => $int, 'b' => $str, 'c' => 1]); + assertType('int', $a); + assertType('string', $b); } /** @@ -282,8 +281,8 @@ function testConstantArray(int $int, string $str) */ function typeHints(\DateTimeInterface $a): \DateTimeInterface { - assertType('U of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\typeHints(), argument)', $a); - return $a; + assertType('U of DateTimeInterface (function PHPStan\Generics\FunctionsAssertType\typeHints(), argument)', $a); + return $a; } /** @@ -293,8 +292,8 @@ function typeHints(\DateTimeInterface $a): \DateTimeInterface */ function typeHintsSuperType(\DateTimeInterface $a): \DateTimeInterface { - assertType('U of DateTime (function PHPStan\Generics\FunctionsAssertType\typeHintsSuperType(), argument)', $a); - return $a; + assertType('U of DateTime (function PHPStan\Generics\FunctionsAssertType\typeHintsSuperType(), argument)', $a); + return $a; } /** @@ -304,15 +303,15 @@ function typeHintsSuperType(\DateTimeInterface $a): \DateTimeInterface */ function typeHintsSubType(\DateTime $a): \DateTimeInterface { - assertType('DateTime', $a); - return $a; + assertType('DateTime', $a); + return $a; } function testTypeHints(): void { - assertType('DateTime', typeHints(new \DateTime())); - assertType('DateTime', typeHintsSuperType(new \DateTime())); - assertType('DateTimeInterface', typeHintsSubType(new \DateTime())); + assertType('DateTime', typeHints(new \DateTime())); + assertType('DateTime', typeHintsSuperType(new \DateTime())); + assertType('DateTimeInterface', typeHintsSubType(new \DateTime())); } /** @@ -323,12 +322,12 @@ function testTypeHints(): void */ function expectsException($a, $b) { - return $b; + return $b; } function testUpperBounds(\Throwable $t) { - assertType('Exception', expectsException(new \Exception(), $t)); + assertType('Exception', expectsException(new \Exception(), $t)); } /** @@ -338,12 +337,12 @@ function testUpperBounds(\Throwable $t) */ function varAnnotation($cb) { - /** @var T */ - $v = $cb(); + /** @var T */ + $v = $cb(); - assertType('T (function PHPStan\Generics\FunctionsAssertType\varAnnotation(), argument)', $v); + assertType('T (function PHPStan\Generics\FunctionsAssertType\varAnnotation(), argument)', $v); - return $v; + return $v; } /** @@ -351,35 +350,34 @@ function varAnnotation($cb) */ class C { - /** @var T */ - private $a; + /** @var T */ + private $a; - /** - * @param T $p - * @param callable $cb - */ - public function f($p, $cb) - { - assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $p); + /** + * @param T $p + * @param callable $cb + */ + public function f($p, $cb) + { + assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $p); - /** @var T */ - $v = $cb(); + /** @var T */ + $v = $cb(); - assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $v); + assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $v); - assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $this->a); + assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $this->a); - $a = new class - { - /** @return T */ - public function g() - { - throw new \Exception(); - } - }; + $a = new class() { + /** @return T */ + public function g() + { + throw new \Exception(); + } + }; - assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $a->g()); - } + assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $a->g()); + } } /** @@ -387,41 +385,40 @@ public function g() */ class A { - /** @var T */ - private $a; - - /** @var T */ - public $b; - - /** - * A::__construct() - * - * @param T $a - */ - public function __construct($a) - { - $this->a = $a; - $this->b = $a; - } - - /** - * @return T - */ - public function get() - { - asserType('T (class PHPStan\Generics\FunctionsAssertType\A, argument)', $this->a); - asserType('T (class PHPStan\Generics\FunctionsAssertType\A, argument)', $this->b); - return $this->a; - } - - /** - * @param T $a - */ - public function set($a) - { - $this->a = $a; - } - + /** @var T */ + private $a; + + /** @var T */ + public $b; + + /** + * A::__construct() + * + * @param T $a + */ + public function __construct($a) + { + $this->a = $a; + $this->b = $a; + } + + /** + * @return T + */ + public function get() + { + asserType('T (class PHPStan\Generics\FunctionsAssertType\A, argument)', $this->a); + asserType('T (class PHPStan\Generics\FunctionsAssertType\A, argument)', $this->b); + return $this->a; + } + + /** + * @param T $a + */ + public function set($a) + { + $this->a = $a; + } } /** @@ -429,10 +426,10 @@ public function set($a) */ class AOfDateTime extends A { - public function __construct() - { - parent::__construct(new \DateTime()); - } + public function __construct() + { + parent::__construct(new \DateTime()); + } } /** @@ -442,15 +439,15 @@ public function __construct() */ class B extends A { - /** - * B::__construct() - * - * @param T $a - */ - public function __construct($a) - { - parent::__construct($a); - } + /** + * B::__construct() + * + * @param T $a + */ + public function __construct($a) + { + parent::__construct($a); + } } /** @@ -458,19 +455,19 @@ public function __construct($a) */ interface I { - /** - * I::get() - * - * @return T - */ - function get(); + /** + * I::get() + * + * @return T + */ + public function get(); - /** - * I::getInheritdoc() - * - * @return T - */ - function getInheritdoc(); + /** + * I::getInheritdoc() + * + * @return T + */ + public function getInheritdoc(); } /** @@ -478,14 +475,14 @@ function getInheritdoc(); */ class CofI implements I { - public function get() - { - } + public function get() + { + } - /** @inheritdoc */ - public function getInheritdoc() - { - } + /** @inheritdoc */ + public function getInheritdoc() + { + } } /** @@ -495,13 +492,13 @@ public function getInheritdoc() */ interface SuperIfaceA { - /** - * SuperIfaceA::get() - * - * @param A $a - * @return A - */ - public function getA($a); + /** + * SuperIfaceA::get() + * + * @param A $a + * @return A + */ + public function getA($a); } /** @@ -511,13 +508,13 @@ public function getA($a); */ interface SuperIfaceB { - /** - * SuperIfaceB::get() - * - * @param B $b - * @return B - */ - public function getB($b); + /** + * SuperIfaceB::get() + * + * @param B $b + * @return B + */ + public function getB($b); } /** @@ -539,17 +536,17 @@ interface IfaceAB extends SuperIfaceA, SuperIfaceB */ class ABImpl implements IfaceAB { - public function getA($a) - { - assertType('int', $a); - return 1; - } + public function getA($a) + { + assertType('int', $a); + return 1; + } - public function getB($b) - { - assertType('DateTime', $b); - return new \DateTime(); - } + public function getB($b) + { + assertType('DateTime', $b); + return new \DateTime(); + } } /** @@ -557,11 +554,11 @@ public function getB($b) */ class X implements SuperIfaceA { - public function getA($a) - { - assertType('int', $a); - return 1; - } + public function getA($a) + { + assertType('int', $a); + return 1; + } } /** @@ -580,7 +577,7 @@ class NoConstructor extends A */ function acceptsClassString(string $s) { - return new $s; + return new $s(); } /** @@ -590,7 +587,7 @@ function acceptsClassString(string $s) */ function anotherAcceptsClassString(string $s) { - assertType('U (function PHPStan\Generics\FunctionsAssertType\anotherAcceptsClassString(), argument)', acceptsClassString($s)); + assertType('U (function PHPStan\Generics\FunctionsAssertType\anotherAcceptsClassString(), argument)', acceptsClassString($s)); } /** @@ -600,7 +597,7 @@ function anotherAcceptsClassString(string $s) */ function returnsClassString($object) { - return get_class($object); + return get_class($object); } /** @@ -610,7 +607,7 @@ function returnsClassString($object) */ function acceptsClassStringUpperBound($string) { - return new $string; + return new $string(); } @@ -619,15 +616,15 @@ function acceptsClassStringUpperBound($string) */ interface GenericRule { - /** - * @return TNodeType - */ - public function getNodeInstance(): Node; + /** + * @return TNodeType + */ + public function getNodeInstance(): Node; - /** - * @return class-string - */ - public function getNodeType(): string; + /** + * @return class-string + */ + public function getNodeType(): string; } /** @@ -635,28 +632,28 @@ public function getNodeType(): string; */ class SomeRule implements GenericRule { - public function getNodeInstance(): Node - { - return new StaticCall(new Name(\stdClass::class), '__construct'); - } + public function getNodeInstance(): Node + { + return new StaticCall(new Name(\stdClass::class), '__construct'); + } - public function getNodeType(): string - { - return StaticCall::class; - } + public function getNodeType(): string + { + return StaticCall::class; + } } class SomeRule2 implements GenericRule { - public function getNodeInstance(): Node - { - return new StaticCall(new Name(\stdClass::class), '__construct'); - } + public function getNodeInstance(): Node + { + return new StaticCall(new Name(\stdClass::class), '__construct'); + } - public function getNodeType(): string - { - return Node::class; - } + public function getNodeType(): string + { + return Node::class; + } } /** @@ -670,7 +667,7 @@ public function getNodeType(): string */ function inferFromGeneric($x) { - return $x->get(); + return $x->get(); } /** @@ -679,87 +676,87 @@ function inferFromGeneric($x) */ class Factory { - private $a; - private $b; - - /** - * @param A $a - * @param B $b - */ - public function __construct($a, $b) - { - $this->a = $a; - $this->b = $b; - } - - /** - * @template C - * @template D - * - * @param A $a - * @param C $c - * @param D $d - * - * @return array{A, B, C, D} - */ - public function create($a, $c, $d): array - { - return [$a, $this->b, $c, $d]; - } + private $a; + private $b; + + /** + * @param A $a + * @param B $b + */ + public function __construct($a, $b) + { + $this->a = $a; + $this->b = $b; + } + + /** + * @template C + * @template D + * + * @param A $a + * @param C $c + * @param D $d + * + * @return array{A, B, C, D} + */ + public function create($a, $c, $d): array + { + return [$a, $this->b, $c, $d]; + } } function testClasses() { - $a = new A(1); - assertType('PHPStan\Generics\FunctionsAssertType\A', $a); - assertType('int', $a->get()); - assertType('int', $a->b); + $a = new A(1); + assertType('PHPStan\Generics\FunctionsAssertType\A', $a); + assertType('int', $a->get()); + assertType('int', $a->b); - $a = new AOfDateTime(); - assertType('PHPStan\Generics\FunctionsAssertType\AOfDateTime', $a); - assertType('DateTime', $a->get()); - assertType('DateTime', $a->b); + $a = new AOfDateTime(); + assertType('PHPStan\Generics\FunctionsAssertType\AOfDateTime', $a); + assertType('DateTime', $a->get()); + assertType('DateTime', $a->b); - $b = new B(1); - assertType('PHPStan\Generics\FunctionsAssertType\B', $b); - assertType('int', $b->get()); - assertType('int', $b->b); + $b = new B(1); + assertType('PHPStan\Generics\FunctionsAssertType\B', $b); + assertType('int', $b->get()); + assertType('int', $b->b); - $c = new CofI(); - assertType('PHPStan\Generics\FunctionsAssertType\CofI', $c); - assertType('int', $c->get()); - assertType('int', $c->getInheritdoc()); + $c = new CofI(); + assertType('PHPStan\Generics\FunctionsAssertType\CofI', $c); + assertType('int', $c->get()); + assertType('int', $c->getInheritdoc()); - $ab = new ABImpl(); - assertType('int', $ab->getA(0)); - assertType('DateTime', $ab->getB(new \DateTime())); + $ab = new ABImpl(); + assertType('int', $ab->getA(0)); + assertType('DateTime', $ab->getB(new \DateTime())); - $noConstructor = new NoConstructor(1); - assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor', $noConstructor); + $noConstructor = new NoConstructor(1); + assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor', $noConstructor); - assertType('stdClass', acceptsClassString(\stdClass::class)); - assertType('class-string', returnsClassString(new \stdClass())); + assertType('stdClass', acceptsClassString(\stdClass::class)); + assertType('class-string', returnsClassString(new \stdClass())); - assertType('Exception', acceptsClassStringUpperBound(\Exception::class)); - assertType('Exception', acceptsClassStringUpperBound(\Throwable::class)); - assertType('InvalidArgumentException', acceptsClassStringUpperBound(\InvalidArgumentException::class)); + assertType('Exception', acceptsClassStringUpperBound(\Exception::class)); + assertType('Exception', acceptsClassStringUpperBound(\Throwable::class)); + assertType('InvalidArgumentException', acceptsClassStringUpperBound(\InvalidArgumentException::class)); - $rule = new SomeRule(); - assertType(StaticCall::class, $rule->getNodeInstance()); - assertType('class-string<' . StaticCall::class . '>', $rule->getNodeType()); + $rule = new SomeRule(); + assertType(StaticCall::class, $rule->getNodeInstance()); + assertType('class-string<' . StaticCall::class . '>', $rule->getNodeType()); - $rule2 = new SomeRule2(); - assertType(Node::class, $rule2->getNodeInstance()); - assertType('class-string<' . Node::class . '>', $rule2->getNodeType()); + $rule2 = new SomeRule2(); + assertType(Node::class, $rule2->getNodeInstance()); + assertType('class-string<' . Node::class . '>', $rule2->getNodeType()); - $a = inferFromGeneric(new A(new A(new \DateTime()))); - assertType('PHPStan\Generics\FunctionsAssertType\A', $a); + $a = inferFromGeneric(new A(new A(new \DateTime()))); + assertType('PHPStan\Generics\FunctionsAssertType\A', $a); - $factory = new Factory(new \DateTime(), new A(1)); - assertType( - 'array(DateTime, PHPStan\Generics\FunctionsAssertType\A, string, PHPStan\Generics\FunctionsAssertType\A)', - $factory->create(new \DateTime(), '', new A(new \DateTime())) - ); + $factory = new Factory(new \DateTime(), new A(1)); + assertType( + 'array(DateTime, PHPStan\Generics\FunctionsAssertType\A, string, PHPStan\Generics\FunctionsAssertType\A)', + $factory->create(new \DateTime(), '', new A(new \DateTime())) + ); } /** @@ -767,28 +764,26 @@ function testClasses() */ interface GenericIterator extends IteratorAggregate { - - /** - * @return \Iterator - */ - public function getIterator(): \Iterator; - + /** + * @return \Iterator + */ + public function getIterator(): \Iterator; } function () { - /** @var GenericIterator $iterator */ - $iterator = doFoo(); - assertType('PHPStan\Generics\FunctionsAssertType\GenericIterator', $iterator); - assertType('Iterator', $iterator->getIterator()); - - foreach ($iterator as $int) { - assertType('int', $int); - } + /** @var GenericIterator $iterator */ + $iterator = doFoo(); + assertType('PHPStan\Generics\FunctionsAssertType\GenericIterator', $iterator); + assertType('Iterator', $iterator->getIterator()); + + foreach ($iterator as $int) { + assertType('int', $int); + } }; function (GenericRule $rule): void { - assertType('class-string', $rule->getNodeType()); - assertType(Node::class, $rule->getNodeInstance()); + assertType('class-string', $rule->getNodeType()); + assertType(Node::class, $rule->getNodeInstance()); }; /** @@ -796,25 +791,20 @@ function (GenericRule $rule): void { */ class GenericClassWithProperty { - - /** @var T */ - public $a; - + /** @var T */ + public $a; } -function (GenericClassWithProperty $obj): void -{ - assertType(Node::class, $obj->a); +function (GenericClassWithProperty $obj): void { + assertType(Node::class, $obj->a); }; class ClassThatExtendsGenericClassWithPropertyWithoutSpecifyingTemplateType extends GenericClassWithProperty { - } -function (ClassThatExtendsGenericClassWithPropertyWithoutSpecifyingTemplateType $obj): void -{ - assertType(Node::class, $obj->a); +function (ClassThatExtendsGenericClassWithPropertyWithoutSpecifyingTemplateType $obj): void { + assertType(Node::class, $obj->a); }; /** @@ -822,16 +812,16 @@ function (ClassThatExtendsGenericClassWithPropertyWithoutSpecifyingTemplateType */ class GenericThis { - /** @param T $foo */ - public function __construct(\DateTimeInterface $foo) - { - assertType('T of DateTimeInterface (class PHPStan\Generics\FunctionsAssertType\GenericThis, argument)', $this->getFoo()); - } + /** @param T $foo */ + public function __construct(\DateTimeInterface $foo) + { + assertType('T of DateTimeInterface (class PHPStan\Generics\FunctionsAssertType\GenericThis, argument)', $this->getFoo()); + } - /** @return T */ - public function getFoo() - { - } + /** @return T */ + public function getFoo() + { + } } /** @@ -839,21 +829,21 @@ public function getFoo() */ class Cache { - /** - * @param T $t - */ - public function __construct($t) - { - } + /** + * @param T $t + */ + public function __construct($t) + { + } - /** - * Function Cache::get - * - * @return T - */ - public function get() - { - } + /** + * Function Cache::get + * + * @return T + */ + public function get() + { + } } /** @@ -863,9 +853,10 @@ public function get() * * @param T $t */ -function cache0($t): void { - $c = new Cache($t); - assertType('T (function PHPStan\Generics\FunctionsAssertType\cache0(), argument)', $c->get()); +function cache0($t): void +{ + $c = new Cache($t); + assertType('T (function PHPStan\Generics\FunctionsAssertType\cache0(), argument)', $c->get()); } /** @@ -875,15 +866,17 @@ function cache0($t): void { * * @param T $t */ -function cache1($t): void { - $c = new Cache($t); - assertType('T (function PHPStan\Generics\FunctionsAssertType\cache1(), argument)', $c->get()); +function cache1($t): void +{ + $c = new Cache($t); + assertType('T (function PHPStan\Generics\FunctionsAssertType\cache1(), argument)', $c->get()); } -function newHandling(): void { - assertType('PHPStan\Generics\FunctionsAssertType\C', new C()); - assertType('PHPStan\Generics\FunctionsAssertType\A', new A(new \stdClass())); - assertType('PHPStan\Generics\FunctionsAssertType\A', new A()); +function newHandling(): void +{ + assertType('PHPStan\Generics\FunctionsAssertType\C', new C()); + assertType('PHPStan\Generics\FunctionsAssertType\A', new A(new \stdClass())); + assertType('PHPStan\Generics\FunctionsAssertType\A', new A()); } /** @@ -892,76 +885,70 @@ function newHandling(): void { */ class StdClassCollection { + /** @var array */ + private $list; - /** @var array */ - private $list; - - /** - * @param array $list - */ - public function __construct(array $list) - { - - } - - /** - * @return array - */ - public function getAll(): array - { - return $this->list; - } - - /** - * @return static - */ - public function returnStatic(): self - { + /** + * @param array $list + */ + public function __construct(array $list) + { + } - } + /** + * @return array + */ + public function getAll(): array + { + return $this->list; + } + /** + * @return static + */ + public function returnStatic(): self + { + } } function () { - $stdEmpty = new StdClassCollection([]); - assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $stdEmpty); - assertType('array', $stdEmpty->getAll()); - - $std = new StdClassCollection([new \stdClass()]); - assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $std); - assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $std->returnStatic()); - assertType('array', $std->getAll()); + $stdEmpty = new StdClassCollection([]); + assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $stdEmpty); + assertType('array', $stdEmpty->getAll()); + + $std = new StdClassCollection([new \stdClass()]); + assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $std); + assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection', $std->returnStatic()); + assertType('array', $std->getAll()); }; class ClassWithMethodCachingIssue { + /** + * @template T + * @param T $a + */ + public function doFoo($a) + { + assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $a); - /** - * @template T - * @param T $a - */ - public function doFoo($a) - { - assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $a); + /** @var T $b */ + $b = doFoo(); + assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $b); + } - /** @var T $b */ - $b = doFoo(); - assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $b); - } - - /** - * @template T - * @param T $a - */ - public function doBar($a) - { - assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $a); - - /** @var T $b */ - $b = doFoo(); - assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $b); - } + /** + * @template T + * @param T $a + */ + public function doBar($a) + { + assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $a); + /** @var T $b */ + $b = doFoo(); + assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $b); + } } /** @@ -969,23 +956,21 @@ public function doBar($a) */ function testReflectionClass($ref) { - assertType('class-string', $ref->name); - assertType('class-string', $ref->getName()); - assertType('PHPStan\Generics\FunctionsAssertType\Foo', $ref->newInstanceWithoutConstructor()); + assertType('class-string', $ref->name); + assertType('class-string', $ref->getName()); + assertType('PHPStan\Generics\FunctionsAssertType\Foo', $ref->newInstanceWithoutConstructor()); - assertType('ReflectionClass', new \ReflectionClass(Foo::class)); + assertType('ReflectionClass', new \ReflectionClass(Foo::class)); } class CreateClassReflectionOfStaticClass { - - public function doFoo() - { - assertType('PHPStan\Generics\FunctionsAssertType\CreateClassReflectionOfStaticClass', (new \ReflectionClass(self::class))->newInstanceWithoutConstructor()); - assertType('static(PHPStan\Generics\FunctionsAssertType\CreateClassReflectionOfStaticClass)', (new \ReflectionClass(static::class))->newInstanceWithoutConstructor()); - assertType('class-string', (new \ReflectionClass(static::class))->name); - } - + public function doFoo() + { + assertType('PHPStan\Generics\FunctionsAssertType\CreateClassReflectionOfStaticClass', (new \ReflectionClass(self::class))->newInstanceWithoutConstructor()); + assertType('static(PHPStan\Generics\FunctionsAssertType\CreateClassReflectionOfStaticClass)', (new \ReflectionClass(static::class))->newInstanceWithoutConstructor()); + assertType('class-string', (new \ReflectionClass(static::class))->name); + } } /** @@ -994,14 +979,14 @@ public function doFoo() */ function testIterateOverTraversable($t1, $t2) { - foreach ($t1 as $int) { - assertType('int', $int); - } + foreach ($t1 as $int) { + assertType('int', $int); + } - foreach ($t2 as $key => $value) { - assertType('int', $key); - assertType('stdClass', $value); - } + foreach ($t2 as $key => $value) { + assertType('int', $key); + assertType('stdClass', $value); + } } /** @@ -1009,14 +994,14 @@ function testIterateOverTraversable($t1, $t2) */ function getGenerator(): \Generator { - $stdClass = yield 'foo'; - assertType(\stdClass::class, $stdClass); + $stdClass = yield 'foo'; + assertType(\stdClass::class, $stdClass); } function testYieldFrom() { - $yield = yield from getGenerator(); - assertType('Exception', $yield); + $yield = yield from getGenerator(); + assertType('Exception', $yield); } /** @@ -1024,27 +1009,25 @@ function testYieldFrom() */ class StaticClassConstant { + public function doFoo() + { + $staticClassName = static::class; + assertType('class-string', $staticClassName); + assertType('static(PHPStan\Generics\FunctionsAssertType\StaticClassConstant)', new $staticClassName()); + } - public function doFoo() - { - $staticClassName = static::class; - assertType('class-string', $staticClassName); - assertType('static(PHPStan\Generics\FunctionsAssertType\StaticClassConstant)', new $staticClassName); - } - - /** - * @param class-string $type - */ - public function doBar(string $type) - { - if ($type !== Foo::class && $type !== C::class) { - assertType('class-string', $type); - throw new \InvalidArgumentException; - } - - assertType('\'PHPStan\\\\Generics\\\\FunctionsAssertType\\\\C\'|\'PHPStan\\\\Generics\\\\FunctionsAssertType\\\\Foo\'', $type); - } + /** + * @param class-string $type + */ + public function doBar(string $type) + { + if ($type !== Foo::class && $type !== C::class) { + assertType('class-string', $type); + throw new \InvalidArgumentException(); + } + assertType('\'PHPStan\\\\Generics\\\\FunctionsAssertType\\\\C\'|\'PHPStan\\\\Generics\\\\FunctionsAssertType\\\\Foo\'', $type); + } } /** @@ -1055,8 +1038,8 @@ public function doBar(string $type) */ function testBounds($a, $b): void { - assertType('T of DateTime (function PHPStan\Generics\FunctionsAssertType\testBounds(), argument)', $a); - assertType('U of DateTime (function PHPStan\Generics\FunctionsAssertType\testBounds(), argument)', $b); + assertType('T of DateTime (function PHPStan\Generics\FunctionsAssertType\testBounds(), argument)', $a); + assertType('U of DateTime (function PHPStan\Generics\FunctionsAssertType\testBounds(), argument)', $b); } /** @@ -1066,7 +1049,7 @@ function testBounds($a, $b): void */ function testGenericObjectWithoutClassType($a) { - return $a; + return $a; } /** @@ -1076,23 +1059,23 @@ function testGenericObjectWithoutClassType($a) */ function testGenericObjectWithoutClassType2($a) { - assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $a); - assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', testGenericObjectWithoutClassType($a)); - $b = $a; - if ($b instanceof \stdClass) { - return $a; - } + assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $a); + assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', testGenericObjectWithoutClassType($a)); + $b = $a; + if ($b instanceof \stdClass) { + return $a; + } - assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $b); + assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $b); - return $a; + return $a; } function () { - $a = new \stdClass(); - assertType('stdClass', testGenericObjectWithoutClassType($a)); - assertType('stdClass', testGenericObjectWithoutClassType(testGenericObjectWithoutClassType($a))); - assertType('stdClass', testGenericObjectWithoutClassType2(testGenericObjectWithoutClassType($a))); + $a = new \stdClass(); + assertType('stdClass', testGenericObjectWithoutClassType($a)); + assertType('stdClass', testGenericObjectWithoutClassType(testGenericObjectWithoutClassType($a))); + assertType('stdClass', testGenericObjectWithoutClassType2(testGenericObjectWithoutClassType($a))); }; /** @@ -1101,12 +1084,10 @@ function () { */ class GenericReflectionClass extends \ReflectionClass { - - public function newInstanceWithoutConstructor() - { - return parent::newInstanceWithoutConstructor(); - } - + public function newInstanceWithoutConstructor() + { + return parent::newInstanceWithoutConstructor(); + } } /** @@ -1114,12 +1095,10 @@ public function newInstanceWithoutConstructor() */ class SpecificReflectionClass extends \ReflectionClass { - - public function newInstanceWithoutConstructor() - { - return parent::newInstanceWithoutConstructor(); - } - + public function newInstanceWithoutConstructor() + { + return parent::newInstanceWithoutConstructor(); + } } /** @@ -1127,8 +1106,8 @@ public function newInstanceWithoutConstructor() */ function testGenericReflectionClass($ref) { - assertType('class-string', $ref->name); - assertType('stdClass', $ref->newInstanceWithoutConstructor()); + assertType('class-string', $ref->name); + assertType('stdClass', $ref->newInstanceWithoutConstructor()); }; /** @@ -1136,8 +1115,8 @@ function testGenericReflectionClass($ref) */ function testSpecificReflectionClass($ref) { - assertType('class-string', $ref->name); - assertType('stdClass', $ref->newInstanceWithoutConstructor()); + assertType('class-string', $ref->name); + assertType('stdClass', $ref->newInstanceWithoutConstructor()); }; /** @@ -1146,10 +1125,8 @@ function testSpecificReflectionClass($ref) */ class PrefixedTemplateWins { - - /** @var T */ - public $name; - + /** @var T */ + public $name; } /** @@ -1158,10 +1135,8 @@ class PrefixedTemplateWins */ class PrefixedTemplateWins2 { - - /** @var T */ - public $name; - + /** @var T */ + public $name; } /** @@ -1171,10 +1146,8 @@ class PrefixedTemplateWins2 */ class PrefixedTemplateWins3 { - - /** @var T */ - public $name; - + /** @var T */ + public $name; } /** @@ -1183,10 +1156,8 @@ class PrefixedTemplateWins3 */ class PrefixedTemplateWins4 { - - /** @var T */ - public $name; - + /** @var T */ + public $name; } /** @@ -1195,25 +1166,22 @@ class PrefixedTemplateWins4 */ class PrefixedTemplateWins5 { - - /** @var T */ - public $name; - + /** @var T */ + public $name; } function testPrefixed( - PrefixedTemplateWins $a, - PrefixedTemplateWins2 $b, - PrefixedTemplateWins3 $c, - PrefixedTemplateWins4 $d, - PrefixedTemplateWins5 $e - + PrefixedTemplateWins $a, + PrefixedTemplateWins2 $b, + PrefixedTemplateWins3 $c, + PrefixedTemplateWins4 $d, + PrefixedTemplateWins5 $e ) { - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $a->name); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $b->name); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $c->name); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $d->name); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $e->name); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $a->name); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $b->name); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $c->name); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $d->name); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $e->name); }; /** @@ -1223,25 +1191,22 @@ function testPrefixed( */ function acceptsClassStringOfObject(string $classString) { - } /** * @param class-string $classString */ function testClassString( - string $classString -) -{ - assertType('class-string', $classString); - assertType('object', acceptsClassString($classString)); - assertType('Exception', acceptsClassStringUpperBound($classString)); - assertType('object', acceptsClassStringOfObject($classString)); + string $classString +) { + assertType('class-string', $classString); + assertType('object', acceptsClassString($classString)); + assertType('Exception', acceptsClassStringUpperBound($classString)); + assertType('object', acceptsClassStringOfObject($classString)); } class Bar extends Foo { - } /** @@ -1251,22 +1216,21 @@ class Bar extends Foo * @param class-string $yetAnotherClassString */ function returnStaticOnClassString( - string $classString, - string $anotherClassString, - string $yetAnotherClassString -) -{ - assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::returnsStatic()); - assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::instanceReturnsStatic()); - assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::$staticProp); + string $classString, + string $anotherClassString, + string $yetAnotherClassString +) { + assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::returnsStatic()); + assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::instanceReturnsStatic()); + assertType('T of PHPStan\Generics\FunctionsAssertType\Foo (function PHPStan\Generics\FunctionsAssertType\returnStaticOnClassString(), argument)', $classString::$staticProp); - assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::instanceReturnsStatic()); - assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::returnsStatic()); - assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::$staticProp); + assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::instanceReturnsStatic()); + assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::returnsStatic()); + assertType('PHPStan\Generics\FunctionsAssertType\Foo', $anotherClassString::$staticProp); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::instanceReturnsStatic()); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::returnsStatic()); - assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::$staticProp); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::instanceReturnsStatic()); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::returnsStatic()); + assertType('PHPStan\Generics\FunctionsAssertType\Bar', $yetAnotherClassString::$staticProp); } /** @@ -1274,7 +1238,7 @@ function returnStaticOnClassString( */ function arrayOfGenericClassStrings(array $a): void { - assertType('array>', $a); + assertType('array>', $a); } /** @@ -1292,34 +1256,34 @@ function arrayOfGenericClassStrings(array $a): void */ function getClassOnTemplateType($a, $b, $c, $d, $object, $mixed, $tObject) { - assertType( - 'class-string', - get_class($a) - ); - assertType( - 'class-string', - get_class($b) - ); - assertType( - 'class-string|' . - 'class-string', - get_class($c) - ); - assertType('class-string>', get_class($d)); - - $objectB = new $b; - assertType( - 'U of Exception (function PHPStan\Generics\FunctionsAssertType\getClassOnTemplateType(), argument)', - $objectB - ); - assertType( - 'class-string', - get_class($objectB) - ); - - assertType('class-string', get_class($object)); - assertType('class-string', get_class($mixed)); - assertType('class-string', get_class($tObject)); + assertType( + 'class-string', + get_class($a) + ); + assertType( + 'class-string', + get_class($b) + ); + assertType( + 'class-string|' . + 'class-string', + get_class($c) + ); + assertType('class-string>', get_class($d)); + + $objectB = new $b(); + assertType( + 'U of Exception (function PHPStan\Generics\FunctionsAssertType\getClassOnTemplateType(), argument)', + $objectB + ); + assertType( + 'class-string', + get_class($objectB) + ); + + assertType('class-string', get_class($object)); + assertType('class-string', get_class($mixed)); + assertType('class-string', get_class($tObject)); } /** @@ -1327,14 +1291,16 @@ function getClassOnTemplateType($a, $b, $c, $d, $object, $mixed, $tObject) */ class TagMergingGrandparent { - /** @var T */ - public $property; + /** @var T */ + public $property; - /** - * @param T $one - * @param int $two - */ - public function method($one, $two): void {} + /** + * @param T $one + * @param int $two + */ + public function method($one, $two): void + { + } } /** @@ -1343,10 +1309,12 @@ public function method($one, $two): void {} */ class TagMergingParent extends TagMergingGrandparent { - /** - * @param TT $one - */ - public function method($one, $two): void {} + /** + * @param TT $one + */ + public function method($one, $two): void + { + } } /** @@ -1354,48 +1322,57 @@ public function method($one, $two): void {} */ class TagMergingChild extends TagMergingParent { - public function method($one, $two): void - { - assertType('float', $one); - assertType('int', $two); - assertType('float', $this->property); - } + public function method($one, $two): void + { + assertType('float', $one); + assertType('int', $two); + assertType('float', $this->property); + } } /** * @template T */ -interface GeneralFactoryInterface { - /** - * @return T - */ - public static function create(); +interface GeneralFactoryInterface +{ + /** + * @return T + */ + public static function create(); } -class Car {} +class Car +{ +} /** * @implements GeneralFactoryInterface */ -class CarFactory implements GeneralFactoryInterface { - public static function create() { return new Car(); } +class CarFactory implements GeneralFactoryInterface +{ + public static function create() + { + return new Car(); + } } -class CarFactoryProcessor { - /** - * @param class-string $class - */ - public function process($class): void { - $car = $class::create(); - assertType(Car::class, $car); - } +class CarFactoryProcessor +{ + /** + * @param class-string $class + */ + public function process($class): void + { + $car = $class::create(); + assertType(Car::class, $car); + } } function (\Throwable $e): void { - assertType('mixed', $e->getCode()); + assertType('mixed', $e->getCode()); }; function (): void { - $array = ['a' => 1, 'b' => 2]; - assertType('array(\'a\' => int, \'b\' => int)', a($array)); + $array = ['a' => 1, 'b' => 2]; + assertType('array(\'a\' => int, \'b\' => int)', a($array)); }; diff --git a/tests/PHPStan/Analyser/data/get-parent-class.php b/tests/PHPStan/Analyser/data/get-parent-class.php index 8eab620666..182f9bb731 100644 --- a/tests/PHPStan/Analyser/data/get-parent-class.php +++ b/tests/PHPStan/Analyser/data/get-parent-class.php @@ -4,36 +4,30 @@ class Foo { - - public function doFoo() - { - 'inParentClass'; - } - + public function doFoo() + { + 'inParentClass'; + } } class Bar extends Foo { + use FooTrait; - use FooTrait; - - public function doBar() - { - 'inChildClass'; - } - + public function doBar() + { + 'inChildClass'; + } } function (string $s) { - die; + die; }; trait FooTrait { - - public function doBaz() - { - 'inTrait'; - } - + public function doBaz() + { + 'inTrait'; + } } diff --git a/tests/PHPStan/Analyser/data/if-defined.php b/tests/PHPStan/Analyser/data/if-defined.php index 81832db199..08d59b65d4 100644 --- a/tests/PHPStan/Analyser/data/if-defined.php +++ b/tests/PHPStan/Analyser/data/if-defined.php @@ -4,25 +4,19 @@ class Foo implements \ArrayAccess { + public function offsetExists($offset) + { + } - public function offsetExists($offset) - { + public function offsetGet($offset) + { + } - } - - public function offsetGet($offset) - { - - } - - public function offsetSet($offset, $value) - { - - } - - public function offsetUnset($offset) - { - - } + public function offsetSet($offset, $value) + { + } + public function offsetUnset($offset) + { + } } diff --git a/tests/PHPStan/Analyser/data/if.php b/tests/PHPStan/Analyser/data/if.php index ee0a7a24e3..3427d5f8a9 100644 --- a/tests/PHPStan/Analyser/data/if.php +++ b/tests/PHPStan/Analyser/data/if.php @@ -1,375 +1,378 @@ getReader()->consumeFrame($that->getReadBuffer())) === null) { - $integerOrNullFromWhile = 1; - $nonexistentVariableOutsideWhile = 1; - } - - /** @var array $someArray */ - $someArray = doFoo(); - $integerOrNullFromForeach = null; - foreach ($someArray as $someValue) { - $integerOrNullFromForeach = 1; - $nonexistentVariableOutsideForeach = null; - } - - $nullableIntegers = [1, 2, 3]; - $nullableIntegers[] = null; - - $union = [1, 2, 3]; - $union[] = 'foo'; - - $$lorem = 'ipsum'; - - $trueOrFalse = true; - $falseOrTrue = false; - $true = true; - $false = false; - if (doFoo()) { - $trueOrFalse = false; - $falseOrTrue = true; - $true = true; - $false = false; - } - - /** @var string|null $notNullableString */ - $notNullableString = 'foo'; - if ($notNullableString === null) { - return; - } - - /** @var string|null $anotherNotNullableString */ - $anotherNotNullableString = 'foo'; - if ($anotherNotNullableString !== null) { - $alsoNotNullableString = $anotherNotNullableString; - } else { - return; - } - - /** @var Foo|null $notNullableObject */ - $notNullableObject = doFoo(); - if ($notNullableObject === null) { - $notNullableObject = new Foo(); - } - - /** @var string|null $nullableString */ - $nullableString = 'foo'; - if ($nullableString !== null) { - $whatever = $nullableString; - } - - /** @var int|null $integerOrString */ - $integerOrString = 1; - if ($integerOrString === null) { - $integerOrString = 'str'; - } - - /** @var int|null $stillNullableInteger */ - $stillNullableInteger = 1; - if (is_int($stillNullableInteger)) { - $stillNullableInteger = 2; - } - - /** @var int|null $nullableIntegerAfterNeverCondition */ - $nullableIntegerAfterNeverCondition = 1; - if ($nullableIntegerAfterNeverCondition === false) { - $nullableIntegerAfterNeverCondition = 1; - } - - $arrayOfIntegers = [1, 2, 3]; - - $arrayAccessObject = new \ObjectWithArrayAccess\Foo(); - $arrayAccessObject[] = 1; - $arrayAccessObject[] = 2; - - $width = 1; - $scale = 2.0; - $width *= $scale; - - /** @var mixed $mixed */ - $mixed = doFoo(); - if (is_bool($mixed)) { - $mixed = 1; - } - - if (rand(0, 1)) { - /** @var mixed $issetBar */ - $issetBar = doFoo(); - /** @var mixed $issetBaz */ - $issetBaz = doFoo(); - } - - try { - $inTryTwo = 1; - maybeThrows(); - } catch (\Exception $e) { - $exception = $e; - if (something()) { - bar(); - } elseif (foo() || $foo = exists() || preg_match('#.*#', $subject, $matches2)) { - if (isset($issetFoo, $issetBar) && isset($issetBaz)) { - $anotherF = 1; - for ($i = 0; $i < 5; $i++, $f = $i, $anotherF = $i) { - $arr = [ - [1, 2], - ]; - foreach ($arr as list($listOne, $listTwo)) { - if (is_array($arrayOfIntegers)) { - (bool)preg_match('~.*~', $attributes, $ternaryMatches) ? die : null; - } - } - } - } - } - } + if (foo()) { + $ifVar = 1; + $issetFoo = new Foo(); + $maybeDefinedButLaterCertainlyDefined = 1; + if ($test) { + $ifNestedVar = 1; + $ifNotNestedVar = 1; + } elseif (fooBar()) { + $ifNotNestedVar = 2; + $variableOnlyInEarlyTerminatingElse = 1; + throw $e; + } else { + $ifNestedVar = 2; + } + $ifNotVar = 1; + } elseif (bar()) { + $ifVar = 2; + $issetFoo = null; + $ifNestedVar = 2; + $ifNotNestedVar = 2; + $ifNotVar = 2; + } elseif ($ifNestedVar = 3) { + $ifVar = 3; + $ifNotNestedVar = 3; + } else { + $variableOnlyInEarlyTerminatingElse = 1; + return; + } + + if (foo()) { + $maybeDefinedButLaterCertainlyDefined = 2; + } else { + $maybeDefinedButLaterCertainlyDefined = 3; + } + + $exceptionFromTry = null; + try { + $inTry = 1; + $fooObjectFromTryCatch = new InTryCatchFoo(); + $inTryNotInCatch = 1; + $mixedVarFromTryCatch = 1; + $nullableIntegerFromTryCatch = 1; + $anotherNullableIntegerFromTryCatch = null; + $someVariableThatWillGetOverrideInFinally = 1; + } catch (\SomeConcreteException $e) { + $inTry = 1; + $fooObjectFromTryCatch = new InTryCatchFoo(); + $mixedVarFromTryCatch = 1.0; + $nullableIntegerFromTryCatch = null; + $anotherNullableIntegerFromTryCatch = 1; + } catch (\Exception $e) { + throw $e; + } finally { + $someVariableThatWillGetOverrideInFinally = 'foo'; + restore_error_handler(); + } + + $exceptionFromTryCatch = null; + try { + maybeThrows(); + } catch (\SomeConcreteException $exceptionFromTryCatch) { + return; + } catch (\AnotherException $exceptionFromTryCatch) { + } catch (\YetAnotherException $exceptionFromTryCatch) { + doFoo(); + } + + $lorem = 1; + $arrOne[] = 'one'; + $arrTwo['test'] = 'two'; + $anotherArray['test'][] = 'another'; + doSomething($one, $callParameter = 3); + $arrTwo[] = new Foo([ + $inArray = 1, + ]); + $arrThree = null; + $arrThree[] = 'three'; + preg_match('#.*#', 'foo', $matches); + if ((bool) preg_match('#.*#', 'foo', $matches3)) { + foo(); + } elseif (preg_match('#.*#', 'foo', $matches4)) { + foo(); + } + + $trueOrFalseFromSwitch = true; + switch (foo()) { + case 1: + $switchVar = 1; + $noSwitchVar = 1; + $trueOrFalseFromSwitch = false; + break; + case 'foo': + $trueOrFalseFromSwitch = 1; + return; + case 2: + $switchVar = 2; + break; + case 3: + $anotherNoSwitchVar = 1; + // no break + case 4: + default: + $switchVar = 3; + if (doFoo()) { + $switchVar = 4; + break; + } + } + + $trueOrFalseInSwitchWithDefault = false; + $nullableTrueOrFalse = null; + switch ('foo') { + case 'foo': + $trueOrFalseInSwitchWithDefault = true; + $nullableTrueOrFalse = true; + continue; + case 'bar': + $nullableTrueOrFalse = false; + break; + default: + break; + } + + $trueOrFalseInSwitchInAllCases = false; + switch ('foo') { + case 'foo': + $trueOrFalseInSwitchInAllCases = true; + break; + case 'bar': + $trueOrFalseInSwitchInAllCases = true; + break; + } + $trueOrFalseInSwitchInAllCasesWithDefault = false; + switch ('foo') { + case 'foo': + $trueOrFalseInSwitchInAllCasesWithDefault = true; + break; + case 'bar': + $trueOrFalseInSwitchInAllCasesWithDefault = true; + break; + default: + break; + } + $trueOrFalseInSwitchInAllCasesWithDefaultCase = false; + switch ('foo') { + case 'foo': + $trueOrFalseInSwitchInAllCasesWithDefaultCase = true; + break; + case 'bar': + $trueOrFalseInSwitchInAllCasesWithDefaultCase = true; + break; + default: + $trueOrFalseInSwitchInAllCasesWithDefaultCase = true; + break; + } + + switch ('foo') { + case 'foo': + $variableDefinedInSwitchWithOtherCasesWithEarlyTermination = true; + break; + case 'bar': + throw new \Exception(); + default: + throw new \Exception(); + } + + switch ('foo') { + case 'foo': + throw new \Exception(); + case 'bar': + $anotherVariableDefinedInSwitchWithOtherCasesWithEarlyTermination = true; + break; + default: + throw new \Exception(); + } + + switch ('foo') { + case 'foo': + $variableDefinedOnlyInEarlyTerminatingSwitchCases = true; + throw new \Exception(); + case 'bar': + $variableDefinedOnlyInEarlyTerminatingSwitchCases = true; + return; + case 'baz': + break; + default: + $variableDefinedOnlyInEarlyTerminatingSwitchCases = true; + return; + } + + switch ('foo') { + case 'a': + $variableDefinedInSwitchWithoutEarlyTermination = true; + // no break + case 'b': + $variableDefinedInSwitchWithoutEarlyTermination = false; + } + + switch ('foo') { + case 'a': + $anotherVariableDefinedInSwitchWithoutEarlyTermination = true; + break; + case 'b': + $anotherVariableDefinedInSwitchWithoutEarlyTermination = false; + } + + switch (doFoo()) { + case 1: + case 2: + case 3: + $alwaysDefinedFromSwitch = 1; + break; + + default: + $alwaysDefinedFromSwitch = null; + } + + $nullOverwrittenInSwitchToOne = null; + switch (doFoo()) { + case 1: + if (doFoo()) { + throw new \Exception(); + } + $nullOverwrittenInSwitchToOne = 1; + break; + default: + throw new \Exception(); + } + + switch (doFoo()) { + case 1: + if (rand(0, 1)) { + $variableFromSwitchShouldBeBool = true; + break; + } + + // no break + default: + $variableFromSwitchShouldBeBool = false; + } + + do { + $doWhileVar = 1; + } while (something()); + + $integerOrNullFromFor = null; + for ($previousI = 0, $previousJ = 0; $previousI < 1; $previousI++) { + $integerOrNullFromFor = 1; + $nonexistentVariableOutsideFor = 1; + } + + $integerOrNullFromWhile = null; + while (($frame = $that->getReader()->consumeFrame($that->getReadBuffer())) === null) { + $integerOrNullFromWhile = 1; + $nonexistentVariableOutsideWhile = 1; + } + + /** @var array $someArray */ + $someArray = doFoo(); + $integerOrNullFromForeach = null; + foreach ($someArray as $someValue) { + $integerOrNullFromForeach = 1; + $nonexistentVariableOutsideForeach = null; + } + + $nullableIntegers = [1, 2, 3]; + $nullableIntegers[] = null; + + $union = [1, 2, 3]; + $union[] = 'foo'; + + $$lorem = 'ipsum'; + + $trueOrFalse = true; + $falseOrTrue = false; + $true = true; + $false = false; + if (doFoo()) { + $trueOrFalse = false; + $falseOrTrue = true; + $true = true; + $false = false; + } + + /** @var string|null $notNullableString */ + $notNullableString = 'foo'; + if ($notNullableString === null) { + return; + } + + /** @var string|null $anotherNotNullableString */ + $anotherNotNullableString = 'foo'; + if ($anotherNotNullableString !== null) { + $alsoNotNullableString = $anotherNotNullableString; + } else { + return; + } + + /** @var Foo|null $notNullableObject */ + $notNullableObject = doFoo(); + if ($notNullableObject === null) { + $notNullableObject = new Foo(); + } + + /** @var string|null $nullableString */ + $nullableString = 'foo'; + if ($nullableString !== null) { + $whatever = $nullableString; + } + + /** @var int|null $integerOrString */ + $integerOrString = 1; + if ($integerOrString === null) { + $integerOrString = 'str'; + } + + /** @var int|null $stillNullableInteger */ + $stillNullableInteger = 1; + if (is_int($stillNullableInteger)) { + $stillNullableInteger = 2; + } + + /** @var int|null $nullableIntegerAfterNeverCondition */ + $nullableIntegerAfterNeverCondition = 1; + if ($nullableIntegerAfterNeverCondition === false) { + $nullableIntegerAfterNeverCondition = 1; + } + + $arrayOfIntegers = [1, 2, 3]; + + $arrayAccessObject = new \ObjectWithArrayAccess\Foo(); + $arrayAccessObject[] = 1; + $arrayAccessObject[] = 2; + + $width = 1; + $scale = 2.0; + $width *= $scale; + + /** @var mixed $mixed */ + $mixed = doFoo(); + if (is_bool($mixed)) { + $mixed = 1; + } + + if (rand(0, 1)) { + /** @var mixed $issetBar */ + $issetBar = doFoo(); + /** @var mixed $issetBaz */ + $issetBaz = doFoo(); + } + + try { + $inTryTwo = 1; + maybeThrows(); + } catch (\Exception $e) { + $exception = $e; + if (something()) { + bar(); + } elseif (foo() || $foo = exists() || preg_match('#.*#', $subject, $matches2)) { + if (isset($issetFoo, $issetBar) && isset($issetBaz)) { + $anotherF = 1; + for ($i = 0; $i < 5; $i++, $f = $i, $anotherF = $i) { + $arr = [ + [1, 2], + ]; + foreach ($arr as list($listOne, $listTwo)) { + if (is_array($arrayOfIntegers)) { + (bool)preg_match('~.*~', $attributes, $ternaryMatches) ? die : null; + } + } + } + } + } + } }; diff --git a/tests/PHPStan/Analyser/data/ignore-line.php b/tests/PHPStan/Analyser/data/ignore-line.php index fbbe06b7cb..4041df1f44 100644 --- a/tests/PHPStan/Analyser/data/ignore-line.php +++ b/tests/PHPStan/Analyser/data/ignore-line.php @@ -4,26 +4,24 @@ class Foo { + public function doFoo(): void + { + fail(); // reported - public function doFoo(): void - { - fail(); // reported + fail(); // @phpstan-ignore-line - fail(); // @phpstan-ignore-line + fail(); /* @phpstan-ignore-line */ - fail(); /* @phpstan-ignore-line */ + fail(); /** @phpstan-ignore-line */ - fail(); /** @phpstan-ignore-line */ + fail(); /** @phpstan-ignore-line */ + fail(); // reported - fail(); /** @phpstan-ignore-line */ - fail(); // reported + if (fail()) { // @phpstan-ignore-line + fail(); // reported + } - if (fail()) { // @phpstan-ignore-line - fail(); // reported - } - - - succ(); /** @phpstan-ignore-line reported as unmatched */ - } + succ(); /** @phpstan-ignore-line reported as unmatched */ + } } diff --git a/tests/PHPStan/Analyser/data/ignore-next-line.php b/tests/PHPStan/Analyser/data/ignore-next-line.php index e47394d51a..18d7656e2b 100644 --- a/tests/PHPStan/Analyser/data/ignore-next-line.php +++ b/tests/PHPStan/Analyser/data/ignore-next-line.php @@ -4,38 +4,36 @@ class Foo { - - public function doFoo(): void - { - fail(); // reported - - // @phpstan-ignore-next-line - fail(); - - /* @phpstan-ignore-next-line */ - fail(); - - /** @phpstan-ignore-next-line */ - fail(); - - /* - * @phpstan-ignore-next-line - */ - fail(); - - /** - * @phpstan-ignore-next-line - */ - fail(); - fail(); // reported - - // @phpstan-ignore-next-line - if (fail()) { - fail(); // reported - } - - /** @phpstan-ignore-next-line */ - succ(); // reported as unmatched - } - + public function doFoo(): void + { + fail(); // reported + + // @phpstan-ignore-next-line + fail(); + + /* @phpstan-ignore-next-line */ + fail(); + + /** @phpstan-ignore-next-line */ + fail(); + + /* + * @phpstan-ignore-next-line + */ + fail(); + + /** + * @phpstan-ignore-next-line + */ + fail(); + fail(); // reported + + // @phpstan-ignore-next-line + if (fail()) { + fail(); // reported + } + + /** @phpstan-ignore-next-line */ + succ(); // reported as unmatched + } } diff --git a/tests/PHPStan/Analyser/data/impure-method.php b/tests/PHPStan/Analyser/data/impure-method.php index d471c0d54a..f97cacdd7d 100644 --- a/tests/PHPStan/Analyser/data/impure-method.php +++ b/tests/PHPStan/Analyser/data/impure-method.php @@ -6,76 +6,74 @@ class Foo { - - /** @var int */ - private $fooProp; - - public function voidMethod(): void - { - $this->fooProp = rand(0, 1); - } - - public function ordinaryMethod(): int - { - return 1; - } - - /** - * @phpstan-impure - * @return int - */ - public function impureMethod(): int - { - $this->fooProp = rand(0, 1); - - return $this->fooProp; - } - - /** - * @impure - * @return int - */ - public function impureMethod2(): int - { - $this->fooProp = rand(0, 1); - - return $this->fooProp; - } - - public function doFoo(): void - { - $this->fooProp = 1; - assertType('1', $this->fooProp); - - $this->voidMethod(); - assertType('int', $this->fooProp); - } - - public function doBar(): void - { - $this->fooProp = 1; - assertType('1', $this->fooProp); - - $this->ordinaryMethod(); - assertType('1', $this->fooProp); - } - - public function doBaz(): void - { - $this->fooProp = 1; - assertType('1', $this->fooProp); - - $this->impureMethod(); - assertType('int', $this->fooProp); - } - - public function doLorem(): void - { - $this->fooProp = 1; - assertType('1', $this->fooProp); - - $this->impureMethod2(); - assertType('int', $this->fooProp); - } - + /** @var int */ + private $fooProp; + + public function voidMethod(): void + { + $this->fooProp = rand(0, 1); + } + + public function ordinaryMethod(): int + { + return 1; + } + + /** + * @phpstan-impure + * @return int + */ + public function impureMethod(): int + { + $this->fooProp = rand(0, 1); + + return $this->fooProp; + } + + /** + * @impure + * @return int + */ + public function impureMethod2(): int + { + $this->fooProp = rand(0, 1); + + return $this->fooProp; + } + + public function doFoo(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->voidMethod(); + assertType('int', $this->fooProp); + } + + public function doBar(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->ordinaryMethod(); + assertType('1', $this->fooProp); + } + + public function doBaz(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->impureMethod(); + assertType('int', $this->fooProp); + } + + public function doLorem(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->impureMethod2(); + assertType('int', $this->fooProp); + } } diff --git a/tests/PHPStan/Analyser/data/in-array.php b/tests/PHPStan/Analyser/data/in-array.php index ca8246d0d1..baaea12f67 100644 --- a/tests/PHPStan/Analyser/data/in-array.php +++ b/tests/PHPStan/Analyser/data/in-array.php @@ -4,44 +4,41 @@ class Foo { - - /** - * @param string $s - * @param string $r - * @param $mixed - * @param string[] $strings - */ - public function doFoo( - string $s, - string $r, - $mixed, - array $strings - ) - { - if (!in_array($s, ['foo', 'bar'], true)) { - return; - } - - if (!in_array($mixed, $strings, true)) { - return; - } - - if (in_array($r, $strings, true)) { - return; - } - - $fooOrBarOrBaz = 'foo'; - if (rand(0, 1) === 1) { - $fooOrBarOrBaz = 'bar'; - } elseif (rand(0, 1) === 1) { - $fooOrBarOrBaz = 'baz'; - } - - if (in_array($fooOrBarOrBaz, ['bar', 'baz'], true)) { - return; - } - - die; - } - + /** + * @param string $s + * @param string $r + * @param $mixed + * @param string[] $strings + */ + public function doFoo( + string $s, + string $r, + $mixed, + array $strings + ) { + if (!in_array($s, ['foo', 'bar'], true)) { + return; + } + + if (!in_array($mixed, $strings, true)) { + return; + } + + if (in_array($r, $strings, true)) { + return; + } + + $fooOrBarOrBaz = 'foo'; + if (rand(0, 1) === 1) { + $fooOrBarOrBaz = 'bar'; + } elseif (rand(0, 1) === 1) { + $fooOrBarOrBaz = 'baz'; + } + + if (in_array($fooOrBarOrBaz, ['bar', 'baz'], true)) { + return; + } + + die; + } } diff --git a/tests/PHPStan/Analyser/data/inc-dec-in-conditions.php b/tests/PHPStan/Analyser/data/inc-dec-in-conditions.php index fe3e49285d..b456a05e00 100644 --- a/tests/PHPStan/Analyser/data/inc-dec-in-conditions.php +++ b/tests/PHPStan/Analyser/data/inc-dec-in-conditions.php @@ -6,96 +6,96 @@ function incLeft(int $a, int $b, int $c, int $d): void { - if (++$a < 0) { - assertType('int', $a); - } else { - assertType('int<0, max>', $a); - } - if (++$b <= 0) { - assertType('int', $b); - } else { - assertType('int<1, max>', $b); - } - if ($c++ < 0) { - assertType('int', $c); - } else { - assertType('int<1, max>', $c); - } - if ($d++ <= 0) { - assertType('int', $d); - } else { - assertType('int<2, max>', $d); - } + if (++$a < 0) { + assertType('int', $a); + } else { + assertType('int<0, max>', $a); + } + if (++$b <= 0) { + assertType('int', $b); + } else { + assertType('int<1, max>', $b); + } + if ($c++ < 0) { + assertType('int', $c); + } else { + assertType('int<1, max>', $c); + } + if ($d++ <= 0) { + assertType('int', $d); + } else { + assertType('int<2, max>', $d); + } } function incRight(int $a, int $b, int $c, int $d): void { - if (0 < ++$a) { - assertType('int<1, max>', $a); - } else { - assertType('int', $a); - } - if (0 <= ++$b) { - assertType('int<0, max>', $b); - } else { - assertType('int', $b); - } - if (0 < $c++) { - assertType('int<2, max>', $c); - } else { - assertType('int', $c); - } - if (0 <= $d++) { - assertType('int<1, max>', $d); - } else { - assertType('int', $d); - } + if (0 < ++$a) { + assertType('int<1, max>', $a); + } else { + assertType('int', $a); + } + if (0 <= ++$b) { + assertType('int<0, max>', $b); + } else { + assertType('int', $b); + } + if (0 < $c++) { + assertType('int<2, max>', $c); + } else { + assertType('int', $c); + } + if (0 <= $d++) { + assertType('int<1, max>', $d); + } else { + assertType('int', $d); + } } function decLeft(int $a, int $b, int $c, int $d): void { - if (--$a < 0) { - assertType('int', $a); - } else { - assertType('int<0, max>', $a); - } - if (--$b <= 0) { - assertType('int', $b); - } else { - assertType('int<1, max>', $b); - } - if ($c-- < 0) { - assertType('int', $c); - } else { - assertType('int<-1, max>', $c); - } - if ($d-- <= 0) { - assertType('int', $d); - } else { - assertType('int<0, max>', $d); - } + if (--$a < 0) { + assertType('int', $a); + } else { + assertType('int<0, max>', $a); + } + if (--$b <= 0) { + assertType('int', $b); + } else { + assertType('int<1, max>', $b); + } + if ($c-- < 0) { + assertType('int', $c); + } else { + assertType('int<-1, max>', $c); + } + if ($d-- <= 0) { + assertType('int', $d); + } else { + assertType('int<0, max>', $d); + } } function decRight(int $a, int $b, int $c, int $d): void { - if (0 < --$a) { - assertType('int<1, max>', $a); - } else { - assertType('int', $a); - } - if (0 <= --$b) { - assertType('int<0, max>', $b); - } else { - assertType('int', $b); - } - if (0 < $c--) { - assertType('int<0, max>', $c); - } else { - assertType('int', $c); - } - if (0 <= $d--) { - assertType('int<-1, max>', $d); - } else { - assertType('int', $d); - } + if (0 < --$a) { + assertType('int<1, max>', $a); + } else { + assertType('int', $a); + } + if (0 <= --$b) { + assertType('int<0, max>', $b); + } else { + assertType('int', $b); + } + if (0 < $c--) { + assertType('int<0, max>', $c); + } else { + assertType('int', $c); + } + if (0 <= $d--) { + assertType('int<-1, max>', $d); + } else { + assertType('int', $d); + } } diff --git a/tests/PHPStan/Analyser/data/infer-private-property-type-from-constructor.php b/tests/PHPStan/Analyser/data/infer-private-property-type-from-constructor.php index ab83462338..f02def26fc 100644 --- a/tests/PHPStan/Analyser/data/infer-private-property-type-from-constructor.php +++ b/tests/PHPStan/Analyser/data/infer-private-property-type-from-constructor.php @@ -4,50 +4,47 @@ class Foo { - - /** @var int */ - private $intProp; - - private $stringProp; - - private $unionProp; - - private $stdClassProp; - - /** @ORM\Column */ - private $unrelatedDocComment; - - /** @var mixed */ - private $explicitMixed; - - private $bool; - - private $array; - - /** - * @param self|Bar $unionProp - */ - public function __construct( - string $intProp, - string $stringProp, - $unionProp, - \stdClass $unrelatedDocComment, - \stdClass $explicitMixed - ) - { - $this->intProp = $intProp; - $this->stringProp = $stringProp; - $this->unionProp = $unionProp; - $this->stdClassProp = new \stdClass(); - $this->unrelatedDocComment = $unrelatedDocComment; - $this->explicitMixed = $explicitMixed; - $this->bool = false; - $this->array = []; - } - - public function doFoo() - { - die; - } - + /** @var int */ + private $intProp; + + private $stringProp; + + private $unionProp; + + private $stdClassProp; + + /** @ORM\Column */ + private $unrelatedDocComment; + + /** @var mixed */ + private $explicitMixed; + + private $bool; + + private $array; + + /** + * @param self|Bar $unionProp + */ + public function __construct( + string $intProp, + string $stringProp, + $unionProp, + \stdClass $unrelatedDocComment, + \stdClass $explicitMixed + ) { + $this->intProp = $intProp; + $this->stringProp = $stringProp; + $this->unionProp = $unionProp; + $this->stdClassProp = new \stdClass(); + $this->unrelatedDocComment = $unrelatedDocComment; + $this->explicitMixed = $explicitMixed; + $this->bool = false; + $this->array = []; + } + + public function doFoo() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-param.php b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-param.php index 16d5922e7c..d29e828be2 100644 --- a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-param.php +++ b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-param.php @@ -1,36 +1,45 @@ -method()); + assertType('InheritDocMergingReturn\B', $foo->method()); }; function (ChildClass $foo): void { - assertType('InheritDocMergingReturn\B', $foo->method()); + assertType('InheritDocMergingReturn\B', $foo->method()); }; function (ChildClass2 $foo): void { - assertType('InheritDocMergingReturn\D', $foo->method()); + assertType('InheritDocMergingReturn\D', $foo->method()); }; diff --git a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-template.php b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-template.php index e340baba85..c5b5dcad6e 100644 --- a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-template.php +++ b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-template.php @@ -1,4 +1,6 @@ - - */ - public function doFoo($a, $b) - { - - } - + /** + * @template T + * @template U + * @param T $a + * @param U $b + * @return T|array + */ + public function doFoo($a, $b) + { + } } class Bar extends Foo { - - public function doFoo($a, $b) - { - assertType('T (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $a); - assertType('U (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $b); - } - - public function doBar() - { - assertType('array|int', $this->doFoo(1, 'hahaha')); - } - + public function doFoo($a, $b) + { + assertType('T (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $a); + assertType('U (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $b); + } + + public function doBar() + { + assertType('array|int', $this->doFoo(1, 'hahaha')); + } } class Dolor extends Foo { - - /** - * @param T $a - * @param U $b - * @return T|array - */ - public function doFoo($a, $b) - { - assertType('InheritDocMergingTemplate\T', $a); - assertType('InheritDocMergingTemplate\U', $b); - } - - public function doBar() - { - assertType('array|InheritDocMergingTemplate\T', $this->doFoo(1, 'hahaha')); - } - + /** + * @param T $a + * @param U $b + * @return T|array + */ + public function doFoo($a, $b) + { + assertType('InheritDocMergingTemplate\T', $a); + assertType('InheritDocMergingTemplate\U', $b); + } + + public function doBar() + { + assertType('array|InheritDocMergingTemplate\T', $this->doFoo(1, 'hahaha')); + } } class Sit extends Foo { - - /** - * @template T - * @param T $a - * @param U $b - * @return T|array - */ - public function doFoo($a, $b) - { - assertType('T (method InheritDocMergingTemplate\Sit::doFoo(), argument)', $a); - assertType('InheritDocMergingTemplate\U', $b); - } - - public function doBar() - { - assertType('array|int', $this->doFoo(1, 'hahaha')); - } - + /** + * @template T + * @param T $a + * @param U $b + * @return T|array + */ + public function doFoo($a, $b) + { + assertType('T (method InheritDocMergingTemplate\Sit::doFoo(), argument)', $a); + assertType('InheritDocMergingTemplate\U', $b); + } + + public function doBar() + { + assertType('array|int', $this->doFoo(1, 'hahaha')); + } } class Amet extends Foo { - - /** SomeComment */ - public function doFoo($a, $b) - { - assertType('T (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $a); - assertType('U (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $b); - } - - public function doBar() - { - assertType('array|int', $this->doFoo(1, 'hahaha')); - } - + /** SomeComment */ + public function doFoo($a, $b) + { + assertType('T (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $a); + assertType('U (method InheritDocMergingTemplate\Foo::doFoo(), argument)', $b); + } + + public function doBar() + { + assertType('array|int', $this->doFoo(1, 'hahaha')); + } } /** @@ -102,25 +93,20 @@ public function doBar() */ class Baz { - - /** - * @param T $a - */ - public function doFoo($a) - { - - } - + /** + * @param T $a + */ + public function doFoo($a) + { + } } class Lorem extends Baz { - - public function doFoo($a) - { - assertType('object', $a); - } - + public function doFoo($a) + { + assertType('object', $a); + } } /** @@ -128,10 +114,8 @@ public function doFoo($a) */ class Ipsum extends Baz { - - public function doFoo($a) - { - assertType('stdClass', $a); - } - + public function doFoo($a) + { + assertType('stdClass', $a); + } } diff --git a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-var.php b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-var.php index 14f499a575..ea23f6ba1d 100644 --- a/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-var.php +++ b/tests/PHPStan/Analyser/data/inherit-phpdoc-merging-var.php @@ -1,84 +1,86 @@ -property); - } + public function method(): void + { + assertType('InheritDocMergingVar\A', $this->property); + } } class Two extends One { - /** @var B */ - protected $property; + /** @var B */ + protected $property; - public function method(): void - { - assertType('InheritDocMergingVar\B', $this->property); - } + public function method(): void + { + assertType('InheritDocMergingVar\B', $this->property); + } } class Three extends Two { - /** Some comment */ - protected $property; + /** Some comment */ + protected $property; - public function method(): void - { - assertType('InheritDocMergingVar\B', $this->property); - } + public function method(): void + { + assertType('InheritDocMergingVar\B', $this->property); + } } class Four extends Three { - protected $property; + protected $property; - public function method(): void - { - assertType('InheritDocMergingVar\B', $this->property); - } + public function method(): void + { + assertType('InheritDocMergingVar\B', $this->property); + } } class Five extends Four { - - public function method(): void - { - assertType('InheritDocMergingVar\B', $this->property); - } - + public function method(): void + { + assertType('InheritDocMergingVar\B', $this->property); + } } class Six extends Five { - protected $property; + protected $property; - public function method(): void - { - assertType('InheritDocMergingVar\B', $this->property); - } + public function method(): void + { + assertType('InheritDocMergingVar\B', $this->property); + } } class Seven extends One { - - /** - * @inheritDoc - * @var B - */ - protected $property; - + /** + * @inheritDoc + * @var B + */ + protected $property; } /** @@ -86,22 +88,18 @@ class Seven extends One */ class ClassWithTemplate { - - /** @var T */ - protected $prop; - + /** @var T */ + protected $prop; } class ChildClassExtendingClassWithTemplate extends ClassWithTemplate { + protected $prop; - protected $prop; - - public function doFoo() - { - assertType('object', $this->prop); - } - + public function doFoo() + { + assertType('object', $this->prop); + } } /** @@ -109,13 +107,11 @@ public function doFoo() */ class ChildClass2ExtendingClassWithTemplate extends ClassWithTemplate { + /** someComment */ + protected $prop; - /** someComment */ - protected $prop; - - public function doFoo() - { - assertType('stdClass', $this->prop); - } - + public function doFoo() + { + assertType('stdClass', $this->prop); + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-constructors.php b/tests/PHPStan/Analyser/data/inheritdoc-constructors.php index 502379facd..0a5b75dd27 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-constructors.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-constructors.php @@ -6,25 +6,21 @@ class Foo { - - /** - * @param string[] $data - */ - public function __construct($data) - { - assertType('array', $data); - } - + /** + * @param string[] $data + */ + public function __construct($data) + { + assertType('array', $data); + } } class Bar extends Foo { - - public function __construct($name, $data) - { - parent::__construct($data); - assertType('mixed', $name); - assertType('array', $data); - } - + public function __construct($name, $data) + { + parent::__construct($data); + assertType('mixed', $name); + assertType('array', $data); + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-interface.php b/tests/PHPStan/Analyser/data/inheritdoc-from-interface.php index 238c7f14b3..bc53417dac 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-interface.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-interface.php @@ -4,28 +4,23 @@ class Foo extends FooParent implements FooInterface { - - /** - * {@inheritdoc} - */ - public function doFoo($string) - { - die; - } - + /** + * {@inheritdoc} + */ + public function doFoo($string) + { + die; + } } abstract class FooParent { - } interface FooInterface { - - /** - * @param string $string - */ - public function doFoo($string); - + /** + * @param string $string + */ + public function doFoo($string); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-interface2-definition.php b/tests/PHPStan/Analyser/data/inheritdoc-from-interface2-definition.php index 25281e9489..fbae43110a 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-interface2-definition.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-interface2-definition.php @@ -8,10 +8,8 @@ interface FooInterface extends BarInterface interface BarInterface { - - /** - * @param int $int - */ - public function doBar($int); - + /** + * @param int $int + */ + public function doBar($int); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-interface2.php b/tests/PHPStan/Analyser/data/inheritdoc-from-interface2.php index bb19338f37..d3aa04d53e 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-interface2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-interface2.php @@ -4,13 +4,11 @@ class Foo implements FooInterface { - - /** - * {@inheritdoc} - */ - public function doBar($int) - { - die; - } - + /** + * {@inheritdoc} + */ + public function doBar($int) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-trait.php b/tests/PHPStan/Analyser/data/inheritdoc-from-trait.php index dc289bc9c4..c600cb2a92 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-trait.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-trait.php @@ -4,28 +4,24 @@ class Foo implements FooInterface { - use FooTrait; + use FooTrait; } trait FooTrait { - - /** - * {@inheritdoc} - */ - public function doFoo($string) - { - die; - } - + /** + * {@inheritdoc} + */ + public function doFoo($string) + { + die; + } } interface FooInterface { - - /** - * @param string $string - */ - public function doFoo($string); - + /** + * @param string $string + */ + public function doFoo($string); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition.php b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition.php index f1fcfd6c82..cefdf0e9f8 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition.php @@ -4,13 +4,11 @@ trait FooTrait { - - /** - * @param string $string - */ - public function doFoo($string) - { - die; - } - + /** + * @param string $string + */ + public function doFoo($string) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition2.php b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition2.php index bed3e017e0..6d160eb651 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2-definition2.php @@ -4,5 +4,5 @@ class FooParent { - use FooTrait; + use FooTrait; } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2.php b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2.php index 405dbdacbc..21386cb174 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-from-trait2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-from-trait2.php @@ -4,13 +4,11 @@ class Foo extends FooParent { - - /** - * {@inheritdoc} - */ - public function doFoo($string) - { - die; - } - + /** + * {@inheritdoc} + */ + public function doFoo($string) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-parameter-remapping.php b/tests/PHPStan/Analyser/data/inheritdoc-parameter-remapping.php index 311bbaf064..fb38c8e507 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-parameter-remapping.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-parameter-remapping.php @@ -6,42 +6,35 @@ class Lorem { - - /** - * @param B $b - * @param C $c - * @param A $a - * @param D $d - */ - public function doLorem($a, $b, $c, $d) - { - - } - + /** + * @param B $b + * @param C $c + * @param A $a + * @param D $d + */ + public function doLorem($a, $b, $c, $d) + { + } } class Ipsum extends Lorem { - - public function doLorem($x, $y, $z, $d) - { - assertType('InheritDocParameterRemapping\A', $x); - assertType('InheritDocParameterRemapping\B', $y); - assertType('InheritDocParameterRemapping\C', $z); - assertType('InheritDocParameterRemapping\D', $d); - } - + public function doLorem($x, $y, $z, $d) + { + assertType('InheritDocParameterRemapping\A', $x); + assertType('InheritDocParameterRemapping\B', $y); + assertType('InheritDocParameterRemapping\C', $z); + assertType('InheritDocParameterRemapping\D', $d); + } } class Dolor extends Ipsum { - - public function doLorem($g, $h, $i, $d) - { - assertType('InheritDocParameterRemapping\A', $g); - assertType('InheritDocParameterRemapping\B', $h); - assertType('InheritDocParameterRemapping\C', $i); - assertType('InheritDocParameterRemapping\D', $d); - } - + public function doLorem($g, $h, $i, $d) + { + assertType('InheritDocParameterRemapping\A', $g); + assertType('InheritDocParameterRemapping\B', $h); + assertType('InheritDocParameterRemapping\C', $i); + assertType('InheritDocParameterRemapping\D', $d); + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface.php index 619fa37e0c..97de74de43 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface.php @@ -4,28 +4,23 @@ class Foo extends FooParent implements FooInterface { - - /** - * @inheritdoc - */ - public function doFoo($string) - { - die; - } - + /** + * @inheritdoc + */ + public function doFoo($string) + { + die; + } } abstract class FooParent { - } interface FooInterface { - - /** - * @param string $string - */ - public function doFoo($string); - + /** + * @param string $string + */ + public function doFoo($string); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2-definition.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2-definition.php index 953f24af07..9a3cec991b 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2-definition.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2-definition.php @@ -8,10 +8,8 @@ interface FooInterface extends BarInterface interface BarInterface { - - /** - * @param int $int - */ - public function doBar($int); - + /** + * @param int $int + */ + public function doBar($int); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2.php index e5f4b54669..f265e0a2a2 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-interface2.php @@ -4,13 +4,11 @@ class Foo implements FooInterface { - - /** - * @inheritdoc - */ - public function doBar($int) - { - die; - } - + /** + * @inheritdoc + */ + public function doBar($int) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait.php index 13202a1025..074ec2d1be 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait.php @@ -4,28 +4,24 @@ class Foo implements FooInterface { - use FooTrait; + use FooTrait; } trait FooTrait { - - /** - * @inheritdoc - */ - public function doFoo($string) - { - die; - } - + /** + * @inheritdoc + */ + public function doFoo($string) + { + die; + } } interface FooInterface { - - /** - * @param string $string - */ - public function doFoo($string); - + /** + * @param string $string + */ + public function doFoo($string); } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition.php index 52e27ee302..09a954dec0 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition.php @@ -4,13 +4,11 @@ trait FooTrait { - - /** - * @param string $string - */ - public function doFoo($string) - { - die; - } - + /** + * @param string $string + */ + public function doFoo($string) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition2.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition2.php index 49f6418b43..ac754feec8 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2-definition2.php @@ -4,5 +4,5 @@ class FooParent { - use FooTrait; + use FooTrait; } diff --git a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2.php b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2.php index ad4af814c2..3d10638efd 100644 --- a/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2.php +++ b/tests/PHPStan/Analyser/data/inheritdoc-without-curly-braces-from-trait2.php @@ -4,13 +4,11 @@ class Foo extends FooParent { - - /** - * @inheritdoc - */ - public function doFoo($string) - { - die; - } - + /** + * @inheritdoc + */ + public function doFoo($string) + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/instanceof-class-string.php b/tests/PHPStan/Analyser/data/instanceof-class-string.php index dc2bcaa2f3..f89e74d97a 100644 --- a/tests/PHPStan/Analyser/data/instanceof-class-string.php +++ b/tests/PHPStan/Analyser/data/instanceof-class-string.php @@ -6,34 +6,30 @@ class Foo { - - public function doFoo(Foo $foo): void - { - $class = get_class($foo); - assertType('class-string', $class); - assertType(self::class, $foo); - if ($foo instanceof $class) { - assertType(self::class, $foo); - } else { - assertType(self::class, $foo); - } - } - + public function doFoo(Foo $foo): void + { + $class = get_class($foo); + assertType('class-string', $class); + assertType(self::class, $foo); + if ($foo instanceof $class) { + assertType(self::class, $foo); + } else { + assertType(self::class, $foo); + } + } } class Bar extends Foo { - - public function doBar(Foo $foo, Bar $bar): void - { - $class = get_class($bar); - assertType('class-string', $class); - assertType(Foo::class, $foo); - if ($foo instanceof $class) { - assertType(self::class, $foo); - } else { - assertType('InstanceOfClassString\Foo~InstanceOfClassString\Bar', $foo); - } - } - + public function doBar(Foo $foo, Bar $bar): void + { + $class = get_class($bar); + assertType('class-string', $class); + assertType(Foo::class, $foo); + if ($foo instanceof $class) { + assertType(self::class, $foo); + } else { + assertType('InstanceOfClassString\Foo~InstanceOfClassString\Bar', $foo); + } + } } diff --git a/tests/PHPStan/Analyser/data/instanceof.php b/tests/PHPStan/Analyser/data/instanceof.php index 21b871dd64..f1b28f9ec7 100644 --- a/tests/PHPStan/Analyser/data/instanceof.php +++ b/tests/PHPStan/Analyser/data/instanceof.php @@ -8,205 +8,201 @@ interface BarInterface { - } abstract class BarParent { - } class Foo extends BarParent { - - public function someMethod(Expr $foo) - { - $bar = $foo; - $baz = doFoo(); - $intersected = new Foo(); - $parent = doFoo(); - - if ($baz instanceof Foo) { - // ... - } else { - while ($foo instanceof ArrayDimFetch) { - assert($lorem instanceof Lorem); - if ($dolor instanceof Dolor && $sit instanceof Sit) { - if ($static instanceof static) { - if ($self instanceof self) { - if ($intersected instanceof BarInterface) { - if ($this instanceof BarInterface) { - if ($parent instanceof parent) { - assertType('PhpParser\Node\Expr\ArrayDimFetch', $foo); - assertType('PhpParser\Node\Expr', $bar); - assertType('*ERROR*', $baz); - assertType('InstanceOfNamespace\Lorem', $lorem); - assertType('InstanceOfNamespace\Dolor', $dolor); - assertType('InstanceOfNamespace\Sit', $sit); - assertType('InstanceOfNamespace\Foo', $self); - assertType('static(InstanceOfNamespace\Foo)', $static); - assertType('static(InstanceOfNamespace\Foo)', clone $static); - assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $intersected); - assertType('$this(InstanceOfNamespace\Foo)&InstanceOfNamespace\BarInterface', $this); - assertType('InstanceOfNamespace\BarParent', $parent); - } - } - } - } - } - } - } - } - } - - /** - * @template ObjectT of BarInterface - * @template MixedT - * - * @param class-string $classString - * @param class-string|class-string $union - * @param class-string&class-string $intersection - * @param BarInterface $instance - * @param ObjectT $objectT - * @param class-string $objectTString - * @param class-string $mixedTString - * @param object $object - */ - public function testExprInstanceof($subject, string $classString, $union, $intersection, string $other, $instance, $objectT, $objectTString, $mixedTString, string $string, $object) - { - assertType('bool', $subject instanceof $classString); - if ($subject instanceof $classString) { - assertType('InstanceOfNamespace\Foo', $subject); - assertType('true', $subject instanceof Foo); - assertType('bool', $subject instanceof $classString); - } else { - assertType('mixed~InstanceOfNamespace\Foo', $subject); - assertType('false', $subject instanceof Foo); - assertType('false', $subject instanceof $classString); - } - - $constantString = 'InstanceOfNamespace\BarParent'; - - assertType('bool', $subject instanceof $constantString); - if ($subject instanceof $constantString) { - assertType('InstanceOfNamespace\BarParent', $subject); - assertType('true', $subject instanceof BarParent); - assertType('true', $subject instanceof $constantString); - } else { - assertType('mixed~InstanceOfNamespace\BarParent', $subject); - assertType('false', $subject instanceof BarParent); - assertType('false', $subject instanceof $constantString); - } - - assertType('bool', $subject instanceof $union); - if ($subject instanceof $union) { - assertType('InstanceOfNamespace\BarInterface|InstanceOfNamespace\Foo', $subject); - assertType('bool', $subject instanceof $union); - assertType('bool', $subject instanceof BarInterface); - assertType('bool', $subject instanceof Foo); - assertType('true', $subject instanceof Foo || $subject instanceof BarInterface); - } - - if ($subject instanceof $intersection) { - assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $subject); - assertType('bool', $subject instanceof $intersection); - assertType('true', $subject instanceof BarInterface); - assertType('true', $subject instanceof Foo); - } - - if ($subject instanceof $instance) { - assertType('InstanceOfNamespace\BarInterface', $subject); - assertType('bool', $subject instanceof $instance); - assertType('true', $subject instanceof BarInterface); - } - - if ($subject instanceof $other) { - assertType('object', $subject); - assertType('bool', $subject instanceof $other); - } else { - assertType('mixed', $subject); - assertType('bool', $subject instanceof $other); - } - - if ($subject instanceof $objectT) { - assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('bool', $subject instanceof $objectT); - } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $objectT); - } - - if ($subject instanceof $objectTString) { - assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('bool', $subject instanceof $objectTString); - } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $objectTString); - } - - if ($subject instanceof $mixedTString) { - assertType('MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)&object', $subject); - assertType('bool', $subject instanceof $mixedTString); - } else { - assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $mixedTString); - } - - if ($subject instanceof $string) { - assertType('object', $subject); - assertType('bool', $subject instanceof $string); - } else { - assertType('mixed', $subject); - assertType('bool', $subject instanceof $string); - } - - if ($object instanceof $string) { - assertType('object', $object); - assertType('bool', $object instanceof $string); - } else { - assertType('object', $object); - assertType('bool', $object instanceof $string); - } - - if ($object instanceof $object) { - assertType('object', $object); - assertType('bool', $object instanceof $object); - } else { - assertType('object', $object); - assertType('bool', $object instanceof $object); - } - - if ($object instanceof $classString) { - assertType('InstanceOfNamespace\Foo', $object); - assertType('bool', $object instanceof $classString); - } else { - assertType('object~InstanceOfNamespace\Foo', $object); - assertType('false', $object instanceof $classString); - } - - if ($instance instanceof $string) { - assertType('InstanceOfNamespace\BarInterface', $instance); - assertType('bool', $instance instanceof $string); - } else { - assertType('InstanceOfNamespace\BarInterface', $instance); - assertType('bool', $instance instanceof $string); - } - - if ($instance instanceof $object) { - assertType('InstanceOfNamespace\BarInterface', $instance); - assertType('bool', $instance instanceof $object); - } else { - assertType('InstanceOfNamespace\BarInterface', $instance); - assertType('bool', $object instanceof $object); - } - - if ($instance instanceof $classString) { - assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $instance); - assertType('bool', $instance instanceof $classString); - } else { - assertType('InstanceOfNamespace\BarInterface', $instance); - assertType('bool', $instance instanceof $classString); - } - } - + public function someMethod(Expr $foo) + { + $bar = $foo; + $baz = doFoo(); + $intersected = new Foo(); + $parent = doFoo(); + + if ($baz instanceof Foo) { + // ... + } else { + while ($foo instanceof ArrayDimFetch) { + assert($lorem instanceof Lorem); + if ($dolor instanceof Dolor && $sit instanceof Sit) { + if ($static instanceof static) { + if ($self instanceof self) { + if ($intersected instanceof BarInterface) { + if ($this instanceof BarInterface) { + if ($parent instanceof parent) { + assertType('PhpParser\Node\Expr\ArrayDimFetch', $foo); + assertType('PhpParser\Node\Expr', $bar); + assertType('*ERROR*', $baz); + assertType('InstanceOfNamespace\Lorem', $lorem); + assertType('InstanceOfNamespace\Dolor', $dolor); + assertType('InstanceOfNamespace\Sit', $sit); + assertType('InstanceOfNamespace\Foo', $self); + assertType('static(InstanceOfNamespace\Foo)', $static); + assertType('static(InstanceOfNamespace\Foo)', clone $static); + assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $intersected); + assertType('$this(InstanceOfNamespace\Foo)&InstanceOfNamespace\BarInterface', $this); + assertType('InstanceOfNamespace\BarParent', $parent); + } + } + } + } + } + } + } + } + } + + /** + * @template ObjectT of BarInterface + * @template MixedT + * + * @param class-string $classString + * @param class-string|class-string $union + * @param class-string&class-string $intersection + * @param BarInterface $instance + * @param ObjectT $objectT + * @param class-string $objectTString + * @param class-string $mixedTString + * @param object $object + */ + public function testExprInstanceof($subject, string $classString, $union, $intersection, string $other, $instance, $objectT, $objectTString, $mixedTString, string $string, $object) + { + assertType('bool', $subject instanceof $classString); + if ($subject instanceof $classString) { + assertType('InstanceOfNamespace\Foo', $subject); + assertType('true', $subject instanceof Foo); + assertType('bool', $subject instanceof $classString); + } else { + assertType('mixed~InstanceOfNamespace\Foo', $subject); + assertType('false', $subject instanceof Foo); + assertType('false', $subject instanceof $classString); + } + + $constantString = 'InstanceOfNamespace\BarParent'; + + assertType('bool', $subject instanceof $constantString); + if ($subject instanceof $constantString) { + assertType('InstanceOfNamespace\BarParent', $subject); + assertType('true', $subject instanceof BarParent); + assertType('true', $subject instanceof $constantString); + } else { + assertType('mixed~InstanceOfNamespace\BarParent', $subject); + assertType('false', $subject instanceof BarParent); + assertType('false', $subject instanceof $constantString); + } + + assertType('bool', $subject instanceof $union); + if ($subject instanceof $union) { + assertType('InstanceOfNamespace\BarInterface|InstanceOfNamespace\Foo', $subject); + assertType('bool', $subject instanceof $union); + assertType('bool', $subject instanceof BarInterface); + assertType('bool', $subject instanceof Foo); + assertType('true', $subject instanceof Foo || $subject instanceof BarInterface); + } + + if ($subject instanceof $intersection) { + assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $subject); + assertType('bool', $subject instanceof $intersection); + assertType('true', $subject instanceof BarInterface); + assertType('true', $subject instanceof Foo); + } + + if ($subject instanceof $instance) { + assertType('InstanceOfNamespace\BarInterface', $subject); + assertType('bool', $subject instanceof $instance); + assertType('true', $subject instanceof BarInterface); + } + + if ($subject instanceof $other) { + assertType('object', $subject); + assertType('bool', $subject instanceof $other); + } else { + assertType('mixed', $subject); + assertType('bool', $subject instanceof $other); + } + + if ($subject instanceof $objectT) { + assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('bool', $subject instanceof $objectT); + } else { + assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('false', $subject instanceof $objectT); + } + + if ($subject instanceof $objectTString) { + assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('bool', $subject instanceof $objectTString); + } else { + assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('false', $subject instanceof $objectTString); + } + + if ($subject instanceof $mixedTString) { + assertType('MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)&object', $subject); + assertType('bool', $subject instanceof $mixedTString); + } else { + assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('false', $subject instanceof $mixedTString); + } + + if ($subject instanceof $string) { + assertType('object', $subject); + assertType('bool', $subject instanceof $string); + } else { + assertType('mixed', $subject); + assertType('bool', $subject instanceof $string); + } + + if ($object instanceof $string) { + assertType('object', $object); + assertType('bool', $object instanceof $string); + } else { + assertType('object', $object); + assertType('bool', $object instanceof $string); + } + + if ($object instanceof $object) { + assertType('object', $object); + assertType('bool', $object instanceof $object); + } else { + assertType('object', $object); + assertType('bool', $object instanceof $object); + } + + if ($object instanceof $classString) { + assertType('InstanceOfNamespace\Foo', $object); + assertType('bool', $object instanceof $classString); + } else { + assertType('object~InstanceOfNamespace\Foo', $object); + assertType('false', $object instanceof $classString); + } + + if ($instance instanceof $string) { + assertType('InstanceOfNamespace\BarInterface', $instance); + assertType('bool', $instance instanceof $string); + } else { + assertType('InstanceOfNamespace\BarInterface', $instance); + assertType('bool', $instance instanceof $string); + } + + if ($instance instanceof $object) { + assertType('InstanceOfNamespace\BarInterface', $instance); + assertType('bool', $instance instanceof $object); + } else { + assertType('InstanceOfNamespace\BarInterface', $instance); + assertType('bool', $object instanceof $object); + } + + if ($instance instanceof $classString) { + assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $instance); + assertType('bool', $instance instanceof $classString); + } else { + assertType('InstanceOfNamespace\BarInterface', $instance); + assertType('bool', $instance instanceof $classString); + } + } } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 794c339935..34e921031d 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -5,154 +5,153 @@ use function PHPStan\Testing\assertType; function (int $i) { - if ($i < 3) { - assertType('int', $i); - - $i++; - assertType('int', $i); - } else { - assertType('int<3, max>', $i); - } - - if ($i < 3) { - assertType('int', $i); - - $i--; - assertType('int', $i); - } - - assertType('int|int<3, max>', $i); - - if ($i < 3 && $i > 5) { - assertType('*NEVER*', $i); - } else { - assertType('int|int<3, max>', $i); - } - - if ($i > 3 && $i < 5) { - assertType('4', $i); - } else { - assertType('3|int|int<5, max>', $i); - } - - if ($i >= 3 && $i <= 5) { - assertType('int<3, 5>', $i); - - if ($i === 2) { - assertType('*NEVER*', $i); - } else { - assertType('int<3, 5>', $i); - } - - if ($i !== 3) { - assertType('int<4, 5>', $i); - } else { - assertType('3', $i); - } - } + if ($i < 3) { + assertType('int', $i); + + $i++; + assertType('int', $i); + } else { + assertType('int<3, max>', $i); + } + + if ($i < 3) { + assertType('int', $i); + + $i--; + assertType('int', $i); + } + + assertType('int|int<3, max>', $i); + + if ($i < 3 && $i > 5) { + assertType('*NEVER*', $i); + } else { + assertType('int|int<3, max>', $i); + } + + if ($i > 3 && $i < 5) { + assertType('4', $i); + } else { + assertType('3|int|int<5, max>', $i); + } + + if ($i >= 3 && $i <= 5) { + assertType('int<3, 5>', $i); + + if ($i === 2) { + assertType('*NEVER*', $i); + } else { + assertType('int<3, 5>', $i); + } + + if ($i !== 3) { + assertType('int<4, 5>', $i); + } else { + assertType('3', $i); + } + } }; function () { - for ($i = 0; $i < 5; $i++) { - assertType('int', $i); // should improved to be int<0, 4> - } - - $i = 0; - while ($i < 5) { - assertType('int', $i); // should improved to be int<0, 4> - $i++; - } - - $i = 0; - while ($i++ < 5) { - assertType('int', $i); // should improved to be int<1, 5> - } - - $i = 0; - while (++$i < 5) { - assertType('int', $i); // should improved to be int<1, 4> - } - - $i = 5; - while ($i-- > 0) { - assertType('int<0, max>', $i); // should improved to be int<0, 4> - } - - $i = 5; - while (--$i > 0) { - assertType('int<1, max>', $i); // should improved to be int<1, 4> - } + for ($i = 0; $i < 5; $i++) { + assertType('int', $i); // should improved to be int<0, 4> + } + + $i = 0; + while ($i < 5) { + assertType('int', $i); // should improved to be int<0, 4> + $i++; + } + + $i = 0; + while ($i++ < 5) { + assertType('int', $i); // should improved to be int<1, 5> + } + + $i = 0; + while (++$i < 5) { + assertType('int', $i); // should improved to be int<1, 4> + } + + $i = 5; + while ($i-- > 0) { + assertType('int<0, max>', $i); // should improved to be int<0, 4> + } + + $i = 5; + while (--$i > 0) { + assertType('int<1, max>', $i); // should improved to be int<1, 4> + } }; function (int $j) { - $i = 1; - - assertType('true', $i > 0); - assertType('true', $i >= 1); - assertType('true', $i <= 1); - assertType('true', $i < 2); - - assertType('false', $i < 1); - assertType('false', $i <= 0); - assertType('false', $i >= 2); - assertType('false', $i > 1); - - assertType('true', 0 < $i); - assertType('true', 1 <= $i); - assertType('true', 1 >= $i); - assertType('true', 2 > $i); - - assertType('bool', $j > 0); - assertType('bool', $j >= 0); - assertType('bool', $j <= 0); - assertType('bool', $j < 0); - - if ($j < 5) { - assertType('bool', $j > 0); - assertType('false', $j > 4); - assertType('bool', 0 < $j); - assertType('false', 4 < $j); - - assertType('bool', $j >= 0); - assertType('false', $j >= 5); - assertType('bool', 0 <= $j); - assertType('false', 5 <= $j); - - assertType('true', $j <= 4); - assertType('bool', $j <= 3); - assertType('true', 4 >= $j); - assertType('bool', 3 >= $j); - - assertType('true', $j < 5); - assertType('bool', $j < 4); - assertType('true', 5 > $j); - assertType('bool', 4 > $j); - } + $i = 1; + + assertType('true', $i > 0); + assertType('true', $i >= 1); + assertType('true', $i <= 1); + assertType('true', $i < 2); + + assertType('false', $i < 1); + assertType('false', $i <= 0); + assertType('false', $i >= 2); + assertType('false', $i > 1); + + assertType('true', 0 < $i); + assertType('true', 1 <= $i); + assertType('true', 1 >= $i); + assertType('true', 2 > $i); + + assertType('bool', $j > 0); + assertType('bool', $j >= 0); + assertType('bool', $j <= 0); + assertType('bool', $j < 0); + + if ($j < 5) { + assertType('bool', $j > 0); + assertType('false', $j > 4); + assertType('bool', 0 < $j); + assertType('false', 4 < $j); + + assertType('bool', $j >= 0); + assertType('false', $j >= 5); + assertType('bool', 0 <= $j); + assertType('false', 5 <= $j); + + assertType('true', $j <= 4); + assertType('bool', $j <= 3); + assertType('true', 4 >= $j); + assertType('bool', 3 >= $j); + + assertType('true', $j < 5); + assertType('bool', $j < 4); + assertType('true', 5 > $j); + assertType('bool', 4 > $j); + } }; function (int $a, int $b, int $c): void { + if ($a <= 11) { + return; + } - if ($a <= 11) { - return; - } + assertType('int<12, max>', $a); - assertType('int<12, max>', $a); + if ($b <= 12) { + return; + } - if ($b <= 12) { - return; - } + assertType('int<13, max>', $b); - assertType('int<13, max>', $b); + if ($c <= 13) { + return; + } - if ($c <= 13) { - return; - } + assertType('int<14, max>', $c); - assertType('int<14, max>', $c); - - assertType('int', $a * $b); - assertType('int', $b * $c); - assertType('int', $a * $b * $c); + assertType('int', $a * $b); + assertType('int', $b * $c); + assertType('int', $a * $b * $c); }; diff --git a/tests/PHPStan/Analyser/data/intersection-static.php b/tests/PHPStan/Analyser/data/intersection-static.php index bfcc8f731a..125d16b069 100644 --- a/tests/PHPStan/Analyser/data/intersection-static.php +++ b/tests/PHPStan/Analyser/data/intersection-static.php @@ -6,66 +6,57 @@ interface Foo { - - /** - * @return static - */ - public function returnStatic(): self; - + /** + * @return static + */ + public function returnStatic(): self; } interface Bar { - } interface Baz { - - /** - * @return static - */ - public function returnStatic(): self; - + /** + * @return static + */ + public function returnStatic(): self; } class Lorem { - - /** - * @param Foo&Bar $intersection - */ - public function doFoo($intersection) - { - assertType('IntersectionStatic\Bar&IntersectionStatic\Foo', $intersection); - assertType('IntersectionStatic\Bar&IntersectionStatic\Foo', $intersection->returnStatic()); - } - - /** - * @param Foo&Baz $intersection - */ - public function doBar($intersection) - { - assertType('IntersectionStatic\Baz&IntersectionStatic\Foo', $intersection); - assertType('IntersectionStatic\Baz&IntersectionStatic\Foo', $intersection->returnStatic()); - } - + /** + * @param Foo&Bar $intersection + */ + public function doFoo($intersection) + { + assertType('IntersectionStatic\Bar&IntersectionStatic\Foo', $intersection); + assertType('IntersectionStatic\Bar&IntersectionStatic\Foo', $intersection->returnStatic()); + } + + /** + * @param Foo&Baz $intersection + */ + public function doBar($intersection) + { + assertType('IntersectionStatic\Baz&IntersectionStatic\Foo', $intersection); + assertType('IntersectionStatic\Baz&IntersectionStatic\Foo', $intersection->returnStatic()); + } } abstract class Ipsum implements Foo { - - public function testThis(): void - { - assertType('static(IntersectionStatic\Ipsum)', $this->returnStatic()); - if ($this instanceof Bar) { - assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Bar', $this); - assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Bar', $this->returnStatic()); - } - if ($this instanceof Baz) { - assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Baz', $this); - assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Baz', $this->returnStatic()); - } - } - + public function testThis(): void + { + assertType('static(IntersectionStatic\Ipsum)', $this->returnStatic()); + if ($this instanceof Bar) { + assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Bar', $this); + assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Bar', $this->returnStatic()); + } + if ($this instanceof Baz) { + assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Baz', $this); + assertType('$this(IntersectionStatic\Ipsum)&IntersectionStatic\Baz', $this->returnStatic()); + } + } } diff --git a/tests/PHPStan/Analyser/data/invalidate-object-argument-function.php b/tests/PHPStan/Analyser/data/invalidate-object-argument-function.php index abd0449da4..ad0e0b76bb 100644 --- a/tests/PHPStan/Analyser/data/invalidate-object-argument-function.php +++ b/tests/PHPStan/Analyser/data/invalidate-object-argument-function.php @@ -6,51 +6,46 @@ class Foo { - - public function getName(): string - { - - } - + public function getName(): string + { + } } class Bar { - - /** - * @param object $foo - */ - public function doFoo($foo) - { - assert($foo instanceof Foo); - assertType(Foo::class, $foo); - assertType('string', $foo->getName()); - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - doBar($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - doBaz($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - doLorem($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - doIpsum($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - } - + /** + * @param object $foo + */ + public function doFoo($foo) + { + assert($foo instanceof Foo); + assertType(Foo::class, $foo); + assertType('string', $foo->getName()); + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + doBar($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + doBaz($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + doLorem($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + doIpsum($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + } } /** @@ -58,21 +53,17 @@ public function doFoo($foo) */ function doBar($arg) { - } function doBaz($arg) { - } function doLorem($arg): void { - } /** @phpstan-impure */ function doIpsum($arg) { - } diff --git a/tests/PHPStan/Analyser/data/invalidate-object-argument-static.php b/tests/PHPStan/Analyser/data/invalidate-object-argument-static.php index 0a015983cb..a7e04e473e 100644 --- a/tests/PHPStan/Analyser/data/invalidate-object-argument-static.php +++ b/tests/PHPStan/Analyser/data/invalidate-object-argument-static.php @@ -6,73 +6,64 @@ class Foo { - - public function getName(): string - { - - } - + public function getName(): string + { + } } class Bar { - - /** - * @param object $foo - */ - public function doFoo($foo) - { - assert($foo instanceof Foo); - assertType(Foo::class, $foo); - assertType('string', $foo->getName()); - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - self::doBar($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - self::doBaz($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - self::doLorem($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - self::doIpsum($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - } - - /** - * @phpstan-pure - */ - public static function doBar($arg) - { - - } - - public static function doBaz($arg) - { - - } - - public static function doLorem($arg): void - { - - } - - /** @phpstan-impure */ - public static function doIpsum($arg) - { - - } - + /** + * @param object $foo + */ + public function doFoo($foo) + { + assert($foo instanceof Foo); + assertType(Foo::class, $foo); + assertType('string', $foo->getName()); + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + self::doBar($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + self::doBaz($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + self::doLorem($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + self::doIpsum($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + } + + /** + * @phpstan-pure + */ + public static function doBar($arg) + { + } + + public static function doBaz($arg) + { + } + + public static function doLorem($arg): void + { + } + + /** @phpstan-impure */ + public static function doIpsum($arg) + { + } } diff --git a/tests/PHPStan/Analyser/data/invalidate-object-argument.php b/tests/PHPStan/Analyser/data/invalidate-object-argument.php index 806f1e0d2c..35008a3138 100644 --- a/tests/PHPStan/Analyser/data/invalidate-object-argument.php +++ b/tests/PHPStan/Analyser/data/invalidate-object-argument.php @@ -6,73 +6,64 @@ class Foo { - - public function getName(): string - { - - } - + public function getName(): string + { + } } class Bar { - - /** - * @param object $foo - */ - public function doFoo($foo) - { - assert($foo instanceof Foo); - assertType(Foo::class, $foo); - assertType('string', $foo->getName()); - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - $this->doBar($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - $this->doBaz($foo); - assertType('\'foo\'', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - $this->doLorem($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - - assert($foo->getName() === 'foo'); - assertType('\'foo\'', $foo->getName()); - - $this->doIpsum($foo); - assertType('string', $foo->getName()); - assertType(Foo::class, $foo); - } - - /** - * @phpstan-pure - */ - public function doBar($arg) - { - - } - - public function doBaz($arg) - { - - } - - public function doLorem($arg): void - { - - } - - /** @phpstan-impure */ - public function doIpsum($arg) - { - - } - + /** + * @param object $foo + */ + public function doFoo($foo) + { + assert($foo instanceof Foo); + assertType(Foo::class, $foo); + assertType('string', $foo->getName()); + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + $this->doBar($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + $this->doBaz($foo); + assertType('\'foo\'', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + $this->doLorem($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + + assert($foo->getName() === 'foo'); + assertType('\'foo\'', $foo->getName()); + + $this->doIpsum($foo); + assertType('string', $foo->getName()); + assertType(Foo::class, $foo); + } + + /** + * @phpstan-pure + */ + public function doBar($arg) + { + } + + public function doBaz($arg) + { + } + + public function doLorem($arg): void + { + } + + /** @phpstan-impure */ + public function doIpsum($arg) + { + } } diff --git a/tests/PHPStan/Analyser/data/is-a.php b/tests/PHPStan/Analyser/data/is-a.php index 27bde886c0..031c442a2a 100644 --- a/tests/PHPStan/Analyser/data/is-a.php +++ b/tests/PHPStan/Analyser/data/is-a.php @@ -1,28 +1,28 @@ $fooClassString */ - $fooClassString = 'Foo'; + /** @var class-string $fooClassString */ + $fooClassString = 'Foo'; - if (is_a($foo, $fooClassString)) { - \PHPStan\Testing\assertType('Foo', $foo); - } + if (is_a($foo, $fooClassString)) { + \PHPStan\Testing\assertType('Foo', $foo); + } }; function (string $foo) { - if (is_a($foo, Foo::class, true)) { - \PHPStan\Testing\assertType('class-string', $foo); - } + if (is_a($foo, Foo::class, true)) { + \PHPStan\Testing\assertType('class-string', $foo); + } }; function (string $foo, string $someString) { - if (is_a($foo, $someString, true)) { - \PHPStan\Testing\assertType('class-string', $foo); - } + if (is_a($foo, $someString, true)) { + \PHPStan\Testing\assertType('class-string', $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/is-numeric.php b/tests/PHPStan/Analyser/data/is-numeric.php index 65e339dec1..8708a22807 100644 --- a/tests/PHPStan/Analyser/data/is-numeric.php +++ b/tests/PHPStan/Analyser/data/is-numeric.php @@ -1,9 +1,9 @@ 1, - 'b' => 2, - ]; - } elseif (rand(0, 10) === 0) { - $array = [ - 'a' => 2, - ]; - } elseif (rand(0, 10) === 0) { - $array = [ - 'a' => 3, - 'b' => 3, - 'c' => 4, - ]; - } else { - $array = [ - 'a' => 3, - 'b' => null, - ]; - } - - $arrayCopy = $array; - $anotherArrayCopy = $array; - $yetAnotherArrayCopy = $array; - - if (!isset($array['b'])) { - return; - } - - if (!array_key_exists('b', $arrayCopy)) { - return; - } - if (array_key_exists('b', $anotherArrayCopy)) { - return; - } - if (array_key_exists($string, $yetAnotherArrayCopy)) { - return; - } - - if (!isset($integers['a'])) { - return; - } - - $itemsA = [ - 'foo', - 'derp', - 'herp' - ]; - - $itemsB = [ - 'foo', - 'bar', - 'baz', - ]; - - $lookup = array_fill_keys($itemsB, true); - - $nullableArray = ['a' => null, 'b' => null, 'c' => null]; - if (rand(0, 1) === 1) { - $nullableArray['a'] = 'foo'; - $nullableArray['b'] = 'bar'; - $nullableArray['c'] = 'baz'; - } - - if (!isset($nullableArray['b'])) { - return; - } - - if (!array_key_exists('c', $nullableArray)) { - return; - } - - if (!isset($mixedIsset['a'])) { - return; - } - - if (!array_key_exists('a', $mixedArrayKeyExists)) { - return; - } - - foreach ($itemsA as $a) { - die; - } - } - + /** + * @param int[] $integers + */ + public function doFoo(array $integers, string $string, $mixedIsset, $mixedArrayKeyExists) + { + if (rand(0, 10) === 0) { + $array = [ + 'a' => 1, + 'b' => 2, + ]; + } elseif (rand(0, 10) === 0) { + $array = [ + 'a' => 2, + ]; + } elseif (rand(0, 10) === 0) { + $array = [ + 'a' => 3, + 'b' => 3, + 'c' => 4, + ]; + } else { + $array = [ + 'a' => 3, + 'b' => null, + ]; + } + + $arrayCopy = $array; + $anotherArrayCopy = $array; + $yetAnotherArrayCopy = $array; + + if (!isset($array['b'])) { + return; + } + + if (!array_key_exists('b', $arrayCopy)) { + return; + } + if (array_key_exists('b', $anotherArrayCopy)) { + return; + } + if (array_key_exists($string, $yetAnotherArrayCopy)) { + return; + } + + if (!isset($integers['a'])) { + return; + } + + $itemsA = [ + 'foo', + 'derp', + 'herp' + ]; + + $itemsB = [ + 'foo', + 'bar', + 'baz', + ]; + + $lookup = array_fill_keys($itemsB, true); + + $nullableArray = ['a' => null, 'b' => null, 'c' => null]; + if (rand(0, 1) === 1) { + $nullableArray['a'] = 'foo'; + $nullableArray['b'] = 'bar'; + $nullableArray['c'] = 'baz'; + } + + if (!isset($nullableArray['b'])) { + return; + } + + if (!array_key_exists('c', $nullableArray)) { + return; + } + + if (!isset($mixedIsset['a'])) { + return; + } + + if (!array_key_exists('a', $mixedArrayKeyExists)) { + return; + } + + foreach ($itemsA as $a) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/iterable.php b/tests/PHPStan/Analyser/data/iterable.php index 592e52ab03..fa7d473815 100644 --- a/tests/PHPStan/Analyser/data/iterable.php +++ b/tests/PHPStan/Analyser/data/iterable.php @@ -4,137 +4,128 @@ interface Collection extends \Traversable { - } interface CollectionOfIntegers extends \Iterator { - public function current(): int; + public function current(): int; } class Foo { - - /** - * @var iterable - */ - private $iterableProperty; - - /** - * @var string[]|iterable - */ - private $stringIterableProperty; - - /** - * @var mixed[]|iterable - */ - private $mixedIterableProperty; - - /** - * @var string[]|iterable|int - */ - private $iterablePropertyAlsoWithSomethingElse; - - /** - * @var string[]|int[]|iterable|int - */ - private $iterablePropertyWithTwoItemTypes; - - /** - * @var CollectionOfIntegers|string[] - */ - private $collectionOfIntegersOrArrayOfStrings; - - /** - * @param iterable $iterableWithIterableTypehint - * @param Bar[] $iterableWithConcreteTypehint - * @param iterable $arrayWithIterableTypehint - * @param Bar[]|Collection $unionIterableType - * @param Foo[]|Bar[]|Collection|array $mixedUnionIterableType - * @param Bar[]|Collection $unionIterableIterableType - * @param int[]|iterable $integers - * @param mixed[]|iterable $mixeds - * @param \Generator $generatorOfFoos - * @param \ArrayObject $arrayObject - */ - public function doFoo( - iterable $iterableWithoutTypehint, - iterable $iterableWithIterableTypehint, - iterable $iterableWithConcreteTypehint, - array $arrayWithIterableTypehint, - Collection $unionIterableType, - array $mixedUnionIterableType, - iterable $unionIterableIterableType, - $iterableSpecifiedLater, - iterable $integers, - iterable $mixeds, - $generatorOfFoos, - $arrayObject - ) - { - if (!is_iterable($iterableSpecifiedLater)) { - return; - } - - foreach ($iterableWithIterableTypehint as $mixed) { - foreach ($iterableWithConcreteTypehint as $bar) { - foreach ($this->doBaz() as $baz) { - foreach ($unionIterableType as $unionBar) { - foreach ($mixedUnionIterableType as $mixedBar) { - foreach ($unionIterableIterableType as $iterableUnionBar) { - foreach ($this->doUnionIterableWithPhpDoc() as $unionBarFromMethod) { - foreach ($generatorOfFoos as $fooFromGenerator) { - foreach ($arrayObject as $arrayObjectKey => $arrayObjectValue) { - die; - } - } - } - } - } - } - } - } - } - } - - /** - * @return iterable - */ - public function doBar(): iterable - { - - } - - /** - * @return Baz[] - */ - public function doBaz(): iterable - { - - } - - /** - * @return Bar[]|\Traversable - */ - public function doUnionIterableWithPhpDoc(): \Traversable - { - - } - - /** - * @return iterable|mixed[] - */ - public function returnIterableMixed(): iterable - { - - } - - /** - * @return iterable|string[] - */ - public function returnIterableString(): iterable - { - - } - + /** + * @var iterable + */ + private $iterableProperty; + + /** + * @var string[]|iterable + */ + private $stringIterableProperty; + + /** + * @var mixed[]|iterable + */ + private $mixedIterableProperty; + + /** + * @var string[]|iterable|int + */ + private $iterablePropertyAlsoWithSomethingElse; + + /** + * @var string[]|int[]|iterable|int + */ + private $iterablePropertyWithTwoItemTypes; + + /** + * @var CollectionOfIntegers|string[] + */ + private $collectionOfIntegersOrArrayOfStrings; + + /** + * @param iterable $iterableWithIterableTypehint + * @param Bar[] $iterableWithConcreteTypehint + * @param iterable $arrayWithIterableTypehint + * @param Bar[]|Collection $unionIterableType + * @param Foo[]|Bar[]|Collection|array $mixedUnionIterableType + * @param Bar[]|Collection $unionIterableIterableType + * @param int[]|iterable $integers + * @param mixed[]|iterable $mixeds + * @param \Generator $generatorOfFoos + * @param \ArrayObject $arrayObject + */ + public function doFoo( + iterable $iterableWithoutTypehint, + iterable $iterableWithIterableTypehint, + iterable $iterableWithConcreteTypehint, + array $arrayWithIterableTypehint, + Collection $unionIterableType, + array $mixedUnionIterableType, + iterable $unionIterableIterableType, + $iterableSpecifiedLater, + iterable $integers, + iterable $mixeds, + $generatorOfFoos, + $arrayObject + ) { + if (!is_iterable($iterableSpecifiedLater)) { + return; + } + + foreach ($iterableWithIterableTypehint as $mixed) { + foreach ($iterableWithConcreteTypehint as $bar) { + foreach ($this->doBaz() as $baz) { + foreach ($unionIterableType as $unionBar) { + foreach ($mixedUnionIterableType as $mixedBar) { + foreach ($unionIterableIterableType as $iterableUnionBar) { + foreach ($this->doUnionIterableWithPhpDoc() as $unionBarFromMethod) { + foreach ($generatorOfFoos as $fooFromGenerator) { + foreach ($arrayObject as $arrayObjectKey => $arrayObjectValue) { + die; + } + } + } + } + } + } + } + } + } + } + + /** + * @return iterable + */ + public function doBar(): iterable + { + } + + /** + * @return Baz[] + */ + public function doBaz(): iterable + { + } + + /** + * @return Bar[]|\Traversable + */ + public function doUnionIterableWithPhpDoc(): \Traversable + { + } + + /** + * @return iterable|mixed[] + */ + public function returnIterableMixed(): iterable + { + } + + /** + * @return iterable|string[] + */ + public function returnIterableString(): iterable + { + } } diff --git a/tests/PHPStan/Analyser/data/iterator-iterator.php b/tests/PHPStan/Analyser/data/iterator-iterator.php index e17aae1d25..88e90ca9c2 100644 --- a/tests/PHPStan/Analyser/data/iterator-iterator.php +++ b/tests/PHPStan/Analyser/data/iterator-iterator.php @@ -6,15 +6,13 @@ class Foo { - - /** - * @param \ArrayIterator $it - */ - public function doFoo(\ArrayIterator $it): void - { - $iteratorIterator = new \IteratorIterator($it); - assertType('Iterator', $iteratorIterator->getInnerIterator()); - assertType('array', $iteratorIterator->getArrayCopy()); - } - + /** + * @param \ArrayIterator $it + */ + public function doFoo(\ArrayIterator $it): void + { + $iteratorIterator = new \IteratorIterator($it); + assertType('Iterator', $iteratorIterator->getInnerIterator()); + assertType('array', $iteratorIterator->getArrayCopy()); + } } diff --git a/tests/PHPStan/Analyser/data/iterator_to_array.php b/tests/PHPStan/Analyser/data/iterator_to_array.php index 0ef007f922..fe4c85afd2 100644 --- a/tests/PHPStan/Analyser/data/iterator_to_array.php +++ b/tests/PHPStan/Analyser/data/iterator_to_array.php @@ -8,11 +8,11 @@ class Foo { - /** - * @param Traversable $ints - */ - public function doFoo(Traversable $ints) - { - assertType('array', iterator_to_array($ints)); - } + /** + * @param Traversable $ints + */ + public function doFoo(Traversable $ints) + { + assertType('array', iterator_to_array($ints)); + } } diff --git a/tests/PHPStan/Analyser/data/ldap-exop-passwd.php b/tests/PHPStan/Analyser/data/ldap-exop-passwd.php index f82062765e..9795467f86 100644 --- a/tests/PHPStan/Analyser/data/ldap-exop-passwd.php +++ b/tests/PHPStan/Analyser/data/ldap-exop-passwd.php @@ -4,13 +4,11 @@ class Foo { - - /** - * @param resource $r - */ - public function doFoo($r): void - { - ldap_exop_passwd($r); - } - + /** + * @param resource $r + */ + public function doFoo($r): void + { + ldap_exop_passwd($r); + } } diff --git a/tests/PHPStan/Analyser/data/list-type.php b/tests/PHPStan/Analyser/data/list-type.php index 8fccd9f831..72034e3c0d 100644 --- a/tests/PHPStan/Analyser/data/list-type.php +++ b/tests/PHPStan/Analyser/data/list-type.php @@ -6,92 +6,91 @@ class Foo { - /** @param list $list */ - public function directAssertion($list): void - { - assertType('array', $list); - } + /** @param list $list */ + public function directAssertion($list): void + { + assertType('array', $list); + } - /** @param list $list */ - public function directAssertionParamHint(array $list): void - { - assertType('array', $list); - } + /** @param list $list */ + public function directAssertionParamHint(array $list): void + { + assertType('array', $list); + } - /** @param list $list */ - public function directAssertionNullableParamHint(array $list = null): void - { - assertType('array|null', $list); - } + /** @param list $list */ + public function directAssertionNullableParamHint(array $list = null): void + { + assertType('array|null', $list); + } - /** @param list<\DateTime> $list */ - public function directAssertionObjectParamHint($list): void - { - assertType('array', $list); - } + /** @param list<\DateTime> $list */ + public function directAssertionObjectParamHint($list): void + { + assertType('array', $list); + } - public function withoutGenerics(): void - { - /** @var list $list */ - $list = []; - $list[] = '1'; - $list[] = true; - $list[] = new \stdClass(); - assertType('array&nonEmpty', $list); - } + public function withoutGenerics(): void + { + /** @var list $list */ + $list = []; + $list[] = '1'; + $list[] = true; + $list[] = new \stdClass(); + assertType('array&nonEmpty', $list); + } - public function withMixedType(): void - { - /** @var list $list */ - $list = []; - $list[] = '1'; - $list[] = true; - $list[] = new \stdClass(); - assertType('array&nonEmpty', $list); - } + public function withMixedType(): void + { + /** @var list $list */ + $list = []; + $list[] = '1'; + $list[] = true; + $list[] = new \stdClass(); + assertType('array&nonEmpty', $list); + } - public function withObjectType(): void - { - /** @var list<\DateTime> $list */ - $list = []; - $list[] = new \DateTime(); - assertType('array&nonEmpty', $list); - } + public function withObjectType(): void + { + /** @var list<\DateTime> $list */ + $list = []; + $list[] = new \DateTime(); + assertType('array&nonEmpty', $list); + } - /** @return list */ - public function withScalarGoodContent(): void - { - /** @var list $list */ - $list = []; - $list[] = '1'; - $list[] = true; - assertType('array&nonEmpty', $list); - } + /** @return list */ + public function withScalarGoodContent(): void + { + /** @var list $list */ + $list = []; + $list[] = '1'; + $list[] = true; + assertType('array&nonEmpty', $list); + } - public function withNumericKey(): void - { - /** @var list $list */ - $list = []; - $list[] = '1'; - $list['1'] = true; - assertType('array&nonEmpty', $list); - } + public function withNumericKey(): void + { + /** @var list $list */ + $list = []; + $list[] = '1'; + $list['1'] = true; + assertType('array&nonEmpty', $list); + } - public function withFullListFunctionality(): void - { - // These won't output errors for now but should when list type will be fully implemented - /** @var list $list */ - $list = []; - $list[] = '1'; - $list[] = '2'; - unset($list[0]);//break list behaviour - assertType('array', $list); - - /** @var list $list2 */ - $list2 = []; - $list2[2] = '1';//Most likely to create a gap in indexes - assertType('array&nonEmpty', $list2); - } + public function withFullListFunctionality(): void + { + // These won't output errors for now but should when list type will be fully implemented + /** @var list $list */ + $list = []; + $list[] = '1'; + $list[] = '2'; + unset($list[0]);//break list behaviour + assertType('array', $list); + /** @var list $list2 */ + $list2 = []; + $list2[2] = '1';//Most likely to create a gap in indexes + assertType('array&nonEmpty', $list2); + } } diff --git a/tests/PHPStan/Analyser/data/literal-arrays-keys.php b/tests/PHPStan/Analyser/data/literal-arrays-keys.php index a6fd722e54..b0813fadc9 100644 --- a/tests/PHPStan/Analyser/data/literal-arrays-keys.php +++ b/tests/PHPStan/Analyser/data/literal-arrays-keys.php @@ -4,126 +4,124 @@ class Foo { - - public function getString(): string - { - return '1'; - } - + public function getString(): string + { + return '1'; + } } function () { - $foo = new Foo(); - - foreach ([ - 'one', - 'two', - 'three', - ] as $key => $value) { - 'NoKeysArray'; - } - - foreach ([ - 0 => 'one', - 'two', - 'three', - ] as $key => $value) { - 'IntegersAndNoKeysArray'; - } - - - foreach ([ - 'foo' => 'one', - 'two', - 'three', - ] as $key => $value) { - 'StringsAndNoKeysArray'; - } - - foreach ([ - '1' => 'one', - 'two', - 'three', - ] as $key => $value) { - 'IntegersAsStringsAndNoKeysArray'; - } - - foreach ([ - '1' => 'one', - '2' => 'two', - \STRING_ONE => 'three', - ] as $key => $value) { - 'IntegersAsStringsArray'; - } - - foreach ([ - 1 => 'one', - 2 => 'two', - \INT_ONE => 'three', - ] as $key => $value) { - 'IntegersArray'; - } - - foreach ([ - 1 => 'one', - 2.5 => 'two', - 3.2 => 'three', - ] as $key => $value) { - 'IntegersWithFloatsArray'; - } - - foreach ([ - 'foo' => 'one', - 'bar' => 'two', - \STRING_FOO => 'three', - ] as $key => $value) { - 'StringsArray'; - } - - foreach ([ - null => 'one', - 'bar' => 'two', - 'baz' => 'three', - ] as $key => $value) { - 'StringsWithNullArray'; - } - - foreach ([ - 1 => 'one', - 2 => 'two', - $foo->getString() => 'three', - ] as $key => $value) { - 'IntegersWithStringFromMethodArray'; - } - - foreach ([ - 1 => 'one', - 2 => 'two', - 'foo' => 'three', - ] as $key => $value) { - 'IntegersAndStringsArray'; - } - - foreach ([ - true => 'one', - false => 'two', - ] as $key => $value) { - 'BooleansArray'; - } - - foreach ([ - 1 => 'one', - 2 => 'two', - 'foo' => 'three', - ] as $key => $value) { - 'IntegersAndStringsArray'; - } - - foreach ([ - UNKNOWN_CONSTANT => 'one', - 2 => 'two', - 'foo' => 'three', - ] as $key => $value) { - 'UnknownConstantArray'; - } + $foo = new Foo(); + + foreach ([ + 'one', + 'two', + 'three', + ] as $key => $value) { + 'NoKeysArray'; + } + + foreach ([ + 0 => 'one', + 'two', + 'three', + ] as $key => $value) { + 'IntegersAndNoKeysArray'; + } + + + foreach ([ + 'foo' => 'one', + 'two', + 'three', + ] as $key => $value) { + 'StringsAndNoKeysArray'; + } + + foreach ([ + '1' => 'one', + 'two', + 'three', + ] as $key => $value) { + 'IntegersAsStringsAndNoKeysArray'; + } + + foreach ([ + '1' => 'one', + '2' => 'two', + \STRING_ONE => 'three', + ] as $key => $value) { + 'IntegersAsStringsArray'; + } + + foreach ([ + 1 => 'one', + 2 => 'two', + \INT_ONE => 'three', + ] as $key => $value) { + 'IntegersArray'; + } + + foreach ([ + 1 => 'one', + 2.5 => 'two', + 3.2 => 'three', + ] as $key => $value) { + 'IntegersWithFloatsArray'; + } + + foreach ([ + 'foo' => 'one', + 'bar' => 'two', + \STRING_FOO => 'three', + ] as $key => $value) { + 'StringsArray'; + } + + foreach ([ + null => 'one', + 'bar' => 'two', + 'baz' => 'three', + ] as $key => $value) { + 'StringsWithNullArray'; + } + + foreach ([ + 1 => 'one', + 2 => 'two', + $foo->getString() => 'three', + ] as $key => $value) { + 'IntegersWithStringFromMethodArray'; + } + + foreach ([ + 1 => 'one', + 2 => 'two', + 'foo' => 'three', + ] as $key => $value) { + 'IntegersAndStringsArray'; + } + + foreach ([ + true => 'one', + false => 'two', + ] as $key => $value) { + 'BooleansArray'; + } + + foreach ([ + 1 => 'one', + 2 => 'two', + 'foo' => 'three', + ] as $key => $value) { + 'IntegersAndStringsArray'; + } + + foreach ([ + UNKNOWN_CONSTANT => 'one', + 2 => 'two', + 'foo' => 'three', + ] as $key => $value) { + 'UnknownConstantArray'; + } }; diff --git a/tests/PHPStan/Analyser/data/literal-arrays.php b/tests/PHPStan/Analyser/data/literal-arrays.php index cdebb6479e..6d66e14b7f 100644 --- a/tests/PHPStan/Analyser/data/literal-arrays.php +++ b/tests/PHPStan/Analyser/data/literal-arrays.php @@ -6,15 +6,15 @@ $mixedArray = [0, 'foo']; $nestedArray = [ - 'foo' => [ - 'foo' => [ - 'foo' => 'bar', - ], - ], - 'bar' => [], - 'baz' => [ - 'lorem' => [], - ], + 'foo' => [ + 'foo' => [ + 'foo' => 'bar', + ], + ], + 'bar' => [], + 'baz' => [ + 'lorem' => [], + ], ]; die; diff --git a/tests/PHPStan/Analyser/data/loop-variables-defined.php b/tests/PHPStan/Analyser/data/loop-variables-defined.php index 7277a64af1..43ce74835e 100644 --- a/tests/PHPStan/Analyser/data/loop-variables-defined.php +++ b/tests/PHPStan/Analyser/data/loop-variables-defined.php @@ -4,20 +4,16 @@ class Foo { - } class Bar { - } class Baz { - } class Lorem { - } diff --git a/tests/PHPStan/Analyser/data/match-expr.php b/tests/PHPStan/Analyser/data/match-expr.php index 2f37aeba1c..04beddc89f 100644 --- a/tests/PHPStan/Analyser/data/match-expr.php +++ b/tests/PHPStan/Analyser/data/match-expr.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace MatchExpr; @@ -6,66 +8,68 @@ class Foo { + /** + * @param 1|2|3|4 $i + */ + public function doFoo(int $i): void + { + assertType('*NEVER*', match ($i) { + 0 => $i, + }); + assertType('1|2|3|4', $i); + assertType('1', match ($i) { + 1 => $i, + }); + assertType('1|2|3|4', $i); + assertType('1|2', match ($i) { + 1, 2 => $i, + }); + assertType('1|2|3|4', $i); + assertType('1|2|3', match ($i) { + 1, 2, 3 => $i, + }); + assertType('1|2|3|4', $i); + assertType('2|3', match ($i) { + 1 => exit(), + 2, 3 => $i, + }); + assertType('1|2|3|4', $i); + } - /** - * @param 1|2|3|4 $i - */ - public function doFoo(int $i): void - { - assertType('*NEVER*', match ($i) { - 0 => $i, - }); - assertType('1|2|3|4', $i); - assertType('1', match ($i) { - 1 => $i, - }); - assertType('1|2|3|4', $i); - assertType('1|2', match ($i) { - 1, 2 => $i, - }); - assertType('1|2|3|4', $i); - assertType('1|2|3', match ($i) { - 1, 2, 3 => $i, - }); - assertType('1|2|3|4', $i); - assertType('2|3', match ($i) { - 1 => exit(), - 2, 3 => $i, - }); - assertType('1|2|3|4', $i); - } - - /** - * @param 1|2|3|4 $i - */ - public function doBar(int $i): void - { - match ($i) { - 0 => assertType('*NEVER*', $i), - default => assertType('1|2|3|4', $i), - }; - assertType('1|2|3|4', $i); - match ($i) { - 1 => assertType('1', $i), - default => assertType('2|3|4', $i), - }; - assertType('1|2|3|4', $i); - match ($i) { - 1, 2 => assertType('1|2', $i), - default => assertType('3|4', $i), - }; - assertType('1|2|3|4', $i); - match ($i) { - 1, 2, 3 => assertType('1|2|3', $i), - default => assertType('4', $i), - }; - assertType('1|2|3|4', $i); - - match ($i) { - assertType('1|2|3|4', $i), 1, assertType('2|3|4', $i) => null, - assertType('2|3|4', $i) => null, - default => assertType('2|3|4', $i), - }; - } + /** + * @param 1|2|3|4 $i + */ + public function doBar(int $i): void + { + match ($i) { + 0 => assertType('*NEVER*', $i), + default => assertType('1|2|3|4', $i), + }; + assertType('1|2|3|4', $i); + match ($i) { + 1 => assertType('1', $i), + // no break + default => assertType('2|3|4', $i), + }; + assertType('1|2|3|4', $i); + match ($i) { + 1, 2 => assertType('1|2', $i), + // no break + default => assertType('3|4', $i), + }; + assertType('1|2|3|4', $i); + match ($i) { + 1, 2, 3 => assertType('1|2|3', $i), + // no break + default => assertType('4', $i), + }; + assertType('1|2|3|4', $i); + match ($i) { + assertType('1|2|3|4', $i), 1, assertType('2|3|4', $i) => null, + assertType('2|3|4', $i) => null, + // no break + default => assertType('2|3|4', $i), + }; + } } diff --git a/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc-without-curly-braces.php b/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc-without-curly-braces.php index c0dff8a645..4f4995b414 100644 --- a/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc-without-curly-braces.php +++ b/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc-without-curly-braces.php @@ -7,66 +7,62 @@ class FooInheritDocChild extends Foo { + /** + * @inheritdoc + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new Foo(); - /** - * @inheritdoc - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new Foo(); + /** @var self $inlineSelf */ + $inlineSelf = doFoo(); - /** @var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @var Bar $inlineBar */ - $inlineBar = doFoo(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - /** - * @inheritdoc - */ - private function privateMethodWithPhpDoc() - { - - } + /** @var Bar $inlineBar */ + $inlineBar = doFoo(); + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } + /** + * @inheritdoc + */ + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc.php b/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc.php index 5c4ddbef6d..9cf78b37f8 100644 --- a/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc.php +++ b/tests/PHPStan/Analyser/data/method-phpDocs-inheritdoc.php @@ -7,66 +7,62 @@ class FooInheritDocChild extends Foo { + /** + * {@inheritdoc} + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new Foo(); - /** - * {@inheritdoc} - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new Foo(); + /** @var self $inlineSelf */ + $inlineSelf = doFoo(); - /** @var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @var Bar $inlineBar */ - $inlineBar = doFoo(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - /** - * {@inheritdoc} - */ - private function privateMethodWithPhpDoc() - { - - } + /** @var Bar $inlineBar */ + $inlineBar = doFoo(); + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } + /** + * {@inheritdoc} + */ + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-defined.php b/tests/PHPStan/Analyser/data/methodPhpDocs-defined.php index 4613086bdd..c4674e2198 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-defined.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-defined.php @@ -6,45 +6,38 @@ class Bar { - - /** - * @return self - */ - public function doBar() - { - - } - - /** - * @return static - */ - public function doFluent() - { - - } - - /** - * @return static|null - */ - public function doFluentNullable() - { - - } - - /** - * @return static[] - */ - public function doFluentArray(): array - { - - } - - /** - * @return static[]|Collection - */ - public function doFluentUnionIterable(): Collection - { - - } - + /** + * @return self + */ + public function doBar() + { + } + + /** + * @return static + */ + public function doFluent() + { + } + + /** + * @return static|null + */ + public function doFluentNullable() + { + } + + /** + * @return static[] + */ + public function doFluentArray(): array + { + } + + /** + * @return static[]|Collection + */ + public function doFluentUnionIterable(): Collection + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-defined2.php b/tests/PHPStan/Analyser/data/methodPhpDocs-defined2.php index 2fedee8455..ad3c746935 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-defined2.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-defined2.php @@ -6,108 +6,93 @@ interface FooInterface { - - /** - * @return void - */ - public function phpDocVoidMethodFromInterface(); - + /** + * @return void + */ + public function phpDocVoidMethodFromInterface(); } class FooParentParent { - - /** - * @return void - */ - public function phpDocVoidParentMethod() - { - - } - - /** - * @return void - */ - public function phpDocWithoutCurlyBracesVoidParentMethod() - { - - } - + /** + * @return void + */ + public function phpDocVoidParentMethod() + { + } + + /** + * @return void + */ + public function phpDocWithoutCurlyBracesVoidParentMethod() + { + } } abstract class FooParent extends FooParentParent implements FooInterface { - - /** - * @return Static - */ - public function doLorem() - { - - } - - /** - * @return static - */ - public function doIpsum(): self - { - - } - - /** - * @return $this - */ - public function doThis() - { - return $this; - } - - /** - * @return $this|null - */ - public function doThisNullable() - { - return $this; - } - - /** - * @return $this|Bar|null - */ - public function doThisUnion() - { - - } - - /** - * @return void - */ - public function phpDocVoidMethod() - { - - } - - /** - * {@inheritDoc} - */ - public function phpDocVoidParentMethod() - { - - } - - /** - * @inheritDoc - */ - public function phpDocWithoutCurlyBracesVoidParentMethod() - { - - } - - /** - * @return string[] - */ - private function privateMethodWithPhpDoc() - { - - } - + /** + * @return Static + */ + public function doLorem() + { + } + + /** + * @return static + */ + public function doIpsum(): self + { + } + + /** + * @return $this + */ + public function doThis() + { + return $this; + } + + /** + * @return $this|null + */ + public function doThisNullable() + { + return $this; + } + + /** + * @return $this|Bar|null + */ + public function doThisUnion() + { + } + + /** + * @return void + */ + public function phpDocVoidMethod() + { + } + + /** + * {@inheritDoc} + */ + public function phpDocVoidParentMethod() + { + } + + /** + * @inheritDoc + */ + public function phpDocWithoutCurlyBracesVoidParentMethod() + { + } + + /** + * @return string[] + */ + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-defined3.php b/tests/PHPStan/Analyser/data/methodPhpDocs-defined3.php index c2af2b6fb0..e5bdfaf6b5 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-defined3.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-defined3.php @@ -6,5 +6,4 @@ class Baz extends Bar { - } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-defined4.php b/tests/PHPStan/Analyser/data/methodPhpDocs-defined4.php index 6101a9fb35..ffbcb86762 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-defined4.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-defined4.php @@ -4,5 +4,4 @@ interface Collection extends \Traversable { - } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-implicitInheritance.php b/tests/PHPStan/Analyser/data/methodPhpDocs-implicitInheritance.php index cedd25b75e..1a57d7a32d 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-implicitInheritance.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-implicitInheritance.php @@ -7,65 +7,60 @@ class FooPhpDocsImplicitInheritanceChild extends Foo { + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new Foo(); - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new Foo(); + /** @var self $inlineSelf */ + $inlineSelf = doFoo(); - /** @var self $inlineSelf */ - $inlineSelf = doFoo(); + /** @var Bar $inlineBar */ + $inlineBar = doFoo(); + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } - /** @var Bar $inlineBar */ - $inlineBar = doFoo(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } + public function returnsStringArray(): array + { + } + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-phpstanPrefix.php b/tests/PHPStan/Analyser/data/methodPhpDocs-phpstanPrefix.php index 32b65d1167..55455fe608 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-phpstanPrefix.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-phpstanPrefix.php @@ -7,168 +7,153 @@ class FooPhpstanPrefix extends FooParent { - - /** - * @phpstan-return Bar - */ - public static function doSomethingStatic() - { - - } - - /** - * @phpstan-param Foo|Bar $unionTypeParameter - * @phpstan-param int $anotherMixedParameter - * @phpstan-param int $anotherMixedParameter - * @phpstan-paran int $yetAnotherMixedProperty - * @phpstan-param int $integerParameter - * @phpstan-param integer $anotherIntegerParameter - * @phpstan-param aRray $arrayParameterOne - * @phpstan-param mixed[] $arrayParameterOther - * @phpstan-param Lorem $objectRelative - * @phpstan-param \SomeOtherNamespace\Ipsum $objectFullyQualified - * @phpstan-param Dolor $objectUsed - * @phpstan-param null|int $nullableInteger - * @phpstan-param Dolor|null $nullableObject - * @phpstan-param Dolor $anotherNullableObject - * @phpstan-param self $selfType - * @phpstan-param static $staticType - * @phpstan-param Null $nullType - * @phpstan-param Bar $barObject - * @phpstan-param Foo $conflictedObject - * @phpstan-param Baz $moreSpecifiedObject - * @phpstan-param resource $resource - * @phpstan-param array[array] $yetAnotherAnotherMixedParameter - * @phpstan-param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter - * @phpstan-param New $yetAnotherAnotherAnotherAnotherMixedParameter - * @phpstan-param void $voidParameter - * @phpstan-param Consecteur $useWithoutAlias - * @phpstan-param true $true - * @phpstan-param false $false - * @phpstan-param true $boolTrue - * @phpstan-param false $boolFalse - * @phpstan-param bool $trueBoolean - * @phpstan-param bool $parameterWithDefaultValueFalse - * @phpstan-param object $objectWithoutNativeTypehint - * @phpstan-param object $objectWithNativeTypehint - * @phpstan-return Foo - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new self(); - - /** @phpstan-var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @phpstan-var Bar $inlineBar */ - $inlineBar = doFoo(); - - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - /** - * @phpstan-return self[] - */ - public function doBar(): array - { - - } - - public function returnParent(): parent - { - - } - - /** - * @phpstan-return parent - */ - public function returnPhpDocParent() - { - - } - - /** - * @phpstan-return NULL[] - */ - public function returnNulls(): array - { - - } - - public function returnObject(): object - { - - } - - public function phpDocVoidMethod(): self - { - - } - - public function phpDocVoidMethodFromInterface(): self - { - - } - - public function phpDocVoidParentMethod(): self - { - - } - - public function phpDocWithoutCurlyBracesVoidParentMethod(): self - { - - } - - /** - * @phpstan-return string[] - */ - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } - + /** + * @phpstan-return Bar + */ + public static function doSomethingStatic() + { + } + + /** + * @phpstan-param Foo|Bar $unionTypeParameter + * @phpstan-param int $anotherMixedParameter + * @phpstan-param int $anotherMixedParameter + * @phpstan-paran int $yetAnotherMixedProperty + * @phpstan-param int $integerParameter + * @phpstan-param integer $anotherIntegerParameter + * @phpstan-param aRray $arrayParameterOne + * @phpstan-param mixed[] $arrayParameterOther + * @phpstan-param Lorem $objectRelative + * @phpstan-param \SomeOtherNamespace\Ipsum $objectFullyQualified + * @phpstan-param Dolor $objectUsed + * @phpstan-param null|int $nullableInteger + * @phpstan-param Dolor|null $nullableObject + * @phpstan-param Dolor $anotherNullableObject + * @phpstan-param self $selfType + * @phpstan-param static $staticType + * @phpstan-param Null $nullType + * @phpstan-param Bar $barObject + * @phpstan-param Foo $conflictedObject + * @phpstan-param Baz $moreSpecifiedObject + * @phpstan-param resource $resource + * @phpstan-param array[array] $yetAnotherAnotherMixedParameter + * @phpstan-param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter + * @phpstan-param New $yetAnotherAnotherAnotherAnotherMixedParameter + * @phpstan-param void $voidParameter + * @phpstan-param Consecteur $useWithoutAlias + * @phpstan-param true $true + * @phpstan-param false $false + * @phpstan-param true $boolTrue + * @phpstan-param false $boolFalse + * @phpstan-param bool $trueBoolean + * @phpstan-param bool $parameterWithDefaultValueFalse + * @phpstan-param object $objectWithoutNativeTypehint + * @phpstan-param object $objectWithNativeTypehint + * @phpstan-return Foo + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new self(); + + /** @phpstan-var self $inlineSelf */ + $inlineSelf = doFoo(); + + /** @phpstan-var Bar $inlineBar */ + $inlineBar = doFoo(); + + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } + + /** + * @phpstan-return self[] + */ + public function doBar(): array + { + } + + public function returnParent(): parent + { + } + + /** + * @phpstan-return parent + */ + public function returnPhpDocParent() + { + } + + /** + * @phpstan-return NULL[] + */ + public function returnNulls(): array + { + } + + public function returnObject(): object + { + } + + public function phpDocVoidMethod(): self + { + } + + public function phpDocVoidMethodFromInterface(): self + { + } + + public function phpDocVoidParentMethod(): self + { + } + + public function phpDocWithoutCurlyBracesVoidParentMethod(): self + { + } + + /** + * @phpstan-return string[] + */ + public function returnsStringArray(): array + { + } + + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-psalmPrefix.php b/tests/PHPStan/Analyser/data/methodPhpDocs-psalmPrefix.php index b168c05e4d..044ae3d204 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-psalmPrefix.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-psalmPrefix.php @@ -7,168 +7,153 @@ class FooPsalmPrefix extends FooParent { - - /** - * @psalm-return Bar - */ - public static function doSomethingStatic() - { - - } - - /** - * @psalm-param Foo|Bar $unionTypeParameter - * @psalm-param int $anotherMixedParameter - * @psalm-param int $anotherMixedParameter - * @psalm-paran int $yetAnotherMixedProperty - * @psalm-param int $integerParameter - * @psalm-param integer $anotherIntegerParameter - * @psalm-param aRray $arrayParameterOne - * @psalm-param mixed[] $arrayParameterOther - * @psalm-param Lorem $objectRelative - * @psalm-param \SomeOtherNamespace\Ipsum $objectFullyQualified - * @psalm-param Dolor $objectUsed - * @psalm-param null|int $nullableInteger - * @psalm-param Dolor|null $nullableObject - * @psalm-param Dolor $anotherNullableObject - * @psalm-param self $selfType - * @psalm-param static $staticType - * @psalm-param Null $nullType - * @psalm-param Bar $barObject - * @psalm-param Foo $conflictedObject - * @psalm-param Baz $moreSpecifiedObject - * @psalm-param resource $resource - * @psalm-param array[array] $yetAnotherAnotherMixedParameter - * @psalm-param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter - * @psalm-param New $yetAnotherAnotherAnotherAnotherMixedParameter - * @psalm-param void $voidParameter - * @psalm-param Consecteur $useWithoutAlias - * @psalm-param true $true - * @psalm-param false $false - * @psalm-param true $boolTrue - * @psalm-param false $boolFalse - * @psalm-param bool $trueBoolean - * @psalm-param bool $parameterWithDefaultValueFalse - * @psalm-param object $objectWithoutNativeTypehint - * @psalm-param object $objectWithNativeTypehint - * @psalm-return Foo - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new self(); - - /** @psalm-var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @psalm-var Bar $inlineBar */ - $inlineBar = doFoo(); - - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - /** - * @psalm-return self[] - */ - public function doBar(): array - { - - } - - public function returnParent(): parent - { - - } - - /** - * @psalm-return parent - */ - public function returnPhpDocParent() - { - - } - - /** - * @psalm-return NULL[] - */ - public function returnNulls(): array - { - - } - - public function returnObject(): object - { - - } - - public function phpDocVoidMethod(): self - { - - } - - public function phpDocVoidMethodFromInterface(): self - { - - } - - public function phpDocVoidParentMethod(): self - { - - } - - public function phpDocWithoutCurlyBracesVoidParentMethod(): self - { - - } - - /** - * @psalm-return string[] - */ - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } - + /** + * @psalm-return Bar + */ + public static function doSomethingStatic() + { + } + + /** + * @psalm-param Foo|Bar $unionTypeParameter + * @psalm-param int $anotherMixedParameter + * @psalm-param int $anotherMixedParameter + * @psalm-paran int $yetAnotherMixedProperty + * @psalm-param int $integerParameter + * @psalm-param integer $anotherIntegerParameter + * @psalm-param aRray $arrayParameterOne + * @psalm-param mixed[] $arrayParameterOther + * @psalm-param Lorem $objectRelative + * @psalm-param \SomeOtherNamespace\Ipsum $objectFullyQualified + * @psalm-param Dolor $objectUsed + * @psalm-param null|int $nullableInteger + * @psalm-param Dolor|null $nullableObject + * @psalm-param Dolor $anotherNullableObject + * @psalm-param self $selfType + * @psalm-param static $staticType + * @psalm-param Null $nullType + * @psalm-param Bar $barObject + * @psalm-param Foo $conflictedObject + * @psalm-param Baz $moreSpecifiedObject + * @psalm-param resource $resource + * @psalm-param array[array] $yetAnotherAnotherMixedParameter + * @psalm-param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter + * @psalm-param New $yetAnotherAnotherAnotherAnotherMixedParameter + * @psalm-param void $voidParameter + * @psalm-param Consecteur $useWithoutAlias + * @psalm-param true $true + * @psalm-param false $false + * @psalm-param true $boolTrue + * @psalm-param false $boolFalse + * @psalm-param bool $trueBoolean + * @psalm-param bool $parameterWithDefaultValueFalse + * @psalm-param object $objectWithoutNativeTypehint + * @psalm-param object $objectWithNativeTypehint + * @psalm-return Foo + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new self(); + + /** @psalm-var self $inlineSelf */ + $inlineSelf = doFoo(); + + /** @psalm-var Bar $inlineBar */ + $inlineBar = doFoo(); + + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } + + /** + * @psalm-return self[] + */ + public function doBar(): array + { + } + + public function returnParent(): parent + { + } + + /** + * @psalm-return parent + */ + public function returnPhpDocParent() + { + } + + /** + * @psalm-return NULL[] + */ + public function returnNulls(): array + { + } + + public function returnObject(): object + { + } + + public function phpDocVoidMethod(): self + { + } + + public function phpDocVoidMethodFromInterface(): self + { + } + + public function phpDocVoidParentMethod(): self + { + } + + public function phpDocWithoutCurlyBracesVoidParentMethod(): self + { + } + + /** + * @psalm-return string[] + */ + public function returnsStringArray(): array + { + } + + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-recursiveTrait.php b/tests/PHPStan/Analyser/data/methodPhpDocs-recursiveTrait.php index 03f9a8dd5c..6fb801e37b 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-recursiveTrait.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-recursiveTrait.php @@ -7,89 +7,73 @@ trait RecursiveFooTrait { - - use FooTrait; - + use FooTrait; } class FooWithRecursiveTrait extends FooParent { - - use RecursiveFooTrait; - - /** - * @return Bar - */ - public static function doSomethingStatic() - { - - } - - /** - * @return self[] - */ - public function doBar(): array - { - - } - - public function returnParent(): parent - { - - } - - /** - * @return parent - */ - public function returnPhpDocParent() - { - - } - - /** - * @return NULL[] - */ - public function returnNulls(): array - { - - } - - public function returnObject(): object - { - - } - - public function phpDocVoidMethod(): self - { - - } - - public function phpDocVoidMethodFromInterface(): self - { - - } - - public function phpDocVoidParentMethod(): self - { - - } - - public function phpDocWithoutCurlyBracesVoidParentMethod(): self - { - - } - - /** - * @return string[] - */ - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } - + use RecursiveFooTrait; + + /** + * @return Bar + */ + public static function doSomethingStatic() + { + } + + /** + * @return self[] + */ + public function doBar(): array + { + } + + public function returnParent(): parent + { + } + + /** + * @return parent + */ + public function returnPhpDocParent() + { + } + + /** + * @return NULL[] + */ + public function returnNulls(): array + { + } + + public function returnObject(): object + { + } + + public function phpDocVoidMethod(): self + { + } + + public function phpDocVoidMethodFromInterface(): self + { + } + + public function phpDocVoidParentMethod(): self + { + } + + public function phpDocWithoutCurlyBracesVoidParentMethod(): self + { + } + + /** + * @return string[] + */ + public function returnsStringArray(): array + { + } + + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-trait-defined.php b/tests/PHPStan/Analyser/data/methodPhpDocs-trait-defined.php index afe0e75bc1..f7167fd53c 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-trait-defined.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-trait-defined.php @@ -7,92 +7,89 @@ trait FooTrait { + /** + * @param Foo|Bar $unionTypeParameter + * @param int $anotherMixedParameter + * @param int $anotherMixedParameter + * @paran int $yetAnotherMixedProperty + * @param int $integerParameter + * @param integer $anotherIntegerParameter + * @param aRray $arrayParameterOne + * @param mixed[] $arrayParameterOther + * @param Lorem $objectRelative + * @param \SomeOtherNamespace\Ipsum $objectFullyQualified + * @param Dolor $objectUsed + * @param null|int $nullableInteger + * @param Dolor|null $nullableObject + * @param Dolor $anotherNullableObject + * @param self $selfType + * @param static $staticType + * @param Null $nullType + * @param Bar $barObject + * @param Foo $conflictedObject + * @param Baz $moreSpecifiedObject + * @param resource $resource + * @param array[array] $yetAnotherAnotherMixedParameter + * @param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter + * @param New $yetAnotherAnotherAnotherAnotherMixedParameter + * @param void $voidParameter + * @param Consecteur $useWithoutAlias + * @param true $true + * @param false $false + * @param true $boolTrue + * @param false $boolFalse + * @param bool $trueBoolean + * @param bool $parameterWithDefaultValueFalse + * @param object $objectWithoutNativeTypehint + * @param object $objectWithNativeTypehint + * @return Foo + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new self(); - /** - * @param Foo|Bar $unionTypeParameter - * @param int $anotherMixedParameter - * @param int $anotherMixedParameter - * @paran int $yetAnotherMixedProperty - * @param int $integerParameter - * @param integer $anotherIntegerParameter - * @param aRray $arrayParameterOne - * @param mixed[] $arrayParameterOther - * @param Lorem $objectRelative - * @param \SomeOtherNamespace\Ipsum $objectFullyQualified - * @param Dolor $objectUsed - * @param null|int $nullableInteger - * @param Dolor|null $nullableObject - * @param Dolor $anotherNullableObject - * @param self $selfType - * @param static $staticType - * @param Null $nullType - * @param Bar $barObject - * @param Foo $conflictedObject - * @param Baz $moreSpecifiedObject - * @param resource $resource - * @param array[array] $yetAnotherAnotherMixedParameter - * @param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter - * @param New $yetAnotherAnotherAnotherAnotherMixedParameter - * @param void $voidParameter - * @param Consecteur $useWithoutAlias - * @param true $true - * @param false $false - * @param true $boolTrue - * @param false $boolFalse - * @param bool $trueBoolean - * @param bool $parameterWithDefaultValueFalse - * @param object $objectWithoutNativeTypehint - * @param object $objectWithNativeTypehint - * @return Foo - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new self(); - - /** @var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @var Bar $inlineBar */ - $inlineBar = doFoo(); - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } + /** @var self $inlineSelf */ + $inlineSelf = doFoo(); + /** @var Bar $inlineBar */ + $inlineBar = doFoo(); + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-trait.php b/tests/PHPStan/Analyser/data/methodPhpDocs-trait.php index 3fd17e45fb..a1f884d326 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-trait.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-trait.php @@ -7,82 +7,68 @@ class FooWithTrait extends FooParent { - - use FooTrait; - - /** - * @return Bar - */ - public static function doSomethingStatic() - { - - } - - /** - * @return self[] - */ - public function doBar(): array - { - - } - - public function returnParent(): parent - { - - } - - /** - * @return parent - */ - public function returnPhpDocParent() - { - - } - - /** - * @return NULL[] - */ - public function returnNulls(): array - { - - } - - public function returnObject(): object - { - - } - - public function phpDocVoidMethod(): self - { - - } - - public function phpDocVoidMethodFromInterface(): self - { - - } - - public function phpDocVoidParentMethod(): self - { - - } - - public function phpDocWithoutCurlyBracesVoidParentMethod(): self - { - - } - - /** - * @return string[] - */ - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } - + use FooTrait; + + /** + * @return Bar + */ + public static function doSomethingStatic() + { + } + + /** + * @return self[] + */ + public function doBar(): array + { + } + + public function returnParent(): parent + { + } + + /** + * @return parent + */ + public function returnPhpDocParent() + { + } + + /** + * @return NULL[] + */ + public function returnNulls(): array + { + } + + public function returnObject(): object + { + } + + public function phpDocVoidMethod(): self + { + } + + public function phpDocVoidMethodFromInterface(): self + { + } + + public function phpDocVoidParentMethod(): self + { + } + + public function phpDocWithoutCurlyBracesVoidParentMethod(): self + { + } + + /** + * @return string[] + */ + public function returnsStringArray(): array + { + } + + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs-traitInSameFileAsClass.php b/tests/PHPStan/Analyser/data/methodPhpDocs-traitInSameFileAsClass.php index 14b4d3895a..6d92422ecb 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs-traitInSameFileAsClass.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs-traitInSameFileAsClass.php @@ -4,25 +4,21 @@ trait FooTrait { - - /** - * @return string - */ - public function getFoo() - { - return 'foo'; - } - + /** + * @return string + */ + public function getFoo() + { + return 'foo'; + } } class Foo { + use FooTrait; - use FooTrait; - - public function bar() - { - die; - } - + public function bar() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/methodPhpDocs.php b/tests/PHPStan/Analyser/data/methodPhpDocs.php index 5a6b301c2d..50fd838ecc 100644 --- a/tests/PHPStan/Analyser/data/methodPhpDocs.php +++ b/tests/PHPStan/Analyser/data/methodPhpDocs.php @@ -7,168 +7,153 @@ class Foo extends FooParent { - - /** - * @return Bar - */ - public static function doSomethingStatic() - { - - } - - /** - * @param Foo|Bar $unionTypeParameter - * @param int $anotherMixedParameter - * @param int $anotherMixedParameter - * @paran int $yetAnotherMixedProperty - * @param int $integerParameter - * @param integer $anotherIntegerParameter - * @param aRray $arrayParameterOne - * @param mixed[] $arrayParameterOther - * @param Lorem $objectRelative - * @param \SomeOtherNamespace\Ipsum $objectFullyQualified - * @param Dolor $objectUsed - * @param null|int $nullableInteger - * @param Dolor|null $nullableObject - * @param Dolor $anotherNullableObject - * @param self $selfType - * @param static $staticType - * @param Null $nullType - * @param Bar $barObject - * @param Foo $conflictedObject - * @param Baz $moreSpecifiedObject - * @param resource $resource - * @param array[array] $yetAnotherAnotherMixedParameter - * @param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter - * @param New $yetAnotherAnotherAnotherAnotherMixedParameter - * @param void $voidParameter - * @param Consecteur $useWithoutAlias - * @param true $true - * @param false $false - * @param true $boolTrue - * @param false $boolFalse - * @param bool $trueBoolean - * @param bool $parameterWithDefaultValueFalse - * @param object $objectWithoutNativeTypehint - * @param object $objectWithNativeTypehint - * @return Foo - */ - public function doFoo( - $mixedParameter, - $unionTypeParameter, - $anotherMixedParameter, - $yetAnotherMixedParameter, - $integerParameter, - $anotherIntegerParameter, - $arrayParameterOne, - $arrayParameterOther, - $objectRelative, - $objectFullyQualified, - $objectUsed, - $nullableInteger, - $nullableObject, - $selfType, - $staticType, - $nullType, - $barObject, - Bar $conflictedObject, - Bar $moreSpecifiedObject, - $resource, - $yetAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherMixedParameter, - $yetAnotherAnotherAnotherAnotherMixedParameter, - $voidParameter, - $useWithoutAlias, - $true, - $false, - bool $boolTrue, - bool $boolFalse, - bool $trueBoolean, - $objectWithoutNativeTypehint, - object $objectWithNativeTypehint, - $parameterWithDefaultValueFalse = false, - $anotherNullableObject = null - ) - { - $parent = new FooParent(); - $differentInstance = new self(); - - /** @var self $inlineSelf */ - $inlineSelf = doFoo(); - - /** @var Bar $inlineBar */ - $inlineBar = doFoo(); - - foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { - die; - } - } - - /** - * @return self[] - */ - public function doBar(): array - { - - } - - public function returnParent(): parent - { - - } - - /** - * @return parent - */ - public function returnPhpDocParent() - { - - } - - /** - * @return NULL[] - */ - public function returnNulls(): array - { - - } - - public function returnObject(): object - { - - } - - public function phpDocVoidMethod(): self - { - - } - - public function phpDocVoidMethodFromInterface(): self - { - - } - - public function phpDocVoidParentMethod(): self - { - - } - - public function phpDocWithoutCurlyBracesVoidParentMethod(): self - { - - } - - /** - * @return string[] - */ - public function returnsStringArray(): array - { - - } - - private function privateMethodWithPhpDoc() - { - - } - + /** + * @return Bar + */ + public static function doSomethingStatic() + { + } + + /** + * @param Foo|Bar $unionTypeParameter + * @param int $anotherMixedParameter + * @param int $anotherMixedParameter + * @paran int $yetAnotherMixedProperty + * @param int $integerParameter + * @param integer $anotherIntegerParameter + * @param aRray $arrayParameterOne + * @param mixed[] $arrayParameterOther + * @param Lorem $objectRelative + * @param \SomeOtherNamespace\Ipsum $objectFullyQualified + * @param Dolor $objectUsed + * @param null|int $nullableInteger + * @param Dolor|null $nullableObject + * @param Dolor $anotherNullableObject + * @param self $selfType + * @param static $staticType + * @param Null $nullType + * @param Bar $barObject + * @param Foo $conflictedObject + * @param Baz $moreSpecifiedObject + * @param resource $resource + * @param array[array] $yetAnotherAnotherMixedParameter + * @param \\Test\Bar $yetAnotherAnotherAnotherMixedParameter + * @param New $yetAnotherAnotherAnotherAnotherMixedParameter + * @param void $voidParameter + * @param Consecteur $useWithoutAlias + * @param true $true + * @param false $false + * @param true $boolTrue + * @param false $boolFalse + * @param bool $trueBoolean + * @param bool $parameterWithDefaultValueFalse + * @param object $objectWithoutNativeTypehint + * @param object $objectWithNativeTypehint + * @return Foo + */ + public function doFoo( + $mixedParameter, + $unionTypeParameter, + $anotherMixedParameter, + $yetAnotherMixedParameter, + $integerParameter, + $anotherIntegerParameter, + $arrayParameterOne, + $arrayParameterOther, + $objectRelative, + $objectFullyQualified, + $objectUsed, + $nullableInteger, + $nullableObject, + $selfType, + $staticType, + $nullType, + $barObject, + Bar $conflictedObject, + Bar $moreSpecifiedObject, + $resource, + $yetAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherMixedParameter, + $yetAnotherAnotherAnotherAnotherMixedParameter, + $voidParameter, + $useWithoutAlias, + $true, + $false, + bool $boolTrue, + bool $boolFalse, + bool $trueBoolean, + $objectWithoutNativeTypehint, + object $objectWithNativeTypehint, + $parameterWithDefaultValueFalse = false, + $anotherNullableObject = null + ) { + $parent = new FooParent(); + $differentInstance = new self(); + + /** @var self $inlineSelf */ + $inlineSelf = doFoo(); + + /** @var Bar $inlineBar */ + $inlineBar = doFoo(); + + foreach ($moreSpecifiedObject->doFluentUnionIterable() as $fluentUnionIterableBaz) { + die; + } + } + + /** + * @return self[] + */ + public function doBar(): array + { + } + + public function returnParent(): parent + { + } + + /** + * @return parent + */ + public function returnPhpDocParent() + { + } + + /** + * @return NULL[] + */ + public function returnNulls(): array + { + } + + public function returnObject(): object + { + } + + public function phpDocVoidMethod(): self + { + } + + public function phpDocVoidMethodFromInterface(): self + { + } + + public function phpDocVoidParentMethod(): self + { + } + + public function phpDocWithoutCurlyBracesVoidParentMethod(): self + { + } + + /** + * @return string[] + */ + public function returnsStringArray(): array + { + } + + private function privateMethodWithPhpDoc() + { + } } diff --git a/tests/PHPStan/Analyser/data/minmax-arrays.php b/tests/PHPStan/Analyser/data/minmax-arrays.php index 762c70be68..dd93982245 100644 --- a/tests/PHPStan/Analyser/data/minmax-arrays.php +++ b/tests/PHPStan/Analyser/data/minmax-arrays.php @@ -6,10 +6,10 @@ function dummy(): void { - assertType('1', min([1])); - assertType('false', min([])); - assertType('1', max([1])); - assertType('false', max([])); + assertType('1', min([1])); + assertType('false', min([])); + assertType('1', max([1])); + assertType('false', max([])); } /** @@ -17,76 +17,76 @@ function dummy(): void */ function dummy2(array $ints): void { - if (count($ints) === 0) { - assertType('false', min($ints)); - assertType('false', max($ints)); - } else { - assertType('int', min($ints)); - assertType('int', max($ints)); - } - if (count($ints) === 1) { - assertType('int', min($ints)); - assertType('int', max($ints)); - } else { - assertType('int|false', min($ints)); - assertType('int|false', max($ints)); - } - if (count($ints) !== 0) { - assertType('int', min($ints)); - assertType('int', max($ints)); - } else { - assertType('false', min($ints)); - assertType('false', max($ints)); - } - if (count($ints) !== 1) { - assertType('int|false', min($ints)); - assertType('int|false', max($ints)); - } else { - assertType('int', min($ints)); - assertType('int', max($ints)); - } - if (count($ints) > 0) { - assertType('int', min($ints)); - assertType('int', max($ints)); - } else { - assertType('false', min($ints)); - assertType('false', max($ints)); - } - if (count($ints) >= 1) { - assertType('int', min($ints)); - assertType('int', max($ints)); - } else { - assertType('false', min($ints)); - assertType('false', max($ints)); - } - if (count($ints) >= 2) { - assertType('int', min($ints)); - assertType('int', max($ints)); - } else { - assertType('int|false', min($ints)); - assertType('int|false', max($ints)); - } - if (count($ints) <= 0) { - assertType('false', min($ints)); - assertType('false', max($ints)); - } else { - assertType('int', min($ints)); - assertType('int', max($ints)); - } - if (count($ints) < 1) { - assertType('false', min($ints)); - assertType('false', max($ints)); - } else { - assertType('int', min($ints)); - assertType('int', max($ints)); - } - if (count($ints) < 2) { - assertType('int|false', min($ints)); - assertType('int|false', max($ints)); - } else { - assertType('int', min($ints)); - assertType('int', max($ints)); - } + if (count($ints) === 0) { + assertType('false', min($ints)); + assertType('false', max($ints)); + } else { + assertType('int', min($ints)); + assertType('int', max($ints)); + } + if (count($ints) === 1) { + assertType('int', min($ints)); + assertType('int', max($ints)); + } else { + assertType('int|false', min($ints)); + assertType('int|false', max($ints)); + } + if (count($ints) !== 0) { + assertType('int', min($ints)); + assertType('int', max($ints)); + } else { + assertType('false', min($ints)); + assertType('false', max($ints)); + } + if (count($ints) !== 1) { + assertType('int|false', min($ints)); + assertType('int|false', max($ints)); + } else { + assertType('int', min($ints)); + assertType('int', max($ints)); + } + if (count($ints) > 0) { + assertType('int', min($ints)); + assertType('int', max($ints)); + } else { + assertType('false', min($ints)); + assertType('false', max($ints)); + } + if (count($ints) >= 1) { + assertType('int', min($ints)); + assertType('int', max($ints)); + } else { + assertType('false', min($ints)); + assertType('false', max($ints)); + } + if (count($ints) >= 2) { + assertType('int', min($ints)); + assertType('int', max($ints)); + } else { + assertType('int|false', min($ints)); + assertType('int|false', max($ints)); + } + if (count($ints) <= 0) { + assertType('false', min($ints)); + assertType('false', max($ints)); + } else { + assertType('int', min($ints)); + assertType('int', max($ints)); + } + if (count($ints) < 1) { + assertType('false', min($ints)); + assertType('false', max($ints)); + } else { + assertType('int', min($ints)); + assertType('int', max($ints)); + } + if (count($ints) < 2) { + assertType('int|false', min($ints)); + assertType('int|false', max($ints)); + } else { + assertType('int', min($ints)); + assertType('int', max($ints)); + } } /** @@ -94,27 +94,28 @@ function dummy2(array $ints): void */ function dummy3(array $ints): void { - assertType('int|false', min($ints)); - assertType('int|false', max($ints)); + assertType('int|false', min($ints)); + assertType('int|false', max($ints)); } function dummy4(\DateTimeInterface $dateA, ?\DateTimeInterface $dateB): void { - assertType('array(0 => DateTimeInterface, ?1 => DateTimeInterface)', array_filter([$dateA, $dateB])); - assertType('DateTimeInterface', min(array_filter([$dateA, $dateB]))); - assertType('DateTimeInterface', max(array_filter([$dateA, $dateB]))); - assertType('array(?0 => DateTimeInterface)', array_filter([$dateB])); - assertType('DateTimeInterface|false', min(array_filter([$dateB]))); - assertType('DateTimeInterface|false', max(array_filter([$dateB]))); + assertType('array(0 => DateTimeInterface, ?1 => DateTimeInterface)', array_filter([$dateA, $dateB])); + assertType('DateTimeInterface', min(array_filter([$dateA, $dateB]))); + assertType('DateTimeInterface', max(array_filter([$dateA, $dateB]))); + assertType('array(?0 => DateTimeInterface)', array_filter([$dateB])); + assertType('DateTimeInterface|false', min(array_filter([$dateB]))); + assertType('DateTimeInterface|false', max(array_filter([$dateB]))); } function dummy5(int $i, int $j): void { - assertType('array(?0 => int|int<1, max>, ?1 => int|int<1, max>)', array_filter([$i, $j])); - assertType('array(1 => true)', array_filter([false, true])); + assertType('array(?0 => int|int<1, max>, ?1 => int|int<1, max>)', array_filter([$i, $j])); + assertType('array(1 => true)', array_filter([false, true])); } -function dummy6(string $s, string $t): void { - assertType('array(?0 => string, ?1 => string)', array_filter([$s, $t])); +function dummy6(string $s, string $t): void +{ + assertType('array(?0 => string, ?1 => string)', array_filter([$s, $t])); } diff --git a/tests/PHPStan/Analyser/data/misleading-types-without-namespace.php b/tests/PHPStan/Analyser/data/misleading-types-without-namespace.php index 25afdac432..4f520d1f79 100644 --- a/tests/PHPStan/Analyser/data/misleading-types-without-namespace.php +++ b/tests/PHPStan/Analyser/data/misleading-types-without-namespace.php @@ -2,20 +2,16 @@ class FooClassForNodeScopeResolverTestingWithoutNamespace { + public function misleadingBoolReturnType(): \boolean + { + } - public function misleadingBoolReturnType(): \boolean - { - - } - - public function misleadingIntReturnType(): \integer - { - - } - + public function misleadingIntReturnType(): \integer + { + } } function () { - $foo = new FooClassForNodeScopeResolverTestingWithoutNamespace(); - die; + $foo = new FooClassForNodeScopeResolverTestingWithoutNamespace(); + die; }; diff --git a/tests/PHPStan/Analyser/data/misleading-types.php b/tests/PHPStan/Analyser/data/misleading-types.php index 1da194f288..95c0e20d77 100644 --- a/tests/PHPStan/Analyser/data/misleading-types.php +++ b/tests/PHPStan/Analyser/data/misleading-types.php @@ -4,25 +4,20 @@ class Foo { + public function misleadingBoolReturnType(): \MisleadingTypes\boolean + { + } - public function misleadingBoolReturnType(): \MisleadingTypes\boolean - { - - } - - public function misleadingIntReturnType(): \MisleadingTypes\integer - { - - } - - public function misleadingMixedReturnType(): mixed - { - - } + public function misleadingIntReturnType(): \MisleadingTypes\integer + { + } + public function misleadingMixedReturnType(): mixed + { + } } function () { - $foo = new Foo(); - die; + $foo = new Foo(); + die; }; diff --git a/tests/PHPStan/Analyser/data/mixed-elements.php b/tests/PHPStan/Analyser/data/mixed-elements.php index 33e4e36a57..09768ed693 100644 --- a/tests/PHPStan/Analyser/data/mixed-elements.php +++ b/tests/PHPStan/Analyser/data/mixed-elements.php @@ -3,5 +3,5 @@ namespace MixedElements; function ($mixed) { - die; + die; }; diff --git a/tests/PHPStan/Analyser/data/mixed-typehint.php b/tests/PHPStan/Analyser/data/mixed-typehint.php index 8d7ce4ad16..6f96b31a90 100644 --- a/tests/PHPStan/Analyser/data/mixed-typehint.php +++ b/tests/PHPStan/Analyser/data/mixed-typehint.php @@ -6,34 +6,30 @@ class Foo { - - public function doFoo(mixed $foo) - { - assertType('mixed', $foo); - assertType('mixed', $this->doBar()); - } - - public function doBar(): mixed - { - - } - + public function doFoo(mixed $foo) + { + assertType('mixed', $foo); + assertType('mixed', $this->doBar()); + } + + public function doBar(): mixed + { + } } function doFoo(mixed $foo) { - assertType('mixed', $foo); + assertType('mixed', $foo); } function (mixed $foo) { - assertType('mixed', $foo); - $f = function (): mixed { - - }; - assertType('void', $f()); - - $f = function () use ($foo): mixed { - return $foo; - }; - assertType('mixed', $f()); + assertType('mixed', $foo); + $f = function (): mixed { + }; + assertType('void', $f()); + + $f = function () use ($foo): mixed { + return $foo; + }; + assertType('mixed', $f()); }; diff --git a/tests/PHPStan/Analyser/data/multi-assign.php b/tests/PHPStan/Analyser/data/multi-assign.php index ab4e4de1cf..e4310bcd9e 100644 --- a/tests/PHPStan/Analyser/data/multi-assign.php +++ b/tests/PHPStan/Analyser/data/multi-assign.php @@ -5,79 +5,79 @@ use function PHPStan\Testing\assertType; function (): void { - $foo = $bar = $baz = null; - assertType('null', $foo); - assertType('null', $bar); - assertType('null', $baz); - if (!$foo) { - assertType('null', $foo); - assertType('null', $bar); - assertType('null', $baz); - } + $foo = $bar = $baz = null; + assertType('null', $foo); + assertType('null', $bar); + assertType('null', $baz); + if (!$foo) { + assertType('null', $foo); + assertType('null', $bar); + assertType('null', $baz); + } - if (!$bar) { - assertType('null', $foo); - assertType('null', $bar); - assertType('null', $baz); - } + if (!$bar) { + assertType('null', $foo); + assertType('null', $bar); + assertType('null', $baz); + } }; function (bool $b): void { - $foo = $bar = $baz = $b; - if ($b) { - assertType('true', $b); - assertType('bool', $foo); - assertType('bool', $bar); - assertType('bool', $baz); - } else { - assertType('false', $b); - assertType('bool', $foo); - assertType('bool', $bar); - assertType('bool', $baz); - } + $foo = $bar = $baz = $b; + if ($b) { + assertType('true', $b); + assertType('bool', $foo); + assertType('bool', $bar); + assertType('bool', $baz); + } else { + assertType('false', $b); + assertType('bool', $foo); + assertType('bool', $bar); + assertType('bool', $baz); + } }; function (bool $b): void { - $foo = $bar = $baz = $b; - if ($foo) { - assertType('true', $b); - assertType('true', $foo); - assertType('bool', $bar); - assertType('bool', $baz); - } else { - assertType('false', $b); - assertType('false', $foo); - assertType('bool', $bar); - assertType('bool', $baz); - } + $foo = $bar = $baz = $b; + if ($foo) { + assertType('true', $b); + assertType('true', $foo); + assertType('bool', $bar); + assertType('bool', $baz); + } else { + assertType('false', $b); + assertType('false', $foo); + assertType('bool', $bar); + assertType('bool', $baz); + } }; function (bool $b): void { - $foo = $bar = $baz = $b; - if ($bar) { - assertType('bool', $b); - assertType('bool', $foo); - assertType('true', $bar); - assertType('bool', $baz); - } else { - assertType('bool', $b); - assertType('bool', $foo); - assertType('false', $bar); - assertType('bool', $baz); - } + $foo = $bar = $baz = $b; + if ($bar) { + assertType('bool', $b); + assertType('bool', $foo); + assertType('true', $bar); + assertType('bool', $baz); + } else { + assertType('bool', $b); + assertType('bool', $foo); + assertType('false', $bar); + assertType('bool', $baz); + } }; function (bool $b): void { - $foo = $bar = $baz = $b; - if ($baz) { - assertType('bool', $b); - assertType('bool', $foo); - assertType('bool', $bar); - assertType('true', $baz); - } else { - assertType('bool', $b); - assertType('bool', $foo); - assertType('bool', $bar); - assertType('false', $baz); - } + $foo = $bar = $baz = $b; + if ($baz) { + assertType('bool', $b); + assertType('bool', $foo); + assertType('bool', $bar); + assertType('true', $baz); + } else { + assertType('bool', $b); + assertType('bool', $foo); + assertType('bool', $bar); + assertType('false', $baz); + } }; diff --git a/tests/PHPStan/Analyser/data/multiple-classes-per-file.php b/tests/PHPStan/Analyser/data/multiple-classes-per-file.php index e7811d1c9e..3da2c30094 100644 --- a/tests/PHPStan/Analyser/data/multiple-classes-per-file.php +++ b/tests/PHPStan/Analyser/data/multiple-classes-per-file.php @@ -4,42 +4,36 @@ class Foo { - - /** - * @param self $self - */ - public function doFoo($self) - { - 'Foo'; - } - - /** - * @return self - */ - public function returnSelf() - { - - } - + /** + * @param self $self + */ + public function doFoo($self) + { + 'Foo'; + } + + /** + * @return self + */ + public function returnSelf() + { + } } class Bar { - - /** - * @param self $self - */ - public function doFoo($self) - { - 'Bar'; - } - - /** - * @return self - */ - public function returnSelf() - { - - } - + /** + * @param self $self + */ + public function doFoo($self) + { + 'Bar'; + } + + /** + * @return self + */ + public function returnSelf() + { + } } diff --git a/tests/PHPStan/Analyser/data/native-types.php b/tests/PHPStan/Analyser/data/native-types.php index c24de594fb..55793fc441 100644 --- a/tests/PHPStan/Analyser/data/native-types.php +++ b/tests/PHPStan/Analyser/data/native-types.php @@ -7,169 +7,165 @@ class Foo { - - /** - * @param self $foo - * @param \DateTimeImmutable $dateTime - * @param \DateTimeImmutable $dateTimeMutable - * @param string $nullableString - * @param string|null $nonNullableString - */ - public function doFoo( - $foo, - \DateTimeInterface $dateTime, - \DateTime $dateTimeMutable, - ?string $nullableString, - string $nonNullableString - ): void - { - assertType(Foo::class, $foo); - assertNativeType('mixed', $foo); - - // change type after assignment - $foo = new Foo(); - assertType(Foo::class, $foo); - assertNativeType(Foo::class, $foo); - - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - - $f = function (Foo $foo) use ($dateTime) { - assertType(Foo::class, $foo); - assertNativeType(Foo::class, $foo); - - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - }; - - assertType(\DateTime::class, $dateTimeMutable); - assertNativeType(\DateTime::class, $dateTimeMutable); - - assertType('string|null', $nullableString); - assertNativeType('string|null', $nullableString); - - if (is_string($nullableString)) { - // change specified type - assertType('string', $nullableString); - assertNativeType('string', $nullableString); - - // preserve other variables - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - } - - // preserve after merging scopes - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - - assertType('string', $nonNullableString); - assertNativeType('string', $nonNullableString); - - unset($nonNullableString); - assertType('*ERROR*', $nonNullableString); - assertNativeType('*ERROR*', $nonNullableString); - - // preserve other variables - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - } - - /** - * @param array $array - */ - public function doForeach(array $array): void - { - assertType('array', $array); - assertNativeType('array', $array); - - foreach ($array as $key => $value) { - assertType('array&nonEmpty', $array); - assertNativeType('array&nonEmpty', $array); - - assertType('string', $key); - assertNativeType('(int|string)', $key); - - assertType('int', $value); - assertNativeType('mixed', $value); - } - } - - /** - * @param self $foo - */ - public function doCatch($foo): void - { - assertType(Foo::class, $foo); - assertNativeType('mixed', $foo); - - try { - throw new \Exception(); - } catch (\InvalidArgumentException $foo) { - assertType(\InvalidArgumentException::class, $foo); - assertNativeType(\InvalidArgumentException::class, $foo); - } catch (\Exception $e) { - assertType('Exception~InvalidArgumentException', $e); - assertNativeType('Exception~InvalidArgumentException', $e); - - assertType(Foo::class, $foo); - assertNativeType('mixed', $foo); - } - } - - /** - * @param array $array - */ - public function doForeachArrayDestructuring(array $array) - { - assertType('array', $array); - assertNativeType('array', $array); - foreach ($array as $key => [$i, $s]) { - assertType('array&nonEmpty', $array); - assertNativeType('array&nonEmpty', $array); - - assertType('string', $key); - assertNativeType('(int|string)', $key); - - assertType('int', $i); - // assertNativeType('mixed', $i); - - assertType('string', $s); - // assertNativeType('mixed', $s); - } - } - - /** - * @param \DateTimeImmutable $date - */ - public function doIfElse(\DateTimeInterface $date): void - { - if ($date instanceof \DateTimeInterface) { - assertType(\DateTimeImmutable::class, $date); - assertNativeType(\DateTimeInterface::class, $date); - } else { - assertType('*NEVER*', $date); - assertNativeType('*NEVER*', $date); - } - - assertType(\DateTimeImmutable::class, $date); - assertNativeType(\DateTimeInterface::class, $date); - - if ($date instanceof \DateTimeImmutable) { - assertType(\DateTimeImmutable::class, $date); - assertNativeType(\DateTimeImmutable::class, $date); - } else { - assertType('*NEVER*', $date); - assertNativeType('DateTimeInterface~DateTimeImmutable', $date); - } - - assertType(\DateTimeImmutable::class, $date); - assertNativeType(\DateTimeImmutable::class, $date); // could be DateTimeInterface - - if ($date instanceof \DateTime) { - - } - } - + /** + * @param self $foo + * @param \DateTimeImmutable $dateTime + * @param \DateTimeImmutable $dateTimeMutable + * @param string $nullableString + * @param string|null $nonNullableString + */ + public function doFoo( + $foo, + \DateTimeInterface $dateTime, + \DateTime $dateTimeMutable, + ?string $nullableString, + string $nonNullableString + ): void { + assertType(Foo::class, $foo); + assertNativeType('mixed', $foo); + + // change type after assignment + $foo = new Foo(); + assertType(Foo::class, $foo); + assertNativeType(Foo::class, $foo); + + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + + $f = function (Foo $foo) use ($dateTime) { + assertType(Foo::class, $foo); + assertNativeType(Foo::class, $foo); + + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + }; + + assertType(\DateTime::class, $dateTimeMutable); + assertNativeType(\DateTime::class, $dateTimeMutable); + + assertType('string|null', $nullableString); + assertNativeType('string|null', $nullableString); + + if (is_string($nullableString)) { + // change specified type + assertType('string', $nullableString); + assertNativeType('string', $nullableString); + + // preserve other variables + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + } + + // preserve after merging scopes + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + + assertType('string', $nonNullableString); + assertNativeType('string', $nonNullableString); + + unset($nonNullableString); + assertType('*ERROR*', $nonNullableString); + assertNativeType('*ERROR*', $nonNullableString); + + // preserve other variables + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + } + + /** + * @param array $array + */ + public function doForeach(array $array): void + { + assertType('array', $array); + assertNativeType('array', $array); + + foreach ($array as $key => $value) { + assertType('array&nonEmpty', $array); + assertNativeType('array&nonEmpty', $array); + + assertType('string', $key); + assertNativeType('(int|string)', $key); + + assertType('int', $value); + assertNativeType('mixed', $value); + } + } + + /** + * @param self $foo + */ + public function doCatch($foo): void + { + assertType(Foo::class, $foo); + assertNativeType('mixed', $foo); + + try { + throw new \Exception(); + } catch (\InvalidArgumentException $foo) { + assertType(\InvalidArgumentException::class, $foo); + assertNativeType(\InvalidArgumentException::class, $foo); + } catch (\Exception $e) { + assertType('Exception~InvalidArgumentException', $e); + assertNativeType('Exception~InvalidArgumentException', $e); + + assertType(Foo::class, $foo); + assertNativeType('mixed', $foo); + } + } + + /** + * @param array $array + */ + public function doForeachArrayDestructuring(array $array) + { + assertType('array', $array); + assertNativeType('array', $array); + foreach ($array as $key => [$i, $s]) { + assertType('array&nonEmpty', $array); + assertNativeType('array&nonEmpty', $array); + + assertType('string', $key); + assertNativeType('(int|string)', $key); + + assertType('int', $i); + // assertNativeType('mixed', $i); + + assertType('string', $s); + // assertNativeType('mixed', $s); + } + } + + /** + * @param \DateTimeImmutable $date + */ + public function doIfElse(\DateTimeInterface $date): void + { + if ($date instanceof \DateTimeInterface) { + assertType(\DateTimeImmutable::class, $date); + assertNativeType(\DateTimeInterface::class, $date); + } else { + assertType('*NEVER*', $date); + assertNativeType('*NEVER*', $date); + } + + assertType(\DateTimeImmutable::class, $date); + assertNativeType(\DateTimeInterface::class, $date); + + if ($date instanceof \DateTimeImmutable) { + assertType(\DateTimeImmutable::class, $date); + assertNativeType(\DateTimeImmutable::class, $date); + } else { + assertType('*NEVER*', $date); + assertNativeType('DateTimeInterface~DateTimeImmutable', $date); + } + + assertType(\DateTimeImmutable::class, $date); + assertNativeType(\DateTimeImmutable::class, $date); // could be DateTimeInterface + + if ($date instanceof \DateTime) { + } + } } /** @@ -180,25 +176,24 @@ public function doIfElse(\DateTimeInterface $date): void * @param string|null $nonNullableString */ function fooFunction( - $foo, - \DateTimeInterface $dateTime, - \DateTime $dateTimeMutable, - ?string $nullableString, - string $nonNullableString -): void -{ - assertType(Foo::class, $foo); - assertNativeType('mixed', $foo); - - assertType(\DateTimeImmutable::class, $dateTime); - assertNativeType(\DateTimeInterface::class, $dateTime); - - assertType(\DateTime::class, $dateTimeMutable); - assertNativeType(\DateTime::class, $dateTimeMutable); - - assertType('string|null', $nullableString); - assertNativeType('string|null', $nullableString); - - assertType('string', $nonNullableString); - assertNativeType('string', $nonNullableString); + $foo, + \DateTimeInterface $dateTime, + \DateTime $dateTimeMutable, + ?string $nullableString, + string $nonNullableString +): void { + assertType(Foo::class, $foo); + assertNativeType('mixed', $foo); + + assertType(\DateTimeImmutable::class, $dateTime); + assertNativeType(\DateTimeInterface::class, $dateTime); + + assertType(\DateTime::class, $dateTimeMutable); + assertNativeType(\DateTime::class, $dateTimeMutable); + + assertType('string|null', $nullableString); + assertNativeType('string|null', $nullableString); + + assertType('string', $nonNullableString); + assertNativeType('string', $nonNullableString); } diff --git a/tests/PHPStan/Analyser/data/negated-instanceof.php b/tests/PHPStan/Analyser/data/negated-instanceof.php index dbb8afda81..0fef465b86 100644 --- a/tests/PHPStan/Analyser/data/negated-instanceof.php +++ b/tests/PHPStan/Analyser/data/negated-instanceof.php @@ -4,51 +4,49 @@ class Foo { - - public function someMethod($foo, $bar, $otherBar, $lorem, $otherLorem, $dolor, $sit, $mixedFoo, $mixedBar, $self, $static, $anotherFoo, $fooAndBar) - { - if (!$foo instanceof Foo) { - return; - } - - if (!$bar instanceof Bar || get_class($bar) !== get_class($otherBar)) { - return; - } - - if (!($lorem instanceof Lorem || get_class($lorem) === get_class($otherLorem))) { // still mixed after if - return; - } - - if ($dolor instanceof Dolor) { // still mixed after if - return; - } - - if (!(!$sit instanceof Sit)) { // still mixed after if - return; - } - - if ($mixedFoo instanceof Foo && doFoo()) { - return; - } - - if (!($mixedBar instanceof Bar) && doFoo()) { - return; - } - - if (!$self instanceof self) { - return; - } - - if (!$static instanceof static) { - return; - } - if ($anotherFoo instanceof Foo === false) { - return; - } - - if ($fooAndBar instanceof Foo && $fooAndBar instanceof Bar) { - die; - } - } - + public function someMethod($foo, $bar, $otherBar, $lorem, $otherLorem, $dolor, $sit, $mixedFoo, $mixedBar, $self, $static, $anotherFoo, $fooAndBar) + { + if (!$foo instanceof Foo) { + return; + } + + if (!$bar instanceof Bar || get_class($bar) !== get_class($otherBar)) { + return; + } + + if (!($lorem instanceof Lorem || get_class($lorem) === get_class($otherLorem))) { // still mixed after if + return; + } + + if ($dolor instanceof Dolor) { // still mixed after if + return; + } + + if (!(!$sit instanceof Sit)) { // still mixed after if + return; + } + + if ($mixedFoo instanceof Foo && doFoo()) { + return; + } + + if (!($mixedBar instanceof Bar) && doFoo()) { + return; + } + + if (!$self instanceof self) { + return; + } + + if (!$static instanceof static) { + return; + } + if ($anotherFoo instanceof Foo === false) { + return; + } + + if ($fooAndBar instanceof Foo && $fooAndBar instanceof Bar) { + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/nested-functions.php b/tests/PHPStan/Analyser/data/nested-functions.php index 1d12b75157..42ebb85391 100644 --- a/tests/PHPStan/Analyser/data/nested-functions.php +++ b/tests/PHPStan/Analyser/data/nested-functions.php @@ -4,135 +4,133 @@ class Foo { - - public function doFoo(): self - { - return $this; - } - + public function doFoo(): self + { + return $this; + } } function () { - $foo = new Foo(); - $foo->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo() - ->doFoo(); + $foo = new Foo(); + $foo->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo() + ->doFoo(); }; diff --git a/tests/PHPStan/Analyser/data/nested-generic-incomplete-constructor.php b/tests/PHPStan/Analyser/data/nested-generic-incomplete-constructor.php index 847936097c..8c54a2871b 100644 --- a/tests/PHPStan/Analyser/data/nested-generic-incomplete-constructor.php +++ b/tests/PHPStan/Analyser/data/nested-generic-incomplete-constructor.php @@ -10,26 +10,23 @@ */ class Foo { - - /** @var T */ - public $t; - - /** @var U */ - public $u; - - /** - * @param T $t - */ - public function __construct($t) - { - - } - + /** @var T */ + public $t; + + /** @var U */ + public $u; + + /** + * @param T $t + */ + public function __construct($t) + { + } } function (): void { - $foo = new Foo(1); - //assertType('NestedGenericIncompleteConstructor\Foo', $foo); - assertType('int', $foo->t); - assertType('int', $foo->u); + $foo = new Foo(1); + //assertType('NestedGenericIncompleteConstructor\Foo', $foo); + assertType('int', $foo->t); + assertType('int', $foo->u); }; diff --git a/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping-covariant.php b/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping-covariant.php index c26933218a..19837d6363 100644 --- a/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping-covariant.php +++ b/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping-covariant.php @@ -4,24 +4,33 @@ use function PHPStan\Testing\assertType; -interface BasePackage {} +interface BasePackage +{ +} -interface InnerPackage extends BasePackage {} +interface InnerPackage extends BasePackage +{ +} /** * @template-covariant TInnerPackage of InnerPackage */ -interface GenericPackage extends BasePackage { - /** @return TInnerPackage */ - public function unwrap() : InnerPackage; +interface GenericPackage extends BasePackage +{ + /** @return TInnerPackage */ + public function unwrap(): InnerPackage; } -interface SomeInnerPackage extends InnerPackage {} +interface SomeInnerPackage extends InnerPackage +{ +} /** * @extends GenericPackage */ -interface SomePackage extends GenericPackage {} +interface SomePackage extends GenericPackage +{ +} /** * @template TInnerPackage of InnerPackage @@ -29,11 +38,12 @@ interface SomePackage extends GenericPackage {} * @param TGenericPackage $package * @return TInnerPackage */ -function unwrapGeneric(GenericPackage $package) { - $result = $package->unwrap(); - assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\unwrapGeneric(), argument)', $result); +function unwrapGeneric(GenericPackage $package) +{ + $result = $package->unwrap(); + assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\unwrapGeneric(), argument)', $result); - return $result; + return $result; } /** @@ -42,10 +52,11 @@ function unwrapGeneric(GenericPackage $package) { * @param TGenericPackage $package * @return TGenericPackage */ -function unwrapGeneric2(GenericPackage $package) { - assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\unwrapGeneric2(), argument)', $package); +function unwrapGeneric2(GenericPackage $package) +{ + assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\unwrapGeneric2(), argument)', $package); - return $package; + return $package; } /** @@ -54,12 +65,13 @@ function unwrapGeneric2(GenericPackage $package) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithDirectUnwrap(string $class) { - $package = new $class(); - $result = $package->unwrap(); - assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\loadWithDirectUnwrap(), argument)', $result); +function loadWithDirectUnwrap(string $class) +{ + $package = new $class(); + $result = $package->unwrap(); + assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\loadWithDirectUnwrap(), argument)', $result); - return $result; + return $result; } /** @@ -68,12 +80,13 @@ function loadWithDirectUnwrap(string $class) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithIndirectUnwrap(string $class) { - $package = new $class(); - $result = unwrapGeneric($package); - assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap(), argument)', $result); +function loadWithIndirectUnwrap(string $class) +{ + $package = new $class(); + $result = unwrapGeneric($package); + assertType('TInnerPackage of NestedGenericTypesUnwrappingCovariant\InnerPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap(), argument)', $result); - return $result; + return $result; } /** @@ -82,34 +95,35 @@ function loadWithIndirectUnwrap(string $class) { * @param class-string $class FQCN to be instantiated * @return TGenericPackage */ -function loadWithIndirectUnwrap2(string $class) { - $package = new $class(); - assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap2(), argument)', $package); - $result = unwrapGeneric2($package); - assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap2(), argument)', $result); - - return $result; +function loadWithIndirectUnwrap2(string $class) +{ + $package = new $class(); + assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap2(), argument)', $package); + $result = unwrapGeneric2($package); + assertType('TGenericPackage of NestedGenericTypesUnwrappingCovariant\GenericPackage (function NestedGenericTypesUnwrappingCovariant\loadWithIndirectUnwrap2(), argument)', $result); + + return $result; } function (): void { - $result = loadWithDirectUnwrap(SomePackage::class); - assertType(SomeInnerPackage::class, $result); + $result = loadWithDirectUnwrap(SomePackage::class); + assertType(SomeInnerPackage::class, $result); }; function (): void { - $result = loadWithIndirectUnwrap(SomePackage::class); - assertType(SomeInnerPackage::class, $result); + $result = loadWithIndirectUnwrap(SomePackage::class); + assertType(SomeInnerPackage::class, $result); }; function (): void { - $result = loadWithIndirectUnwrap2(SomePackage::class); - assertType(SomePackage::class, $result); + $result = loadWithIndirectUnwrap2(SomePackage::class); + assertType(SomePackage::class, $result); }; function (SomePackage $somePackage): void { - $result = unwrapGeneric($somePackage); - assertType(SomeInnerPackage::class, $result); + $result = unwrapGeneric($somePackage); + assertType(SomeInnerPackage::class, $result); - $result = unwrapGeneric2($somePackage); - assertType(SomePackage::class, $result); + $result = unwrapGeneric2($somePackage); + assertType(SomePackage::class, $result); }; diff --git a/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping.php b/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping.php index 567fa07732..d9aea4be59 100644 --- a/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping.php +++ b/tests/PHPStan/Analyser/data/nested-generic-types-unwrapping.php @@ -4,24 +4,33 @@ use function PHPStan\Testing\assertType; -interface BasePackage {} +interface BasePackage +{ +} -interface InnerPackage extends BasePackage {} +interface InnerPackage extends BasePackage +{ +} /** * @template TInnerPackage of InnerPackage */ -interface GenericPackage extends BasePackage { - /** @return TInnerPackage */ - public function unwrap() : InnerPackage; +interface GenericPackage extends BasePackage +{ + /** @return TInnerPackage */ + public function unwrap(): InnerPackage; } -interface SomeInnerPackage extends InnerPackage {} +interface SomeInnerPackage extends InnerPackage +{ +} /** * @extends GenericPackage */ -interface SomePackage extends GenericPackage {} +interface SomePackage extends GenericPackage +{ +} /** * @template TInnerPackage of InnerPackage @@ -29,11 +38,12 @@ interface SomePackage extends GenericPackage {} * @param TGenericPackage $package * @return TInnerPackage */ -function unwrapGeneric(GenericPackage $package) { - $result = $package->unwrap(); - assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\unwrapGeneric(), argument)', $result); +function unwrapGeneric(GenericPackage $package) +{ + $result = $package->unwrap(); + assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\unwrapGeneric(), argument)', $result); - return $result; + return $result; } /** @@ -42,10 +52,11 @@ function unwrapGeneric(GenericPackage $package) { * @param TGenericPackage $package * @return TGenericPackage */ -function unwrapGeneric2(GenericPackage $package) { - assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\unwrapGeneric2(), argument)', $package); +function unwrapGeneric2(GenericPackage $package) +{ + assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\unwrapGeneric2(), argument)', $package); - return $package; + return $package; } /** @@ -54,12 +65,13 @@ function unwrapGeneric2(GenericPackage $package) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithDirectUnwrap(string $class) { - $package = new $class(); - $result = $package->unwrap(); - assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\loadWithDirectUnwrap(), argument)', $result); +function loadWithDirectUnwrap(string $class) +{ + $package = new $class(); + $result = $package->unwrap(); + assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\loadWithDirectUnwrap(), argument)', $result); - return $result; + return $result; } /** @@ -68,12 +80,13 @@ function loadWithDirectUnwrap(string $class) { * @param class-string $class FQCN to be instantiated * @return TInnerPackage */ -function loadWithIndirectUnwrap(string $class) { - $package = new $class(); - $result = unwrapGeneric($package); - assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap(), argument)', $result); +function loadWithIndirectUnwrap(string $class) +{ + $package = new $class(); + $result = unwrapGeneric($package); + assertType('TInnerPackage of NestedGenericTypesUnwrapping\InnerPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap(), argument)', $result); - return $result; + return $result; } /** @@ -82,34 +95,35 @@ function loadWithIndirectUnwrap(string $class) { * @param class-string $class FQCN to be instantiated * @return TGenericPackage */ -function loadWithIndirectUnwrap2(string $class) { - $package = new $class(); - assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap2(), argument)', $package); - $result = unwrapGeneric2($package); - assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap2(), argument)', $result); - - return $result; +function loadWithIndirectUnwrap2(string $class) +{ + $package = new $class(); + assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap2(), argument)', $package); + $result = unwrapGeneric2($package); + assertType('TGenericPackage of NestedGenericTypesUnwrapping\GenericPackage (function NestedGenericTypesUnwrapping\loadWithIndirectUnwrap2(), argument)', $result); + + return $result; } function (): void { - $result = loadWithDirectUnwrap(SomePackage::class); - assertType(SomeInnerPackage::class, $result); + $result = loadWithDirectUnwrap(SomePackage::class); + assertType(SomeInnerPackage::class, $result); }; function (): void { - $result = loadWithIndirectUnwrap(SomePackage::class); - assertType(SomeInnerPackage::class, $result); + $result = loadWithIndirectUnwrap(SomePackage::class); + assertType(SomeInnerPackage::class, $result); }; function (): void { - $result = loadWithIndirectUnwrap2(SomePackage::class); - assertType(SomePackage::class, $result); + $result = loadWithIndirectUnwrap2(SomePackage::class); + assertType(SomePackage::class, $result); }; function (SomePackage $somePackage): void { - $result = unwrapGeneric($somePackage); - assertType(SomeInnerPackage::class, $result); + $result = unwrapGeneric($somePackage); + assertType(SomeInnerPackage::class, $result); - $result = unwrapGeneric2($somePackage); - assertType(SomePackage::class, $result); + $result = unwrapGeneric2($somePackage); + assertType(SomePackage::class, $result); }; diff --git a/tests/PHPStan/Analyser/data/nested-generic-types.php b/tests/PHPStan/Analyser/data/nested-generic-types.php index 179163f1f2..92963b6b66 100644 --- a/tests/PHPStan/Analyser/data/nested-generic-types.php +++ b/tests/PHPStan/Analyser/data/nested-generic-types.php @@ -7,7 +7,6 @@ /** @template T */ interface SomeInterface { - } /** @@ -18,27 +17,25 @@ interface SomeInterface */ class Foo { + /** @var T */ + public $t; - /** @var T */ - public $t; - - /** @var TT */ - public $tt; - - /** @var U */ - public $u; + /** @var TT */ + public $tt; - /** @var V */ - public $v; + /** @var U */ + public $u; - public function doFoo(): void - { - assertType('T of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->t); - assertType('TT of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->tt); - assertType('U (class NestedGenericTypes\Foo, argument)', $this->u); - assertType('V of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->v); - } + /** @var V */ + public $v; + public function doFoo(): void + { + assertType('T of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->t); + assertType('TT of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->tt); + assertType('U (class NestedGenericTypes\Foo, argument)', $this->u); + assertType('V of NestedGenericTypes\SomeInterface (class NestedGenericTypes\Foo, argument)', $this->v); + } } /** @@ -53,22 +50,20 @@ public function doFoo(): void */ function testFoo($t, $tt, $u, $v): void { - assertType('T of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $t); - assertType('TT of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $tt); - assertType('U (function NestedGenericTypes\testFoo(), argument)', $u); - assertType('V of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $v); + assertType('T of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $t); + assertType('TT of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $tt); + assertType('U (function NestedGenericTypes\testFoo(), argument)', $u); + assertType('V of NestedGenericTypes\SomeInterface (function NestedGenericTypes\testFoo(), argument)', $v); } /** @template T */ interface SomeFoo { - } /** @template T */ interface SomeBar { - } /** @@ -79,7 +74,6 @@ interface SomeBar */ function testSome($foo) { - } /** @@ -90,19 +84,16 @@ function testSome($foo) */ function testSomeUnwrap($foo) { - } -function (SomeFoo $foo): void -{ - assertType('NestedGenericTypes\SomeFoo', testSome($foo)); - assertType('mixed', testSomeUnwrap($foo)); +function (SomeFoo $foo): void { + assertType('NestedGenericTypes\SomeFoo', testSome($foo)); + assertType('mixed', testSomeUnwrap($foo)); }; -function (SomeBar $bar): void -{ - assertType('NestedGenericTypes\SomeFoo', testSome($bar)); - assertType('mixed', testSomeUnwrap($bar)); +function (SomeBar $bar): void { + assertType('NestedGenericTypes\SomeFoo', testSome($bar)); + assertType('mixed', testSomeUnwrap($bar)); }; /** @@ -110,8 +101,8 @@ function (SomeBar $bar): void */ function testSome2($foo) { - assertType('NestedGenericTypes\SomeFoo', testSome($foo)); - assertType('string', testSomeUnwrap($foo)); + assertType('NestedGenericTypes\SomeFoo', testSome($foo)); + assertType('string', testSomeUnwrap($foo)); } /** @@ -119,8 +110,8 @@ function testSome2($foo) */ function testSome3($bar) { - assertType('NestedGenericTypes\SomeFoo', testSome($bar)); - assertType('mixed', testSomeUnwrap($bar)); + assertType('NestedGenericTypes\SomeFoo', testSome($bar)); + assertType('mixed', testSomeUnwrap($bar)); } /** @@ -129,11 +120,10 @@ function testSome3($bar) */ function unwrapWithoutParam() { - } function (): void { - assertType('mixed', unwrapWithoutParam()); + assertType('mixed', unwrapWithoutParam()); }; /** @@ -143,11 +133,10 @@ function (): void { */ function unwrapGenericWithoutParam() { - } function (): void { - assertType('NestedGenericTypes\SomeFoo', unwrapGenericWithoutParam()); + assertType('NestedGenericTypes\SomeFoo', unwrapGenericWithoutParam()); }; /** @@ -157,10 +146,10 @@ function (): void { */ function nonGenericBoundOfGenericClass($t) { - return $t; + return $t; } function (SomeFoo $foo, SomeBar $bar): void { - assertType(SomeFoo::class, nonGenericBoundOfGenericClass($foo)); - assertType(SomeFoo::class, nonGenericBoundOfGenericClass($bar)); + assertType(SomeFoo::class, nonGenericBoundOfGenericClass($foo)); + assertType(SomeFoo::class, nonGenericBoundOfGenericClass($bar)); }; diff --git a/tests/PHPStan/Analyser/data/nested-namespaces.php b/tests/PHPStan/Analyser/data/nested-namespaces.php index 0f8bb769f4..3dbcebcffc 100644 --- a/tests/PHPStan/Analyser/data/nested-namespaces.php +++ b/tests/PHPStan/Analyser/data/nested-namespaces.php @@ -1,21 +1,28 @@ -boo = $boo; - $this->baz = $baz; - } - } + /** @var \x\baz */ + private $baz; + public function __construct(boo $boo, baz $baz) + { + $this->boo = $boo; + $this->baz = $baz; + } + } } diff --git a/tests/PHPStan/Analyser/data/non-empty-array-key-type.php b/tests/PHPStan/Analyser/data/non-empty-array-key-type.php index 90ae50bcd5..d8684c5ee6 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array-key-type.php +++ b/tests/PHPStan/Analyser/data/non-empty-array-key-type.php @@ -6,32 +6,30 @@ class Foo { + /** + * @param \stdClass[] $items + */ + public function doFoo(array $items) + { + assertType('array', $items); - /** - * @param \stdClass[] $items - */ - public function doFoo(array $items) - { - assertType('array', $items); - - if (count($items) > 0) { - assertType('array&nonEmpty', $items); - foreach ($items as $i => $val) { - assertType('(int|string)', $i); - assertType('stdClass', $val); - } - } - } - - /** - * @param \stdClass[] $items - */ - public function doBar(array $items) - { - foreach ($items as $i => $val) { - assertType('(int|string)', $i); - assertType('stdClass', $val); - } - } + if (count($items) > 0) { + assertType('array&nonEmpty', $items); + foreach ($items as $i => $val) { + assertType('(int|string)', $i); + assertType('stdClass', $val); + } + } + } + /** + * @param \stdClass[] $items + */ + public function doBar(array $items) + { + foreach ($items as $i => $val) { + assertType('(int|string)', $i); + assertType('stdClass', $val); + } + } } diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index fa054e9259..7b5546af87 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -6,32 +6,29 @@ class Foo { - - /** - * @param non-empty-array $array - * @param non-empty-list $list - * @param non-empty-array $arrayOfStrings - * @param non-empty-list<\stdClass> $listOfStd - * @param non-empty-list<\stdClass> $listOfStd2 - * @param non-empty-list $invalidList - */ - public function doFoo( - array $array, - array $list, - array $arrayOfStrings, - array $listOfStd, - $listOfStd2, - array $invalidList, - $invalidList2 - ): void - { - assertType('array&nonEmpty', $array); - assertType('array&nonEmpty', $list); - assertType('array&nonEmpty', $arrayOfStrings); - assertType('array&nonEmpty', $listOfStd); - assertType('array&nonEmpty', $listOfStd2); - assertType('array', $invalidList); - assertType('mixed', $invalidList2); - } - + /** + * @param non-empty-array $array + * @param non-empty-list $list + * @param non-empty-array $arrayOfStrings + * @param non-empty-list<\stdClass> $listOfStd + * @param non-empty-list<\stdClass> $listOfStd2 + * @param non-empty-list $invalidList + */ + public function doFoo( + array $array, + array $list, + array $arrayOfStrings, + array $listOfStd, + $listOfStd2, + array $invalidList, + $invalidList2 + ): void { + assertType('array&nonEmpty', $array); + assertType('array&nonEmpty', $list); + assertType('array&nonEmpty', $arrayOfStrings); + assertType('array&nonEmpty', $listOfStd); + assertType('array&nonEmpty', $listOfStd2); + assertType('array', $invalidList); + assertType('mixed', $invalidList2); + } } diff --git a/tests/PHPStan/Analyser/data/nullable-returnTypes.php b/tests/PHPStan/Analyser/data/nullable-returnTypes.php index 78467f9405..5a4a03694c 100644 --- a/tests/PHPStan/Analyser/data/nullable-returnTypes.php +++ b/tests/PHPStan/Analyser/data/nullable-returnTypes.php @@ -4,34 +4,29 @@ class Foo { - - public function doFoo(): ?int - { - die; - } - - /** - * @return int|null - */ - public function doBar(): ?int - { - - } - - /** - * @return int - */ - public function doConflictingNullable(): ?int - { - - } - - /** - * @return int|null - */ - public function doAnotherConflictingNullable(): int - { - - } - + public function doFoo(): ?int + { + die; + } + + /** + * @return int|null + */ + public function doBar(): ?int + { + } + + /** + * @return int + */ + public function doConflictingNullable(): ?int + { + } + + /** + * @return int|null + */ + public function doAnotherConflictingNullable(): int + { + } } diff --git a/tests/PHPStan/Analyser/data/nullsafe.php b/tests/PHPStan/Analyser/data/nullsafe.php index fcb27c2ebd..d886e1deb8 100644 --- a/tests/PHPStan/Analyser/data/nullsafe.php +++ b/tests/PHPStan/Analyser/data/nullsafe.php @@ -6,97 +6,95 @@ class Foo { - - private ?self $nullableSelf; - - private self $self; - - public function doFoo(?\Exception $e) - { - assertType('string|null', $e?->getMessage()); - assertType('Exception|null', $e); - - assertType('Throwable|null', $e?->getPrevious()); - assertType('string|null', $e?->getPrevious()?->getMessage()); - - $e?->getMessage(assertType('Exception', $e)); - } - - public function doBar(?\ReflectionClass $r) - { - assertType('class-string', $r->name); - assertType('class-string|null', $r?->name); - - assertType('Nullsafe\Foo|null', $this->nullableSelf?->self); - assertType('Nullsafe\Foo|null', $this->nullableSelf?->self->self); - } - - public function doBaz(?self $self) - { - if ($self?->nullableSelf) { - assertType('Nullsafe\Foo', $self); - assertType('Nullsafe\Foo', $self->nullableSelf); - assertType('Nullsafe\Foo', $self?->nullableSelf); - } else { - assertType('Nullsafe\Foo|null', $self); - //assertType('null', $self->nullableSelf); - //assertType('null', $self?->nullableSelf); - } - - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self->nullableSelf); - assertType('Nullsafe\Foo|null', $self?->nullableSelf); - } - - public function doLorem(?self $self) - { - if ($self?->nullableSelf !== null) { - assertType('Nullsafe\Foo', $self); - assertType('Nullsafe\Foo', $self->nullableSelf); - assertType('Nullsafe\Foo', $self?->nullableSelf); - } else { - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self->nullableSelf); - assertType('null', $self?->nullableSelf); - } - - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self->nullableSelf); - assertType('Nullsafe\Foo|null', $self?->nullableSelf); - } - - public function doIpsum(?self $self) - { - if ($self?->nullableSelf === null) { - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self); - assertType('null', $self?->nullableSelf); - } else { - assertType('Nullsafe\Foo', $self); - assertType('Nullsafe\Foo', $self->nullableSelf); - assertType('Nullsafe\Foo', $self?->nullableSelf); - } - - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self->nullableSelf); - assertType('Nullsafe\Foo|null', $self?->nullableSelf); - } - - public function doDolor(?self $self) - { - if (!$self?->nullableSelf) { - assertType('Nullsafe\Foo|null', $self); - //assertType('null', $self->nullableSelf); - //assertType('null', $self?->nullableSelf); - } else { - assertType('Nullsafe\Foo', $self); - assertType('Nullsafe\Foo', $self->nullableSelf); - assertType('Nullsafe\Foo', $self?->nullableSelf); - } - - assertType('Nullsafe\Foo|null', $self); - assertType('Nullsafe\Foo|null', $self->nullableSelf); - assertType('Nullsafe\Foo|null', $self?->nullableSelf); - } - + private ?self $nullableSelf; + + private self $self; + + public function doFoo(?\Exception $e) + { + assertType('string|null', $e?->getMessage()); + assertType('Exception|null', $e); + + assertType('Throwable|null', $e?->getPrevious()); + assertType('string|null', $e?->getPrevious()?->getMessage()); + + $e?->getMessage(assertType('Exception', $e)); + } + + public function doBar(?\ReflectionClass $r) + { + assertType('class-string', $r->name); + assertType('class-string|null', $r?->name); + + assertType('Nullsafe\Foo|null', $this->nullableSelf?->self); + assertType('Nullsafe\Foo|null', $this->nullableSelf?->self->self); + } + + public function doBaz(?self $self) + { + if ($self?->nullableSelf) { + assertType('Nullsafe\Foo', $self); + assertType('Nullsafe\Foo', $self->nullableSelf); + assertType('Nullsafe\Foo', $self?->nullableSelf); + } else { + assertType('Nullsafe\Foo|null', $self); + //assertType('null', $self->nullableSelf); + //assertType('null', $self?->nullableSelf); + } + + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self->nullableSelf); + assertType('Nullsafe\Foo|null', $self?->nullableSelf); + } + + public function doLorem(?self $self) + { + if ($self?->nullableSelf !== null) { + assertType('Nullsafe\Foo', $self); + assertType('Nullsafe\Foo', $self->nullableSelf); + assertType('Nullsafe\Foo', $self?->nullableSelf); + } else { + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self->nullableSelf); + assertType('null', $self?->nullableSelf); + } + + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self->nullableSelf); + assertType('Nullsafe\Foo|null', $self?->nullableSelf); + } + + public function doIpsum(?self $self) + { + if ($self?->nullableSelf === null) { + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self); + assertType('null', $self?->nullableSelf); + } else { + assertType('Nullsafe\Foo', $self); + assertType('Nullsafe\Foo', $self->nullableSelf); + assertType('Nullsafe\Foo', $self?->nullableSelf); + } + + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self->nullableSelf); + assertType('Nullsafe\Foo|null', $self?->nullableSelf); + } + + public function doDolor(?self $self) + { + if (!$self?->nullableSelf) { + assertType('Nullsafe\Foo|null', $self); + //assertType('null', $self->nullableSelf); + //assertType('null', $self?->nullableSelf); + } else { + assertType('Nullsafe\Foo', $self); + assertType('Nullsafe\Foo', $self->nullableSelf); + assertType('Nullsafe\Foo', $self?->nullableSelf); + } + + assertType('Nullsafe\Foo|null', $self); + assertType('Nullsafe\Foo|null', $self->nullableSelf); + assertType('Nullsafe\Foo|null', $self?->nullableSelf); + } } diff --git a/tests/PHPStan/Analyser/data/offset-value-after-assign.php b/tests/PHPStan/Analyser/data/offset-value-after-assign.php index 1a876b58a6..2c8cd465ce 100644 --- a/tests/PHPStan/Analyser/data/offset-value-after-assign.php +++ b/tests/PHPStan/Analyser/data/offset-value-after-assign.php @@ -6,19 +6,17 @@ class Foo { + /** + * @param array $a + */ + public function doFoo(array $a, int $i): void + { + assertType('string', $a[$i]); - /** - * @param array $a - */ - public function doFoo(array $a, int $i): void - { - assertType('string', $a[$i]); - - $a[$i] = 'foo'; - assertType('\'foo\'', $a[$i]); - - $i = 1; - assertType('string', $a[$i]); - } + $a[$i] = 'foo'; + assertType('\'foo\'', $a[$i]); + $i = 1; + assertType('string', $a[$i]); + } } diff --git a/tests/PHPStan/Analyser/data/override-root-scope-variable.php b/tests/PHPStan/Analyser/data/override-root-scope-variable.php index 13c746edf0..2c378e247e 100644 --- a/tests/PHPStan/Analyser/data/override-root-scope-variable.php +++ b/tests/PHPStan/Analyser/data/override-root-scope-variable.php @@ -11,23 +11,23 @@ assertVariableCertainty(TrinaryLogic::createYes(), $foo); function (): void { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createNo(), $foo); - /** @var Foo $foo */ + /** @var Foo $foo */ - assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createNo(), $foo); }; function (): void { - if (rand(0, 1) === 0) { - $foo = doFoo(); - } + if (rand(0, 1) === 0) { + $foo = doFoo(); + } - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - /** @var Foo $foo */ + /** @var Foo $foo */ - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); }; assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); @@ -35,11 +35,11 @@ function (): void { assertVariableCertainty(TrinaryLogic::createYes(), $bar); function (): void { - assertVariableCertainty(TrinaryLogic::createNo(), $bar); + assertVariableCertainty(TrinaryLogic::createNo(), $bar); - assert($bar instanceof Foo); + assert($bar instanceof Foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); }; /** @var Foo $lorem */ diff --git a/tests/PHPStan/Analyser/data/overwritingVariable-defined.php b/tests/PHPStan/Analyser/data/overwritingVariable-defined.php index 2ceb507ca7..7b75973fc3 100644 --- a/tests/PHPStan/Analyser/data/overwritingVariable-defined.php +++ b/tests/PHPStan/Analyser/data/overwritingVariable-defined.php @@ -4,15 +4,11 @@ class Bar { - - public function methodFoo(): Foo - { - - } - + public function methodFoo(): Foo + { + } } class Foo { - } diff --git a/tests/PHPStan/Analyser/data/passed-by-reference.php b/tests/PHPStan/Analyser/data/passed-by-reference.php index a644c5d6dd..f46cb9d760 100644 --- a/tests/PHPStan/Analyser/data/passed-by-reference.php +++ b/tests/PHPStan/Analyser/data/passed-by-reference.php @@ -4,23 +4,20 @@ class Foo { + public function doFoo() + { + $arr = [1, 2, 3]; + reset($arr); - public function doFoo() - { - $arr = [1, 2, 3]; - reset($arr); + preg_match('a', 'b', $matches); - preg_match('a', 'b', $matches); + $s = ''; + $this->doBar($s); - $s = ''; - $this->doBar($s); - - die; - } - - public function doBar(string &$s) - { - - } + die; + } + public function doBar(string &$s) + { + } } diff --git a/tests/PHPStan/Analyser/data/php73_functions.php b/tests/PHPStan/Analyser/data/php73_functions.php index 670b013bb3..a3129618ad 100644 --- a/tests/PHPStan/Analyser/data/php73_functions.php +++ b/tests/PHPStan/Analyser/data/php73_functions.php @@ -4,42 +4,39 @@ class Foo { - - /** - * @param $mixed - * @param int $integer - * @param array $mixedArray - * @param array $nonEmptyArray - * @param array $arrayWithStringKeys - */ - public function doFoo( - $mixed, - int $integer, - array $mixedArray, - array $nonEmptyArray, - array $arrayWithStringKeys - ) - { - if (count($nonEmptyArray) === 0) { - return; - } - - $emptyArray = []; - $literalArray = [1, 2, 3]; - $anotherLiteralArray = $literalArray; - if (rand(0, 1) === 0) { - $anotherLiteralArray[] = 4; - } - - /** @var bool $bool */ - $bool = doBar(); - - $hrtime1 = hrtime(); - $hrtime2 = hrtime(false); - $hrtime3 = hrtime(true); - $hrtime4 = hrtime($bool); - - die; - } - + /** + * @param $mixed + * @param int $integer + * @param array $mixedArray + * @param array $nonEmptyArray + * @param array $arrayWithStringKeys + */ + public function doFoo( + $mixed, + int $integer, + array $mixedArray, + array $nonEmptyArray, + array $arrayWithStringKeys + ) { + if (count($nonEmptyArray) === 0) { + return; + } + + $emptyArray = []; + $literalArray = [1, 2, 3]; + $anotherLiteralArray = $literalArray; + if (rand(0, 1) === 0) { + $anotherLiteralArray[] = 4; + } + + /** @var bool $bool */ + $bool = doBar(); + + $hrtime1 = hrtime(); + $hrtime2 = hrtime(false); + $hrtime3 = hrtime(true); + $hrtime4 = hrtime($bool); + + die; + } } diff --git a/tests/PHPStan/Analyser/data/phpdoc-in-closure-bind.php b/tests/PHPStan/Analyser/data/phpdoc-in-closure-bind.php index 6b02fab73c..0c3c653ee8 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-in-closure-bind.php +++ b/tests/PHPStan/Analyser/data/phpdoc-in-closure-bind.php @@ -7,9 +7,9 @@ use function PHPStan\Testing\assertType; function ($mixed): void { - $foo = new Analyser(); - Closure::bind(function () use ($mixed) { - /** @var \stdClass $mixed */ - assertType('stdClass', $mixed); - }, $foo, Analyser::class); + $foo = new Analyser(); + Closure::bind(function () use ($mixed) { + /** @var \stdClass $mixed */ + assertType('stdClass', $mixed); + }, $foo, Analyser::class); }; diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php index 75ed167f90..24df2f4df6 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php @@ -3,28 +3,28 @@ use function PHPStan\Testing\assertType; function () { - /** @var Number $number */ - $number = doFoo(); + /** @var Number $number */ + $number = doFoo(); - /** @var Boolean $boolean */ - $boolean = doFoo(); + /** @var Boolean $boolean */ + $boolean = doFoo(); - /** @var Numeric $numeric */ - $numeric = doFoo(); + /** @var Numeric $numeric */ + $numeric = doFoo(); - /** @var Never $never */ - $never = doFoo(); + /** @var Never $never */ + $never = doFoo(); - /** @var Resource $resource */ - $resource = doFoo(); + /** @var Resource $resource */ + $resource = doFoo(); - /** @var Double $double */ - $double = doFoo(); + /** @var Double $double */ + $double = doFoo(); - assertType('float|int', $number); - assertType('float|int|(string&numeric)', $numeric); - assertType('bool', $boolean); - assertType('resource', $resource); - assertType('*NEVER*', $never); - assertType('float', $double); + assertType('float|int', $number); + assertType('float|int|(string&numeric)', $numeric); + assertType('bool', $boolean); + assertType('resource', $resource); + assertType('*NEVER*', $never); + assertType('float', $double); }; diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php index b7ff3c7fda..73a47dcd81 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php @@ -4,36 +4,48 @@ use function PHPStan\Testing\assertType; -class Number {} -class Numeric {} -class Boolean {} -class Resource {} -class Never {} -class Double {} +class Number +{ +} +class Numeric +{ +} +class Boolean +{ +} +class Resource +{ +} +class Never +{ +} +class Double +{ +} function () { - /** @var Number $number */ - $number = doFoo(); + /** @var Number $number */ + $number = doFoo(); - /** @var Boolean $boolean */ - $boolean = doFoo(); + /** @var Boolean $boolean */ + $boolean = doFoo(); - /** @var Numeric $numeric */ - $numeric = doFoo(); + /** @var Numeric $numeric */ + $numeric = doFoo(); - /** @var Never $never */ - $never = doFoo(); + /** @var Never $never */ + $never = doFoo(); - /** @var Resource $resource */ - $resource = doFoo(); + /** @var Resource $resource */ + $resource = doFoo(); - /** @var Double $double */ - $double = doFoo(); + /** @var Double $double */ + $double = doFoo(); - assertType('PhpdocPseudoTypesNamespace\Number', $number); - assertType('PhpdocPseudoTypesNamespace\Numeric', $numeric); - assertType('PhpdocPseudoTypesNamespace\Boolean', $boolean); - assertType('PhpdocPseudoTypesNamespace\Resource', $resource); - assertType('PhpdocPseudoTypesNamespace\Never', $never); - assertType('PhpdocPseudoTypesNamespace\Double', $double); + assertType('PhpdocPseudoTypesNamespace\Number', $number); + assertType('PhpdocPseudoTypesNamespace\Numeric', $numeric); + assertType('PhpdocPseudoTypesNamespace\Boolean', $boolean); + assertType('PhpdocPseudoTypesNamespace\Resource', $resource); + assertType('PhpdocPseudoTypesNamespace\Never', $never); + assertType('PhpdocPseudoTypesNamespace\Double', $double); }; diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-override.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-override.php index afcf6317c8..67b08602f3 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-override.php +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-override.php @@ -10,28 +10,28 @@ use function PHPStan\Testing\assertType; function () { - /** @var Number $number */ - $number = doFoo(); + /** @var Number $number */ + $number = doFoo(); - /** @var Boolean $boolean */ - $boolean = doFoo(); + /** @var Boolean $boolean */ + $boolean = doFoo(); - /** @var Numeric $numeric */ - $numeric = doFoo(); + /** @var Numeric $numeric */ + $numeric = doFoo(); - /** @var Never $never */ - $never = doFoo(); + /** @var Never $never */ + $never = doFoo(); - /** @var Resource $resource */ - $resource = doFoo(); + /** @var Resource $resource */ + $resource = doFoo(); - /** @var Double $double */ - $double = doFoo(); + /** @var Double $double */ + $double = doFoo(); - assertType('Foo\Number', $number); - assertType('Foo\Numeric', $numeric); - assertType('Foo\Boolean', $boolean); - assertType('Foo\Resource', $resource); - assertType('Foo\Never', $never); - assertType('Foo\Double', $double); + assertType('Foo\Number', $number); + assertType('Foo\Numeric', $numeric); + assertType('Foo\Boolean', $boolean); + assertType('Foo\Resource', $resource); + assertType('Foo\Never', $never); + assertType('Foo\Double', $double); }; diff --git a/tests/PHPStan/Analyser/data/pow.php b/tests/PHPStan/Analyser/data/pow.php index 960b88fad3..b96af05c94 100644 --- a/tests/PHPStan/Analyser/data/pow.php +++ b/tests/PHPStan/Analyser/data/pow.php @@ -5,17 +5,17 @@ use function PHPStan\Testing\assertType; function ($a, $b): void { - assertType('(float|int)', pow($a, $b)); + assertType('(float|int)', pow($a, $b)); }; function (int $a, int $b): void { - assertType('(float|int)', pow($a, $b)); + assertType('(float|int)', pow($a, $b)); }; function (\GMP $a, \GMP $b): void { - assertType('GMP', pow($a, $b)); + assertType('GMP', pow($a, $b)); }; function (\stdClass $a, \GMP $b): void { - assertType('GMP|stdClass', pow($a, $b)); + assertType('GMP|stdClass', pow($a, $b)); }; diff --git a/tests/PHPStan/Analyser/data/proc_get_status.php b/tests/PHPStan/Analyser/data/proc_get_status.php index 449b4fd0ff..67086008d4 100644 --- a/tests/PHPStan/Analyser/data/proc_get_status.php +++ b/tests/PHPStan/Analyser/data/proc_get_status.php @@ -6,10 +6,10 @@ use function proc_get_status; function ($r): void { - $status = proc_get_status($r); - if ($status === false) { - return; - } + $status = proc_get_status($r); + if ($status === false) { + return; + } - assertType('array(\'command\' => string, \'pid\' => int, \'running\' => bool, \'signaled\' => bool, \'stopped\' => bool, \'exitcode\' => int, \'termsig\' => int, \'stopsig\' => int)', $status); + assertType('array(\'command\' => string, \'pid\' => int, \'running\' => bool, \'signaled\' => bool, \'stopped\' => bool, \'exitcode\' => int, \'termsig\' => int, \'stopsig\' => int)', $status); }; diff --git a/tests/PHPStan/Analyser/data/promoted-properties-types.php b/tests/PHPStan/Analyser/data/promoted-properties-types.php index 7581c6dbe2..bdac445d08 100644 --- a/tests/PHPStan/Analyser/data/promoted-properties-types.php +++ b/tests/PHPStan/Analyser/data/promoted-properties-types.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace PromotedPropertiesTypes; @@ -10,44 +12,47 @@ */ class Foo { - - /** - * @param array $anotherPhpDocArray - * @param T $anotherTemplateProperty - * @param string $bothProperty - * @param array $anotherBothProperty - */ - public function __construct( - public $noType, - public int $nativeIntType, - /** @var array */ public $phpDocArray, - public $anotherPhpDocArray, - /** @var array */ public array $yetAnotherPhpDocArray, - /** @var T */ public $templateProperty, - public $anotherTemplateProperty, - /** @var int */ public $bothProperty, - /** @var array */ public $anotherBothProperty - ) { - assertType('array', $phpDocArray); - assertNativeType('mixed', $phpDocArray); - assertType('array', $anotherPhpDocArray); - assertNativeType('mixed', $anotherPhpDocArray); - assertType('array', $yetAnotherPhpDocArray); - assertNativeType('array', $yetAnotherPhpDocArray); - assertType('int', $bothProperty); - assertType('array', $anotherBothProperty); - } - + /** + * @param array $anotherPhpDocArray + * @param T $anotherTemplateProperty + * @param string $bothProperty + * @param array $anotherBothProperty + */ + public function __construct( + public $noType, + public int $nativeIntType, + /** @var array */ + public $phpDocArray, + public $anotherPhpDocArray, + /** @var array */ + public array $yetAnotherPhpDocArray, + /** @var T */ + public $templateProperty, + public $anotherTemplateProperty, + /** @var int */ + public $bothProperty, + /** @var array */ + public $anotherBothProperty + ) { + assertType('array', $phpDocArray); + assertNativeType('mixed', $phpDocArray); + assertType('array', $anotherPhpDocArray); + assertNativeType('mixed', $anotherPhpDocArray); + assertType('array', $yetAnotherPhpDocArray); + assertNativeType('array', $yetAnotherPhpDocArray); + assertType('int', $bothProperty); + assertType('array', $anotherBothProperty); + } } function (Foo $foo): void { - assertType('mixed', $foo->noType); - assertType('int', $foo->nativeIntType); - assertType('array', $foo->phpDocArray); - assertType('array', $foo->anotherPhpDocArray); - assertType('array', $foo->yetAnotherPhpDocArray); - assertType('int', $foo->bothProperty); - assertType('array', $foo->anotherBothProperty); + assertType('mixed', $foo->noType); + assertType('int', $foo->nativeIntType); + assertType('array', $foo->phpDocArray); + assertType('array', $foo->anotherPhpDocArray); + assertType('array', $foo->yetAnotherPhpDocArray); + assertType('int', $foo->bothProperty); + assertType('array', $foo->anotherBothProperty); }; /** @@ -55,12 +60,11 @@ function (Foo $foo): void { */ class Bar extends Foo { - } function (Bar $bar): void { - assertType('stdClass', $bar->templateProperty); - assertType('stdClass', $bar->anotherTemplateProperty); + assertType('stdClass', $bar->templateProperty); + assertType('stdClass', $bar->anotherTemplateProperty); }; /** @@ -68,19 +72,18 @@ function (Bar $bar): void { */ class Lorem { - - /** - * @param T $foo - */ - public function __construct( - public $foo - ) { } - + /** + * @param T $foo + */ + public function __construct( + public $foo + ) { + } } function (): void { - $lorem = new Lorem(new \stdClass); - assertType('stdClass', $lorem->foo); + $lorem = new Lorem(new \stdClass()); + assertType('stdClass', $lorem->foo); }; /** @@ -88,18 +91,15 @@ function (): void { */ class Baz extends Foo { - - public function __construct( - public $anotherPhpDocArray - ) - { - assertType('array', $anotherPhpDocArray); - assertNativeType('mixed', $anotherPhpDocArray); - } - + public function __construct( + public $anotherPhpDocArray + ) { + assertType('array', $anotherPhpDocArray); + assertNativeType('mixed', $anotherPhpDocArray); + } } function (Baz $baz): void { - assertType('array', $baz->anotherPhpDocArray); - assertType('stdClass', $baz->templateProperty); + assertType('array', $baz->anotherPhpDocArray); + assertType('stdClass', $baz->templateProperty); }; diff --git a/tests/PHPStan/Analyser/data/properties-defined.php b/tests/PHPStan/Analyser/data/properties-defined.php index 2ca412e875..bceaf822a3 100644 --- a/tests/PHPStan/Analyser/data/properties-defined.php +++ b/tests/PHPStan/Analyser/data/properties-defined.php @@ -11,30 +11,27 @@ */ class Bar extends DOMDocument { - - /** - * @var Dolor - */ - protected $inheritedProperty; - - /** - * @var self - */ - protected $inheritDocProperty; - - /** - * @var self - */ - protected $inheritDocWithoutCurlyBracesProperty; - - /** - * @var self - */ - protected $implicitInheritDocProperty; - - public function doBar(): Self - { - - } - + /** + * @var Dolor + */ + protected $inheritedProperty; + + /** + * @var self + */ + protected $inheritDocProperty; + + /** + * @var self + */ + protected $inheritDocWithoutCurlyBracesProperty; + + /** + * @var self + */ + protected $implicitInheritDocProperty; + + public function doBar(): self + { + } } diff --git a/tests/PHPStan/Analyser/data/properties.php b/tests/PHPStan/Analyser/data/properties.php index 6c0fea15c4..91eccd4377 100644 --- a/tests/PHPStan/Analyser/data/properties.php +++ b/tests/PHPStan/Analyser/data/properties.php @@ -3,7 +3,9 @@ namespace PropertiesNamespace; use SomeNamespace\Amet as Dolor; -use SomeGroupNamespace\{One, Two as Too, Three}; +use SomeGroupNamespace\One; +use SomeGroupNamespace\Two as Too; +use SomeGroupNamespace\Three; /** * @property-read string $overriddenReadOnlyProperty @@ -11,130 +13,128 @@ */ abstract class Foo extends Bar { - - private $mixedProperty; - - /** @var Foo|Bar */ - private $unionTypeProperty; - - /** - * @var int - * @var int - */ - private $anotherMixedProperty; - - /** - * @vaz int - */ - private $yetAnotherMixedProperty; - - /** @var int */ - private $integerProperty; - - /** @var integer */ - private $anotherIntegerProperty; - - /** @var array */ - private $arrayPropertyOne; - - /** @var mixed[] */ - private $arrayPropertyOther; - - /** - * @var Lorem - */ - private $objectRelative; - - /** - * @var \SomeOtherNamespace\Ipsum - */ - private $objectFullyQualified; - - /** - * @var Dolor - */ - private $objectUsed; - - /** - * @var null|int - */ - private $nullableInteger; - - /** - * @var Dolor|null - */ - private $nullableObject; - - /** - * @var self - */ - private $selfType; - - /** - * @var static - */ - private $staticType; - - /** - * @var null - */ - private $nullType; - - /** - * @var Bar - */ - private $barObject; - - /** - * @var [$invalidType] - */ - private $invalidTypeProperty; - - /** - * @var resource - */ - private $resource; - - /** - * @var array[array] - */ - private $yetAnotherAnotherMixedParameter; - - /** - * @var \\Test\Bar - */ - private $yetAnotherAnotherAnotherMixedParameter; - - /** - * @var string - */ - private static $staticStringProperty; - - /** - * @var One - */ - private $groupUseProperty; - - /** - * @var Too - */ - private $anotherGroupUseProperty; - - /** - * {@inheritDoc} - */ - protected $inheritDocProperty; - - /** - * @inheritDoc - */ - protected $inheritDocWithoutCurlyBracesProperty; - - protected $implicitInheritDocProperty; - - public function doFoo() - { - die; - } - + private $mixedProperty; + + /** @var Foo|Bar */ + private $unionTypeProperty; + + /** + * @var int + * @var int + */ + private $anotherMixedProperty; + + /** + * @vaz int + */ + private $yetAnotherMixedProperty; + + /** @var int */ + private $integerProperty; + + /** @var integer */ + private $anotherIntegerProperty; + + /** @var array */ + private $arrayPropertyOne; + + /** @var mixed[] */ + private $arrayPropertyOther; + + /** + * @var Lorem + */ + private $objectRelative; + + /** + * @var \SomeOtherNamespace\Ipsum + */ + private $objectFullyQualified; + + /** + * @var Dolor + */ + private $objectUsed; + + /** + * @var null|int + */ + private $nullableInteger; + + /** + * @var Dolor|null + */ + private $nullableObject; + + /** + * @var self + */ + private $selfType; + + /** + * @var static + */ + private $staticType; + + /** + * @var null + */ + private $nullType; + + /** + * @var Bar + */ + private $barObject; + + /** + * @var [$invalidType] + */ + private $invalidTypeProperty; + + /** + * @var resource + */ + private $resource; + + /** + * @var array[array] + */ + private $yetAnotherAnotherMixedParameter; + + /** + * @var \\Test\Bar + */ + private $yetAnotherAnotherAnotherMixedParameter; + + /** + * @var string + */ + private static $staticStringProperty; + + /** + * @var One + */ + private $groupUseProperty; + + /** + * @var Too + */ + private $anotherGroupUseProperty; + + /** + * {@inheritDoc} + */ + protected $inheritDocProperty; + + /** + * @inheritDoc + */ + protected $inheritDocWithoutCurlyBracesProperty; + + protected $implicitInheritDocProperty; + + public function doFoo() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/property-array.php b/tests/PHPStan/Analyser/data/property-array.php index ad86845fe3..07b170be76 100644 --- a/tests/PHPStan/Analyser/data/property-array.php +++ b/tests/PHPStan/Analyser/data/property-array.php @@ -4,16 +4,14 @@ class Foo { + private $property; - private $property; - - public function doFoo() - { - 'start'; - $this->property = []; - 'emptyArray'; - $this->property['foo'] = 1; - 'afterAssignment'; - } - + public function doFoo() + { + 'start'; + $this->property = []; + 'emptyArray'; + $this->property['foo'] = 1; + 'afterAssignment'; + } } diff --git a/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php b/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php index 7b66441340..7d4f7bcd70 100644 --- a/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php +++ b/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php @@ -4,23 +4,21 @@ abstract class Base { - /** @var string */ - private $foo; + /** @var string */ + private $foo; - public function __construct(string $foo) - { - \assert($this instanceof Frontend || $this instanceof Backend); + public function __construct(string $foo) + { + \assert($this instanceof Frontend || $this instanceof Backend); - $this->foo = $foo; - } + $this->foo = $foo; + } } class Frontend extends Base { - } class Backend extends Base { - } diff --git a/tests/PHPStan/Analyser/data/property-native-types.php b/tests/PHPStan/Analyser/data/property-native-types.php index 2892179613..694253db07 100644 --- a/tests/PHPStan/Analyser/data/property-native-types.php +++ b/tests/PHPStan/Analyser/data/property-native-types.php @@ -1,20 +1,20 @@ -= 7.4 += 7.4 namespace PropertyNativeTypes; class Foo { + private string $stringProp; - private string $stringProp; - - private self $selfProp; - - /** @var int[] */ - private array $integersProp; + private self $selfProp; - public function doFoo() - { - die; - } + /** @var int[] */ + private array $integersProp; + public function doFoo() + { + die; + } } diff --git a/tests/PHPStan/Analyser/data/psalm-prefix-unresolvable.php b/tests/PHPStan/Analyser/data/psalm-prefix-unresolvable.php index 036f9b996d..fa1f37e316 100644 --- a/tests/PHPStan/Analyser/data/psalm-prefix-unresolvable.php +++ b/tests/PHPStan/Analyser/data/psalm-prefix-unresolvable.php @@ -6,31 +6,29 @@ class Foo { + /** + * @return array + * @psalm-return list + */ + public function doFoo() + { + return []; + } - /** - * @return array - * @psalm-return list - */ - public function doFoo() - { - return []; - } - - public function doBar(): void - { - assertType('array', $this->doFoo()); - } - - /** - * @param Foo $param - * @param Foo $param2 - * @psalm-param foo-bar $param - * @psalm-param foo-bar $param2 - */ - public function doBaz($param, $param2) - { - assertType('PsalmPrefixedTagsWithUnresolvableTypes\Foo', $param); - assertType('PsalmPrefixedTagsWithUnresolvableTypes\Foo', $param2); - } + public function doBar(): void + { + assertType('array', $this->doFoo()); + } + /** + * @param Foo $param + * @param Foo $param2 + * @psalm-param foo-bar $param + * @psalm-param foo-bar $param2 + */ + public function doBaz($param, $param2) + { + assertType('PsalmPrefixedTagsWithUnresolvableTypes\Foo', $param); + assertType('PsalmPrefixedTagsWithUnresolvableTypes\Foo', $param2); + } } diff --git a/tests/PHPStan/Analyser/data/random-int.php b/tests/PHPStan/Analyser/data/random-int.php index 709f9282f0..7f93f225f1 100644 --- a/tests/PHPStan/Analyser/data/random-int.php +++ b/tests/PHPStan/Analyser/data/random-int.php @@ -3,22 +3,22 @@ use function PHPStan\Testing\assertType; function (int $min) { - \assert($min === 10 || $min === 15); - assertType('int<10, 20>', random_int($min, 20)); + \assert($min === 10 || $min === 15); + assertType('int<10, 20>', random_int($min, 20)); }; function (int $min) { - \assert($min <= 0); - assertType('int', random_int($min, 20)); + \assert($min <= 0); + assertType('int', random_int($min, 20)); }; function (int $max) { - \assert($min >= 0); - assertType('int<0, max>', random_int(0, $max)); + \assert($min >= 0); + assertType('int<0, max>', random_int(0, $max)); }; function (int $i) { - assertType('int', random_int($i, $i)); + assertType('int', random_int($i, $i)); }; assertType('0', random_int(0, 0)); diff --git a/tests/PHPStan/Analyser/data/replaceFunctions.php b/tests/PHPStan/Analyser/data/replaceFunctions.php index 164e18b29b..6bb5a471b6 100644 --- a/tests/PHPStan/Analyser/data/replaceFunctions.php +++ b/tests/PHPStan/Analyser/data/replaceFunctions.php @@ -3,41 +3,42 @@ namespace ReplaceFunctions; function ($mixed) { - - $array = ['a' => 'a', 'b' => 'b']; - $string = 'str'; - - $arrayOrString = []; - if (doFoo()) { - $arrayOrString = 'foo'; - } - - /** @var callable[] $callbacks */ - $callbacks = []; - - $expectedString = str_replace('aaa', 'bbb', $string); - $expectedArray = str_replace('aaa', 'bbb', $array); - $expectedArrayOrString = str_replace('aaa', 'bbb', $arrayOrString); - $expectedBenevolentArrayOrString = str_replace('aaa', 'bbb', $mixed); - - $anotherExpectedString = preg_replace('aaa', 'bbb', $string); - $anotherExpectedArray = preg_replace('aaa', 'bbb', $array); - $anotherExpectedArrayOrString = preg_replace('aaa', 'bbb', $arrayOrString); - - $expectedString2 = preg_replace_callback('aaa', function () {}, $string); - $expectedArray2 = preg_replace_callback('aaa', function () {}, $array); - $expectedArrayOrString2 = preg_replace_callback('aaa', function () {}, $arrayOrString); - - $expectedString3 = str_ireplace('aaa', 'bbb', $string); - $expectedArray3 = str_ireplace('aaa', 'bbb', $array); - $expectedArrayOrString3 = str_ireplace('aaa', 'bbb', $arrayOrString); - $expectedBenevolentArrayOrString3 = str_ireplace('aaa', 'bbb', $mixed); - - /** @var Foo[] $arr */ - $arr = doFoo(); - - foreach ($arr as $intOrStringKey => $value) { - die; - } - + $array = ['a' => 'a', 'b' => 'b']; + $string = 'str'; + + $arrayOrString = []; + if (doFoo()) { + $arrayOrString = 'foo'; + } + + /** @var callable[] $callbacks */ + $callbacks = []; + + $expectedString = str_replace('aaa', 'bbb', $string); + $expectedArray = str_replace('aaa', 'bbb', $array); + $expectedArrayOrString = str_replace('aaa', 'bbb', $arrayOrString); + $expectedBenevolentArrayOrString = str_replace('aaa', 'bbb', $mixed); + + $anotherExpectedString = preg_replace('aaa', 'bbb', $string); + $anotherExpectedArray = preg_replace('aaa', 'bbb', $array); + $anotherExpectedArrayOrString = preg_replace('aaa', 'bbb', $arrayOrString); + + $expectedString2 = preg_replace_callback('aaa', function () { + }, $string); + $expectedArray2 = preg_replace_callback('aaa', function () { + }, $array); + $expectedArrayOrString2 = preg_replace_callback('aaa', function () { + }, $arrayOrString); + + $expectedString3 = str_ireplace('aaa', 'bbb', $string); + $expectedArray3 = str_ireplace('aaa', 'bbb', $array); + $expectedArrayOrString3 = str_ireplace('aaa', 'bbb', $arrayOrString); + $expectedBenevolentArrayOrString3 = str_ireplace('aaa', 'bbb', $mixed); + + /** @var Foo[] $arr */ + $arr = doFoo(); + + foreach ($arr as $intOrStringKey => $value) { + die; + } }; diff --git a/tests/PHPStan/Analyser/data/resolve-static.php b/tests/PHPStan/Analyser/data/resolve-static.php index 7b5089b000..5756b09259 100644 --- a/tests/PHPStan/Analyser/data/resolve-static.php +++ b/tests/PHPStan/Analyser/data/resolve-static.php @@ -4,46 +4,41 @@ class Foo { - - /** - * @return static - */ - public static function create() - { - return new static(); - } - - /** - * @return array{foo: static} - */ - public function returnConstantArray(): array - { - return [$this]; - } - - /** - * @return static - */ - public function nullabilityNotInSync(): ?self - { - - } - - /** - * @return static|null - */ - public function anotherNullabilityNotInSync(): self - { - - } - + /** + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * @return array{foo: static} + */ + public function returnConstantArray(): array + { + return [$this]; + } + + /** + * @return static + */ + public function nullabilityNotInSync(): ?self + { + } + + /** + * @return static|null + */ + public function anotherNullabilityNotInSync(): self + { + } } class Bar extends Foo { - } function (Bar $bar) { - die; + die; }; diff --git a/tests/PHPStan/Analyser/data/root-scope-maybe-defined.php b/tests/PHPStan/Analyser/data/root-scope-maybe-defined.php index b8fa432f9b..565373e47f 100644 --- a/tests/PHPStan/Analyser/data/root-scope-maybe-defined.php +++ b/tests/PHPStan/Analyser/data/root-scope-maybe-defined.php @@ -12,12 +12,12 @@ \PHPStan\Testing\assertType('\'str\'', $bar); if (!isset($baz)) { - $baz = 1; - \PHPStan\Testing\assertType('1', $baz); + $baz = 1; + \PHPStan\Testing\assertType('1', $baz); } \PHPStan\Testing\assertType('mixed', $baz); function () { - \PHPStan\Testing\assertVariableCertainty(TrinaryLogic::createNo(), $foo); + \PHPStan\Testing\assertVariableCertainty(TrinaryLogic::createNo(), $foo); }; diff --git a/tests/PHPStan/Analyser/data/shadowed-trait-methods.php b/tests/PHPStan/Analyser/data/shadowed-trait-methods.php index cbddeb4e1d..5d751765a4 100644 --- a/tests/PHPStan/Analyser/data/shadowed-trait-methods.php +++ b/tests/PHPStan/Analyser/data/shadowed-trait-methods.php @@ -6,31 +6,25 @@ trait FooTrait { - - public function doFoo() - { - $a = 1; - assertType('foo', $a); // doesn't get evaluated - } - + public function doFoo() + { + $a = 1; + assertType('foo', $a); // doesn't get evaluated + } } trait BarTrait { + use FooTrait; - use FooTrait; - - public function doFoo() - { - $a = 2; - assertType('2', $a); - } - + public function doFoo() + { + $a = 2; + assertType('2', $a); + } } class Foo { - - use BarTrait; - + use BarTrait; } diff --git a/tests/PHPStan/Analyser/data/specified-function-call.php b/tests/PHPStan/Analyser/data/specified-function-call.php index 37e451d17e..ed78da54e9 100644 --- a/tests/PHPStan/Analyser/data/specified-function-call.php +++ b/tests/PHPStan/Analyser/data/specified-function-call.php @@ -4,27 +4,25 @@ class IsFileChecks { + public function isFile(string $autoloadFile) + { + if (\is_file($autoloadFile) === true) { + 'first'; + if (\is_file($autoloadFile) === true) { + 'second'; + } + } + } - public function isFile(string $autoloadFile) - { - if (\is_file($autoloadFile) === true) { - 'first'; - if (\is_file($autoloadFile) === true) { - 'second'; - } - } - } - - public function isFileAnother(string $autoloadFile, string $other) - { - if (\is_file($autoloadFile) === true) { - 'third'; - $autoloadFile = $other; - 'fourth'; - if (\is_file($autoloadFile) === true) { - 'fifth'; - } - } - } - + public function isFileAnother(string $autoloadFile, string $other) + { + if (\is_file($autoloadFile) === true) { + 'third'; + $autoloadFile = $other; + 'fourth'; + if (\is_file($autoloadFile) === true) { + 'fifth'; + } + } + } } diff --git a/tests/PHPStan/Analyser/data/specified-types-closure-use.php b/tests/PHPStan/Analyser/data/specified-types-closure-use.php index 37bb0107c8..39fbea203a 100644 --- a/tests/PHPStan/Analyser/data/specified-types-closure-use.php +++ b/tests/PHPStan/Analyser/data/specified-types-closure-use.php @@ -8,30 +8,28 @@ class Foo { - - public function doFoo(MethodCall $call, MethodCall $bar): void - { - if ($call->name instanceof Identifier && $bar->name instanceof Identifier) { - function () use ($call): void { - assertType('PhpParser\Node\Identifier', $call->name); - assertType('mixed', $bar->name); - }; - - assertType('PhpParser\Node\Identifier', $call->name); - } - } - - public function doBar(MethodCall $call, MethodCall $bar): void - { - if ($call->name instanceof Identifier && $bar->name instanceof Identifier) { - $a = 1; - function () use ($call, &$a): void { - assertType('PhpParser\Node\Identifier', $call->name); - assertType('mixed', $bar->name); - }; - - assertType('PhpParser\Node\Identifier', $call->name); - } - } - + public function doFoo(MethodCall $call, MethodCall $bar): void + { + if ($call->name instanceof Identifier && $bar->name instanceof Identifier) { + function () use ($call): void { + assertType('PhpParser\Node\Identifier', $call->name); + assertType('mixed', $bar->name); + }; + + assertType('PhpParser\Node\Identifier', $call->name); + } + } + + public function doBar(MethodCall $call, MethodCall $bar): void + { + if ($call->name instanceof Identifier && $bar->name instanceof Identifier) { + $a = 1; + function () use ($call, &$a): void { + assertType('PhpParser\Node\Identifier', $call->name); + assertType('mixed', $bar->name); + }; + + assertType('PhpParser\Node\Identifier', $call->name); + } + } } diff --git a/tests/PHPStan/Analyser/data/specifiedTypesUsingIsFunctions.php b/tests/PHPStan/Analyser/data/specifiedTypesUsingIsFunctions.php index c7d2ca259c..c9d832134c 100644 --- a/tests/PHPStan/Analyser/data/specifiedTypesUsingIsFunctions.php +++ b/tests/PHPStan/Analyser/data/specifiedTypesUsingIsFunctions.php @@ -1,183 +1,182 @@ */ - public function method() - { - - } - - /** @return array */ - public function staticMethod() - { - - } - - public function doFoo() - { - assertType('array', $this->method()); - assertType('array', $this->method()[0]->method()); - assertType('array', self::staticMethod()); - assertType('array', static::staticMethod()); - } - + /** @return array */ + public function method() + { + } + + /** @return array */ + public function staticMethod() + { + } + + public function doFoo() + { + assertType('array', $this->method()); + assertType('array', $this->method()[0]->method()); + assertType('array', self::staticMethod()); + assertType('array', static::staticMethod()); + } } class Bar extends Foo { - - public function doFoo() - { - assertType('array', $this->method()); - assertType('array', $this->method()[0]->method()); - assertType('array', self::staticMethod()); - assertType('array', static::staticMethod()); - } - + public function doFoo() + { + assertType('array', $this->method()); + assertType('array', $this->method()[0]->method()); + assertType('array', self::staticMethod()); + assertType('array', static::staticMethod()); + } } function (Foo $foo, Bar $bar) { - assertType('array', $foo->method()); - assertType('array', $bar->method()); - assertType('array', $bar->method()[0]->method()); + assertType('array', $foo->method()); + assertType('array', $bar->method()); + assertType('array', $bar->method()[0]->method()); - assertType('array', Foo::staticMethod()); - assertType('array', Bar::staticMethod()); + assertType('array', Foo::staticMethod()); + assertType('array', Bar::staticMethod()); }; diff --git a/tests/PHPStan/Analyser/data/static-properties.php b/tests/PHPStan/Analyser/data/static-properties.php index bd4312496e..224c626227 100644 --- a/tests/PHPStan/Analyser/data/static-properties.php +++ b/tests/PHPStan/Analyser/data/static-properties.php @@ -6,40 +6,36 @@ class Foo { - - /** @var array */ - public $prop; - - /** @var array */ - public static $staticProp; - - public function doFoo() - { - assertType('array', $this->prop); - assertType('array', $this->prop[0]->prop); - assertType('array', self::$staticProp); - assertType('array', static::$staticProp); - } - + /** @var array */ + public $prop; + + /** @var array */ + public static $staticProp; + + public function doFoo() + { + assertType('array', $this->prop); + assertType('array', $this->prop[0]->prop); + assertType('array', self::$staticProp); + assertType('array', static::$staticProp); + } } class Bar extends Foo { - - public function doFoo() - { - assertType('array', $this->prop); - assertType('array', $this->prop[0]->prop); - assertType('array', self::$staticProp); - assertType('array', static::$staticProp); - } - + public function doFoo() + { + assertType('array', $this->prop); + assertType('array', $this->prop[0]->prop); + assertType('array', self::$staticProp); + assertType('array', static::$staticProp); + } } function (Foo $foo, Bar $bar) { - assertType('array', $foo->prop); - assertType('array', $bar->prop); + assertType('array', $foo->prop); + assertType('array', $bar->prop); - assertType('array', Foo::$staticProp); - assertType('array', Bar::$staticProp); + assertType('array', Foo::$staticProp); + assertType('array', Bar::$staticProp); }; diff --git a/tests/PHPStan/Analyser/data/switch-get-class.php b/tests/PHPStan/Analyser/data/switch-get-class.php index 0f979c2671..e6fda3e819 100644 --- a/tests/PHPStan/Analyser/data/switch-get-class.php +++ b/tests/PHPStan/Analyser/data/switch-get-class.php @@ -4,21 +4,19 @@ class Foo { + public function doFoo() + { + $lorem = doFoo(); - public function doFoo() - { - $lorem = doFoo(); - - switch (get_class($lorem)) { - case Ipsum::class: - break; - case Lorem::class: - 'normalName'; - break; - case self::class: - 'selfReferentialName'; - break; - } - } - + switch (get_class($lorem)) { + case Ipsum::class: + break; + case Lorem::class: + 'normalName'; + break; + case self::class: + 'selfReferentialName'; + break; + } + } } diff --git a/tests/PHPStan/Analyser/data/switch-instanceof-fallthrough.php b/tests/PHPStan/Analyser/data/switch-instanceof-fallthrough.php index f40b31b729..f63deb87eb 100644 --- a/tests/PHPStan/Analyser/data/switch-instanceof-fallthrough.php +++ b/tests/PHPStan/Analyser/data/switch-instanceof-fallthrough.php @@ -4,17 +4,15 @@ class Foo { - - /** - * @param object $object - */ - public function doFoo($object) - { - switch (true) { - case $object instanceof A: - case $object instanceof B: - die; - } - } - + /** + * @param object $object + */ + public function doFoo($object) + { + switch (true) { + case $object instanceof A: + case $object instanceof B: + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/switch-instanceof-not.php b/tests/PHPStan/Analyser/data/switch-instanceof-not.php index 2ce5aa7e1e..f19ab9949d 100644 --- a/tests/PHPStan/Analyser/data/switch-instanceof-not.php +++ b/tests/PHPStan/Analyser/data/switch-instanceof-not.php @@ -5,6 +5,6 @@ $foo = doFoo(); switch (false) { - case $foo instanceof Foo: - die; + case $foo instanceof Foo: + die; } diff --git a/tests/PHPStan/Analyser/data/switch-instanceof-truthy.php b/tests/PHPStan/Analyser/data/switch-instanceof-truthy.php index 52f58c7ff3..f1817aa3b4 100644 --- a/tests/PHPStan/Analyser/data/switch-instanceof-truthy.php +++ b/tests/PHPStan/Analyser/data/switch-instanceof-truthy.php @@ -10,11 +10,11 @@ $baz = doBaz(); switch ($object) { - case $foo instanceof Foo: - break; - case $bar instanceof Bar: - break; - case $baz instanceof Baz: - die; - break; + case $foo instanceof Foo: + break; + case $bar instanceof Bar: + break; + case $baz instanceof Baz: + die; + break; } diff --git a/tests/PHPStan/Analyser/data/switch-instanceof.php b/tests/PHPStan/Analyser/data/switch-instanceof.php index 44a0aece6b..86f2ae2e97 100644 --- a/tests/PHPStan/Analyser/data/switch-instanceof.php +++ b/tests/PHPStan/Analyser/data/switch-instanceof.php @@ -7,11 +7,11 @@ $baz = doBaz(); switch (true) { - case $foo instanceof Foo: - break; - case $bar instanceof Bar: - break; - case $baz instanceof Baz: - die; - break; + case $foo instanceof Foo: + break; + case $bar instanceof Bar: + break; + case $baz instanceof Baz: + die; + break; } diff --git a/tests/PHPStan/Analyser/data/switch-type-elimination.php b/tests/PHPStan/Analyser/data/switch-type-elimination.php index 8b83142b93..178191567e 100644 --- a/tests/PHPStan/Analyser/data/switch-type-elimination.php +++ b/tests/PHPStan/Analyser/data/switch-type-elimination.php @@ -4,18 +4,16 @@ class Foo { - - /** - * @param string|int $stringOrInt - */ - public function doFoo($stringOrInt) - { - switch (true) { - case is_int($stringOrInt): - break; - case doFoo(): - die; - } - } - + /** + * @param string|int $stringOrInt + */ + public function doFoo($stringOrInt) + { + switch (true) { + case is_int($stringOrInt): + break; + case doFoo(): + die; + } + } } diff --git a/tests/PHPStan/Analyser/data/ternary-specified-types.php b/tests/PHPStan/Analyser/data/ternary-specified-types.php index 5a1e915c21..9801de2675 100644 --- a/tests/PHPStan/Analyser/data/ternary-specified-types.php +++ b/tests/PHPStan/Analyser/data/ternary-specified-types.php @@ -6,34 +6,32 @@ class Foo { + public function doFoo(bool $a, bool $b) + { + if ($a ? $b : false) { + // $a && $b + assertType('true', $a); + assertType('true', $b); + } else { + // !$a || !$b + assertType('bool', $a); + assertType('bool', $b); + if (!$a) { + assertType('false', $a); + assertType('bool', $b); + } else { + assertType('true', $a); + assertType('false', $b); + } + } + } - public function doFoo(bool $a, bool $b) - { - if ($a ? $b : false) { - // $a && $b - assertType('true', $a); - assertType('true', $b); - } else { - // !$a || !$b - assertType('bool', $a); - assertType('bool', $b); - if (!$a) { - assertType('false', $a); - assertType('bool', $b); - } else { - assertType('true', $a); - assertType('false', $b); - } - } - } - - public function doBar(bool $a) - { - if ($a ?: false) { - assertType('true', $a); - } else { - assertType('false', $a); - } - } - + public function doBar(bool $a) + { + if ($a ?: false) { + assertType('true', $a); + } else { + assertType('false', $a); + } + } } diff --git a/tests/PHPStan/Analyser/data/throw-expr.php b/tests/PHPStan/Analyser/data/throw-expr.php index 581e8b1d3e..ae279e9786 100644 --- a/tests/PHPStan/Analyser/data/throw-expr.php +++ b/tests/PHPStan/Analyser/data/throw-expr.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace ThrowExpr; @@ -6,16 +8,14 @@ class Foo { + public function doFoo(bool $b): void + { + $result = $b ? true : throw new \Exception(); + assertType('true', $result); + } - public function doFoo(bool $b): void - { - $result = $b ? true : throw new \Exception(); - assertType('true', $result); - } - - public function doBar(): void - { - assertType('*NEVER*', throw new \Exception()); - } - + public function doBar(): void + { + assertType('*NEVER*', throw new \Exception()); + } } diff --git a/tests/PHPStan/Analyser/data/throw-points/and.php b/tests/PHPStan/Analyser/data/throw-points/and.php index 645e3775b1..80b4bc717e 100644 --- a/tests/PHPStan/Analyser/data/throw-points/and.php +++ b/tests/PHPStan/Analyser/data/throw-points/and.php @@ -8,33 +8,33 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - $foo = (doesntThrow() && doesntThrow()); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = (doesntThrow() && doesntThrow()); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo = (doesntThrow() && maybeThrows()); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = (doesntThrow() && maybeThrows()); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo = (doesntThrow() and doesntThrow()); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = (doesntThrow() and doesntThrow()); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo = (doesntThrow() and maybeThrows()); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = (doesntThrow() and maybeThrows()); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/array-dim-fetch.php b/tests/PHPStan/Analyser/data/throw-points/array-dim-fetch.php index 3a64f2c3a2..3859ebc934 100644 --- a/tests/PHPStan/Analyser/data/throw-points/array-dim-fetch.php +++ b/tests/PHPStan/Analyser/data/throw-points/array-dim-fetch.php @@ -8,19 +8,19 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - [][doesntThrow()]; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [][doesntThrow()]; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [][maybeThrows()]; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [][maybeThrows()]; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/array.php b/tests/PHPStan/Analyser/data/throw-points/array.php index 97decfb281..b5e96de5a2 100644 --- a/tests/PHPStan/Analyser/data/throw-points/array.php +++ b/tests/PHPStan/Analyser/data/throw-points/array.php @@ -8,60 +8,60 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - []; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + []; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [doesntThrow()]; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [doesntThrow()]; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [maybeThrows()]; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [maybeThrows()]; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - [doesntThrow(), $foo = 1]; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [doesntThrow(), $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [maybeThrows(), $foo = 1]; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [maybeThrows(), $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - [doesntThrow() => 1, $foo = 1]; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [doesntThrow() => 1, $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [maybeThrows() => 1, $foo = 1]; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [maybeThrows() => 1, $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/assign-op.php b/tests/PHPStan/Analyser/data/throw-points/assign-op.php index c419633fab..17761d05c2 100644 --- a/tests/PHPStan/Analyser/data/throw-points/assign-op.php +++ b/tests/PHPStan/Analyser/data/throw-points/assign-op.php @@ -10,121 +10,121 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - $foo .= doesntThrow(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo .= doesntThrow(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo .= maybeThrows(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo .= maybeThrows(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo[0] .= doesntThrow(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo[0] .= doesntThrow(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo[0] .= maybeThrows(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo[0] .= maybeThrows(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo[doesntThrow()] .= 0; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo[doesntThrow()] .= 0; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo[maybeThrows()] .= 0; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo[maybeThrows()] .= 0; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo = new stdClass(); - $foo->bar = 'a'; - $foo->bar .= [doesntThrow(), 'b'][1]; - } finally { - assertType('\'ab\'', $foo->bar); - } + try { + $foo = new stdClass(); + $foo->bar = 'a'; + $foo->bar .= [doesntThrow(), 'b'][1]; + } finally { + assertType('\'ab\'', $foo->bar); + } }; function () { - try { - $foo = new stdClass(); - $foo->bar = 'a'; - $foo->bar .= [maybeThrows(), 'b'][1]; - } finally { - assertType('\'a\'|\'ab\'', $foo->bar); - } + try { + $foo = new stdClass(); + $foo->bar = 'a'; + $foo->bar .= [maybeThrows(), 'b'][1]; + } finally { + assertType('\'a\'|\'ab\'', $foo->bar); + } }; function () { - try { - $obj = new stdClass(); - $obj->{doesntThrow()} .= ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $obj = new stdClass(); + $obj->{doesntThrow()} .= ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $obj = new stdClass(); - $obj->{maybeThrows()} .= ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $obj = new stdClass(); + $obj->{maybeThrows()} .= ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - Foo::$bar = 'a'; - Foo::$bar .= [doesntThrow(), 'b'][1]; - } finally { - assertType('\'ab\'', Foo::$bar); - } + try { + Foo::$bar = 'a'; + Foo::$bar .= [doesntThrow(), 'b'][1]; + } finally { + assertType('\'ab\'', Foo::$bar); + } }; function () { - try { - Foo::$bar = 'a'; - Foo::$bar .= [maybeThrows(), 'b'][1]; - } finally { - assertType('\'a\'|\'ab\'', Foo::$bar); - } + try { + Foo::$bar = 'a'; + Foo::$bar .= [maybeThrows(), 'b'][1]; + } finally { + assertType('\'a\'|\'ab\'', Foo::$bar); + } }; function () { - try { - Foo::${doesntThrow()} .= ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + Foo::${doesntThrow()} .= ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - Foo::${maybeThrows()} .= ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + Foo::${maybeThrows()} .= ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/assign.php b/tests/PHPStan/Analyser/data/throw-points/assign.php index 13357533a4..d83fd599ef 100644 --- a/tests/PHPStan/Analyser/data/throw-points/assign.php +++ b/tests/PHPStan/Analyser/data/throw-points/assign.php @@ -11,160 +11,158 @@ class Foo { - - /** @var bool */ - public static $bar = true; - + /** @var bool */ + public static $bar = true; } function () { - try { - $foo = doesntThrow(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = doesntThrow(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo = maybeThrows(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = maybeThrows(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo[0] = doesntThrow(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo[0] = doesntThrow(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo[0] = maybeThrows(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo[0] = maybeThrows(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo[doesntThrow()] = 0; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo[doesntThrow()] = 0; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo[maybeThrows()] = 0; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo[maybeThrows()] = 0; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo = new stdClass(); - $foo->bar = false; - $foo->bar = (doesntThrow() || true); - } finally { - assertType('true', $foo->bar); - } + try { + $foo = new stdClass(); + $foo->bar = false; + $foo->bar = (doesntThrow() || true); + } finally { + assertType('true', $foo->bar); + } }; function () { - try { - $foo = new stdClass(); - $foo->bar = false; - $foo->bar = (maybeThrows() || true); - } finally { - assertType('bool', $foo->bar); - } + try { + $foo = new stdClass(); + $foo->bar = false; + $foo->bar = (maybeThrows() || true); + } finally { + assertType('bool', $foo->bar); + } }; function () { - try { - $obj = new stdClass(); - $obj->{doesntThrow()} = ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $obj = new stdClass(); + $obj->{doesntThrow()} = ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $obj = new stdClass(); - $obj->{maybeThrows()} = ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $obj = new stdClass(); + $obj->{maybeThrows()} = ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - Foo::$bar = false; - Foo::$bar = (doesntThrow() || true); - } finally { - assertType('true', Foo::$bar); - } + try { + Foo::$bar = false; + Foo::$bar = (doesntThrow() || true); + } finally { + assertType('true', Foo::$bar); + } }; function () { - try { - Foo::$bar = false; - Foo::$bar = (maybeThrows() || true); - } finally { - assertType('bool', Foo::$bar); - } + try { + Foo::$bar = false; + Foo::$bar = (maybeThrows() || true); + } finally { + assertType('bool', Foo::$bar); + } }; function () { - try { - Foo::${doesntThrow()} = ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + Foo::${doesntThrow()} = ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - Foo::${maybeThrows()} = ($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + Foo::${maybeThrows()} = ($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - [$foo] = doesntThrow(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [$foo] = doesntThrow(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [$foo] = maybeThrows(); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [$foo] = maybeThrows(); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - [$foo[doesntThrow()]] = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + [$foo[doesntThrow()]] = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - [$foo[maybeThrows()]] = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + [$foo[maybeThrows()]] = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/do-while.php b/tests/PHPStan/Analyser/data/throw-points/do-while.php index 771ed0de69..57caf9329b 100644 --- a/tests/PHPStan/Analyser/data/throw-points/do-while.php +++ b/tests/PHPStan/Analyser/data/throw-points/do-while.php @@ -7,23 +7,23 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - do { - maybeThrows(); - } while (random_int(0, 1)); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + do { + maybeThrows(); + } while (random_int(0, 1)); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - do { - maybeThrows(); - } while (false); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + do { + maybeThrows(); + } while (false); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/for.php b/tests/PHPStan/Analyser/data/throw-points/for.php index 2f0d52b609..831aee5abe 100644 --- a/tests/PHPStan/Analyser/data/throw-points/for.php +++ b/tests/PHPStan/Analyser/data/throw-points/for.php @@ -7,12 +7,12 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - for (;random_int(0, 1);) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + for (;random_int(0, 1);) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/foreach.php b/tests/PHPStan/Analyser/data/throw-points/foreach.php index b9f534c944..244b6ce060 100644 --- a/tests/PHPStan/Analyser/data/throw-points/foreach.php +++ b/tests/PHPStan/Analyser/data/throw-points/foreach.php @@ -8,24 +8,23 @@ use function ThrowPoints\Helpers\maybeThrows; function (iterable $iterable) { - try { - foreach ($iterable as $v) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + foreach ($iterable as $v) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - foreach ([] as $v) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + foreach ([] as $v) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; - diff --git a/tests/PHPStan/Analyser/data/throw-points/func-call.php b/tests/PHPStan/Analyser/data/throw-points/func-call.php index 34c5f4595e..b0a0cb74b3 100644 --- a/tests/PHPStan/Analyser/data/throw-points/func-call.php +++ b/tests/PHPStan/Analyser/data/throw-points/func-call.php @@ -9,60 +9,60 @@ use function ThrowPoints\Helpers\throws; function () { - try { - throws(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + throws(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - maybeThrows(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + maybeThrows(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - doesntThrow(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + doesntThrow(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - doesntThrow()($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + doesntThrow()($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - maybeThrows()($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + maybeThrows()($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - doesntThrow(doesntThrow(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + doesntThrow(doesntThrow(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - doesntThrow(maybeThrows(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + doesntThrow(maybeThrows(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/helpers.php b/tests/PHPStan/Analyser/data/throw-points/helpers.php index 3fa407e763..11407e8bf3 100644 --- a/tests/PHPStan/Analyser/data/throw-points/helpers.php +++ b/tests/PHPStan/Analyser/data/throw-points/helpers.php @@ -29,49 +29,49 @@ function doesntThrow(...$args) class ThrowPointTestObject { - /** - * @return mixed - * @throws Exception - */ - public function throws(...$args) - { - } + /** + * @return mixed + * @throws Exception + */ + public function throws(...$args) + { + } - /** - * @return mixed - */ - public function maybeThrows(...$args) - { - } + /** + * @return mixed + */ + public function maybeThrows(...$args) + { + } - /** - * @return mixed - * @throws void - */ - public function doesntThrow(...$args) - { - } + /** + * @return mixed + * @throws void + */ + public function doesntThrow(...$args) + { + } - /** - * @return mixed - * @throws Exception - */ - public static function staticThrows(...$args) - { - } + /** + * @return mixed + * @throws Exception + */ + public static function staticThrows(...$args) + { + } - /** - * @return mixed - */ - public static function staticMaybeThrows(...$args) - { - } + /** + * @return mixed + */ + public static function staticMaybeThrows(...$args) + { + } - /** - * @return mixed - * @throws void - */ - public static function staticDoesntThrow(...$args) - { - } + /** + * @return mixed + * @throws void + */ + public static function staticDoesntThrow(...$args) + { + } } diff --git a/tests/PHPStan/Analyser/data/throw-points/if.php b/tests/PHPStan/Analyser/data/throw-points/if.php index cc5fbc9758..884dc519ce 100644 --- a/tests/PHPStan/Analyser/data/throw-points/if.php +++ b/tests/PHPStan/Analyser/data/throw-points/if.php @@ -8,36 +8,36 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - if (random_int(0, 1)) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + if (random_int(0, 1)) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - if (random_int(0, 1)) { - } else { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + if (random_int(0, 1)) { + } else { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - if (random_int(0, 1)) { - } elseif (random_int(0, 1)) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + if (random_int(0, 1)) { + } elseif (random_int(0, 1)) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/method-call.php b/tests/PHPStan/Analyser/data/throw-points/method-call.php index 55eb2db541..f7dd35d0e9 100644 --- a/tests/PHPStan/Analyser/data/throw-points/method-call.php +++ b/tests/PHPStan/Analyser/data/throw-points/method-call.php @@ -9,85 +9,85 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->throws(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->throws(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->maybeThrows(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->maybeThrows(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->doesntThrow(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->doesntThrow(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - doesntThrow()->{$foo = 1}($bar = 2); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } + try { + doesntThrow()->{$foo = 1}($bar = 2); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } }; function () { - try { - maybeThrows()->{$foo = 1}($bar = 2); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - } + try { + maybeThrows()->{$foo = 1}($bar = 2); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->{doesntThrow()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->{doesntThrow()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->{maybeThrows()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->{maybeThrows()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->doesntThrow(doesntThrow(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->doesntThrow(doesntThrow(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj->doesntThrow(maybeThrows(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj->doesntThrow(maybeThrows(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/or.php b/tests/PHPStan/Analyser/data/throw-points/or.php index 8be98a68e2..10f79dda84 100644 --- a/tests/PHPStan/Analyser/data/throw-points/or.php +++ b/tests/PHPStan/Analyser/data/throw-points/or.php @@ -8,33 +8,33 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - $foo = (doesntThrow() || doesntThrow()); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = (doesntThrow() || doesntThrow()); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo = (doesntThrow() || maybeThrows()); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = (doesntThrow() || maybeThrows()); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo = (doesntThrow() or doesntThrow()); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = (doesntThrow() or doesntThrow()); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - $foo = (doesntThrow() or maybeThrows()); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = (doesntThrow() or maybeThrows()); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/php8/null-safe-method-call.php b/tests/PHPStan/Analyser/data/throw-points/php8/null-safe-method-call.php index 7be88b9090..72563841fd 100644 --- a/tests/PHPStan/Analyser/data/throw-points/php8/null-safe-method-call.php +++ b/tests/PHPStan/Analyser/data/throw-points/php8/null-safe-method-call.php @@ -9,85 +9,85 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->throws(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->throws(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->maybeThrows(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->maybeThrows(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->doesntThrow(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->doesntThrow(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - doesntThrow()?->{$foo = 1}($bar = 2); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } + try { + doesntThrow()?->{$foo = 1}($bar = 2); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } }; function () { - try { - maybeThrows()?->{$foo = 1}($bar = 2); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - } + try { + maybeThrows()?->{$foo = 1}($bar = 2); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->{doesntThrow()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->{doesntThrow()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->{maybeThrows()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->{maybeThrows()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->doesntThrow(doesntThrow(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->doesntThrow(doesntThrow(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - $obj = new ThrowPointTestObject(); - try { - $obj?->doesntThrow(maybeThrows(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + $obj = new ThrowPointTestObject(); + try { + $obj?->doesntThrow(maybeThrows(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/property-fetch.php b/tests/PHPStan/Analyser/data/throw-points/property-fetch.php index ee6cfeae3e..49fe17acaf 100644 --- a/tests/PHPStan/Analyser/data/throw-points/property-fetch.php +++ b/tests/PHPStan/Analyser/data/throw-points/property-fetch.php @@ -8,19 +8,19 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - doesntThrow()->foo; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + doesntThrow()->foo; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - maybeThrows()->foo; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + maybeThrows()->foo; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/static-call.php b/tests/PHPStan/Analyser/data/throw-points/static-call.php index 0488ecc413..51ddd070e2 100644 --- a/tests/PHPStan/Analyser/data/throw-points/static-call.php +++ b/tests/PHPStan/Analyser/data/throw-points/static-call.php @@ -9,60 +9,60 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - ThrowPointTestObject::staticThrows(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + ThrowPointTestObject::staticThrows(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - ThrowPointTestObject::staticMaybeThrows(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + ThrowPointTestObject::staticMaybeThrows(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - ThrowPointTestObject::staticDoesntThrow(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + ThrowPointTestObject::staticDoesntThrow(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - ThrowPointTestObject::{doesntThrow()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + ThrowPointTestObject::{doesntThrow()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - ThrowPointTestObject::{maybeThrows()}($foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + ThrowPointTestObject::{maybeThrows()}($foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - ThrowPointTestObject::staticDoesntThrow(doesntThrow(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + ThrowPointTestObject::staticDoesntThrow(doesntThrow(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - ThrowPointTestObject::staticDoesntThrow(maybeThrows(), $foo = 1); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + ThrowPointTestObject::staticDoesntThrow(maybeThrows(), $foo = 1); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/switch.php b/tests/PHPStan/Analyser/data/throw-points/switch.php index d3dcc6dac5..0ba9c2afe8 100644 --- a/tests/PHPStan/Analyser/data/throw-points/switch.php +++ b/tests/PHPStan/Analyser/data/throw-points/switch.php @@ -8,37 +8,37 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - switch (random_int(0, 1)) { - case 0: - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + switch (random_int(0, 1)) { + case 0: + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - switch (random_int(0, 1)) { - default: - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + switch (random_int(0, 1)) { + default: + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - switch (random_int(0, 1)) { - default: - throw new Exception(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } + try { + switch (random_int(0, 1)) { + default: + throw new Exception(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/throw.php b/tests/PHPStan/Analyser/data/throw-points/throw.php index fdfbdca743..8929447b6c 100644 --- a/tests/PHPStan/Analyser/data/throw-points/throw.php +++ b/tests/PHPStan/Analyser/data/throw-points/throw.php @@ -7,30 +7,30 @@ use function PHPStan\Testing\assertVariableCertainty; function () { - try { - throw new Exception(); - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } + try { + throw new Exception(); + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + } }; function () { - try { - if (random_int(0, 1)) { - throw new Exception(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + if (random_int(0, 1)) { + throw new Exception(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - $foo = 1; - throw new Exception(); - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = 1; + throw new Exception(); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/try-catch-finally.php b/tests/PHPStan/Analyser/data/throw-points/try-catch-finally.php index e9180cc7d8..c6cca8042f 100644 --- a/tests/PHPStan/Analyser/data/throw-points/try-catch-finally.php +++ b/tests/PHPStan/Analyser/data/throw-points/try-catch-finally.php @@ -8,57 +8,57 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - try { - } finally { - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + try { + } finally { + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - try { - maybeThrows(); - } finally { - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + try { + maybeThrows(); + } finally { + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - try { - maybeThrows(); - } catch (Exception $exception) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + try { + maybeThrows(); + } catch (Exception $exception) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - try { - } finally { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + try { + } finally { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/try-catch.php b/tests/PHPStan/Analyser/data/throw-points/try-catch.php index 57faddd9bb..401ce69f8d 100644 --- a/tests/PHPStan/Analyser/data/throw-points/try-catch.php +++ b/tests/PHPStan/Analyser/data/throw-points/try-catch.php @@ -10,174 +10,167 @@ class MyInvalidArgumentException extends \InvalidArgumentException { - } class MyRuntimeException extends \RuntimeException { - } class Foo { - - /** @throws void */ - public static function myRand(): int - { - - } - - public static function createException(): MyInvalidArgumentException - { - - } - - /** - * @throws MyRuntimeException - */ - public static function createExceptionOrThrow(): MyInvalidArgumentException - { - - } - + /** @throws void */ + public static function myRand(): int + { + } + + public static function createException(): MyInvalidArgumentException + { + } + + /** + * @throws MyRuntimeException + */ + public static function createExceptionOrThrow(): MyInvalidArgumentException + { + } } function (): void { - try { - if (Foo::myRand() === 0) { - $foo = 1; - throw new \InvalidArgumentException(); - } - if (Foo::myRand() === 1) { - $foo = 2; - throw new MyInvalidArgumentException(); - } - - if (Foo::myRand() === 2) { - $baz = 1; - throw new \RuntimeException(); - } - if (Foo::myRand() === 3) { - $baz = 2; - throw new MyRuntimeException(); - } - - $bar = 1; - maybeThrows(); - } catch (\InvalidArgumentException $e) { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - assertType('1|2', $foo); - - assertVariableCertainty(TrinaryLogic::createNo(), $bar); - assertVariableCertainty(TrinaryLogic::createNo(), $baz); - } catch (\RuntimeException $e) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createNo(), $bar); - assertVariableCertainty(TrinaryLogic::createYes(), $baz); - assertType('1|2', $baz); - } catch (\Throwable $e) { - assertType('Throwable~InvalidArgumentException|RuntimeException', $e); - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - assertVariableCertainty(TrinaryLogic::createNo(), $baz); - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertType('1|2', $foo); - - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - assertType('1', $bar); - - assertVariableCertainty(TrinaryLogic::createMaybe(), $baz); - assertType('1|2', $baz); - } + try { + if (Foo::myRand() === 0) { + $foo = 1; + throw new \InvalidArgumentException(); + } + if (Foo::myRand() === 1) { + $foo = 2; + throw new MyInvalidArgumentException(); + } + + if (Foo::myRand() === 2) { + $baz = 1; + throw new \RuntimeException(); + } + if (Foo::myRand() === 3) { + $baz = 2; + throw new MyRuntimeException(); + } + + $bar = 1; + maybeThrows(); + } catch (\InvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertType('1|2', $foo); + + assertVariableCertainty(TrinaryLogic::createNo(), $bar); + assertVariableCertainty(TrinaryLogic::createNo(), $baz); + } catch (\RuntimeException $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createNo(), $bar); + assertVariableCertainty(TrinaryLogic::createYes(), $baz); + assertType('1|2', $baz); + } catch (\Throwable $e) { + assertType('Throwable~InvalidArgumentException|RuntimeException', $e); + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + assertVariableCertainty(TrinaryLogic::createNo(), $baz); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertType('1|2', $foo); + + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + assertType('1', $bar); + + assertVariableCertainty(TrinaryLogic::createMaybe(), $baz); + assertType('1|2', $baz); + } }; function (): void { - try { - maybeThrows(); - $foo = 1; - throw new \InvalidArgumentException(); - } catch (\InvalidArgumentException $e) { - assertType('1', $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + maybeThrows(); + $foo = 1; + throw new \InvalidArgumentException(); + } catch (\InvalidArgumentException $e) { + assertType('1', $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function (): void { - try { - $foo = new Foo(); - } catch (Throwable $e) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + $foo = new Foo(); + } catch (Throwable $e) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function (): void { - try { - $foo = new \InvalidArgumentException(); - } catch (Throwable $e) { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + $foo = new \InvalidArgumentException(); + } catch (Throwable $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function (): void { - try { - if (Foo::myRand() === 0) { - $foo = 1; - throw Foo::createException(); - } - - if (Foo::myRand() === 1) { - $bar = 1; - throw Foo::createExceptionOrThrow(); - } - } catch (MyInvalidArgumentException $e) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - } catch (\Throwable $e) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } + try { + if (Foo::myRand() === 0) { + $foo = 1; + throw Foo::createException(); + } + + if (Foo::myRand() === 1) { + $bar = 1; + throw Foo::createExceptionOrThrow(); + } + } catch (MyInvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + } catch (\Throwable $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } }; function (): void { - try { - if (Foo::myRand() === 0) { - $foo = 1; - throw Foo::createException(); - } - - if (Foo::myRand() === 1) { - $bar = 1; - throw Foo::createExceptionOrThrow(); - } - } catch (MyInvalidArgumentException $e) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - } catch (\Exception $e) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } + try { + if (Foo::myRand() === 0) { + $foo = 1; + throw Foo::createException(); + } + + if (Foo::myRand() === 1) { + $bar = 1; + throw Foo::createExceptionOrThrow(); + } + } catch (MyInvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + } catch (\Exception $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } }; function (): void { - try { - if (Foo::myRand() === 0) { - $foo = 1; - throw Foo::createException(); - } - - if (Foo::myRand() === 1) { - $bar = 1; - throw Foo::createExceptionOrThrow(); - } - } catch (MyInvalidArgumentException $e) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); - } catch (\Exception $e) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } catch (\Throwable $e) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $bar); - } + try { + if (Foo::myRand() === 0) { + $foo = 1; + throw Foo::createException(); + } + + if (Foo::myRand() === 1) { + $bar = 1; + throw Foo::createExceptionOrThrow(); + } + } catch (MyInvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + } catch (\Exception $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } catch (\Throwable $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $foo); + assertVariableCertainty(TrinaryLogic::createYes(), $bar); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/variable.php b/tests/PHPStan/Analyser/data/throw-points/variable.php index ff50353dde..fe6ee7a07a 100644 --- a/tests/PHPStan/Analyser/data/throw-points/variable.php +++ b/tests/PHPStan/Analyser/data/throw-points/variable.php @@ -8,19 +8,19 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - ${doesntThrow()}; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + ${doesntThrow()}; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; function () { - try { - ${maybeThrows()}; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + ${maybeThrows()}; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/throw-points/while.php b/tests/PHPStan/Analyser/data/throw-points/while.php index f3bb8ddea6..023d4099d9 100644 --- a/tests/PHPStan/Analyser/data/throw-points/while.php +++ b/tests/PHPStan/Analyser/data/throw-points/while.php @@ -7,23 +7,23 @@ use function ThrowPoints\Helpers\maybeThrows; function () { - try { - while (random_int(0, 1)) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } + try { + while (random_int(0, 1)) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } }; function () { - try { - while (false) { - maybeThrows(); - } - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } + try { + while (false) { + maybeThrows(); + } + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } }; diff --git a/tests/PHPStan/Analyser/data/traits-ignore/Foo.php b/tests/PHPStan/Analyser/data/traits-ignore/Foo.php index 8df3aef116..ac7f12d044 100644 --- a/tests/PHPStan/Analyser/data/traits-ignore/Foo.php +++ b/tests/PHPStan/Analyser/data/traits-ignore/Foo.php @@ -1,10 +1,10 @@ -baz); - assertType('*ERROR*', $this->qux); - } - - } - - /** - * @phpstan-import-type CircularTypeAliasImport1 from Baz - * @phpstan-type CircularTypeAliasImport2 CircularTypeAliasImport1 - */ - class Qux - { - } - - /** - * @phpstan-type LocalTypeAlias callable(string $value): (string|false) - * @phpstan-type NestedLocalTypeAlias LocalTypeAlias[] - * @phpstan-import-type ExportedTypeAlias from Bar as ImportedTypeAlias - * @phpstan-import-type ReexportedTypeAlias from Baz - * @phpstan-type NestedImportedTypeAlias iterable - * @phpstan-import-type ScopedAlias from SubScope\Bar - * @phpstan-import-type ImportedAliasFromNonClass from int - * @phpstan-import-type ImportedAliasFromUnknownClass from UnknownClass - * @phpstan-import-type ImportedUknownAlias from SubScope\Bar - * @phpstan-type Baz never - * @phpstan-type GlobalTypeAlias never - * @phpstan-type RecursiveTypeAlias RecursiveTypeAlias[] - * @phpstan-type CircularTypeAlias1 CircularTypeAlias2 - * @phpstan-type CircularTypeAlias2 CircularTypeAlias1 - * @phpstan-type int ShouldNotHappen - * @property GlobalTypeAlias $globalAliasProperty - * @property LocalTypeAlias $localAliasProperty - * @property ImportedTypeAlias $importedAliasProperty - * @property ReexportedTypeAlias $reexportedAliasProperty - * @property ScopedAlias $scopedAliasProperty - * @property RecursiveTypeAlias $recursiveAliasProperty - * @property CircularTypeAlias1 $circularAliasProperty - */ - class Foo - { - - /** - * @param GlobalTypeAlias $parameter - */ - public function globalAlias($parameter) - { - assertType('int|string', $parameter); - } - - /** - * @param LocalTypeAlias $parameter - */ - public function localAlias($parameter) - { - assertType('callable(string): string|false', $parameter); - } - - /** - * @param NestedLocalTypeAlias $parameter - */ - public function nestedLocalAlias($parameter) - { - assertType('array', $parameter); - } - - /** - * @param ImportedTypeAlias $parameter - */ - public function importedAlias($parameter) - { - assertType('Countable&Traversable', $parameter); - } - - /** - * @param NestedImportedTypeAlias $parameter - */ - public function nestedImportedAlias($parameter) - { - assertType('iterable', $parameter); - } - - /** - * @param ImportedAliasFromNonClass $parameter1 - * @param ImportedAliasFromUnknownClass $parameter2 - * @param ImportedUknownAlias $parameter3 - */ - public function invalidImports($parameter1, $parameter2, $parameter3) - { - assertType('TypeAliasesDataset\ImportedAliasFromNonClass', $parameter1); - assertType('TypeAliasesDataset\ImportedAliasFromUnknownClass', $parameter2); - assertType('TypeAliasesDataset\ImportedUknownAlias', $parameter3); - } - - /** - * @param Baz $parameter - */ - public function conflictingAlias($parameter) - { - assertType('*NEVER*', $parameter); - } - - public function __get(string $name) - { - return null; - } - - /** @param int $int */ - public function testIntAlias($int) - { - assertType('int', $int); - } - - } - - assertType('int|string', (new Foo)->globalAliasProperty); - assertType('callable(string): string|false', (new Foo)->localAliasProperty); - assertType('Countable&Traversable', (new Foo)->importedAliasProperty); - assertType('Countable&Traversable', (new Foo)->reexportedAliasProperty); - assertType('TypeAliasesDataset\SubScope\Foo', (new Foo)->scopedAliasProperty); - assertType('*ERROR*', (new Foo)->recursiveAliasProperty); - assertType('*ERROR*', (new Foo)->circularAliasProperty); - - trait FooTrait - { - - /** - * @param Test $a - * @return Test - */ - public function doFoo($a) - { - assertType(Test::class, $a); - } - - } - - /** @phpstan-type Test array{string, int} */ - class UsesTrait1 - { - - use FooTrait; - - /** @param Test $a */ - public function doBar($a) - { - assertType('array(string, int)', $a); - assertType(Test::class, $this->doFoo()); - } - - } - - /** @phpstan-type Test \stdClass */ - class UsesTrait2 - { - - use FooTrait; - - /** @param Test $a */ - public function doBar($a) - { - assertType('stdClass', $a); - assertType(Test::class, $this->doFoo()); - } - - } - - class UsesTrait3 - { - - use FooTrait; - - /** @param Test $a */ - public function doBar($a) - { - assertType(Test::class, $a); - assertType(Test::class, $this->doFoo()); - } - - } + use function PHPStan\Testing\assertType; + + /** + * @phpstan-type ExportedTypeAlias \Countable&\Traversable + */ + class Bar + { + } + + /** + * @phpstan-import-type ExportedTypeAlias from Bar as ReexportedTypeAlias + * @phpstan-import-type CircularTypeAliasImport2 from Qux + * @phpstan-type CircularTypeAliasImport1 CircularTypeAliasImport2 + * @property CircularTypeAliasImport1 $baz + * @property CircularTypeAliasImport2 $qux + */ + class Baz + { + public function circularAlias() + { + assertType('*ERROR*', $this->baz); + assertType('*ERROR*', $this->qux); + } + } + + /** + * @phpstan-import-type CircularTypeAliasImport1 from Baz + * @phpstan-type CircularTypeAliasImport2 CircularTypeAliasImport1 + */ + class Qux + { + } + + /** + * @phpstan-type LocalTypeAlias callable(string $value): (string|false) + * @phpstan-type NestedLocalTypeAlias LocalTypeAlias[] + * @phpstan-import-type ExportedTypeAlias from Bar as ImportedTypeAlias + * @phpstan-import-type ReexportedTypeAlias from Baz + * @phpstan-type NestedImportedTypeAlias iterable + * @phpstan-import-type ScopedAlias from SubScope\Bar + * @phpstan-import-type ImportedAliasFromNonClass from int + * @phpstan-import-type ImportedAliasFromUnknownClass from UnknownClass + * @phpstan-import-type ImportedUknownAlias from SubScope\Bar + * @phpstan-type Baz never + * @phpstan-type GlobalTypeAlias never + * @phpstan-type RecursiveTypeAlias RecursiveTypeAlias[] + * @phpstan-type CircularTypeAlias1 CircularTypeAlias2 + * @phpstan-type CircularTypeAlias2 CircularTypeAlias1 + * @phpstan-type int ShouldNotHappen + * @property GlobalTypeAlias $globalAliasProperty + * @property LocalTypeAlias $localAliasProperty + * @property ImportedTypeAlias $importedAliasProperty + * @property ReexportedTypeAlias $reexportedAliasProperty + * @property ScopedAlias $scopedAliasProperty + * @property RecursiveTypeAlias $recursiveAliasProperty + * @property CircularTypeAlias1 $circularAliasProperty + */ + class Foo + { + /** + * @param GlobalTypeAlias $parameter + */ + public function globalAlias($parameter) + { + assertType('int|string', $parameter); + } + + /** + * @param LocalTypeAlias $parameter + */ + public function localAlias($parameter) + { + assertType('callable(string): string|false', $parameter); + } + + /** + * @param NestedLocalTypeAlias $parameter + */ + public function nestedLocalAlias($parameter) + { + assertType('array', $parameter); + } + + /** + * @param ImportedTypeAlias $parameter + */ + public function importedAlias($parameter) + { + assertType('Countable&Traversable', $parameter); + } + + /** + * @param NestedImportedTypeAlias $parameter + */ + public function nestedImportedAlias($parameter) + { + assertType('iterable', $parameter); + } + + /** + * @param ImportedAliasFromNonClass $parameter1 + * @param ImportedAliasFromUnknownClass $parameter2 + * @param ImportedUknownAlias $parameter3 + */ + public function invalidImports($parameter1, $parameter2, $parameter3) + { + assertType('TypeAliasesDataset\ImportedAliasFromNonClass', $parameter1); + assertType('TypeAliasesDataset\ImportedAliasFromUnknownClass', $parameter2); + assertType('TypeAliasesDataset\ImportedUknownAlias', $parameter3); + } + + /** + * @param Baz $parameter + */ + public function conflictingAlias($parameter) + { + assertType('*NEVER*', $parameter); + } + + public function __get(string $name) + { + return null; + } + + /** @param int $int */ + public function testIntAlias($int) + { + assertType('int', $int); + } + } + + assertType('int|string', (new Foo())->globalAliasProperty); + assertType('callable(string): string|false', (new Foo())->localAliasProperty); + assertType('Countable&Traversable', (new Foo())->importedAliasProperty); + assertType('Countable&Traversable', (new Foo())->reexportedAliasProperty); + assertType('TypeAliasesDataset\SubScope\Foo', (new Foo())->scopedAliasProperty); + assertType('*ERROR*', (new Foo())->recursiveAliasProperty); + assertType('*ERROR*', (new Foo())->circularAliasProperty); + + trait FooTrait + { + /** + * @param Test $a + * @return Test + */ + public function doFoo($a) + { + assertType(Test::class, $a); + } + } + + /** @phpstan-type Test array{string, int} */ + class UsesTrait1 + { + use FooTrait; + + /** @param Test $a */ + public function doBar($a) + { + assertType('array(string, int)', $a); + assertType(Test::class, $this->doFoo()); + } + } + + /** @phpstan-type Test \stdClass */ + class UsesTrait2 + { + use FooTrait; + + /** @param Test $a */ + public function doBar($a) + { + assertType('stdClass', $a); + assertType(Test::class, $this->doFoo()); + } + } + + class UsesTrait3 + { + use FooTrait; + + /** @param Test $a */ + public function doBar($a) + { + assertType(Test::class, $a); + assertType(Test::class, $this->doFoo()); + } + } } diff --git a/tests/PHPStan/Analyser/data/type-change-after-array-access-assignment.php b/tests/PHPStan/Analyser/data/type-change-after-array-access-assignment.php index 863bf44f59..0e86768fdb 100644 --- a/tests/PHPStan/Analyser/data/type-change-after-array-access-assignment.php +++ b/tests/PHPStan/Analyser/data/type-change-after-array-access-assignment.php @@ -6,35 +6,33 @@ class Foo { + /** + * @param \ArrayAccess $ac + */ + public function doFoo(\ArrayAccess $ac): void + { + assertType('ArrayAccess', $ac); - /** - * @param \ArrayAccess $ac - */ - public function doFoo(\ArrayAccess $ac): void - { - assertType('ArrayAccess', $ac); + $ac['foo'] = 'bar'; + assertType('ArrayAccess', $ac); - $ac['foo'] = 'bar'; - assertType('ArrayAccess', $ac); + $ac[] = 'foo'; + assertType('ArrayAccess', $ac); - $ac[] = 'foo'; - assertType('ArrayAccess', $ac); + $ac[] = 1; + assertType('ArrayAccess', $ac); - $ac[] = 1; - assertType('ArrayAccess', $ac); + $ac[2] = 'bar'; + assertType('ArrayAccess', $ac); - $ac[2] = 'bar'; - assertType('ArrayAccess', $ac); + $i = 1; + $ac[] = $i; + assertType('ArrayAccess', $ac); - $i = 1; - $ac[] = $i; - assertType('ArrayAccess', $ac); - - $ac[] = 'baz'; - assertType('ArrayAccess', $ac); - - $ac[] = ['foo']; - assertType('ArrayAccess', $ac); - } + $ac[] = 'baz'; + assertType('ArrayAccess', $ac); + $ac[] = ['foo']; + assertType('ArrayAccess', $ac); + } } diff --git a/tests/PHPStan/Analyser/data/type-elimination.php b/tests/PHPStan/Analyser/data/type-elimination.php index 716056f570..261d58ba33 100644 --- a/tests/PHPStan/Analyser/data/type-elimination.php +++ b/tests/PHPStan/Analyser/data/type-elimination.php @@ -4,135 +4,132 @@ class Foo { - - /** @var Bar|null */ - private $bar; - - public function getValue(): string - { - - } - - public function test() - { - /** @var Foo|null $foo */ - $foo = doFoo(); - - if ($foo === null) { - 'nullForSure'; - } - - if ($foo !== null) { - 'notNullForSure'; - } - - if ($foo) { - 'notNullForSure2'; - } else { - 'nullForSure2'; - } - - if (!$foo) { - 'nullForSure3'; - } else { - 'notNullForSure3'; - } - - if (!$this->bar) { - 'propertyNullForSure'; - } else { - 'propertyNotNullForSure'; - } - - if (null === $foo) { - 'yodaNullForSure'; - } - - if (null !== $foo) { - 'yodaNotNullForSure'; - } - - /** @var int|false $intOrFalse */ - $intOrFalse = doFoo(); - if ($intOrFalse === false) { - 'falseForSure'; - } - - if ($intOrFalse !== false) { - 'intForSure'; - } - - if (false === $intOrFalse) { - 'yodaFalseForSure'; - } - - if (false !== $intOrFalse) { - 'yodaIntForSure'; - } - - if (!is_bool($intOrFalse)) { - 'yetAnotherIntForSure'; - } - - /** @var int|true $intOrTrue */ - $intOrTrue = doFoo(); - if ($intOrTrue === true) { - 'trueForSure'; - } - - if ($intOrTrue !== true) { - 'anotherIntForSure'; - } - - if (true === $intOrTrue) { - 'yodaTrueForSure'; - } - - if (true !== $intOrTrue) { - 'yodaAnotherIntForSure'; - } - - if (!is_bool($intOrTrue)) { - 'yetYetAnotherIntForSure'; - } - - /** @var Foo|Bar|Baz $fooOrBarOrBaz */ - $fooOrBarOrBaz = doFoo(); - if ($fooOrBarOrBaz instanceof Foo) { - 'fooForSure'; - } else { - 'barOrBazForSure'; - } - - if ($fooOrBarOrBaz instanceof Foo) { - // already tested - } elseif ($fooOrBarOrBaz instanceof Bar) { - 'barForSure'; - } else { - 'bazForSure'; - } - - if (!$fooOrBarOrBaz instanceof Foo) { - 'anotherBarOrBazForSure'; - } else { - 'anotherFooForSure'; - } - - /** @var Foo|string|null $value */ - $value = doFoo(); - $result = $value instanceof Foo ? $value->getValue() : $value; - 'stringOrNullForSure'; - - /** @var Foo|string|null $fooOrStringOrNull */ - $fooOrStringOrNull = doFoo(); - if ($fooOrStringOrNull === null || $fooOrStringOrNull instanceof Foo) { - 'fooOrNull'; - return; - } else { - 'stringForSure'; - } - - 'anotherStringForSure'; - } - + /** @var Bar|null */ + private $bar; + + public function getValue(): string + { + } + + public function test() + { + /** @var Foo|null $foo */ + $foo = doFoo(); + + if ($foo === null) { + 'nullForSure'; + } + + if ($foo !== null) { + 'notNullForSure'; + } + + if ($foo) { + 'notNullForSure2'; + } else { + 'nullForSure2'; + } + + if (!$foo) { + 'nullForSure3'; + } else { + 'notNullForSure3'; + } + + if (!$this->bar) { + 'propertyNullForSure'; + } else { + 'propertyNotNullForSure'; + } + + if (null === $foo) { + 'yodaNullForSure'; + } + + if (null !== $foo) { + 'yodaNotNullForSure'; + } + + /** @var int|false $intOrFalse */ + $intOrFalse = doFoo(); + if ($intOrFalse === false) { + 'falseForSure'; + } + + if ($intOrFalse !== false) { + 'intForSure'; + } + + if (false === $intOrFalse) { + 'yodaFalseForSure'; + } + + if (false !== $intOrFalse) { + 'yodaIntForSure'; + } + + if (!is_bool($intOrFalse)) { + 'yetAnotherIntForSure'; + } + + /** @var int|true $intOrTrue */ + $intOrTrue = doFoo(); + if ($intOrTrue === true) { + 'trueForSure'; + } + + if ($intOrTrue !== true) { + 'anotherIntForSure'; + } + + if (true === $intOrTrue) { + 'yodaTrueForSure'; + } + + if (true !== $intOrTrue) { + 'yodaAnotherIntForSure'; + } + + if (!is_bool($intOrTrue)) { + 'yetYetAnotherIntForSure'; + } + + /** @var Foo|Bar|Baz $fooOrBarOrBaz */ + $fooOrBarOrBaz = doFoo(); + if ($fooOrBarOrBaz instanceof Foo) { + 'fooForSure'; + } else { + 'barOrBazForSure'; + } + + if ($fooOrBarOrBaz instanceof Foo) { + // already tested + } elseif ($fooOrBarOrBaz instanceof Bar) { + 'barForSure'; + } else { + 'bazForSure'; + } + + if (!$fooOrBarOrBaz instanceof Foo) { + 'anotherBarOrBazForSure'; + } else { + 'anotherFooForSure'; + } + + /** @var Foo|string|null $value */ + $value = doFoo(); + $result = $value instanceof Foo ? $value->getValue() : $value; + 'stringOrNullForSure'; + + /** @var Foo|string|null $fooOrStringOrNull */ + $fooOrStringOrNull = doFoo(); + if ($fooOrStringOrNull === null || $fooOrStringOrNull instanceof Foo) { + 'fooOrNull'; + return; + } else { + 'stringForSure'; + } + + 'anotherStringForSure'; + } } diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions3.php b/tests/PHPStan/Analyser/data/type-specifying-extensions3.php index 37a1033c2d..6023f39e27 100644 --- a/tests/PHPStan/Analyser/data/type-specifying-extensions3.php +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions3.php @@ -6,5 +6,5 @@ $bar = null; if ((new \PHPStan\Tests\AssertionClass())->assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/typehints-anonymous-function.php b/tests/PHPStan/Analyser/data/typehints-anonymous-function.php index b934f46dc3..897ecce860 100644 --- a/tests/PHPStan/Analyser/data/typehints-anonymous-function.php +++ b/tests/PHPStan/Analyser/data/typehints-anonymous-function.php @@ -4,23 +4,21 @@ class FooWithAnonymousFunction { - - public function doFoo() - { - function ( - Int $integer, - boOl $boolean, - String $string, - Float $float, - Lorem $loremObject, - $mixed, - Array $array, - bool $isNullable = Null, - Callable $callable, - self $self - ) { - die; - }; - } - + public function doFoo() + { + function ( + Int $integer, + boOl $boolean, + String $string, + Float $float, + Lorem $loremObject, + $mixed, + array $array, + bool $isNullable = null, + callable $callable, + self $self + ) { + die; + }; + } } diff --git a/tests/PHPStan/Analyser/data/typehints.php b/tests/PHPStan/Analyser/data/typehints.php index 7d86a8defb..bff5bc1de3 100644 --- a/tests/PHPStan/Analyser/data/typehints.php +++ b/tests/PHPStan/Analyser/data/typehints.php @@ -4,25 +4,22 @@ class Foo { - - public function doFoo( - int $integer, - bool $boolean, - string $string, - float $float, - Lorem $loremObject, - $mixed, - array $array, - bool $isNullable = null, - callable $callable, - string ...$variadicStrings - ): Bar - { - $loremObjectRef = $loremObject; - $barObject = $this->doFoo(); - $fooObject = new self(); - $anotherBarObject = $fooObject->doFoo(); - die; - } - + public function doFoo( + int $integer, + bool $boolean, + string $string, + float $float, + Lorem $loremObject, + $mixed, + array $array, + bool $isNullable = null, + callable $callable, + string ...$variadicStrings + ): Bar { + $loremObjectRef = $loremObject; + $barObject = $this->doFoo(); + $fooObject = new self(); + $anotherBarObject = $fooObject->doFoo(); + die; + } } diff --git a/tests/PHPStan/Analyser/data/undefined-variable-assign.php b/tests/PHPStan/Analyser/data/undefined-variable-assign.php index 8210c7c347..1195865dab 100644 --- a/tests/PHPStan/Analyser/data/undefined-variable-assign.php +++ b/tests/PHPStan/Analyser/data/undefined-variable-assign.php @@ -1,6 +1,7 @@ name = $name; - } - - public function doFoo(): void - { - $this->doBar($this->name); - $this->doBaz($this->name); // reported - string passed to int - } - - public function doBar(string $name): void - { - - } - - public function doBaz(int $i): void - { - - } - + /** @var string */ + private $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function doFoo(): void + { + $this->doBar($this->name); + $this->doBaz($this->name); // reported - string passed to int + } + + public function doBar(string $name): void + { + } + + public function doBaz(int $i): void + { + } } function () { - $foo = new Foo('foo'); - $foo->doBaz($foo->name); // not reported, is mixed here + $foo = new Foo('foo'); + $foo->doBaz($foo->name); // not reported, is mixed here }; diff --git a/tests/PHPStan/Analyser/data/unresolvable-types.php b/tests/PHPStan/Analyser/data/unresolvable-types.php index c3ef7d20fc..0402166c53 100644 --- a/tests/PHPStan/Analyser/data/unresolvable-types.php +++ b/tests/PHPStan/Analyser/data/unresolvable-types.php @@ -8,9 +8,9 @@ * @param \Foo $genericFoo */ function test( - $arrayWithTooManyArgs, - $iterableWithTooManyArgs, - $genericFoo + $arrayWithTooManyArgs, + $iterableWithTooManyArgs, + $genericFoo ) { - die; + die; } diff --git a/tests/PHPStan/Analyser/data/var-above-declare.php b/tests/PHPStan/Analyser/data/var-above-declare.php index 773a3cfb07..5480859a4e 100644 --- a/tests/PHPStan/Analyser/data/var-above-declare.php +++ b/tests/PHPStan/Analyser/data/var-above-declare.php @@ -4,7 +4,7 @@ * @var string $foo */ -declare(strict_types = 1); +declare(strict_types=1); \PHPStan\Testing\assertVariableCertainty(\PHPStan\TrinaryLogic::createYes(), $foo); \PHPStan\Testing\assertType('string', $foo); diff --git a/tests/PHPStan/Analyser/data/var-annotations.php b/tests/PHPStan/Analyser/data/var-annotations.php index ff6b047108..f4432b2f97 100644 --- a/tests/PHPStan/Analyser/data/var-annotations.php +++ b/tests/PHPStan/Analyser/data/var-annotations.php @@ -4,110 +4,108 @@ class Foo { + public function doFoo() + { + /** @var int $integer */ + $integer = getFoo(); - public function doFoo() - { - /** @var int $integer */ - $integer = getFoo(); + /** @var bool $boolean */ + $boolean = getFoo(); - /** @var bool $boolean */ - $boolean = getFoo(); + /** @var string $string */ + $string = getFoo(); - /** @var string $string */ - $string = getFoo(); + /** @var float $float */ + $float = getFoo(); - /** @var float $float */ - $float = getFoo(); + /** @var Lorem $loremObject */ + $loremObject = getFoo(); - /** @var Lorem $loremObject */ - $loremObject = getFoo(); + /** @var \AnotherNamespace\Bar $barObject */ + $barObject = getFoo(); - /** @var \AnotherNamespace\Bar $barObject */ - $barObject = getFoo(); + /** @var mixed $mixed */ + $mixed = getFoo(); - /** @var mixed $mixed */ - $mixed = getFoo(); + /** @var array $array */ + $array = getFoo(); - /** @var array $array */ - $array = getFoo(); + /** @var bool|null $isNullable */ + $isNullable = getFoo(); - /** @var bool|null $isNullable */ - $isNullable = getFoo(); + /** @var callable $callable */ + $callable = getFoo(); - /** @var callable $callable */ - $callable = getFoo(); + /** @var callable(int $x, string ...$y): void $callableWithTypes */ + $callableWithTypes = getFoo(); - /** @var callable(int $x, string ...$y): void $callableWithTypes */ - $callableWithTypes = getFoo(); + /** @var \Closure(int $x, string ...$y): void $closureWithTypes */ + $closureWithTypes = getFoo(); - /** @var \Closure(int $x, string ...$y): void $closureWithTypes */ - $closureWithTypes = getFoo(); + /** @var self $self */ + $self = getFoo(); - /** @var self $self */ - $self = getFoo(); + /** @var int $invalidInt */ + $invalidInteger = $this->getFloat(); - /** @var int $invalidInt */ - $invalidInteger = $this->getFloat(); + /** @var static $static */ + $static = getFoo(); - /** @var static $static */ - $static = getFoo(); + die; + } - die; - } + public function doFooBar() + { + /** @var int */ + $integer = getFoo(); - public function doFooBar() - { - /** @var int */ - $integer = getFoo(); + /** @var bool */ + $boolean = getFoo(); - /** @var bool */ - $boolean = getFoo(); + /** @var string */ + $string = getFoo(); - /** @var string */ - $string = getFoo(); + /** @var float */ + $float = getFoo(); - /** @var float */ - $float = getFoo(); + /** @var Lorem */ + $loremObject = getFoo(); - /** @var Lorem */ - $loremObject = getFoo(); + /** @var \AnotherNamespace\Bar */ + $barObject = getFoo(); - /** @var \AnotherNamespace\Bar */ - $barObject = getFoo(); + /** @var mixed */ + $mixed = getFoo(); - /** @var mixed */ - $mixed = getFoo(); + /** @var array */ + $array = getFoo(); - /** @var array */ - $array = getFoo(); + /** @var bool|null */ + $isNullable = getFoo(); - /** @var bool|null */ - $isNullable = getFoo(); + /** @var callable */ + $callable = getFoo(); - /** @var callable */ - $callable = getFoo(); + /** @var callable(int $x, string &...$y): void */ + $callableWithTypes = getFoo(); - /** @var callable(int $x, string &...$y): void */ - $callableWithTypes = getFoo(); + /** @var \Closure(int $x, string &...$y): void */ + $closureWithTypes = getFoo(); - /** @var \Closure(int $x, string &...$y): void */ - $closureWithTypes = getFoo(); + /** @var self */ + $self = getFoo(); - /** @var self */ - $self = getFoo(); + /** @var float */ + $invalidInteger = 1.0; - /** @var float */ - $invalidInteger = 1.0; + /** @var static */ + $static = getFoo(); - /** @var static */ - $static = getFoo(); - - die; - } - - public function getFloat(): float - { - return 1.0; - } + die; + } + public function getFloat(): float + { + return 1.0; + } } diff --git a/tests/PHPStan/Analyser/data/var-stmt-annotation.php b/tests/PHPStan/Analyser/data/var-stmt-annotation.php index 32a318e067..eb898d91f7 100644 --- a/tests/PHPStan/Analyser/data/var-stmt-annotation.php +++ b/tests/PHPStan/Analyser/data/var-stmt-annotation.php @@ -4,38 +4,36 @@ class Foo { - - /** - * @param object $object - */ - public function doFoo($object) - { - /** @var self $object */ - echo 'fooo'; - - die; - } - - /** - * @param object $object - */ - public function doBar($object) - { - /** @var self $object */ - $object->foo(); - - die; - } - - /** - * @param object $object - */ - public function doBaz($object) - { - /** @var self $object */ - $test = doFoo(); - - die; - } - + /** + * @param object $object + */ + public function doFoo($object) + { + /** @var self $object */ + echo 'fooo'; + + die; + } + + /** + * @param object $object + */ + public function doBar($object) + { + /** @var self $object */ + $object->foo(); + + die; + } + + /** + * @param object $object + */ + public function doBaz($object) + { + /** @var self $object */ + $test = doFoo(); + + die; + } } diff --git a/tests/PHPStan/Analyser/data/void.php b/tests/PHPStan/Analyser/data/void.php index 938ff0521d..534d73871e 100644 --- a/tests/PHPStan/Analyser/data/void.php +++ b/tests/PHPStan/Analyser/data/void.php @@ -4,26 +4,22 @@ class Foo { - - public function doFoo(): void - { - die; - } - - /** - * @return void - */ - public function doBar(): void - { - - } - - /** - * @return int - */ - public function doConflictingVoid(): void - { - - } - + public function doFoo(): void + { + die; + } + + /** + * @return void + */ + public function doBar(): void + { + } + + /** + * @return int + */ + public function doConflictingVoid(): void + { + } } diff --git a/tests/PHPStan/Analyser/data/while-loop-variables.php b/tests/PHPStan/Analyser/data/while-loop-variables.php index a23194ab82..e33254e6c9 100644 --- a/tests/PHPStan/Analyser/data/while-loop-variables.php +++ b/tests/PHPStan/Analyser/data/while-loop-variables.php @@ -3,42 +3,42 @@ namespace LoopVariables; function () { - $foo = null; - $i = 0; - $nullableVal = null; - $falseOrObject = false; - while (($val = fetch()) && $i++ < 10) { - 'begin'; - $foo = new Foo(); - 'afterAssign'; + $foo = null; + $i = 0; + $nullableVal = null; + $falseOrObject = false; + while (($val = fetch()) && $i++ < 10) { + 'begin'; + $foo = new Foo(); + 'afterAssign'; - if ($nullableVal === null) { - 'nullableValIf'; - $nullableVal = 1; - } else { - $nullableVal *= 10; - 'nullableValElse'; - } + if ($nullableVal === null) { + 'nullableValIf'; + $nullableVal = 1; + } else { + $nullableVal *= 10; + 'nullableValElse'; + } - if ($falseOrObject === false) { - $falseOrObject = new Foo(); - } + if ($falseOrObject === false) { + $falseOrObject = new Foo(); + } - if (something()) { - $foo = new Bar(); - break; - } - if (something()) { - $foo = new Baz(); - return; - } - if (something()) { - $foo = new Lorem(); - continue; - } + if (something()) { + $foo = new Bar(); + break; + } + if (something()) { + $foo = new Baz(); + return; + } + if (something()) { + $foo = new Lorem(); + continue; + } - 'end'; - } + 'end'; + } - 'afterLoop'; + 'afterLoop'; }; diff --git a/tests/PHPStan/Analyser/traits/AnonymousClassUsingTrait.php b/tests/PHPStan/Analyser/traits/AnonymousClassUsingTrait.php index 924b814eb8..7695460e81 100644 --- a/tests/PHPStan/Analyser/traits/AnonymousClassUsingTrait.php +++ b/tests/PHPStan/Analyser/traits/AnonymousClassUsingTrait.php @@ -2,8 +2,6 @@ namespace AnonymousTraitClass; -new class implements FooInterface { - - use TraitWithTypeSpecification; - +new class() implements FooInterface { + use TraitWithTypeSpecification; }; diff --git a/tests/PHPStan/Analyser/traits/Bar.php b/tests/PHPStan/Analyser/traits/Bar.php index b63fddddc1..7c4b321e67 100644 --- a/tests/PHPStan/Analyser/traits/Bar.php +++ b/tests/PHPStan/Analyser/traits/Bar.php @@ -1,14 +1,14 @@ -doFoo(); + } - public function doTraitFoo(): void - { - $this->doFoo(); - } - - public function conflictingMethodWithDifferentArgumentNames(string $string): void - { - $r = strpos($string, 'foo'); - } - + public function conflictingMethodWithDifferentArgumentNames(string $string): void + { + $r = strpos($string, 'foo'); + } } diff --git a/tests/PHPStan/Analyser/traits/NestedBar.php b/tests/PHPStan/Analyser/traits/NestedBar.php index eff46f3443..2336c56476 100644 --- a/tests/PHPStan/Analyser/traits/NestedBar.php +++ b/tests/PHPStan/Analyser/traits/NestedBar.php @@ -1,14 +1,14 @@ -doNestedFoo(); - } - + public function doNestedTraitFoo(): void + { + $this->doNestedFoo(); + } } diff --git a/tests/PHPStan/Analyser/traits/TraitInEvalUse.php b/tests/PHPStan/Analyser/traits/TraitInEvalUse.php index 298f55a1f9..0df7968b76 100644 --- a/tests/PHPStan/Analyser/traits/TraitInEvalUse.php +++ b/tests/PHPStan/Analyser/traits/TraitInEvalUse.php @@ -1,15 +1,15 @@ -doFoo(1); - } - + public function doLorem(): void + { + $this->doFoo(1); + } } diff --git a/tests/PHPStan/Analyser/traits/TraitWithAbstractMethod.php b/tests/PHPStan/Analyser/traits/TraitWithAbstractMethod.php index 3217cd9735..985819eaa4 100644 --- a/tests/PHPStan/Analyser/traits/TraitWithAbstractMethod.php +++ b/tests/PHPStan/Analyser/traits/TraitWithAbstractMethod.php @@ -1,10 +1,10 @@ -string = 'foo'; - $this->nonexistent = 'bar'; - } + public function doFoo(): void + { + if (!$this instanceof FooInterface) { + return; + } + $this->string = 'foo'; + $this->nonexistent = 'bar'; + } } diff --git a/tests/PHPStan/Analyser/traits/classAndTrait.php b/tests/PHPStan/Analyser/traits/classAndTrait.php index b774aba5c8..fa367c6832 100644 --- a/tests/PHPStan/Analyser/traits/classAndTrait.php +++ b/tests/PHPStan/Analyser/traits/classAndTrait.php @@ -4,12 +4,9 @@ class Foo { - - use FooTrait; - + use FooTrait; } trait FooTrait { - } diff --git a/tests/PHPStan/Analyser/traits/duplicateMethod/Lesson.php b/tests/PHPStan/Analyser/traits/duplicateMethod/Lesson.php index 00826a920b..cc3d4c4c36 100644 --- a/tests/PHPStan/Analyser/traits/duplicateMethod/Lesson.php +++ b/tests/PHPStan/Analyser/traits/duplicateMethod/Lesson.php @@ -1,15 +1,15 @@ -doFoo(); - } - + public function test(): void + { + $this->doFoo(); + } } diff --git a/tests/PHPStan/Analyser/traits/duplicateMethod/LessonSubtraitOne.php b/tests/PHPStan/Analyser/traits/duplicateMethod/LessonSubtraitOne.php index 971247fc7d..0b0907665f 100644 --- a/tests/PHPStan/Analyser/traits/duplicateMethod/LessonSubtraitOne.php +++ b/tests/PHPStan/Analyser/traits/duplicateMethod/LessonSubtraitOne.php @@ -1,13 +1,12 @@ -foo = $foo; - } + public function __construct(\stdClass $foo) + { + $this->foo = $foo; + } } diff --git a/tests/PHPStan/Analyser/traits/returnThis/Bar.php b/tests/PHPStan/Analyser/traits/returnThis/Bar.php index d849cc59f8..4e5625c8a1 100644 --- a/tests/PHPStan/Analyser/traits/returnThis/Bar.php +++ b/tests/PHPStan/Analyser/traits/returnThis/Bar.php @@ -4,13 +4,11 @@ class Bar extends Foo { - - public function doFoo(): void - { - (new Foo())->returnsThisWithSelf()->doFoo(); - (new Foo())->returnsThisWithFoo()->doFoo(); - (new Bar())->returnsThisWithSelf()->doFoo(); - (new Bar())->returnsThisWithFoo()->doFoo(); - } - + public function doFoo(): void + { + (new Foo())->returnsThisWithSelf()->doFoo(); + (new Foo())->returnsThisWithFoo()->doFoo(); + (new Bar())->returnsThisWithSelf()->doFoo(); + (new Bar())->returnsThisWithFoo()->doFoo(); + } } diff --git a/tests/PHPStan/Analyser/traits/returnThis/Foo.php b/tests/PHPStan/Analyser/traits/returnThis/Foo.php index d5f2134789..4bee648677 100644 --- a/tests/PHPStan/Analyser/traits/returnThis/Foo.php +++ b/tests/PHPStan/Analyser/traits/returnThis/Foo.php @@ -4,7 +4,5 @@ class Foo { - - use FooTrait; - + use FooTrait; } diff --git a/tests/PHPStan/Analyser/traits/returnThis/FooTrait.php b/tests/PHPStan/Analyser/traits/returnThis/FooTrait.php index 095383e775..4f12c5415e 100644 --- a/tests/PHPStan/Analyser/traits/returnThis/FooTrait.php +++ b/tests/PHPStan/Analyser/traits/returnThis/FooTrait.php @@ -4,21 +4,17 @@ trait FooTrait { + /** + * @return $this + */ + public function returnsThisWithSelf(): self + { + } - /** - * @return $this - */ - public function returnsThisWithSelf(): self - { - - } - - /** - * @return $this - */ - public function returnsThisWithFoo(): Foo - { - - } - + /** + * @return $this + */ + public function returnsThisWithFoo(): Foo + { + } } diff --git a/tests/PHPStan/Analyser/traits/trait-aliases.php b/tests/PHPStan/Analyser/traits/trait-aliases.php index 31f1d42480..29904a5a76 100644 --- a/tests/PHPStan/Analyser/traits/trait-aliases.php +++ b/tests/PHPStan/Analyser/traits/trait-aliases.php @@ -4,39 +4,32 @@ trait BazTrait { - - public function fooMethod(): void - { - - } - + public function fooMethod(): void + { + } } trait BarTrait { - - use BazTrait { - fooMethod as parentFooMethod; - } - - public function fooMethod(): void - { - // some code ... - $this->fooMethod(); - $this->parentFooMethod(); - } - + use BazTrait { + fooMethod as parentFooMethod; + } + + public function fooMethod(): void + { + // some code ... + $this->fooMethod(); + $this->parentFooMethod(); + } } class Foo { + use BarTrait; - use BarTrait; - - public function doFoo(): void - { - $this->fooMethod(); - $this->parentFooMethod(); - } - + public function doFoo(): void + { + $this->fooMethod(); + $this->parentFooMethod(); + } } diff --git a/tests/PHPStan/Analyser/traits/trait-error.php b/tests/PHPStan/Analyser/traits/trait-error.php index ddca658f79..b84ae81ef1 100644 --- a/tests/PHPStan/Analyser/traits/trait-error.php +++ b/tests/PHPStan/Analyser/traits/trait-error.php @@ -1,18 +1,20 @@ -undefined($undefined); - } + public function test(): void + { + echo $undefined; + $this->undefined($undefined); + } } class MyClass { - use MyTrait; + use MyTrait; } diff --git a/tests/PHPStan/Analyser/traits/wrongProperty/Foo.php b/tests/PHPStan/Analyser/traits/wrongProperty/Foo.php index 445c132df8..d441ac84fc 100644 --- a/tests/PHPStan/Analyser/traits/wrongProperty/Foo.php +++ b/tests/PHPStan/Analyser/traits/wrongProperty/Foo.php @@ -6,15 +6,13 @@ class Foo { + use FooTrait; - use FooTrait; - - public function doFoo(): void - { - $this->id = 1; - $this->id = 'foo'; - - $this->bar = 1; - } + public function doFoo(): void + { + $this->id = 1; + $this->id = 'foo'; + $this->bar = 1; + } } diff --git a/tests/PHPStan/Analyser/traits/wrongProperty/FooTrait.php b/tests/PHPStan/Analyser/traits/wrongProperty/FooTrait.php index 118183f989..3aa9af1c9e 100644 --- a/tests/PHPStan/Analyser/traits/wrongProperty/FooTrait.php +++ b/tests/PHPStan/Analyser/traits/wrongProperty/FooTrait.php @@ -6,11 +6,9 @@ trait FooTrait { + /** @var int */ + private $id; - /** @var int */ - private $id; - - /** @var Bar */ - private $bar; - + /** @var Bar */ + private $bar; } diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php b/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php index 7b0fa2c07d..b1b89e6f0c 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php +++ b/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php @@ -1,4 +1,6 @@ -deleteCache(); - - if ($this->originalTraitOneContents !== null) { - $this->revertTrait(__DIR__ . '/data/TraitOne.php', $this->originalTraitOneContents); - } - - if ($this->originalTraitTwoContents !== null) { - $this->revertTrait(__DIR__ . '/data/TraitTwo.php', $this->originalTraitTwoContents); - } - } - - public function dataCachingIssue(): array - { - return [ - [ - false, - false, - [], - ], - [ - false, - true, - [ - 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.', - ], - ], - [ - true, - false, - [ - 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.', - ], - ], - [ - true, - true, - [ - 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.', - 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.', - ], - ], - ]; - } - - /** - * @dataProvider dataCachingIssue - * @param bool $changeOne - * @param bool $changeTwo - * @param string[] $expectedErrors - */ - public function testCachingIssue( - bool $changeOne, - bool $changeTwo, - array $expectedErrors - ): void - { - $this->deleteCache(); - [$statusCode, $errors] = $this->runPhpStan(); - $this->assertSame([], $errors); - $this->assertSame(0, $statusCode); - - if ($changeOne) { - $this->originalTraitOneContents = $this->changeTrait(__DIR__ . '/data/TraitOne.php'); - } - if ($changeTwo) { - $this->originalTraitTwoContents = $this->changeTrait(__DIR__ . '/data/TraitTwo.php'); - } - - $fileHelper = new FileHelper(__DIR__); - - $errorPath = $fileHelper->normalizePath(__DIR__ . '/data/TestClassUsingTrait.php'); - [$statusCode, $errors] = $this->runPhpStan(); - - if (count($expectedErrors) === 0) { - $this->assertSame(0, $statusCode); - $this->assertArrayNotHasKey($errorPath, $errors); - return; - } - - $this->assertSame(1, $statusCode); - $this->assertArrayHasKey($errorPath, $errors); - $this->assertSame(count($expectedErrors), $errors[$errorPath]['errors']); - - foreach ($errors[$errorPath]['messages'] as $i => $error) { - $this->assertSame($expectedErrors[$i], $error['message']); - } - } - - /** - * @return array{int, mixed[]} - */ - private function runPhpStan(): array - { - $phpstanBinPath = __DIR__ . '/../../../../bin/phpstan'; - exec(sprintf('%s %s clear-result-cache --configuration %s', escapeshellarg(PHP_BINARY), $phpstanBinPath, escapeshellarg(__DIR__ . '/phpstan.neon')), $clearResultCacheOutputLines, $clearResultCacheExitCode); - if ($clearResultCacheExitCode !== 0) { - throw new \PHPStan\ShouldNotHappenException('Could not clear result cache.'); - } - - exec( - sprintf( - '%s %s analyse --no-progress --level 8 --configuration %s --error-format json %s', - escapeshellarg(PHP_BINARY), - $phpstanBinPath, - escapeshellarg(__DIR__ . '/phpstan.neon'), - escapeshellarg(__DIR__ . '/data') - ), - $output, - $statusCode - ); - $stringOutput = implode("\n", $output); - $json = \Nette\Utils\Json::decode($stringOutput, \Nette\Utils\Json::FORCE_ARRAY); - - return [$statusCode, $json['files']]; - } - - private function deleteCache(): void - { - $dir = __DIR__ . '/tmp/cache'; - if (!file_exists($dir)) { - return; - } - - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator(__DIR__ . '/tmp/cache', RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($files as $fileinfo) { - if ($fileinfo->isDir()) { - rmdir($fileinfo->getRealPath()); - continue; - } - - unlink($fileinfo->getRealPath()); - } - } - - private function changeTrait(string $traitPath): string - { - $originalTraitContents = FileReader::read($traitPath); - $traitContents = str_replace('use stdClass as Foo;', 'use Exception as Foo;', $originalTraitContents); - $result = file_put_contents($traitPath, $traitContents); - if ($result === false) { - $this->fail(sprintf('Could not save file %s', $traitPath)); - } - - return $originalTraitContents; - } - - private function revertTrait(string $traitPath, string $originalTraitContents): void - { - $result = file_put_contents($traitPath, $originalTraitContents); - if ($result === false) { - $this->fail(sprintf('Could not save file %s', $traitPath)); - } - } - + /** @var string|null */ + private $originalTraitOneContents; + + /** @var string|null */ + private $originalTraitTwoContents; + + public function tearDown(): void + { + $this->deleteCache(); + + if ($this->originalTraitOneContents !== null) { + $this->revertTrait(__DIR__ . '/data/TraitOne.php', $this->originalTraitOneContents); + } + + if ($this->originalTraitTwoContents !== null) { + $this->revertTrait(__DIR__ . '/data/TraitTwo.php', $this->originalTraitTwoContents); + } + } + + public function dataCachingIssue(): array + { + return [ + [ + false, + false, + [], + ], + [ + false, + true, + [ + 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.', + ], + ], + [ + true, + false, + [ + 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.', + ], + ], + [ + true, + true, + [ + 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.', + 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.', + ], + ], + ]; + } + + /** + * @dataProvider dataCachingIssue + * @param bool $changeOne + * @param bool $changeTwo + * @param string[] $expectedErrors + */ + public function testCachingIssue( + bool $changeOne, + bool $changeTwo, + array $expectedErrors + ): void { + $this->deleteCache(); + [$statusCode, $errors] = $this->runPhpStan(); + $this->assertSame([], $errors); + $this->assertSame(0, $statusCode); + + if ($changeOne) { + $this->originalTraitOneContents = $this->changeTrait(__DIR__ . '/data/TraitOne.php'); + } + if ($changeTwo) { + $this->originalTraitTwoContents = $this->changeTrait(__DIR__ . '/data/TraitTwo.php'); + } + + $fileHelper = new FileHelper(__DIR__); + + $errorPath = $fileHelper->normalizePath(__DIR__ . '/data/TestClassUsingTrait.php'); + [$statusCode, $errors] = $this->runPhpStan(); + + if (count($expectedErrors) === 0) { + $this->assertSame(0, $statusCode); + $this->assertArrayNotHasKey($errorPath, $errors); + return; + } + + $this->assertSame(1, $statusCode); + $this->assertArrayHasKey($errorPath, $errors); + $this->assertSame(count($expectedErrors), $errors[$errorPath]['errors']); + + foreach ($errors[$errorPath]['messages'] as $i => $error) { + $this->assertSame($expectedErrors[$i], $error['message']); + } + } + + /** + * @return array{int, mixed[]} + */ + private function runPhpStan(): array + { + $phpstanBinPath = __DIR__ . '/../../../../bin/phpstan'; + exec(sprintf('%s %s clear-result-cache --configuration %s', escapeshellarg(PHP_BINARY), $phpstanBinPath, escapeshellarg(__DIR__ . '/phpstan.neon')), $clearResultCacheOutputLines, $clearResultCacheExitCode); + if ($clearResultCacheExitCode !== 0) { + throw new \PHPStan\ShouldNotHappenException('Could not clear result cache.'); + } + + exec( + sprintf( + '%s %s analyse --no-progress --level 8 --configuration %s --error-format json %s', + escapeshellarg(PHP_BINARY), + $phpstanBinPath, + escapeshellarg(__DIR__ . '/phpstan.neon'), + escapeshellarg(__DIR__ . '/data') + ), + $output, + $statusCode + ); + $stringOutput = implode("\n", $output); + $json = \Nette\Utils\Json::decode($stringOutput, \Nette\Utils\Json::FORCE_ARRAY); + + return [$statusCode, $json['files']]; + } + + private function deleteCache(): void + { + $dir = __DIR__ . '/tmp/cache'; + if (!file_exists($dir)) { + return; + } + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(__DIR__ . '/tmp/cache', RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileinfo) { + if ($fileinfo->isDir()) { + rmdir($fileinfo->getRealPath()); + continue; + } + + unlink($fileinfo->getRealPath()); + } + } + + private function changeTrait(string $traitPath): string + { + $originalTraitContents = FileReader::read($traitPath); + $traitContents = str_replace('use stdClass as Foo;', 'use Exception as Foo;', $originalTraitContents); + $result = file_put_contents($traitPath, $traitContents); + if ($result === false) { + $this->fail(sprintf('Could not save file %s', $traitPath)); + } + + return $originalTraitContents; + } + + private function revertTrait(string $traitPath, string $originalTraitContents): void + { + $result = file_put_contents($traitPath, $originalTraitContents); + if ($result === false) { + $this->fail(sprintf('Could not save file %s', $traitPath)); + } + } } diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/data/TestClassUsingTrait.php b/tests/PHPStan/Analyser/traitsCachingIssue/data/TestClassUsingTrait.php index eac1193bed..1f5078072e 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/data/TestClassUsingTrait.php +++ b/tests/PHPStan/Analyser/traitsCachingIssue/data/TestClassUsingTrait.php @@ -4,31 +4,28 @@ class TestClassUsingTrait { - - use TraitOne; - - /** - * @return \stdClass - */ - public function doBar() - { - return $this->doFoo(); - } - - public function doBaz(): void - { - $class = new class() { - - use TraitTwo; - - /** - * @return \stdClass - */ - public function doBar() - { - return $this->doFoo(); - } - }; - } - + use TraitOne; + + /** + * @return \stdClass + */ + public function doBar() + { + return $this->doFoo(); + } + + public function doBaz(): void + { + $class = new class() { + use TraitTwo; + + /** + * @return \stdClass + */ + public function doBar() + { + return $this->doFoo(); + } + }; + } } diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitOne.php b/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitOne.php index 395dc1333a..85db23c12a 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitOne.php +++ b/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitOne.php @@ -6,13 +6,11 @@ trait TraitOne { - - /** - * @return Foo - */ - public function doFoo() - { - return new Foo(); - } - + /** + * @return Foo + */ + public function doFoo() + { + return new Foo(); + } } diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitTwo.php b/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitTwo.php index 1b9827b727..dc0a701f96 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitTwo.php +++ b/tests/PHPStan/Analyser/traitsCachingIssue/data/TraitTwo.php @@ -6,13 +6,11 @@ trait TraitTwo { - - /** - * @return Foo - */ - public function doFoo() - { - return new Foo(); - } - + /** + * @return Foo + */ + public function doFoo() + { + return new Foo(); + } } diff --git a/tests/PHPStan/Broker/BrokerTest.php b/tests/PHPStan/Broker/BrokerTest.php index 6e32c59a4d..5e131f9d30 100644 --- a/tests/PHPStan/Broker/BrokerTest.php +++ b/tests/PHPStan/Broker/BrokerTest.php @@ -1,4 +1,6 @@ -getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - - $workingDirectory = __DIR__; - $relativePathHelper = new SimpleRelativePathHelper($workingDirectory); - $fileHelper = new FileHelper($workingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); + protected function setUp(): void + { + $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); + $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); - $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider([], [], []); - $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider([]); + $workingDirectory = __DIR__; + $relativePathHelper = new SimpleRelativePathHelper($workingDirectory); + $fileHelper = new FileHelper($workingDirectory); + $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); - $setterReflectionProviderProvider = new SetterReflectionProviderProvider(); - $reflectionProvider = new RuntimeReflectionProvider( - $setterReflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $this->createMock(FunctionReflectionFactory::class), - new FileTypeMapper($setterReflectionProviderProvider, $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), $anonymousClassNameHelper), - self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(NativeFunctionReflectionProvider::class), - self::getContainer()->getByType(StubPhpDocProvider::class), - self::getContainer()->getByType(PhpStormStubsSourceStubber::class) - ); - $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); - $this->broker = new Broker( - $reflectionProvider, - $dynamicReturnTypeExtensionRegistryProvider, - $operatorTypeSpecifyingExtensionRegistryProvider, - [] - ); - $classReflectionExtensionRegistryProvider->setBroker($this->broker); - $dynamicReturnTypeExtensionRegistryProvider->setBroker($this->broker); - $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($this->broker); - } + $classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); + $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider([], [], []); + $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider([]); - public function testClassNotFound(): void - { - $this->expectException(\PHPStan\Broker\ClassNotFoundException::class); - $this->expectExceptionMessage('NonexistentClass'); - $this->broker->getClass('NonexistentClass'); - } + $setterReflectionProviderProvider = new SetterReflectionProviderProvider(); + $reflectionProvider = new RuntimeReflectionProvider( + $setterReflectionProviderProvider, + $classReflectionExtensionRegistryProvider, + $this->createMock(FunctionReflectionFactory::class), + new FileTypeMapper($setterReflectionProviderProvider, $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), $anonymousClassNameHelper), + self::getContainer()->getByType(PhpVersion::class), + self::getContainer()->getByType(NativeFunctionReflectionProvider::class), + self::getContainer()->getByType(StubPhpDocProvider::class), + self::getContainer()->getByType(PhpStormStubsSourceStubber::class) + ); + $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); + $this->broker = new Broker( + $reflectionProvider, + $dynamicReturnTypeExtensionRegistryProvider, + $operatorTypeSpecifyingExtensionRegistryProvider, + [] + ); + $classReflectionExtensionRegistryProvider->setBroker($this->broker); + $dynamicReturnTypeExtensionRegistryProvider->setBroker($this->broker); + $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($this->broker); + } - public function testFunctionNotFound(): void - { - $this->expectException(\PHPStan\Broker\FunctionNotFoundException::class); - $this->expectExceptionMessage('Function nonexistentFunction not found while trying to analyse it - discovering symbols is probably not configured properly.'); + public function testClassNotFound(): void + { + $this->expectException(\PHPStan\Broker\ClassNotFoundException::class); + $this->expectExceptionMessage('NonexistentClass'); + $this->broker->getClass('NonexistentClass'); + } - $scope = $this->createMock(Scope::class); - $scope->method('getNamespace') - ->willReturn(null); - $this->broker->getFunction(new Name('nonexistentFunction'), $scope); - } + public function testFunctionNotFound(): void + { + $this->expectException(\PHPStan\Broker\FunctionNotFoundException::class); + $this->expectExceptionMessage('Function nonexistentFunction not found while trying to analyse it - discovering symbols is probably not configured properly.'); - public function testClassAutoloadingException(): void - { - $this->expectException(\PHPStan\Broker\ClassAutoloadingException::class); - $this->expectExceptionMessage('thrown while looking for class NonexistentClass.'); - spl_autoload_register(static function (): void { - require_once __DIR__ . '/../Analyser/data/parse-error.php'; - }, true, true); - $this->broker->hasClass('NonexistentClass'); - } + $scope = $this->createMock(Scope::class); + $scope->method('getNamespace') + ->willReturn(null); + $this->broker->getFunction(new Name('nonexistentFunction'), $scope); + } + public function testClassAutoloadingException(): void + { + $this->expectException(\PHPStan\Broker\ClassAutoloadingException::class); + $this->expectExceptionMessage('thrown while looking for class NonexistentClass.'); + spl_autoload_register(static function (): void { + require_once __DIR__ . '/../Analyser/data/parse-error.php'; + }, true, true); + $this->broker->hasClass('NonexistentClass'); + } } diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index 844dc62b09..f96fab8dd3 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -1,4 +1,6 @@ -runPath(__DIR__ . '/data/file-without-errors.php', 0); + $this->assertStringContainsString('No errors', $output); + } - public function testExecuteOnAFile(): void - { - $output = $this->runPath(__DIR__ . '/data/file-without-errors.php', 0); - $this->assertStringContainsString('No errors', $output); - } - - public function testExecuteOnANonExistentPath(): void - { - $path = __DIR__ . '/foo'; - $output = $this->runPath($path, 1); - $this->assertStringContainsString(sprintf( - 'File %s does not exist.', - $path - ), $output); - } - - public function testExecuteOnAFileWithErrors(): void - { - $path = __DIR__ . '/../Rules/Functions/data/nonexistent-function.php'; - $output = $this->runPath($path, 1); - $this->assertStringContainsString('Function foobarNonExistentFunction not found.', $output); - } + public function testExecuteOnANonExistentPath(): void + { + $path = __DIR__ . '/foo'; + $output = $this->runPath($path, 1); + $this->assertStringContainsString(sprintf( + 'File %s does not exist.', + $path + ), $output); + } - private function runPath(string $path, int $expectedStatusCode): string - { - if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } - self::getContainer()->getByType(ResultCacheClearer::class)->clear(); - $analyserApplication = self::getContainer()->getByType(AnalyseApplication::class); - $resource = fopen('php://memory', 'w', false); - if ($resource === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $output = new StreamOutput($resource); + public function testExecuteOnAFileWithErrors(): void + { + $path = __DIR__ . '/../Rules/Functions/data/nonexistent-function.php'; + $output = $this->runPath($path, 1); + $this->assertStringContainsString('Function foobarNonExistentFunction not found.', $output); + } - $symfonyOutput = new SymfonyOutput( - $output, - new \PHPStan\Command\Symfony\SymfonyStyle(new SymfonyStyle($this->createMock(InputInterface::class), $output)) - ); + private function runPath(string $path, int $expectedStatusCode): string + { + if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); + } + self::getContainer()->getByType(ResultCacheClearer::class)->clear(); + $analyserApplication = self::getContainer()->getByType(AnalyseApplication::class); + $resource = fopen('php://memory', 'w', false); + if ($resource === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $output = new StreamOutput($resource); - $memoryLimitFile = self::getContainer()->getParameter('memoryLimitFile'); + $symfonyOutput = new SymfonyOutput( + $output, + new \PHPStan\Command\Symfony\SymfonyStyle(new SymfonyStyle($this->createMock(InputInterface::class), $output)) + ); - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), __DIR__, [], DIRECTORY_SEPARATOR); - $errorFormatter = new TableErrorFormatter($relativePathHelper, false); - $analysisResult = $analyserApplication->analyse( - [$path], - true, - $symfonyOutput, - $symfonyOutput, - false, - false, - null, - null, - $this->createMock(InputInterface::class) - ); - if (file_exists($memoryLimitFile)) { - unlink($memoryLimitFile); - } - $statusCode = $errorFormatter->formatErrors($analysisResult, $symfonyOutput); - $this->assertSame($expectedStatusCode, $statusCode); + $memoryLimitFile = self::getContainer()->getParameter('memoryLimitFile'); - rewind($output->getStream()); + $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), __DIR__, [], DIRECTORY_SEPARATOR); + $errorFormatter = new TableErrorFormatter($relativePathHelper, false); + $analysisResult = $analyserApplication->analyse( + [$path], + true, + $symfonyOutput, + $symfonyOutput, + false, + false, + null, + null, + $this->createMock(InputInterface::class) + ); + if (file_exists($memoryLimitFile)) { + unlink($memoryLimitFile); + } + $statusCode = $errorFormatter->formatErrors($analysisResult, $symfonyOutput); + $this->assertSame($expectedStatusCode, $statusCode); - $contents = stream_get_contents($output->getStream()); - if ($contents === false) { - throw new \PHPStan\ShouldNotHappenException(); - } + rewind($output->getStream()); - return $contents; - } + $contents = stream_get_contents($output->getStream()); + if ($contents === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + return $contents; + } } diff --git a/tests/PHPStan/Command/AnalyseCommandTest.php b/tests/PHPStan/Command/AnalyseCommandTest.php index 6754544607..f1a41d8fd3 100644 --- a/tests/PHPStan/Command/AnalyseCommandTest.php +++ b/tests/PHPStan/Command/AnalyseCommandTest.php @@ -1,4 +1,6 @@ -runCommand(1); - $this->assertStringContainsString('Note: Using configuration file ' . $file . '.', $output); - } catch (\Throwable $e) { - chdir($originalDir); - throw $e; - } - } - - public function testInvalidAutoloadFile(): void - { - $dir = realpath(__DIR__ . '/../../../'); - $autoloadFile = $dir . DIRECTORY_SEPARATOR . 'phpstan.123456789.php'; + try { + $output = $this->runCommand(1); + $this->assertStringContainsString('Note: Using configuration file ' . $file . '.', $output); + } catch (\Throwable $e) { + chdir($originalDir); + throw $e; + } + } - $output = $this->runCommand(1, ['--autoload-file' => $autoloadFile]); - $this->assertSame(sprintf('Autoload file "%s" not found.' . PHP_EOL, $autoloadFile), $output); - } + public function testInvalidAutoloadFile(): void + { + $dir = realpath(__DIR__ . '/../../../'); + $autoloadFile = $dir . DIRECTORY_SEPARATOR . 'phpstan.123456789.php'; - public function testValidAutoloadFile(): void - { - $autoloadFile = __DIR__ . DIRECTORY_SEPARATOR . 'data/autoload-file.php'; + $output = $this->runCommand(1, ['--autoload-file' => $autoloadFile]); + $this->assertSame(sprintf('Autoload file "%s" not found.' . PHP_EOL, $autoloadFile), $output); + } - $output = $this->runCommand(0, ['--autoload-file' => $autoloadFile]); - $this->assertStringContainsString('[OK] No errors', $output); - $this->assertStringNotContainsString(sprintf('Autoload file "%s" not found.' . PHP_EOL, $autoloadFile), $output); - $this->assertSame('magic value', SOME_CONSTANT_IN_AUTOLOAD_FILE); - } + public function testValidAutoloadFile(): void + { + $autoloadFile = __DIR__ . DIRECTORY_SEPARATOR . 'data/autoload-file.php'; - /** - * @return string[][] - */ - public static function autoDiscoveryPathsProvider(): array - { - return [ - [ - __DIR__ . '/test-autodiscover', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover' . DIRECTORY_SEPARATOR . 'phpstan.neon', - ], - [ - __DIR__ . '/test-autodiscover-dist', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dist' . DIRECTORY_SEPARATOR . 'phpstan.neon.dist', - ], - [ - __DIR__ . '/test-autodiscover-priority', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-priority' . DIRECTORY_SEPARATOR . 'phpstan.neon', - ], - ]; - } + $output = $this->runCommand(0, ['--autoload-file' => $autoloadFile]); + $this->assertStringContainsString('[OK] No errors', $output); + $this->assertStringNotContainsString(sprintf('Autoload file "%s" not found.' . PHP_EOL, $autoloadFile), $output); + $this->assertSame('magic value', SOME_CONSTANT_IN_AUTOLOAD_FILE); + } - /** - * @param int $expectedStatusCode - * @param array $parameters - * @return string - */ - private function runCommand(int $expectedStatusCode, array $parameters = []): string - { - if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } - $commandTester = new CommandTester(new AnalyseCommand([])); + /** + * @return string[][] + */ + public static function autoDiscoveryPathsProvider(): array + { + return [ + [ + __DIR__ . '/test-autodiscover', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover' . DIRECTORY_SEPARATOR . 'phpstan.neon', + ], + [ + __DIR__ . '/test-autodiscover-dist', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dist' . DIRECTORY_SEPARATOR . 'phpstan.neon.dist', + ], + [ + __DIR__ . '/test-autodiscover-priority', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-priority' . DIRECTORY_SEPARATOR . 'phpstan.neon', + ], + ]; + } - $commandTester->execute([ - 'paths' => [__DIR__ . DIRECTORY_SEPARATOR . 'test'], - ] + $parameters); + /** + * @param int $expectedStatusCode + * @param array $parameters + * @return string + */ + private function runCommand(int $expectedStatusCode, array $parameters = []): string + { + if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); + } + $commandTester = new CommandTester(new AnalyseCommand([])); - $this->assertSame($expectedStatusCode, $commandTester->getStatusCode()); + $commandTester->execute([ + 'paths' => [__DIR__ . DIRECTORY_SEPARATOR . 'test'], + ] + $parameters); - return $commandTester->getDisplay(); - } + $this->assertSame($expectedStatusCode, $commandTester->getStatusCode()); + return $commandTester->getDisplay(); + } } diff --git a/tests/PHPStan/Command/AnalysisResultTest.php b/tests/PHPStan/Command/AnalysisResultTest.php index d72143624f..ffd9dbe4ff 100644 --- a/tests/PHPStan/Command/AnalysisResultTest.php +++ b/tests/PHPStan/Command/AnalysisResultTest.php @@ -1,4 +1,6 @@ -getFileSpecificErrors() - ); - } - + public function testErrorsAreSortedByFileNameAndLine(): void + { + self::assertEquals( + [ + new Error('aa1', 'aaa'), + new Error('aa2', 'aaa', 10), + new Error('aa3', 'aaa', 15), + new Error('aa4', 'aaa', 16), + new Error('aa5', 'aaa', 16), + new Error('aa6', 'aaa', 16), + new Error('bb2', 'bbb', 2), + new Error('bb1', 'bbb', 4), + new Error('ccc', 'ccc'), + new Error('ddd', 'ddd'), + ], + (new AnalysisResult( + [ + new Error('bb1', 'bbb', 4), + new Error('bb2', 'bbb', 2), + new Error('aa1', 'aaa'), + new Error('ddd', 'ddd'), + new Error('ccc', 'ccc'), + new Error('aa2', 'aaa', 10), + new Error('aa3', 'aaa', 15), + new Error('aa5', 'aaa', 16), + new Error('aa6', 'aaa', 16), + new Error('aa4', 'aaa', 16), + ], + [], + [], + [], + false, + null, + true + ))->getFileSpecificErrors() + ); + } } diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index 028f8f2edf..a8a3cfb7c9 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -1,4 +1,6 @@ - 'max', + ], + false, + ], + [ + '', + 'Recursive included file', + __DIR__ . '/data/1.neon', + null, + [], + true, + ], + [ + '', + 'does not exist', + __DIR__ . '/data/nonexistent.neon', + null, + [], + true, + ], + [ + '', + 'is missing or is not readable', + __DIR__ . '/data/containsNonexistent.neon', + null, + [], + true, + ], + [ + '', + 'These files are included multiple times', + __DIR__ . '/../../../conf/config.level7.neon', + '7', + [], + true, + ], + [ + '', + 'These files are included multiple times', + __DIR__ . '/../../../conf/config.level7.neon', + '6', + [], + true, + ], + [ + '', + 'These files are included multiple times', + __DIR__ . '/../../../conf/config.level6.neon', + '7', + [], + true, + ], + [ + '', + '', + __DIR__ . '/data/includePhp.neon', + null, + [ + 'level' => '3', + ], + false, + ], + ]; + } - public function dataBegin(): array - { - return [ - [ - '', - '', - __DIR__ . '/data/testIncludesExpand.neon', - null, - [ - 'level' => 'max', - ], - false, - ], - [ - '', - 'Recursive included file', - __DIR__ . '/data/1.neon', - null, - [], - true, - ], - [ - '', - 'does not exist', - __DIR__ . '/data/nonexistent.neon', - null, - [], - true, - ], - [ - '', - 'is missing or is not readable', - __DIR__ . '/data/containsNonexistent.neon', - null, - [], - true, - ], - [ - '', - 'These files are included multiple times', - __DIR__ . '/../../../conf/config.level7.neon', - '7', - [], - true, - ], - [ - '', - 'These files are included multiple times', - __DIR__ . '/../../../conf/config.level7.neon', - '6', - [], - true, - ], - [ - '', - 'These files are included multiple times', - __DIR__ . '/../../../conf/config.level6.neon', - '7', - [], - true, - ], - [ - '', - '', - __DIR__ . '/data/includePhp.neon', - null, - [ - 'level' => '3', - ], - false, - ], - ]; - } - - /** - * @dataProvider dataBegin - * @param string $input - * @param string $expectedOutput - * @param string|null $projectConfigFile - * @param string|null $level - * @param mixed[] $expectedParameters - * @param bool $expectException - */ - public function testBegin( - string $input, - string $expectedOutput, - ?string $projectConfigFile, - ?string $level, - array $expectedParameters, - bool $expectException - ): void - { - $resource = fopen('php://memory', 'w', false); - if ($resource === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $output = new StreamOutput($resource); - - try { - $result = CommandHelper::begin( - new StringInput($input), - $output, - [__DIR__], - null, - null, - null, - [], - $projectConfigFile, - null, - $level, - false, - true - ); - if ($expectException) { - $this->fail(); - } - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - if (!$expectException) { - rewind($output->getStream()); - $contents = stream_get_contents($output->getStream()); - if ($contents === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $this->fail($contents); - } - } + /** + * @dataProvider dataBegin + * @param string $input + * @param string $expectedOutput + * @param string|null $projectConfigFile + * @param string|null $level + * @param mixed[] $expectedParameters + * @param bool $expectException + */ + public function testBegin( + string $input, + string $expectedOutput, + ?string $projectConfigFile, + ?string $level, + array $expectedParameters, + bool $expectException + ): void { + $resource = fopen('php://memory', 'w', false); + if ($resource === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $output = new StreamOutput($resource); - rewind($output->getStream()); + try { + $result = CommandHelper::begin( + new StringInput($input), + $output, + [__DIR__], + null, + null, + null, + [], + $projectConfigFile, + null, + $level, + false, + true + ); + if ($expectException) { + $this->fail(); + } + } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { + if (!$expectException) { + rewind($output->getStream()); + $contents = stream_get_contents($output->getStream()); + if ($contents === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $this->fail($contents); + } + } - $contents = stream_get_contents($output->getStream()); - if ($contents === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $this->assertStringContainsString($expectedOutput, $contents); + rewind($output->getStream()); - if (isset($result)) { - $parameters = $result->getContainer()->getParameters(); - foreach ($expectedParameters as $name => $expectedValue) { - $this->assertArrayHasKey($name, $parameters); - $this->assertSame($expectedValue, $parameters[$name]); - } - } else { - $this->assertCount(0, $expectedParameters); - } - } + $contents = stream_get_contents($output->getStream()); + if ($contents === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $this->assertStringContainsString($expectedOutput, $contents); - public function dataResolveRelativePaths(): array - { - return [ - [ - __DIR__ . '/relative-paths/root.neon', - [ - 'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', - 'bootstrapFiles' => [ - realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'), - realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'), - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', - ], - 'autoload_files' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', - __DIR__ . DIRECTORY_SEPARATOR . 'up.php', - ], - 'autoload_directories' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths', - realpath(__DIR__ . '/../../../') . '/conf', - ], - 'scanFiles' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', - __DIR__ . DIRECTORY_SEPARATOR . 'up.php', - ], - 'scanDirectories' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths', - realpath(__DIR__ . '/../../../') . '/conf', - ], - 'paths' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - ], - 'memoryLimitFile' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . '.memory_limit', - 'excludes_analyse' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'data', - '*/src/*/data', - ], - ], - ], - [ - __DIR__ . '/relative-paths/nested/nested.neon', - [ - 'autoload_files' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'here.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'up.php', - ], - 'ignoreErrors' => [ - [ - 'message' => '#aaa#', - 'path' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'aaa.php', - ], - [ - 'message' => '#bbb#', - 'paths' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'aaa.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'bbb.php', - ], - ], - ], - ], - ], - ]; - } + if (isset($result)) { + $parameters = $result->getContainer()->getParameters(); + foreach ($expectedParameters as $name => $expectedValue) { + $this->assertArrayHasKey($name, $parameters); + $this->assertSame($expectedValue, $parameters[$name]); + } + } else { + $this->assertCount(0, $expectedParameters); + } + } - /** - * @dataProvider dataResolveRelativePaths - * @param string $configFile - * @param array $expectedParameters - * @throws \PHPStan\Command\InceptionNotSuccessfulException - */ - public function testResolveRelativePaths( - string $configFile, - array $expectedParameters - ): void - { - $result = CommandHelper::begin( - new StringInput(''), - new NullOutput(), - [__DIR__], - null, - null, - null, - [], - $configFile, - null, - '0', - false, - true - ); - $parameters = $result->getContainer()->getParameters(); - foreach ($expectedParameters as $name => $expectedValue) { - $this->assertArrayHasKey($name, $parameters); - $this->assertSame($expectedValue, $parameters[$name]); - } - } + public function dataResolveRelativePaths(): array + { + return [ + [ + __DIR__ . '/relative-paths/root.neon', + [ + 'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', + 'bootstrapFiles' => [ + realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'), + realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'), + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', + ], + 'autoload_files' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', + __DIR__ . DIRECTORY_SEPARATOR . 'up.php', + ], + 'autoload_directories' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths', + realpath(__DIR__ . '/../../../') . '/conf', + ], + 'scanFiles' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', + __DIR__ . DIRECTORY_SEPARATOR . 'up.php', + ], + 'scanDirectories' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths', + realpath(__DIR__ . '/../../../') . '/conf', + ], + 'paths' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', + ], + 'memoryLimitFile' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . '.memory_limit', + 'excludes_analyse' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'data', + '*/src/*/data', + ], + ], + ], + [ + __DIR__ . '/relative-paths/nested/nested.neon', + [ + 'autoload_files' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'here.php', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'up.php', + ], + 'ignoreErrors' => [ + [ + 'message' => '#aaa#', + 'path' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'aaa.php', + ], + [ + 'message' => '#bbb#', + 'paths' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'aaa.php', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'bbb.php', + ], + ], + ], + ], + ], + ]; + } + /** + * @dataProvider dataResolveRelativePaths + * @param string $configFile + * @param array $expectedParameters + * @throws \PHPStan\Command\InceptionNotSuccessfulException + */ + public function testResolveRelativePaths( + string $configFile, + array $expectedParameters + ): void { + $result = CommandHelper::begin( + new StringInput(''), + new NullOutput(), + [__DIR__], + null, + null, + null, + [], + $configFile, + null, + '0', + false, + true + ); + $parameters = $result->getContainer()->getParameters(); + foreach ($expectedParameters as $name => $expectedValue) { + $this->assertArrayHasKey($name, $parameters); + $this->assertSame($expectedValue, $parameters[$name]); + } + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php index bf62cc8e5f..a097fda6e9 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php @@ -1,4 +1,6 @@ -runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon'); + $errors = Json::decode($output, Json::FORCE_ARRAY); + $this->assertSame(10, array_sum($errors['totals'])); + $this->assertCount(6, $errors['files']); + } - public function testErrorWithTrait(): void - { - $output = $this->runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon'); - $errors = Json::decode($output, Json::FORCE_ARRAY); - $this->assertSame(10, array_sum($errors['totals'])); - $this->assertCount(6, $errors['files']); - } - - public function testGenerateBaselineAndRunAgainWithIt(): void - { - $output = $this->runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon', 'baselineNeon'); - $baselineFile = __DIR__ . '/../../../../baseline.neon'; - file_put_contents($baselineFile, $output); - - $output = $this->runPhpStan(__DIR__ . '/data/', $baselineFile); - @unlink($baselineFile); - $errors = Json::decode($output, Json::FORCE_ARRAY); - $this->assertSame(0, array_sum($errors['totals'])); - $this->assertCount(0, $errors['files']); - } + public function testGenerateBaselineAndRunAgainWithIt(): void + { + $output = $this->runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon', 'baselineNeon'); + $baselineFile = __DIR__ . '/../../../../baseline.neon'; + file_put_contents($baselineFile, $output); - public function testRunWindowsFileWithUnixBaseline(): void - { - $output = $this->runPhpStan(__DIR__ . '/data/WindowsNewlines.php', __DIR__ . '/data/unixBaseline.neon'); - $errors = Json::decode($output, Json::FORCE_ARRAY); - $this->assertSame(0, array_sum($errors['totals'])); - $this->assertCount(0, $errors['files']); - } + $output = $this->runPhpStan(__DIR__ . '/data/', $baselineFile); + @unlink($baselineFile); + $errors = Json::decode($output, Json::FORCE_ARRAY); + $this->assertSame(0, array_sum($errors['totals'])); + $this->assertCount(0, $errors['files']); + } - public function testRunUnixFileWithWindowsBaseline(): void - { - $output = $this->runPhpStan(__DIR__ . '/data/UnixNewlines.php', __DIR__ . '/data/windowsBaseline.neon'); - $errors = Json::decode($output, Json::FORCE_ARRAY); - $this->assertSame(0, array_sum($errors['totals'])); - $this->assertCount(0, $errors['files']); - } + public function testRunWindowsFileWithUnixBaseline(): void + { + $output = $this->runPhpStan(__DIR__ . '/data/WindowsNewlines.php', __DIR__ . '/data/unixBaseline.neon'); + $errors = Json::decode($output, Json::FORCE_ARRAY); + $this->assertSame(0, array_sum($errors['totals'])); + $this->assertCount(0, $errors['files']); + } - private function runPhpStan( - string $analysedPath, - ?string $configFile, - string $errorFormatter = 'json' - ): string - { - $originalDir = getcwd(); - if ($originalDir === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - chdir(__DIR__ . '/../../../..'); - exec(sprintf('%s %s clear-result-cache %s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : ''), $clearResultCacheOutputLines, $clearResultCacheExitCode); - if ($clearResultCacheExitCode !== 0) { - throw new \PHPStan\ShouldNotHappenException('Could not clear result cache.'); - } + public function testRunUnixFileWithWindowsBaseline(): void + { + $output = $this->runPhpStan(__DIR__ . '/data/UnixNewlines.php', __DIR__ . '/data/windowsBaseline.neon'); + $errors = Json::decode($output, Json::FORCE_ARRAY); + $this->assertSame(0, array_sum($errors['totals'])); + $this->assertCount(0, $errors['files']); + } - exec(sprintf('%s %s analyse --no-progress --error-format=%s --level=7 %s %s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $errorFormatter, $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : '', escapeshellarg($analysedPath)), $outputLines); - chdir($originalDir); + private function runPhpStan( + string $analysedPath, + ?string $configFile, + string $errorFormatter = 'json' + ): string { + $originalDir = getcwd(); + if ($originalDir === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + chdir(__DIR__ . '/../../../..'); + exec(sprintf('%s %s clear-result-cache %s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : ''), $clearResultCacheOutputLines, $clearResultCacheExitCode); + if ($clearResultCacheExitCode !== 0) { + throw new \PHPStan\ShouldNotHappenException('Could not clear result cache.'); + } - return implode("\n", $outputLines); - } + exec(sprintf('%s %s analyse --no-progress --error-format=%s --level=7 %s %s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $errorFormatter, $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : '', escapeshellarg($analysedPath)), $outputLines); + chdir($originalDir); + return implode("\n", $outputLines); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php index d8a7bbe732..01b31d8093 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php @@ -1,4 +1,6 @@ - '#^Foo$#', - 'count' => 1, - 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', - ], - ], - ]; - - yield [ - 'Multiple file errors', - 1, - 4, - 0, - [ - [ - 'message' => "#^Bar\nBar2$#", - 'count' => 1, - 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', - ], - [ - 'message' => '#^Foo$#', - 'count' => 1, - 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', - ], - [ - 'message' => "#^Bar\nBar2$#", - 'count' => 1, - 'path' => 'foo.php', - ], - [ - 'message' => '#^Foo$#', - 'count' => 1, - 'path' => 'foo.php', - ], - ], - ]; + yield [ + 'One file error', + 1, + 1, + 0, + [ + [ + 'message' => '#^Foo$#', + 'count' => 1, + 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', + ], + ], + ]; - yield [ - 'Multiple file, multiple generic errors', - 1, - 4, - 2, - [ - [ - 'message' => "#^Bar\nBar2$#", - 'count' => 1, - 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', - ], - [ - 'message' => '#^Foo$#', - 'count' => 1, - 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', - ], - [ - 'message' => "#^Bar\nBar2$#", - 'count' => 1, - 'path' => 'foo.php', - ], - [ - 'message' => '#^Foo$#', - 'count' => 1, - 'path' => 'foo.php', - ], - ], - ]; - } + yield [ + 'Multiple file errors', + 1, + 4, + 0, + [ + [ + 'message' => "#^Bar\nBar2$#", + 'count' => 1, + 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', + ], + [ + 'message' => '#^Foo$#', + 'count' => 1, + 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', + ], + [ + 'message' => "#^Bar\nBar2$#", + 'count' => 1, + 'path' => 'foo.php', + ], + [ + 'message' => '#^Foo$#', + 'count' => 1, + 'path' => 'foo.php', + ], + ], + ]; - /** - * @dataProvider dataFormatterOutputProvider - * - * @param string $message - * @param int $exitCode - * @param int $numFileErrors - * @param int $numGenericErrors - * @param mixed[] $expected - */ - public function testFormatErrors( - string $message, - int $exitCode, - int $numFileErrors, - int $numGenericErrors, - array $expected - ): void - { - $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); + yield [ + 'Multiple file, multiple generic errors', + 1, + 4, + 2, + [ + [ + 'message' => "#^Bar\nBar2$#", + 'count' => 1, + 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', + ], + [ + 'message' => '#^Foo$#', + 'count' => 1, + 'path' => 'folder with unicode 😃/file name with "spaces" and unicode 😃.php', + ], + [ + 'message' => "#^Bar\nBar2$#", + 'count' => 1, + 'path' => 'foo.php', + ], + [ + 'message' => '#^Foo$#', + 'count' => 1, + 'path' => 'foo.php', + ], + ], + ]; + } - $this->assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param mixed[] $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + array $expected + ): void { + $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - $this->assertSame(trim(Neon::encode(['parameters' => ['ignoreErrors' => $expected]], Neon::BLOCK)), trim($this->getOutputContent()), sprintf('%s: output do not match', $message)); - } + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + $this->assertSame(trim(Neon::encode(['parameters' => ['ignoreErrors' => $expected]], Neon::BLOCK)), trim($this->getOutputContent()), sprintf('%s: output do not match', $message)); + } - public function testFormatErrorMessagesRegexEscape(): void - { - $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - $result = new AnalysisResult( - [new Error('Escape Regex with file # ~ \' ()', 'Testfile')], - ['Escape Regex without file # ~ <> \' ()'], - [], - [], - false, - null, - true - ); - $formatter->formatErrors( - $result, - $this->getOutput() - ); + public function testFormatErrorMessagesRegexEscape(): void + { + $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - self::assertSame( - trim( - Neon::encode([ - 'parameters' => [ - 'ignoreErrors' => [ - [ - 'message' => "#^Escape Regex with file \\# ~ ' \\(\\)$#", - 'count' => 1, - 'path' => 'Testfile', - ], - ], - ], - ], Neon::BLOCK) - ), - trim($this->getOutputContent()) - ); - } + $result = new AnalysisResult( + [new Error('Escape Regex with file # ~ \' ()', 'Testfile')], + ['Escape Regex without file # ~ <> \' ()'], + [], + [], + false, + null, + true + ); + $formatter->formatErrors( + $result, + $this->getOutput() + ); - public function testEscapeDiNeon(): void - { - $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - $result = new AnalysisResult( - [new Error('Test %value%', 'Testfile')], - [], - [], - [], - false, - null, - true - ); + self::assertSame( + trim( + Neon::encode([ + 'parameters' => [ + 'ignoreErrors' => [ + [ + 'message' => "#^Escape Regex with file \\# ~ ' \\(\\)$#", + 'count' => 1, + 'path' => 'Testfile', + ], + ], + ], + ], Neon::BLOCK) + ), + trim($this->getOutputContent()) + ); + } - $formatter->formatErrors( - $result, - $this->getOutput() - ); - self::assertSame( - trim( - Neon::encode([ - 'parameters' => [ - 'ignoreErrors' => [ - [ - 'message' => '#^Test %%value%%$#', - 'count' => 1, - 'path' => 'Testfile', - ], - ], - ], - ], Neon::BLOCK) - ), - trim($this->getOutputContent()) - ); - } + public function testEscapeDiNeon(): void + { + $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); + $result = new AnalysisResult( + [new Error('Test %value%', 'Testfile')], + [], + [], + [], + false, + null, + true + ); - /** - * @return \Generator}, void, void> - */ - public function outputOrderingProvider(): \Generator - { - $errors = [ - new Error('Error #2', 'TestfileA', 1), - new Error('A different error #1', 'TestfileA', 3), - new Error('Second error in a different file', 'TestfileB', 4), - new Error('Error #1 in a different file', 'TestfileB', 5), - new Error('Second error in a different file', 'TestfileB', 6), - ]; - yield [$errors]; - mt_srand(0); - for ($i = 0; $i < 3; ++$i) { - shuffle($errors); - yield [$errors]; - } - } + $formatter->formatErrors( + $result, + $this->getOutput() + ); + self::assertSame( + trim( + Neon::encode([ + 'parameters' => [ + 'ignoreErrors' => [ + [ + 'message' => '#^Test %%value%%$#', + 'count' => 1, + 'path' => 'Testfile', + ], + ], + ], + ], Neon::BLOCK) + ), + trim($this->getOutputContent()) + ); + } - /** - * @dataProvider outputOrderingProvider - * @param list $errors - */ - public function testOutputOrdering(array $errors): void - { - $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - $result = new AnalysisResult( - $errors, - [], - [], - [], - false, - null, - true - ); + /** + * @return \Generator}, void, void> + */ + public function outputOrderingProvider(): \Generator + { + $errors = [ + new Error('Error #2', 'TestfileA', 1), + new Error('A different error #1', 'TestfileA', 3), + new Error('Second error in a different file', 'TestfileB', 4), + new Error('Error #1 in a different file', 'TestfileB', 5), + new Error('Second error in a different file', 'TestfileB', 6), + ]; + yield [$errors]; + mt_srand(0); + for ($i = 0; $i < 3; ++$i) { + shuffle($errors); + yield [$errors]; + } + } - $formatter->formatErrors( - $result, - $this->getOutput() - ); - self::assertSame( - trim(Neon::encode([ - 'parameters' => [ - 'ignoreErrors' => [ - [ - 'message' => '#^A different error \\#1$#', - 'count' => 1, - 'path' => 'TestfileA', - ], - [ - 'message' => '#^Error \\#2$#', - 'count' => 1, - 'path' => 'TestfileA', - ], - [ - 'message' => '#^Error \\#1 in a different file$#', - 'count' => 1, - 'path' => 'TestfileB', - ], - [ - 'message' => '#^Second error in a different file$#', - 'count' => 2, - 'path' => 'TestfileB', - ], - ], - ], - ], Neon::BLOCK)), - $f = trim($this->getOutputContent()) - ); - } + /** + * @dataProvider outputOrderingProvider + * @param list $errors + */ + public function testOutputOrdering(array $errors): void + { + $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); + $result = new AnalysisResult( + $errors, + [], + [], + [], + false, + null, + true + ); + $formatter->formatErrors( + $result, + $this->getOutput() + ); + self::assertSame( + trim(Neon::encode([ + 'parameters' => [ + 'ignoreErrors' => [ + [ + 'message' => '#^A different error \\#1$#', + 'count' => 1, + 'path' => 'TestfileA', + ], + [ + 'message' => '#^Error \\#2$#', + 'count' => 1, + 'path' => 'TestfileA', + ], + [ + 'message' => '#^Error \\#1 in a different file$#', + 'count' => 1, + 'path' => 'TestfileB', + ], + [ + 'message' => '#^Second error in a different file$#', + 'count' => 2, + 'path' => 'TestfileB', + ], + ], + ], + ], Neon::BLOCK)), + $f = trim($this->getOutputContent()) + ); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php index ab868c23a8..c206eb11de 100644 --- a/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php @@ -1,4 +1,6 @@ - + public function dataFormatterOutputProvider(): iterable + { + yield [ + 'No errors', + 0, + 0, + 0, + ' ', - ]; + ]; - yield [ - 'One file error', - 1, - 1, - 0, - ' + yield [ + 'One file error', + 1, + 1, + 0, + ' ', - ]; + ]; - yield [ - 'One generic error', - 1, - 0, - 1, - ' + yield [ + 'One generic error', + 1, + 0, + 1, + ' ', - ]; + ]; - yield [ - 'Multiple file errors', - 1, - 4, - 0, - ' + yield [ + 'Multiple file errors', + 1, + 4, + 0, + ' @@ -68,14 +69,14 @@ public function dataFormatterOutputProvider(): iterable ', - ]; + ]; - yield [ - 'Multiple generic errors', - 1, - 0, - 2, - ' + yield [ + 'Multiple generic errors', + 1, + 0, + 2, + ' @@ -83,14 +84,14 @@ public function dataFormatterOutputProvider(): iterable ', - ]; + ]; - yield [ - 'Multiple file, multiple generic errors', - 1, - 4, - 2, - ' + yield [ + 'Multiple file, multiple generic errors', + 1, + 4, + 2, + ' @@ -106,63 +107,61 @@ public function dataFormatterOutputProvider(): iterable ', - ]; - } + ]; + } - /** - * @dataProvider dataFormatterOutputProvider - * - * @param string $message - * @param int $exitCode - * @param int $numFileErrors - * @param int $numGenericErrors - * @param string $expected - */ - public function testFormatErrors( - string $message, - int $exitCode, - int $numFileErrors, - int $numGenericErrors, - string $expected - ): void - { - $formatter = new CheckstyleErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new CheckstyleErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - $this->assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); - $outputContent = $this->getOutputContent(); - $this->assertXmlStringEqualsXmlString($expected, $outputContent, sprintf('%s: XML do not match', $message)); - $this->assertStringStartsWith('getOutputContent(); + $this->assertXmlStringEqualsXmlString($expected, $outputContent, sprintf('%s: XML do not match', $message)); + $this->assertStringStartsWith('formatErrors(new AnalysisResult( - [$error], - [], - [], - [], - false, - null, - true - ), $this->getOutput()); - $this->assertXmlStringEqualsXmlString(' + public function testTraitPath(): void + { + $formatter = new CheckstyleErrorFormatter(new SimpleRelativePathHelper(__DIR__)); + $error = new Error( + 'Foo', + __DIR__ . '/FooTrait.php (in context of class Foo)', + 5, + true, + __DIR__ . '/Foo.php', + __DIR__ . '/FooTrait.php' + ); + $formatter->formatErrors(new AnalysisResult( + [$error], + [], + [], + [], + false, + null, + true + ), $this->getOutput()); + $this->assertXmlStringEqualsXmlString(' ', $this->getOutputContent()); - } - + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php index 1b52ff5515..c762873f4b 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); - - $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); - } - + ]; + } + + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); + $formatter = new GithubErrorFormatter( + $relativePathHelper, + new TableErrorFormatter($relativePathHelper, false) + ); + + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + + $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php index cadb05c53d..7464f8bf88 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + * + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new GitlabErrorFormatter(new SimpleRelativePathHelper('/data/folder')); - $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); - } + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php index 48d2b2e209..0453dfdb36 100644 --- a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), $message); - - $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent()); - } - - /** - * @dataProvider dataFormatterOutputProvider - * - * @param string $message - * @param int $exitCode - * @param int $numFileErrors - * @param int $numGenericErrors - * @param string $expected - * - */ - public function testFormatErrors( - string $message, - int $exitCode, - int $numFileErrors, - int $numGenericErrors, - string $expected - ): void - { - $formatter = new JsonErrorFormatter(false); - - $this->assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); - - $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent(), sprintf('%s: JSON do not match', $message)); - } - + ]; + } + + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testPrettyFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new JsonErrorFormatter(true); + + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), $message); + + $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent()); + } + + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + * + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new JsonErrorFormatter(false); + + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + + $this->assertJsonStringEqualsJsonString($expected, $this->getOutputContent(), sprintf('%s: JSON do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php index cbe281f7ad..43a546c317 100644 --- a/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php @@ -1,4 +1,6 @@ -formatter = new JunitErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); - } - - /** - * @return \Generator> - */ - public function dataFormatterOutputProvider(): Generator - { - yield 'No errors' => [ - 0, - 0, - 0, - ' + /** @var \PHPStan\Command\ErrorFormatter\JunitErrorFormatter */ + private $formatter; + + public function setUp(): void + { + parent::setUp(); + + $this->formatter = new JunitErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH)); + } + + /** + * @return \Generator> + */ + public function dataFormatterOutputProvider(): Generator + { + yield 'No errors' => [ + 0, + 0, + 0, + ' ', - ]; + ]; - yield 'One file error' => [ - 1, - 1, - 0, - ' + yield 'One file error' => [ + 1, + 1, + 0, + ' ', - ]; + ]; - yield 'One generic error' => [ - 1, - 0, - 1, - ' + yield 'One generic error' => [ + 1, + 0, + 1, + ' ', - ]; + ]; - yield 'Multiple file errors' => [ - 1, - 4, - 0, - ' + yield 'Multiple file errors' => [ + 1, + 4, + 0, + ' @@ -82,13 +83,13 @@ public function dataFormatterOutputProvider(): Generator ', - ]; + ]; - yield 'Multiple generic errors' => [ - 1, - 0, - 2, - ' + yield 'Multiple generic errors' => [ + 1, + 0, + 2, + ' @@ -98,13 +99,13 @@ public function dataFormatterOutputProvider(): Generator ', - ]; + ]; - yield 'Multiple file, multiple generic errors' => [ - 1, - 4, - 2, - ' + yield 'Multiple file, multiple generic errors' => [ + 1, + 4, + 2, + ' @@ -126,43 +127,41 @@ public function dataFormatterOutputProvider(): Generator ', - ]; - } - - /** - * Test generated use cases for JUnit output format. - * - * @dataProvider dataFormatterOutputProvider - */ - public function testFormatErrors( - int $exitCode, - int $numFileErrors, - int $numGeneralErrors, - string $expected - ): void - { - $this->assertSame( - $exitCode, - $this->formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGeneralErrors), - $this->getOutput() - ), - 'Response code do not match' - ); - - $xml = new DOMDocument(); - $xml->loadXML($this->getOutputContent()); - - $this->assertTrue( - $xml->schemaValidate(__DIR__ . '/junit-schema.xsd'), - 'Schema do not validate' - ); - - $this->assertXmlStringEqualsXmlString( - $expected, - $this->getOutputContent(), - 'XML do not match' - ); - } - + ]; + } + + /** + * Test generated use cases for JUnit output format. + * + * @dataProvider dataFormatterOutputProvider + */ + public function testFormatErrors( + int $exitCode, + int $numFileErrors, + int $numGeneralErrors, + string $expected + ): void { + $this->assertSame( + $exitCode, + $this->formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGeneralErrors), + $this->getOutput() + ), + 'Response code do not match' + ); + + $xml = new DOMDocument(); + $xml->loadXML($this->getOutputContent()); + + $this->assertTrue( + $xml->schemaValidate(__DIR__ . '/junit-schema.xsd'), + 'Schema do not validate' + ); + + $this->assertXmlStringEqualsXmlString( + $expected, + $this->getOutputContent(), + 'XML do not match' + ); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php index 0f35ad8fda..6920658fea 100644 --- a/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new RawErrorFormatter(); - $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); - } + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 42c0de2381..7dffd7a125 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); - - $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); - } - + ]; + } + + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $formatter = new TableErrorFormatter(new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'), false); + + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + + $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php index d90ec904f6..aef7e6757c 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php @@ -1,4 +1,6 @@ -assertSame($exitCode, $formatter->formatErrors( - $this->getAnalysisResult($numFileErrors, $numGenericErrors), - $this->getOutput() - ), sprintf('%s: response code do not match', $message)); + /** + * @dataProvider dataFormatterOutputProvider + * + * @param string $message + * @param int $exitCode + * @param int $numFileErrors + * @param int $numGenericErrors + * @param string $expected + */ + public function testFormatErrors( + string $message, + int $exitCode, + int $numFileErrors, + int $numGenericErrors, + string $expected + ): void { + $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); + $formatter = new TeamcityErrorFormatter( + $relativePathHelper + ); - $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); - } + $this->assertSame($exitCode, $formatter->formatErrors( + $this->getAnalysisResult($numFileErrors, $numGenericErrors), + $this->getOutput() + ), sprintf('%s: response code do not match', $message)); + $this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message)); + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/Bar.php b/tests/PHPStan/Command/ErrorFormatter/data/Bar.php index 330aff4cec..c6cba83bbd 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/Bar.php +++ b/tests/PHPStan/Command/ErrorFormatter/data/Bar.php @@ -4,15 +4,13 @@ class Bar { + use FooTrait; - use FooTrait; - - /** - * @return array> - */ - public function doFoo(): array - { - return [['foo']]; - } - + /** + * @return array> + */ + public function doFoo(): array + { + return [['foo']]; + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/Foo.php b/tests/PHPStan/Command/ErrorFormatter/data/Foo.php index 75a0dd0f42..33348d89d9 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/Foo.php +++ b/tests/PHPStan/Command/ErrorFormatter/data/Foo.php @@ -4,12 +4,10 @@ class Foo { + use FooTrait; - use FooTrait; - - public function doFoo(): int - { - return 'string'; - } - + public function doFoo(): int + { + return 'string'; + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/FooTrait.php b/tests/PHPStan/Command/ErrorFormatter/data/FooTrait.php index 887b2a3cc3..62f84b648d 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/FooTrait.php +++ b/tests/PHPStan/Command/ErrorFormatter/data/FooTrait.php @@ -4,10 +4,8 @@ trait FooTrait { - - public function doBar(): string - { - return 1; - } - + public function doBar(): string + { + return 1; + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/UnixNewlines.php b/tests/PHPStan/Command/ErrorFormatter/data/UnixNewlines.php index a5563d578a..0a98a6cdb1 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/UnixNewlines.php +++ b/tests/PHPStan/Command/ErrorFormatter/data/UnixNewlines.php @@ -2,15 +2,15 @@ namespace BaselineIntegration; -class UnixNewlines { - - /** - * The following phpdoc is invalid and should trigger a error message containing newlines. - * - * @param - * $object - */ - public function phpdocWithNewlines($object) { - } - +class UnixNewlines +{ + /** + * The following phpdoc is invalid and should trigger a error message containing newlines. + * + * @param + * $object + */ + public function phpdocWithNewlines($object) + { + } } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/WindowsNewlines.php b/tests/PHPStan/Command/ErrorFormatter/data/WindowsNewlines.php index adea9777bb..ce70a6677f 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/WindowsNewlines.php +++ b/tests/PHPStan/Command/ErrorFormatter/data/WindowsNewlines.php @@ -2,15 +2,15 @@ namespace BaselineIntegration; -class WindowsNewlines { - - /** - * The following phpdoc is invalid and should trigger a error message containing newlines. - * - * @param - * $object - */ - public function phpdocWithNewlines($object) { - } - +class WindowsNewlines +{ + /** + * The following phpdoc is invalid and should trigger a error message containing newlines. + * + * @param + * $object + */ + public function phpdocWithNewlines($object) + { + } } diff --git a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php index 0220251751..56f6f92213 100644 --- a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php +++ b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php @@ -1,4 +1,6 @@ - 'null, array', + 'string' => 'string', + 'int' => 'int given', + ], + true, + false, + ], + [ + '#Parameter \#2 $destination of method Nette\\\\Application\\\\UI\\\\Component::redirect\(\) expects string|null, array|Foo|Bar given#', + [ + 'null' => 'null, array', + ], + true, + false, + ], + [ + '#Parameter \#2 $destination of method Nette\\\\Application\\\\UI\\\\Component::redirect\(\) expects string\|null, array\|string\|int given#', + [], + true, + false, + ], + [ + '#Invalid array key type array|string\.#', + [ + 'string' => 'string\\.', + ], + false, + false, + ], + [ + '#Invalid array key type array\|string\.#', + [], + false, + false, + ], + [ + '#Array (array) does not accept key resource|iterable\.#', + [ + 'iterable' => 'iterable\.', + ], + false, + false, + ], + [ + '#Parameter \#1 $i of method Levels\\\\AcceptTypes\\\\Foo::doBarArray\(\) expects array, array given.#', + [ + 'int' => 'int> given.', + ], + true, + false, + ], + [ + '#Parameter \#1 \$i of method Levels\\\\AcceptTypes\\\\Foo::doBarArray\(\) expects array|callable, array given.#', + [ + 'callable' => 'callable, array 'int> given.', + ], + false, + false, + ], + [ + '#Unclosed parenthesis(\)#', + [], + false, + false, + ], + [ + '~Result of || is always true.~', + [], + false, + true, + ], + [ + '#Method PragmaRX\Notified\Data\Repositories\Notified::firstOrCreateByEvent() should return PragmaRX\Notified\Data\Models\Notified but returns Illuminate\Database\Eloquent\Model|null#', + [], + false, + true, + ], + ]; + } - public function dataValidate(): array - { - return [ - [ - '#^Call to function method_exists\\(\\) with ReflectionProperty and \'(?:hasType|getType)\' will always evaluate to true\\.$#iu', - [], - false, - false, - ], - [ - '#^Call to function method_exists\\(\\) with ReflectionProperty and \'(?:hasType|getType)\' will always evaluate to true\\.$#', - [], - false, - false, - ], - [ - '#Call to function method_exists\\(\\) with ReflectionProperty and \'(?:hasType|getType)\' will always evaluate to true\\.#', - [], - false, - false, - ], - [ - '#Parameter \#2 $destination of method Nette\\\\Application\\\\UI\\\\Component::redirect\(\) expects string|null, array|string|int given#', - [ - 'null' => 'null, array', - 'string' => 'string', - 'int' => 'int given', - ], - true, - false, - ], - [ - '#Parameter \#2 $destination of method Nette\\\\Application\\\\UI\\\\Component::redirect\(\) expects string|null, array|Foo|Bar given#', - [ - 'null' => 'null, array', - ], - true, - false, - ], - [ - '#Parameter \#2 $destination of method Nette\\\\Application\\\\UI\\\\Component::redirect\(\) expects string\|null, array\|string\|int given#', - [], - true, - false, - ], - [ - '#Invalid array key type array|string\.#', - [ - 'string' => 'string\\.', - ], - false, - false, - ], - [ - '#Invalid array key type array\|string\.#', - [], - false, - false, - ], - [ - '#Array (array) does not accept key resource|iterable\.#', - [ - 'iterable' => 'iterable\.', - ], - false, - false, - ], - [ - '#Parameter \#1 $i of method Levels\\\\AcceptTypes\\\\Foo::doBarArray\(\) expects array, array given.#', - [ - 'int' => 'int> given.', - ], - true, - false, - ], - [ - '#Parameter \#1 \$i of method Levels\\\\AcceptTypes\\\\Foo::doBarArray\(\) expects array|callable, array given.#', - [ - 'callable' => 'callable, array 'int> given.', - ], - false, - false, - ], - [ - '#Unclosed parenthesis(\)#', - [], - false, - false, - ], - [ - '~Result of || is always true.~', - [], - false, - true, - ], - [ - '#Method PragmaRX\Notified\Data\Repositories\Notified::firstOrCreateByEvent() should return PragmaRX\Notified\Data\Models\Notified but returns Illuminate\Database\Eloquent\Model|null#', - [], - false, - true, - ], - ]; - } - - /** - * @dataProvider dataValidate - * @param string $regex - * @param string[] $expectedTypes - * @param bool $expectedHasAnchors - * @param bool $expectAllErrorsIgnored - */ - public function testValidate( - string $regex, - array $expectedTypes, - bool $expectedHasAnchors, - bool $expectAllErrorsIgnored - ): void - { - $grammar = new \Hoa\File\Read('hoa://Library/Regex/Grammar.pp'); - $parser = \Hoa\Compiler\Llk\Llk::load($grammar); - $validator = new IgnoredRegexValidator($parser, self::getContainer()->getByType(TypeStringResolver::class)); - - $result = $validator->validate($regex); - $this->assertSame($expectedTypes, $result->getIgnoredTypes()); - $this->assertSame($expectedHasAnchors, $result->hasAnchorsInTheMiddle()); - $this->assertSame($expectAllErrorsIgnored, $result->areAllErrorsIgnored()); - } + /** + * @dataProvider dataValidate + * @param string $regex + * @param string[] $expectedTypes + * @param bool $expectedHasAnchors + * @param bool $expectAllErrorsIgnored + */ + public function testValidate( + string $regex, + array $expectedTypes, + bool $expectedHasAnchors, + bool $expectAllErrorsIgnored + ): void { + $grammar = new \Hoa\File\Read('hoa://Library/Regex/Grammar.pp'); + $parser = \Hoa\Compiler\Llk\Llk::load($grammar); + $validator = new IgnoredRegexValidator($parser, self::getContainer()->getByType(TypeStringResolver::class)); + $result = $validator->validate($regex); + $this->assertSame($expectedTypes, $result->getIgnoredTypes()); + $this->assertSame($expectedHasAnchors, $result->hasAnchorsInTheMiddle()); + $this->assertSame($expectAllErrorsIgnored, $result->areAllErrorsIgnored()); + } } diff --git a/tests/PHPStan/Command/data/autoload-file.php b/tests/PHPStan/Command/data/autoload-file.php index 1ec680a9f9..874180bfff 100644 --- a/tests/PHPStan/Command/data/autoload-file.php +++ b/tests/PHPStan/Command/data/autoload-file.php @@ -1,3 +1,5 @@ - [ - 'level' => '3', - ], - 'includes' => [ - 'testIncludesExpand.neon' - ] + 'parameters' => [ + 'level' => '3', + ], + 'includes' => [ + 'testIncludesExpand.neon' + ] ]; diff --git a/tests/PHPStan/Command/relative-paths/here.php b/tests/PHPStan/Command/relative-paths/here.php index 3c6b265174..174d7fd709 100644 --- a/tests/PHPStan/Command/relative-paths/here.php +++ b/tests/PHPStan/Command/relative-paths/here.php @@ -1 +1,3 @@ -followLinks(); + $autoloadFiles = []; + $vendorPath = realpath(__DIR__ . '/../../../vendor'); + if ($vendorPath === false) { + throw new \PHPStan\ShouldNotHappenException(); + } - public function testExpectedFiles(): void - { - $finder = new Finder(); - $finder->followLinks(); - $autoloadFiles = []; - $vendorPath = realpath(__DIR__ . '/../../../vendor'); - if ($vendorPath === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $fileHelper = new FileHelper(__DIR__); - - foreach ($finder->files()->name('composer.json')->in(__DIR__ . '/../../../vendor') as $fileInfo) { - $realpath = $fileInfo->getRealPath(); - if ($realpath === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - $json = Json::decode(FileReader::read($realpath), Json::FORCE_ARRAY); - if (!isset($json['autoload']['files'])) { - continue; - } + $fileHelper = new FileHelper(__DIR__); - foreach ($json['autoload']['files'] as $file) { - $autoloadFile = substr(dirname($realpath) . '/' . $file, strlen($vendorPath) + 1); - $autoloadFiles[] = $fileHelper->normalizePath($autoloadFile); - } - } + foreach ($finder->files()->name('composer.json')->in(__DIR__ . '/../../../vendor') as $fileInfo) { + $realpath = $fileInfo->getRealPath(); + if ($realpath === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + $json = Json::decode(FileReader::read($realpath), Json::FORCE_ARRAY); + if (!isset($json['autoload']['files'])) { + continue; + } - sort($autoloadFiles); + foreach ($json['autoload']['files'] as $file) { + $autoloadFile = substr(dirname($realpath) . '/' . $file, strlen($vendorPath) + 1); + $autoloadFiles[] = $fileHelper->normalizePath($autoloadFile); + } + } - $expectedFiles = [ - 'clue/block-react/src/functions_include.php', // added to phpstan-dist/bootstrap.php - 'hoa/consistency/Prelude.php', // Hoa isn't prefixed, no need to load this eagerly - 'hoa/protocol/Wrapper.php', // Hoa isn't prefixed, no need to load this eagerly - 'jetbrains/phpstorm-stubs/PhpStormStubsMap.php', // added to phpstan-dist/bootstrap.php - 'myclabs/deep-copy/src/DeepCopy/deep_copy.php', // dev dependency of PHPUnit - 'phpstan/php-8-stubs/Php8StubsMap.php', - 'react/promise-stream/src/functions_include.php', // added to phpstan-dist/bootstrap.php - 'react/promise-timer/src/functions_include.php', // added to phpstan-dist/bootstrap.php - 'react/promise/src/functions_include.php', // added to phpstan-dist/bootstrap.php - 'ringcentral/psr7/src/functions_include.php', // added to phpstan-dist/bootstrap.php - 'symfony/polyfill-ctype/bootstrap.php', // afaik polyfills aren't necessary - 'symfony/polyfill-mbstring/bootstrap.php', // afaik polyfills aren't necessary - 'symfony/polyfill-php73/bootstrap.php', // afaik polyfills aren't necessary - 'symfony/polyfill-php80/bootstrap.php', // afaik polyfills aren't necessary - ]; + sort($autoloadFiles); - $phpunitFunctions = 'phpunit/phpunit/src/Framework/Assert/Functions.php'; - if (PHP_VERSION_ID >= 70300) { - array_splice($expectedFiles, 6, 0, [ - $phpunitFunctions, - ]); - } + $expectedFiles = [ + 'clue/block-react/src/functions_include.php', // added to phpstan-dist/bootstrap.php + 'hoa/consistency/Prelude.php', // Hoa isn't prefixed, no need to load this eagerly + 'hoa/protocol/Wrapper.php', // Hoa isn't prefixed, no need to load this eagerly + 'jetbrains/phpstorm-stubs/PhpStormStubsMap.php', // added to phpstan-dist/bootstrap.php + 'myclabs/deep-copy/src/DeepCopy/deep_copy.php', // dev dependency of PHPUnit + 'phpstan/php-8-stubs/Php8StubsMap.php', + 'react/promise-stream/src/functions_include.php', // added to phpstan-dist/bootstrap.php + 'react/promise-timer/src/functions_include.php', // added to phpstan-dist/bootstrap.php + 'react/promise/src/functions_include.php', // added to phpstan-dist/bootstrap.php + 'ringcentral/psr7/src/functions_include.php', // added to phpstan-dist/bootstrap.php + 'symfony/polyfill-ctype/bootstrap.php', // afaik polyfills aren't necessary + 'symfony/polyfill-mbstring/bootstrap.php', // afaik polyfills aren't necessary + 'symfony/polyfill-php73/bootstrap.php', // afaik polyfills aren't necessary + 'symfony/polyfill-php80/bootstrap.php', // afaik polyfills aren't necessary + ]; - $expectedFiles = array_map(static function (string $path) use ($fileHelper): string { - return $fileHelper->normalizePath($path); - }, $expectedFiles); - sort($expectedFiles); + $phpunitFunctions = 'phpunit/phpunit/src/Framework/Assert/Functions.php'; + if (PHP_VERSION_ID >= 70300) { + array_splice($expectedFiles, 6, 0, [ + $phpunitFunctions, + ]); + } - $this->assertSame($expectedFiles, $autoloadFiles); - } + $expectedFiles = array_map(static function (string $path) use ($fileHelper): string { + return $fileHelper->normalizePath($path); + }, $expectedFiles); + sort($expectedFiles); + $this->assertSame($expectedFiles, $autoloadFiles); + } } diff --git a/tests/PHPStan/Dependency/DependencyDumperTest.php b/tests/PHPStan/Dependency/DependencyDumperTest.php index 7ed6cff27b..125f7a7a5c 100644 --- a/tests/PHPStan/Dependency/DependencyDumperTest.php +++ b/tests/PHPStan/Dependency/DependencyDumperTest.php @@ -1,4 +1,6 @@ -getByType(NodeScopeResolver::class); - - /** @var Parser $realParser */ - $realParser = $container->getByType(Parser::class); - - $mockParser = $this->createMock(Parser::class); - $mockParser->method('parseFile') - ->willReturnCallback(static function (string $file) use ($realParser): array { - if (file_exists($file)) { - return $realParser->parseFile($file); - } - - return []; - }); - - /** @var Broker $realBroker */ - $realBroker = $container->getByType(Broker::class); - - $fileHelper = new FileHelper(__DIR__); - - $mockBroker = $this->createMock(Broker::class); - $mockBroker->method('getClass') - ->willReturnCallback(function (string $class) use ($realBroker, $fileHelper): ClassReflection { - if (in_array($class, [ - GrandChild::class, - Child::class, - ParentClass::class, - ], true)) { - return $realBroker->getClass($class); - } - - $nameParts = explode('\\', $class); - $shortClass = array_pop($nameParts); - - $classReflection = $this->createMock(ClassReflection::class); - $classReflection->method('getInterfaces')->willReturn([]); - $classReflection->method('getTraits')->willReturn([]); - $classReflection->method('getParentClass')->willReturn(false); - $classReflection->method('getFilename')->willReturn( - $fileHelper->normalizePath(__DIR__ . '/data/' . $shortClass . '.php') - ); - - return $classReflection; - }); - - $expectedDependencyTree = $this->getExpectedDependencyTree($fileHelper); - - /** @var ScopeFactory $scopeFactory */ - $scopeFactory = $container->getByType(ScopeFactory::class); - - /** @var FileFinder $fileFinder */ - $fileFinder = $container->getService('fileFinderAnalyse'); - - $dumper = new DependencyDumper( - new DependencyResolver($fileHelper, $mockBroker, new ExportedNodeResolver(self::getContainer()->getByType(FileTypeMapper::class), new Standard())), - $nodeScopeResolver, - $mockParser, - $scopeFactory, - $fileFinder - ); - - $dependencies = $dumper->dumpDependencies( - array_merge( - [$fileHelper->normalizePath(__DIR__ . '/data/GrandChild.php')], - array_keys($expectedDependencyTree) - ), - static function (): void { - }, - static function (): void { - }, - null - ); - - $this->assertCount(count($expectedDependencyTree), $dependencies); - foreach ($expectedDependencyTree as $file => $files) { - $this->assertArrayHasKey($file, $dependencies); - $this->assertSame($files, $dependencies[$file]); - } - } - - /** - * @param FileHelper $fileHelper - * @return string[][] - */ - private function getExpectedDependencyTree(FileHelper $fileHelper): array - { - $tree = [ - 'Child.php' => [ - 'GrandChild.php', - ], - 'Parent.php' => [ - 'GrandChild.php', - 'Child.php', - ], - 'MethodNativeReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'MethodPhpDocReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'ParamNativeReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'ParamPhpDocReturnTypehint.php' => [ - 'GrandChild.php', - ], - ]; - - $expectedTree = []; - foreach ($tree as $file => $files) { - $expectedTree[$fileHelper->normalizePath(__DIR__ . '/data/' . $file)] = array_map(static function (string $file) use ($fileHelper): string { - return $fileHelper->normalizePath(__DIR__ . '/data/' . $file); - }, $files); - } - - return $expectedTree; - } - + public function testDumpDependencies(): void + { + $container = self::getContainer(); + + /** @var NodeScopeResolver $nodeScopeResolver */ + $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); + + /** @var Parser $realParser */ + $realParser = $container->getByType(Parser::class); + + $mockParser = $this->createMock(Parser::class); + $mockParser->method('parseFile') + ->willReturnCallback(static function (string $file) use ($realParser): array { + if (file_exists($file)) { + return $realParser->parseFile($file); + } + + return []; + }); + + /** @var Broker $realBroker */ + $realBroker = $container->getByType(Broker::class); + + $fileHelper = new FileHelper(__DIR__); + + $mockBroker = $this->createMock(Broker::class); + $mockBroker->method('getClass') + ->willReturnCallback(function (string $class) use ($realBroker, $fileHelper): ClassReflection { + if (in_array($class, [ + GrandChild::class, + Child::class, + ParentClass::class, + ], true)) { + return $realBroker->getClass($class); + } + + $nameParts = explode('\\', $class); + $shortClass = array_pop($nameParts); + + $classReflection = $this->createMock(ClassReflection::class); + $classReflection->method('getInterfaces')->willReturn([]); + $classReflection->method('getTraits')->willReturn([]); + $classReflection->method('getParentClass')->willReturn(false); + $classReflection->method('getFilename')->willReturn( + $fileHelper->normalizePath(__DIR__ . '/data/' . $shortClass . '.php') + ); + + return $classReflection; + }); + + $expectedDependencyTree = $this->getExpectedDependencyTree($fileHelper); + + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = $container->getByType(ScopeFactory::class); + + /** @var FileFinder $fileFinder */ + $fileFinder = $container->getService('fileFinderAnalyse'); + + $dumper = new DependencyDumper( + new DependencyResolver($fileHelper, $mockBroker, new ExportedNodeResolver(self::getContainer()->getByType(FileTypeMapper::class), new Standard())), + $nodeScopeResolver, + $mockParser, + $scopeFactory, + $fileFinder + ); + + $dependencies = $dumper->dumpDependencies( + array_merge( + [$fileHelper->normalizePath(__DIR__ . '/data/GrandChild.php')], + array_keys($expectedDependencyTree) + ), + static function (): void { + }, + static function (): void { + }, + null + ); + + $this->assertCount(count($expectedDependencyTree), $dependencies); + foreach ($expectedDependencyTree as $file => $files) { + $this->assertArrayHasKey($file, $dependencies); + $this->assertSame($files, $dependencies[$file]); + } + } + + /** + * @param FileHelper $fileHelper + * @return string[][] + */ + private function getExpectedDependencyTree(FileHelper $fileHelper): array + { + $tree = [ + 'Child.php' => [ + 'GrandChild.php', + ], + 'Parent.php' => [ + 'GrandChild.php', + 'Child.php', + ], + 'MethodNativeReturnTypehint.php' => [ + 'GrandChild.php', + ], + 'MethodPhpDocReturnTypehint.php' => [ + 'GrandChild.php', + ], + 'ParamNativeReturnTypehint.php' => [ + 'GrandChild.php', + ], + 'ParamPhpDocReturnTypehint.php' => [ + 'GrandChild.php', + ], + ]; + + $expectedTree = []; + foreach ($tree as $file => $files) { + $expectedTree[$fileHelper->normalizePath(__DIR__ . '/data/' . $file)] = array_map(static function (string $file) use ($fileHelper): string { + return $fileHelper->normalizePath(__DIR__ . '/data/' . $file); + }, $files); + } + + return $expectedTree; + } } diff --git a/tests/PHPStan/Dependency/data/Child.php b/tests/PHPStan/Dependency/data/Child.php index 8e9e8a3495..43f35361e2 100644 --- a/tests/PHPStan/Dependency/data/Child.php +++ b/tests/PHPStan/Dependency/data/Child.php @@ -4,5 +4,4 @@ class Child extends ParentClass { - } diff --git a/tests/PHPStan/Dependency/data/GrandChild.php b/tests/PHPStan/Dependency/data/GrandChild.php index 7406d92bbc..eb0a0be117 100644 --- a/tests/PHPStan/Dependency/data/GrandChild.php +++ b/tests/PHPStan/Dependency/data/GrandChild.php @@ -4,14 +4,12 @@ class GrandChild extends Child { - - /** - * @param ParamPhpDocReturnTypehint $param - * @return MethodPhpDocReturnTypehint - */ - public function doFoo(ParamNativeReturnTypehint $param): MethodNativeReturnTypehint - { - [, $a, $b] = [1, 2, 3]; - } - + /** + * @param ParamPhpDocReturnTypehint $param + * @return MethodPhpDocReturnTypehint + */ + public function doFoo(ParamNativeReturnTypehint $param): MethodNativeReturnTypehint + { + [, $a, $b] = [1, 2, 3]; + } } diff --git a/tests/PHPStan/Dependency/data/Parent.php b/tests/PHPStan/Dependency/data/Parent.php index 230c51f090..1a51a456ed 100644 --- a/tests/PHPStan/Dependency/data/Parent.php +++ b/tests/PHPStan/Dependency/data/Parent.php @@ -4,5 +4,4 @@ abstract class ParentClass { - } diff --git a/tests/PHPStan/File/FileExcluderTest.php b/tests/PHPStan/File/FileExcluderTest.php index 808b36395c..5061412fbc 100644 --- a/tests/PHPStan/File/FileExcluderTest.php +++ b/tests/PHPStan/File/FileExcluderTest.php @@ -1,213 +1,211 @@ -skipIfNotOnWindows(); - /** - * @dataProvider dataExcludeOnWindows - * @param string $filePath - * @param string[] $analyseExcludes - * @param bool $isExcluded - */ - public function testFilesAreExcludedFromAnalysingOnWindows( - string $filePath, - array $analyseExcludes, - bool $isExcluded - ): void - { - $this->skipIfNotOnWindows(); - - $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, []); - - $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); - } + $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, []); - public function dataExcludeOnWindows(): array - { - return [ - [ - __DIR__ . '/data/excluded-file.php', - [], - false, - ], - [ - __DIR__ . '/data/excluded-file.php', - [__DIR__], - true, - ], - [ - __DIR__ . '\Foo\data\excluded-file.php', - [__DIR__ . '/*\/data/*'], - true, - ], - [ - __DIR__ . '\data\func-call.php', - [], - false, - ], - [ - __DIR__ . '\data\parse-error.php', - [__DIR__ . '/*'], - true, - ], - [ - __DIR__ . '\data\parse-error.php', - [__DIR__ . '/data/?a?s?-error.?h?'], - true, - ], - [ - __DIR__ . '\data\parse-error.php', - [__DIR__ . '/data/[pP]arse-[eE]rror.ph[pP]'], - true, - ], - [ - __DIR__ . '\data\parse-error.php', - ['tests/PHPStan/File/data'], - true, - ], - [ - __DIR__ . '\data\parse-error.php', - [__DIR__ . '/aaa'], - false, - ], - [ - 'C:\Temp\data\parse-error.php', - ['C:/Temp/*'], - true, - ], - [ - 'C:\Data\data\parse-error.php', - ['C:/Temp/*'], - false, - ], - [ - 'c:\Temp\data\parse-error.php', - ['C:/Temp/*'], - true, - ], - [ - 'C:\Temp\data\parse-error.php', - ['C:/temp/*'], - true, - ], - [ - 'c:\Data\data\parse-error.php', - ['C:/Temp/*'], - false, - ], - [ - 'c:\etc\phpstan\dummy-1.php', - ['c:\etc\phpstan\\'], - true, - ], - [ - 'c:\etc\phpstan-test\dummy-2.php', - ['c:\etc\phpstan\\'], - false, - ], - [ - 'c:\etc\phpstan-test\dummy-2.php', - ['c:\etc\phpstan'], - true, - ], - ]; - } + $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); + } - /** - * @dataProvider dataExcludeOnUnix - * @param string $filePath - * @param string[] $analyseExcludes - * @param bool $isExcluded - */ - public function testFilesAreExcludedFromAnalysingOnUnix( - string $filePath, - array $analyseExcludes, - bool $isExcluded - ): void - { - $this->skipIfNotOnUnix(); + public function dataExcludeOnWindows(): array + { + return [ + [ + __DIR__ . '/data/excluded-file.php', + [], + false, + ], + [ + __DIR__ . '/data/excluded-file.php', + [__DIR__], + true, + ], + [ + __DIR__ . '\Foo\data\excluded-file.php', + [__DIR__ . '/*\/data/*'], + true, + ], + [ + __DIR__ . '\data\func-call.php', + [], + false, + ], + [ + __DIR__ . '\data\parse-error.php', + [__DIR__ . '/*'], + true, + ], + [ + __DIR__ . '\data\parse-error.php', + [__DIR__ . '/data/?a?s?-error.?h?'], + true, + ], + [ + __DIR__ . '\data\parse-error.php', + [__DIR__ . '/data/[pP]arse-[eE]rror.ph[pP]'], + true, + ], + [ + __DIR__ . '\data\parse-error.php', + ['tests/PHPStan/File/data'], + true, + ], + [ + __DIR__ . '\data\parse-error.php', + [__DIR__ . '/aaa'], + false, + ], + [ + 'C:\Temp\data\parse-error.php', + ['C:/Temp/*'], + true, + ], + [ + 'C:\Data\data\parse-error.php', + ['C:/Temp/*'], + false, + ], + [ + 'c:\Temp\data\parse-error.php', + ['C:/Temp/*'], + true, + ], + [ + 'C:\Temp\data\parse-error.php', + ['C:/temp/*'], + true, + ], + [ + 'c:\Data\data\parse-error.php', + ['C:/Temp/*'], + false, + ], + [ + 'c:\etc\phpstan\dummy-1.php', + ['c:\etc\phpstan\\'], + true, + ], + [ + 'c:\etc\phpstan-test\dummy-2.php', + ['c:\etc\phpstan\\'], + false, + ], + [ + 'c:\etc\phpstan-test\dummy-2.php', + ['c:\etc\phpstan'], + true, + ], + ]; + } - $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, []); + /** + * @dataProvider dataExcludeOnUnix + * @param string $filePath + * @param string[] $analyseExcludes + * @param bool $isExcluded + */ + public function testFilesAreExcludedFromAnalysingOnUnix( + string $filePath, + array $analyseExcludes, + bool $isExcluded + ): void { + $this->skipIfNotOnUnix(); - $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); - } + $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, []); - public function dataExcludeOnUnix(): array - { - return [ - [ - __DIR__ . '/data/excluded-file.php', - [], - false, - ], - [ - __DIR__ . '/data/excluded-file.php', - [__DIR__], - true, - ], - [ - __DIR__ . '/Foo/data/excluded-file.php', - [__DIR__ . '/*/data/*'], - true, - ], - [ - __DIR__ . '/data/func-call.php', - [], - false, - ], - [ - __DIR__ . '/data/parse-error.php', - [__DIR__ . '/*'], - true, - ], - [ - __DIR__ . '/data/parse-error.php', - [__DIR__ . '/data/?a?s?-error.?h?'], - true, - ], - [ - __DIR__ . '/data/parse-error.php', - [__DIR__ . '/data/[pP]arse-[eE]rror.ph[pP]'], - true, - ], - [ - __DIR__ . '/data/parse-error.php', - ['tests/PHPStan/File/data'], - true, - ], - [ - __DIR__ . '/data/parse-error.php', - [__DIR__ . '/aaa'], - false, - ], - [ - '/tmp/data/parse-error.php', - ['/tmp/*'], - true, - ], - [ - '/home/myname/data/parse-error.php', - ['/tmp/*'], - false, - ], - [ - '/etc/phpstan/dummy-1.php', - ['/etc/phpstan/'], - true, - ], - [ - '/etc/phpstan-test/dummy-2.php', - ['/etc/phpstan/'], - false, - ], - [ - '/etc/phpstan-test/dummy-2.php', - ['/etc/phpstan'], - true, - ], - ]; - } + $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); + } + public function dataExcludeOnUnix(): array + { + return [ + [ + __DIR__ . '/data/excluded-file.php', + [], + false, + ], + [ + __DIR__ . '/data/excluded-file.php', + [__DIR__], + true, + ], + [ + __DIR__ . '/Foo/data/excluded-file.php', + [__DIR__ . '/*/data/*'], + true, + ], + [ + __DIR__ . '/data/func-call.php', + [], + false, + ], + [ + __DIR__ . '/data/parse-error.php', + [__DIR__ . '/*'], + true, + ], + [ + __DIR__ . '/data/parse-error.php', + [__DIR__ . '/data/?a?s?-error.?h?'], + true, + ], + [ + __DIR__ . '/data/parse-error.php', + [__DIR__ . '/data/[pP]arse-[eE]rror.ph[pP]'], + true, + ], + [ + __DIR__ . '/data/parse-error.php', + ['tests/PHPStan/File/data'], + true, + ], + [ + __DIR__ . '/data/parse-error.php', + [__DIR__ . '/aaa'], + false, + ], + [ + '/tmp/data/parse-error.php', + ['/tmp/*'], + true, + ], + [ + '/home/myname/data/parse-error.php', + ['/tmp/*'], + false, + ], + [ + '/etc/phpstan/dummy-1.php', + ['/etc/phpstan/'], + true, + ], + [ + '/etc/phpstan-test/dummy-2.php', + ['/etc/phpstan/'], + false, + ], + [ + '/etc/phpstan-test/dummy-2.php', + ['/etc/phpstan'], + true, + ], + ]; + } } diff --git a/tests/PHPStan/File/FileHelperTest.php b/tests/PHPStan/File/FileHelperTest.php index 9e2d88ec8d..fce22011c0 100644 --- a/tests/PHPStan/File/FileHelperTest.php +++ b/tests/PHPStan/File/FileHelperTest.php @@ -1,121 +1,121 @@ -skipIfNotOnWindows(); - $fileHelper = new FileHelper('C:\abcd'); - $this->assertSame($absolutePath, $fileHelper->absolutizePath($path)); - } - - /** - * @return string[][] - */ - public function dataAbsolutizePathOnLinuxOrMac(): array - { - return [ - ['C:/Program Files', '/abcd/C:/Program Files'], - ['C:\Program Files', '/abcd/C:\Program Files'], - ['Program Files', '/abcd/Program Files'], - ['/home/users', '/home/users'], - ['users', '/abcd/users'], - ['../lib', '/abcd/../lib'], - ['./lib', '/abcd/./lib'], - ['phar:///home/users/', 'phar:///home/users/'], - ]; - } + /** + * @dataProvider dataAbsolutizePathOnWindows + * @param string $path + * @param string $absolutePath + */ + public function testAbsolutizePathOnWindows(string $path, string $absolutePath): void + { + $this->skipIfNotOnWindows(); + $fileHelper = new FileHelper('C:\abcd'); + $this->assertSame($absolutePath, $fileHelper->absolutizePath($path)); + } - /** - * @dataProvider dataAbsolutizePathOnLinuxOrMac - * @param string $path - * @param string $absolutePath - */ - public function testAbsolutizePathOnLinuxOrMac(string $path, string $absolutePath): void - { - $this->skipIfNotOnUnix(); - $fileHelper = new FileHelper('/abcd'); - $this->assertSame($absolutePath, $fileHelper->absolutizePath($path)); - } + /** + * @return string[][] + */ + public function dataAbsolutizePathOnLinuxOrMac(): array + { + return [ + ['C:/Program Files', '/abcd/C:/Program Files'], + ['C:\Program Files', '/abcd/C:\Program Files'], + ['Program Files', '/abcd/Program Files'], + ['/home/users', '/home/users'], + ['users', '/abcd/users'], + ['../lib', '/abcd/../lib'], + ['./lib', '/abcd/./lib'], + ['phar:///home/users/', 'phar:///home/users/'], + ]; + } - /** - * @return string[][] - */ - public function dataNormalizePathOnWindows(): array - { - return [ - ['C:/Program Files/PHP', 'C:\Program Files\PHP'], - ['C:/Program Files/./PHP', 'C:\Program Files\PHP'], - ['C:/Program Files/../PHP', 'C:\PHP'], - ['/home/users/phpstan', '\home\users\phpstan'], - ['/home/users/./phpstan', '\home\users\phpstan'], - ['/home/users/../../phpstan/', '\phpstan'], - ['./phpstan/', 'phpstan'], - ]; - } + /** + * @dataProvider dataAbsolutizePathOnLinuxOrMac + * @param string $path + * @param string $absolutePath + */ + public function testAbsolutizePathOnLinuxOrMac(string $path, string $absolutePath): void + { + $this->skipIfNotOnUnix(); + $fileHelper = new FileHelper('/abcd'); + $this->assertSame($absolutePath, $fileHelper->absolutizePath($path)); + } - /** - * @dataProvider dataNormalizePathOnWindows - * @param string $path - * @param string $normalizedPath - */ - public function testNormalizePathOnWindows(string $path, string $normalizedPath): void - { - $this->skipIfNotOnWindows(); - $this->assertSame($normalizedPath, self::getContainer()->getByType(FileHelper::class)->normalizePath($path)); - } + /** + * @return string[][] + */ + public function dataNormalizePathOnWindows(): array + { + return [ + ['C:/Program Files/PHP', 'C:\Program Files\PHP'], + ['C:/Program Files/./PHP', 'C:\Program Files\PHP'], + ['C:/Program Files/../PHP', 'C:\PHP'], + ['/home/users/phpstan', '\home\users\phpstan'], + ['/home/users/./phpstan', '\home\users\phpstan'], + ['/home/users/../../phpstan/', '\phpstan'], + ['./phpstan/', 'phpstan'], + ]; + } - /** - * @return string[][] - */ - public function dataNormalizePathOnLinuxOrMac(): array - { - return [ - ['C:\Program Files\PHP', 'C:/Program Files/PHP'], - ['C:\Program Files\.\PHP', 'C:/Program Files/PHP'], - ['C:\Program Files\..\PHP', 'C:/PHP'], - ['/home/users/phpstan', '/home/users/phpstan'], - ['/home/users/./phpstan', '/home/users/phpstan'], - ['/home/users/../../phpstan/', '/phpstan'], - ['./phpstan/', 'phpstan'], - ['phar:///usr/local/bin/phpstan.phar/tmp/cache/../..', 'phar:///usr/local/bin/phpstan.phar'], - ['phar:///usr/local/bin/phpstan.phar/tmp/cache/../../..', '/usr/local/bin'], - ]; - } + /** + * @dataProvider dataNormalizePathOnWindows + * @param string $path + * @param string $normalizedPath + */ + public function testNormalizePathOnWindows(string $path, string $normalizedPath): void + { + $this->skipIfNotOnWindows(); + $this->assertSame($normalizedPath, self::getContainer()->getByType(FileHelper::class)->normalizePath($path)); + } - /** - * @dataProvider dataNormalizePathOnLinuxOrMac - * @param string $path - * @param string $normalizedPath - */ - public function testNormalizePathOnLinuxOrMac(string $path, string $normalizedPath): void - { - $this->skipIfNotOnUnix(); - $this->assertSame($normalizedPath, self::getContainer()->getByType(FileHelper::class)->normalizePath($path)); - } + /** + * @return string[][] + */ + public function dataNormalizePathOnLinuxOrMac(): array + { + return [ + ['C:\Program Files\PHP', 'C:/Program Files/PHP'], + ['C:\Program Files\.\PHP', 'C:/Program Files/PHP'], + ['C:\Program Files\..\PHP', 'C:/PHP'], + ['/home/users/phpstan', '/home/users/phpstan'], + ['/home/users/./phpstan', '/home/users/phpstan'], + ['/home/users/../../phpstan/', '/phpstan'], + ['./phpstan/', 'phpstan'], + ['phar:///usr/local/bin/phpstan.phar/tmp/cache/../..', 'phar:///usr/local/bin/phpstan.phar'], + ['phar:///usr/local/bin/phpstan.phar/tmp/cache/../../..', '/usr/local/bin'], + ]; + } + /** + * @dataProvider dataNormalizePathOnLinuxOrMac + * @param string $path + * @param string $normalizedPath + */ + public function testNormalizePathOnLinuxOrMac(string $path, string $normalizedPath): void + { + $this->skipIfNotOnUnix(); + $this->assertSame($normalizedPath, self::getContainer()->getByType(FileHelper::class)->normalizePath($path)); + } } diff --git a/tests/PHPStan/File/ParentDirectoryRelativePathHelperTest.php b/tests/PHPStan/File/ParentDirectoryRelativePathHelperTest.php index bd0cb74822..ad67bc7d8a 100644 --- a/tests/PHPStan/File/ParentDirectoryRelativePathHelperTest.php +++ b/tests/PHPStan/File/ParentDirectoryRelativePathHelperTest.php @@ -1,4 +1,6 @@ -assertSame( - $expectedRelativePath, - $helper->getRelativePath($filename) - ); - } - + /** + * @dataProvider dataGetRelativePath + * @param string $parentDirectory + * @param string $filename + * @param string $expectedRelativePath + */ + public function testGetRelativePath( + string $parentDirectory, + string $filename, + string $expectedRelativePath + ): void { + $helper = new ParentDirectoryRelativePathHelper($parentDirectory); + $this->assertSame( + $expectedRelativePath, + $helper->getRelativePath($filename) + ); + } } diff --git a/tests/PHPStan/File/RelativePathHelperTest.php b/tests/PHPStan/File/RelativePathHelperTest.php index 7ede64384d..3cc2cefc99 100644 --- a/tests/PHPStan/File/RelativePathHelperTest.php +++ b/tests/PHPStan/File/RelativePathHelperTest.php @@ -1,237 +1,234 @@ -assertSame( - $expectedResult, - $helper->getRelativePath($filenameToRelativize) - ); - } - - /** - * @dataProvider dataGetRelativePath - * @param string $currentWorkingDirectory - * @param string[] $analysedPaths - * @param string $filenameToRelativize - * @param string $expectedResult - */ - public function testGetRelativePathOnWindows( - string $currentWorkingDirectory, - array $analysedPaths, - string $filenameToRelativize, - string $expectedResult - ): void - { - $sanitize = static function (string $path): string { - if (substr($path, 0, 1) === '/') { - return 'C:\\' . substr(str_replace('/', '\\', $path), 1); - } + /** + * @dataProvider dataGetRelativePath + * @param string $currentWorkingDirectory + * @param string[] $analysedPaths + * @param string $filenameToRelativize + * @param string $expectedResult + */ + public function testGetRelativePathOnUnix( + string $currentWorkingDirectory, + array $analysedPaths, + string $filenameToRelativize, + string $expectedResult + ): void { + $helper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), $currentWorkingDirectory, $analysedPaths, '/'); + $this->assertSame( + $expectedResult, + $helper->getRelativePath($filenameToRelativize) + ); + } - return str_replace('/', '\\', $path); - }; - $helper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), $sanitize($currentWorkingDirectory), array_map($sanitize, $analysedPaths), '\\'); - $this->assertSame( - $sanitize($expectedResult), - $helper->getRelativePath($sanitize($filenameToRelativize)) - ); - } + /** + * @dataProvider dataGetRelativePath + * @param string $currentWorkingDirectory + * @param string[] $analysedPaths + * @param string $filenameToRelativize + * @param string $expectedResult + */ + public function testGetRelativePathOnWindows( + string $currentWorkingDirectory, + array $analysedPaths, + string $filenameToRelativize, + string $expectedResult + ): void { + $sanitize = static function (string $path): string { + if (substr($path, 0, 1) === '/') { + return 'C:\\' . substr(str_replace('/', '\\', $path), 1); + } - public function dataGetRelativePathWindowsSpecific(): array - { - return [ - [ - 'C:\www', - [ - 'C:\www\project/app/src', - 'C:\www\project/app/tests', - ], - 'C:\www\project\app\src\system\Bootstrap.php', - 'project\app\src\system\Bootstrap.php', // should be src\system\Bootstrap.php - ], - [ - 'C:\www', - [ - 'C:\www\project\app/src', - 'C:\www\project\app/tests', - ], - 'C:\www\project\app\src\system\Bootstrap.php', - 'app\src\system\Bootstrap.php', // should be src\system\Bootstrap.php - ], - ]; - } + return str_replace('/', '\\', $path); + }; + $helper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), $sanitize($currentWorkingDirectory), array_map($sanitize, $analysedPaths), '\\'); + $this->assertSame( + $sanitize($expectedResult), + $helper->getRelativePath($sanitize($filenameToRelativize)) + ); + } - /** - * @dataProvider dataGetRelativePathWindowsSpecific - * @param string $currentWorkingDirectory - * @param string[] $analysedPaths - * @param string $filenameToRelativize - * @param string $expectedResult - */ - public function testGetRelativePathWindowsSpecific( - string $currentWorkingDirectory, - array $analysedPaths, - string $filenameToRelativize, - string $expectedResult - ): void - { - $helper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), $currentWorkingDirectory, $analysedPaths, '\\'); - $this->assertSame( - $expectedResult, - $helper->getRelativePath($filenameToRelativize) - ); - } + public function dataGetRelativePathWindowsSpecific(): array + { + return [ + [ + 'C:\www', + [ + 'C:\www\project/app/src', + 'C:\www\project/app/tests', + ], + 'C:\www\project\app\src\system\Bootstrap.php', + 'project\app\src\system\Bootstrap.php', // should be src\system\Bootstrap.php + ], + [ + 'C:\www', + [ + 'C:\www\project\app/src', + 'C:\www\project\app/tests', + ], + 'C:\www\project\app\src\system\Bootstrap.php', + 'app\src\system\Bootstrap.php', // should be src\system\Bootstrap.php + ], + ]; + } + /** + * @dataProvider dataGetRelativePathWindowsSpecific + * @param string $currentWorkingDirectory + * @param string[] $analysedPaths + * @param string $filenameToRelativize + * @param string $expectedResult + */ + public function testGetRelativePathWindowsSpecific( + string $currentWorkingDirectory, + array $analysedPaths, + string $filenameToRelativize, + string $expectedResult + ): void { + $helper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), $currentWorkingDirectory, $analysedPaths, '\\'); + $this->assertSame( + $expectedResult, + $helper->getRelativePath($filenameToRelativize) + ); + } } diff --git a/tests/PHPStan/Fixture/TestDecimal.php b/tests/PHPStan/Fixture/TestDecimal.php index fc4eb05ee3..da540a0fa5 100644 --- a/tests/PHPStan/Fixture/TestDecimal.php +++ b/tests/PHPStan/Fixture/TestDecimal.php @@ -1,8 +1,9 @@ - */ + public function dataCreate(): array + { + return [ + [ + new ObjectType('DateTime'), + new ObjectType('DateTime'), + ], + [ + new MixedType(), + new MixedType(), + ], + [ + null, + new MixedType(), + ], + [ + new StringType(), + new StringType(), + ], + [ + new IntegerType(), + new IntegerType(), + ], + [ + new ErrorType(), + new MixedType(), + ], + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'U', + null, + TemplateTypeVariance::createInvariant() + ), + new MixedType(), + ], + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new UnionType([ + new StringType(), + new IntegerType(), + ]), + ], + ]; + } - /** @return array */ - public function dataCreate(): array - { - return [ - [ - new ObjectType('DateTime'), - new ObjectType('DateTime'), - ], - [ - new MixedType(), - new MixedType(), - ], - [ - null, - new MixedType(), - ], - [ - new StringType(), - new StringType(), - ], - [ - new IntegerType(), - new IntegerType(), - ], - [ - new ErrorType(), - new MixedType(), - ], - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'U', - null, - TemplateTypeVariance::createInvariant() - ), - new MixedType(), - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new UnionType([ - new StringType(), - new IntegerType(), - ]), - ], - ]; - } - - /** - * @dataProvider dataCreate - */ - public function testCreate(?Type $bound, Type $expectedBound): void - { - $scope = TemplateTypeScope::createWithFunction('a'); - $templateType = TemplateTypeFactory::create( - $scope, - 'T', - $bound, - TemplateTypeVariance::createInvariant() - ); - - $this->assertTrue( - $expectedBound->equals($templateType->getBound()), - sprintf('%s -> equals(%s)', $expectedBound->describe(VerbosityLevel::precise()), $templateType->getBound()->describe(VerbosityLevel::precise())) - ); - } + /** + * @dataProvider dataCreate + */ + public function testCreate(?Type $bound, Type $expectedBound): void + { + $scope = TemplateTypeScope::createWithFunction('a'); + $templateType = TemplateTypeFactory::create( + $scope, + 'T', + $bound, + TemplateTypeVariance::createInvariant() + ); + $this->assertTrue( + $expectedBound->equals($templateType->getBound()), + sprintf('%s -> equals(%s)', $expectedBound->describe(VerbosityLevel::precise()), $templateType->getBound()->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Generics/data/bug2574.php b/tests/PHPStan/Generics/data/bug2574.php index 1c761095d6..c0f56559c4 100644 --- a/tests/PHPStan/Generics/data/bug2574.php +++ b/tests/PHPStan/Generics/data/bug2574.php @@ -2,11 +2,13 @@ namespace Generics\Bug2574; -abstract class Model { - /** @return static */ - public function newInstance() { - return new static(); - } +abstract class Model +{ + /** @return static */ + public function newInstance() + { + return new static(); + } } /** @@ -14,6 +16,7 @@ public function newInstance() { * @param T $m * @return T */ -function foo(Model $m) : Model { - return $m->newInstance(); +function foo(Model $m): Model +{ + return $m->newInstance(); } diff --git a/tests/PHPStan/Generics/data/bug2577.php b/tests/PHPStan/Generics/data/bug2577.php index 5c1e3be803..ee7c903730 100644 --- a/tests/PHPStan/Generics/data/bug2577.php +++ b/tests/PHPStan/Generics/data/bug2577.php @@ -2,9 +2,15 @@ namespace Generics\Bug2577; -class A {} -class A1 extends A {} -class A2 extends A {} +class A +{ +} +class A1 extends A +{ +} +class A2 extends A +{ +} /** * @template T of A @@ -12,16 +18,18 @@ class A2 extends A {} * @param \Closure():T $t1 * @param T $t2 */ -function echoOneOrOther(\Closure $t1, A $t2) : void { - echo get_class($t1()); - echo get_class($t2); +function echoOneOrOther(\Closure $t1, A $t2): void +{ + echo get_class($t1()); + echo get_class($t2); } -function test(): void { - echoOneOrOther( - function () : A1 { - return new A1; - }, - new A2 - ); +function test(): void +{ + echoOneOrOther( + function (): A1 { + return new A1(); + }, + new A2() + ); } diff --git a/tests/PHPStan/Generics/data/bug2620.php b/tests/PHPStan/Generics/data/bug2620.php index b2f0a6295d..88d128532b 100644 --- a/tests/PHPStan/Generics/data/bug2620.php +++ b/tests/PHPStan/Generics/data/bug2620.php @@ -2,32 +2,41 @@ namespace Generics\Bug2620; -class Foo { - public function someMethod() : void {} +class Foo +{ + public function someMethod(): void + { + } +} +class Bar +{ } -class Bar {} /** * @implements \IteratorAggregate */ -class SomeIterator implements \IteratorAggregate { +class SomeIterator implements \IteratorAggregate +{ /** * @return \Traversable */ - public function getIterator() { - yield new Bar; + public function getIterator() + { + yield new Bar(); } } /** * @param \IteratorAggregate $i */ -function takesIteratorAggregate(\IteratorAggregate $i): void { +function takesIteratorAggregate(\IteratorAggregate $i): void +{ foreach ($i as $foo) { $foo->someMethod(); } } -function test(): void { - takesIteratorAggregate(new SomeIterator()); +function test(): void +{ + takesIteratorAggregate(new SomeIterator()); } diff --git a/tests/PHPStan/Generics/data/bug2622.php b/tests/PHPStan/Generics/data/bug2622.php index f3aa345443..6ed52a44c7 100644 --- a/tests/PHPStan/Generics/data/bug2622.php +++ b/tests/PHPStan/Generics/data/bug2622.php @@ -6,15 +6,18 @@ * @template TValue * @template-implements \IteratorAggregate */ -class MyArray implements \IteratorAggregate { - /** @var array */ - private $values = []; +class MyArray implements \IteratorAggregate +{ + /** @var array */ + private $values = []; - public function __construct() { - $this->values = []; - } + public function __construct() + { + $this->values = []; + } - public function getIterator() { - return new \ArrayObject($this->values); - } + public function getIterator() + { + return new \ArrayObject($this->values); + } } diff --git a/tests/PHPStan/Generics/data/bug2627.php b/tests/PHPStan/Generics/data/bug2627.php index 66ac28e3ab..d81132bcec 100644 --- a/tests/PHPStan/Generics/data/bug2627.php +++ b/tests/PHPStan/Generics/data/bug2627.php @@ -5,29 +5,32 @@ /** * @template-covariant TValue */ -class Collection { - /** @var array $data */ - private $data; +class Collection +{ + /** @var array $data */ + private $data; - /** - * @param array $data - */ - public function __construct(array $data) { - $this->data = $data; - } + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->data = $data; + } - /** - * @return array - */ - public function getData() { - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data): void { - $this->data = $data; - } + /** + * @return array + */ + public function getData() + { + return $this->data; + } + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } } diff --git a/tests/PHPStan/Generics/data/classes.php b/tests/PHPStan/Generics/data/classes.php index 776a71a207..9006f0c198 100644 --- a/tests/PHPStan/Generics/data/classes.php +++ b/tests/PHPStan/Generics/data/classes.php @@ -13,46 +13,51 @@ * * @template T */ -class A { - /** @var T */ - private $a; - - /** @var T */ - public $b; - - /** @param T $a */ - public function __construct($a) { - $this->a = $a; - $this->b = $a; - } - - /** - * @return T - */ - public function get(int $i) { - if ($i === 0) { - return 1; - } - - return $this->a; - } - - /** - * @param T $a - */ - public function set($a): void { - $this->a = $a; - } - +class A +{ + /** @var T */ + private $a; + + /** @var T */ + public $b; + + /** @param T $a */ + public function __construct($a) + { + $this->a = $a; + $this->b = $a; + } + + /** + * @return T + */ + public function get(int $i) + { + if ($i === 0) { + return 1; + } + + return $this->a; + } + + /** + * @param T $a + */ + public function set($a): void + { + $this->a = $a; + } } /** * @extends A<\DateTime> */ -class AOfDateTime extends A { - public function __construct() { - parent::__construct(new \DateTime()); - } +class AOfDateTime extends A +{ + public function __construct() + { + parent::__construct(new \DateTime()); + } } /** @@ -62,34 +67,37 @@ public function __construct() { * * @extends A */ -class B extends A { - /** - * B::__construct() - * - * @param T $a - */ - public function __construct($a) { - parent::__construct($a); - } +class B extends A +{ + /** + * B::__construct() + * + * @param T $a + */ + public function __construct($a) + { + parent::__construct($a); + } } /** * @template T */ -interface I { - /** - * I::set() - * - * @param T $a - */ - function set($a): void; - - /** - * I::get() - * - * @return T - */ - function get(); +interface I +{ + /** + * I::set() + * + * @param T $a + */ + public function set($a): void; + + /** + * I::get() + * + * @return T + */ + public function get(); } /** @@ -97,17 +105,20 @@ function get(); * * @implements I */ -class C implements I { - /** @var int */ - private $a; - - public function set($a): void { - $this->a = $a; - } - - public function get() { - return $this->a; - } +class C implements I +{ + /** @var int */ + private $a; + + public function set($a): void + { + $this->a = $a; + } + + public function get() + { + return $this->a; + } } /** @@ -115,13 +126,14 @@ public function get() { * * @template T */ -interface SuperIfaceA { - /** - * SuperIfaceA::get() - * - * @return T - */ - public function getA(); +interface SuperIfaceA +{ + /** + * SuperIfaceA::get() + * + * @return T + */ + public function getA(); } /** @@ -129,13 +141,14 @@ public function getA(); * * @template T */ -interface SuperIfaceB { - /** - * SuperIfaceB::get() - * - * @return T - */ - public function getB(); +interface SuperIfaceB +{ + /** + * SuperIfaceB::get() + * + * @return T + */ + public function getB(); } /** @@ -146,7 +159,8 @@ public function getB(); * @extends SuperIfaceA * @extends SuperIfaceB */ -interface IfaceAB extends SuperIfaceA, SuperIfaceB { +interface IfaceAB extends SuperIfaceA, SuperIfaceB +{ } /** @@ -154,37 +168,44 @@ interface IfaceAB extends SuperIfaceA, SuperIfaceB { * * @implements IfaceAB<\DateTime> */ -class ABImpl implements IfaceAB { - public function getA() { - throw new \Exception(); - } - public function getB() { - throw new \Exception(); - } +class ABImpl implements IfaceAB +{ + public function getA() + { + throw new \Exception(); + } + public function getB() + { + throw new \Exception(); + } } /** * @param A<\DateTimeInterface> $a */ -function acceptAofDateTimeInterface($a): void { +function acceptAofDateTimeInterface($a): void +{ } /** * @param SuperIfaceA $a */ -function acceptSuperIFaceAOfInt($a): void { +function acceptSuperIFaceAOfInt($a): void +{ } /** * @param SuperIfaceB<\DateTime> $a */ -function acceptSuperIFaceBOfDateTime($a): void { +function acceptSuperIFaceBOfDateTime($a): void +{ } /** * @param SuperIfaceB $a */ -function acceptSuperIFaceBOfInt($a): void { +function acceptSuperIFaceBOfInt($a): void +{ } /** @@ -192,24 +213,22 @@ function acceptSuperIFaceBOfInt($a): void { */ interface GenericRule { - - /** - * @return class-string - */ - public function getNodeType(): string; - - /** - * @return TNodeType - */ - public function getNodeInstance(): Node; - - /** - * @param TNodeType $node - * @param \PHPStan\Analyser\Scope $scope - * @return RuleError[] errors - */ - public function processNode(\PhpParser\Node $node, Scope $scope): array; - + /** + * @return class-string + */ + public function getNodeType(): string; + + /** + * @return TNodeType + */ + public function getNodeInstance(): Node; + + /** + * @param TNodeType $node + * @param \PHPStan\Analyser\Scope $scope + * @return RuleError[] errors + */ + public function processNode(\PhpParser\Node $node, Scope $scope): array; } /** @@ -217,22 +236,20 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array; */ class SomeRule implements GenericRule { - - public function getNodeType(): string - { - return \PhpParser\Node\Expr\StaticCall::class; - } - - public function getNodeInstance(): Node - { - return new StaticCall(new Name(\stdClass::class), '__construct'); - } - - public function processNode(\PhpParser\Node $node, Scope $scope): array - { - return []; - } - + public function getNodeType(): string + { + return \PhpParser\Node\Expr\StaticCall::class; + } + + public function getNodeInstance(): Node + { + return new StaticCall(new Name(\stdClass::class), '__construct'); + } + + public function processNode(\PhpParser\Node $node, Scope $scope): array + { + return []; + } } /** @@ -242,43 +259,44 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array */ class GenericThis { - /** @param T $foo */ - public function __construct(\DateTimeInterface $foo) - { - $this->setFoo($foo); - } - - /** @param T $foo */ - public function setFoo(\DateTimeInterface $foo): void - { - } + /** @param T $foo */ + public function __construct(\DateTimeInterface $foo) + { + $this->setFoo($foo); + } + + /** @param T $foo */ + public function setFoo(\DateTimeInterface $foo): void + { + } } -function testClasses(): void { - $a = new A(1); - $a->set(2); - $a->set($a->get(0)); - $a->set(''); - - $a = new AOfDateTime(); - $a->set(new \DateTime()); - $a->set($a->get(0)); - $a->set(1); - - $b = new B(1); - $b->set(2); - $b->set($b->get(0)); - $b->set(''); - - $c = new C(); - $c->set(2); - $c->set($c->get()); - $c->set(''); - - $ab = new ABImpl(); - acceptSuperIFaceAOfInt($ab); - acceptSuperIFaceBOfDateTime($ab); - acceptSuperIFaceBOfInt($ab); - - new SomeRule(); +function testClasses(): void +{ + $a = new A(1); + $a->set(2); + $a->set($a->get(0)); + $a->set(''); + + $a = new AOfDateTime(); + $a->set(new \DateTime()); + $a->set($a->get(0)); + $a->set(1); + + $b = new B(1); + $b->set(2); + $b->set($b->get(0)); + $b->set(''); + + $c = new C(); + $c->set(2); + $c->set($c->get()); + $c->set(''); + + $ab = new ABImpl(); + acceptSuperIFaceAOfInt($ab); + acceptSuperIFaceBOfDateTime($ab); + acceptSuperIFaceBOfInt($ab); + + new SomeRule(); } diff --git a/tests/PHPStan/Generics/data/functions.php b/tests/PHPStan/Generics/data/functions.php index bd6ebd9297..ec13c76ee4 100644 --- a/tests/PHPStan/Generics/data/functions.php +++ b/tests/PHPStan/Generics/data/functions.php @@ -11,32 +11,34 @@ * * @return array */ -function f($a, $b) { - $result = []; - foreach ($a as $k => $v) { - $newV = $b($v); - $result[$k] = $newV; - } - return $result; +function f($a, $b) +{ + $result = []; + foreach ($a as $k => $v) { + $newV = $b($v); + $result[$k] = $newV; + } + return $result; } /** * @param array $arrayOfInt * @param null|(callable(int):string) $callableOrNull */ -function testF($arrayOfInt, $callableOrNull) { - f($arrayOfInt, function (int $a): string { - return (string) $a; - }); - f($arrayOfInt, function ($a): string { - return (string) $a; - }); - f($arrayOfInt, function ($a) { - return $a; - }); - f($arrayOfInt, $callableOrNull); - f($arrayOfInt, null); - f($arrayOfInt, ''); +function testF($arrayOfInt, $callableOrNull) +{ + f($arrayOfInt, function (int $a): string { + return (string) $a; + }); + f($arrayOfInt, function ($a): string { + return (string) $a; + }); + f($arrayOfInt, function ($a) { + return $a; + }); + f($arrayOfInt, $callableOrNull); + f($arrayOfInt, null); + f($arrayOfInt, ''); } /** @@ -46,8 +48,9 @@ function testF($arrayOfInt, $callableOrNull) { * * @param T $t */ -function passthru($t): void { - passthru2($t); +function passthru($t): void +{ + passthru2($t); } /** @@ -57,5 +60,6 @@ function passthru($t): void { * * @param T $t */ -function passthru2($t): void { +function passthru2($t): void +{ } diff --git a/tests/PHPStan/Generics/data/invalidReturn.php b/tests/PHPStan/Generics/data/invalidReturn.php index bd69b6741d..f7433e64ba 100644 --- a/tests/PHPStan/Generics/data/invalidReturn.php +++ b/tests/PHPStan/Generics/data/invalidReturn.php @@ -7,8 +7,9 @@ * @param T $a * @return T */ -function invalidReturnA($a) { - return 1; +function invalidReturnA($a) +{ + return 1; } /** @@ -16,8 +17,9 @@ function invalidReturnA($a) { * @param T $a * @return T */ -function invalidReturnB($a) { - return new \DateTime(); +function invalidReturnB($a) +{ + return new \DateTime(); } /** @@ -25,6 +27,7 @@ function invalidReturnB($a) { * @param T $a * @return T */ -function invalidReturnC($a) { - return new \DateTime(); +function invalidReturnC($a) +{ + return new \DateTime(); } diff --git a/tests/PHPStan/Generics/data/pick.php b/tests/PHPStan/Generics/data/pick.php index 346eba5bbd..89cd0cdf84 100644 --- a/tests/PHPStan/Generics/data/pick.php +++ b/tests/PHPStan/Generics/data/pick.php @@ -2,9 +2,15 @@ namespace PHPStan\Generics\Pick; -class A {} -class B {} -class C {} +class A +{ +} +class B +{ +} +class C +{ +} /** * @template T @@ -12,14 +18,18 @@ class C {} * @param T $t2 * @return T */ -function pick($t1, $t2) { - return rand(0, 1) ? $t1 : $t2; +function pick($t1, $t2) +{ + return rand(0, 1) ? $t1 : $t2; } /** @param A|B $c */ -function foo($c) : void {} +function foo($c): void +{ +} -function test() { - foo(pick(new A, new B)); - foo(pick(new A, new C)); +function test() +{ + foo(pick(new A(), new B())); + foo(pick(new A(), new C())); } diff --git a/tests/PHPStan/Generics/data/variance.php b/tests/PHPStan/Generics/data/variance.php index defa4910d3..f4e8ad4884 100644 --- a/tests/PHPStan/Generics/data/variance.php +++ b/tests/PHPStan/Generics/data/variance.php @@ -3,53 +3,62 @@ namespace PHPStan\Generics\Variance; /** @template T */ -interface InvariantIter { - /** @return T */ - public function next(); +interface InvariantIter +{ + /** @return T */ + public function next(); } /** @template-covariant T */ -interface Iter { - /** @return T */ - public function next(); +interface Iter +{ + /** @return T */ + public function next(); } /** @param InvariantIter<\DateTime> $it */ -function acceptInvariantIterOfDateTime($it): void { +function acceptInvariantIterOfDateTime($it): void +{ } /** @param InvariantIter<\DateTimeInterface> $it */ -function acceptInvariantIterOfDateTimeInterface($it): void { +function acceptInvariantIterOfDateTimeInterface($it): void +{ } /** @param Iter<\DateTime> $it */ -function acceptIterOfDateTime($it): void { +function acceptIterOfDateTime($it): void +{ } /** @param Iter<\DateTimeInterface> $it */ -function acceptIterOfDateTimeInterface($it): void { +function acceptIterOfDateTimeInterface($it): void +{ } /** @template T */ -interface Invariant { +interface Invariant +{ } /** @template-covariant T */ -interface Out { +interface Out +{ } /** @template-covariant T */ -interface Foo { - /** - * @param T $a - * @param Invariant $b - * @param Out $c - * @param array $d - * @param Out>> $e - * @return T - */ - function x($a, $b, $c, $d, $e); +interface Foo +{ + /** + * @param T $a + * @param Invariant $b + * @param Out $c + * @param array $d + * @param Out>> $e + * @return T + */ + public function x($a, $b, $c, $d, $e); } /** @@ -57,7 +66,8 @@ function x($a, $b, $c, $d, $e); * @extends Invariant * @extends Out */ -interface Bar extends Invariant, Out { +interface Bar extends Invariant, Out +{ } /** @@ -65,7 +75,8 @@ interface Bar extends Invariant, Out { * @extends Invariant * @extends Out */ -interface Baz extends Invariant, Out { +interface Baz extends Invariant, Out +{ } /** @@ -73,20 +84,23 @@ interface Baz extends Invariant, Out { * @implements Invariant * @implements Out */ -class Qux implements Invariant, Out { +class Qux implements Invariant, Out +{ } /** * @template T */ -class Quux { +class Quux +{ } /** * @template-covariant T * @extends Quux */ -class Quuz extends Quux { +class Quuz extends Quux +{ } /** @@ -98,71 +112,78 @@ class Quuz extends Quux { * @param Out>> $e * @return T */ -function x($a, $b, $c, $d, $e) { - return $a; +function x($a, $b, $c, $d, $e) +{ + return $a; } /** * @template-covariant T * @return Out */ -function returnOut() { - throw new \Exception(); +function returnOut() +{ + throw new \Exception(); } /** * @template-covariant T * @return Invariant */ -function returnInvariant() { - throw new \Exception(); +function returnInvariant() +{ + throw new \Exception(); } /** @template-covariant T of object */ -interface CovariantOfOBject { - /** @param T $v */ - function set($v): void; +interface CovariantOfOBject +{ + /** @param T $v */ + public function set($v): void; } /** * @template-covariant T * @template U */ -class ConstructorAndStatic { - - /** @var mixed */ - private $data; - - /** - * @param T $t - * @param U $u - * @param Invariant $v - * @param Out $w - */ - public function __construct($t, $u, $v, $w) { - $this->data = [$t, $u, $v, $w]; - } - - /** - * @param T $t - * @param U $u - * @param Invariant $v - * @param Out $w - * @return Static - */ - public static function create($t, $u, $v, $w) { - return new self($t, $u, $v, $w); - } +class ConstructorAndStatic +{ + /** @var mixed */ + private $data; + + /** + * @param T $t + * @param U $u + * @param Invariant $v + * @param Out $w + */ + public function __construct($t, $u, $v, $w) + { + $this->data = [$t, $u, $v, $w]; + } + + /** + * @param T $t + * @param U $u + * @param Invariant $v + * @param Out $w + * @return Static + */ + public static function create($t, $u, $v, $w) + { + return new self($t, $u, $v, $w); + } } /** * @param Iter<\DateTime> $itOfDateTime * @param InvariantIter<\DateTime> $invariantItOfDateTime */ -function test($itOfDateTime, $invariantItOfDateTime): void { - acceptInvariantIterOfDateTime($invariantItOfDateTime); - acceptInvariantIterOfDateTimeInterface($invariantItOfDateTime); +function test($itOfDateTime, $invariantItOfDateTime): void +{ + acceptInvariantIterOfDateTime($invariantItOfDateTime); + acceptInvariantIterOfDateTimeInterface($invariantItOfDateTime); - acceptIterOfDateTime($itOfDateTime); - acceptIterOfDateTimeInterface($itOfDateTime); + acceptIterOfDateTime($itOfDateTime); + acceptIterOfDateTimeInterface($itOfDateTime); } diff --git a/tests/PHPStan/Generics/data/varyingAcceptor.php b/tests/PHPStan/Generics/data/varyingAcceptor.php index 054bc02079..2846c03ee9 100644 --- a/tests/PHPStan/Generics/data/varyingAcceptor.php +++ b/tests/PHPStan/Generics/data/varyingAcceptor.php @@ -2,8 +2,12 @@ namespace PHPStan\Generics\VaryingAcceptor; -class A {} -class B extends A {} +class A +{ +} +class B extends A +{ +} /** * @template T @@ -11,9 +15,9 @@ class B extends A {} * @param callable(T):void $t1 * @param T $t2 */ -function apply(callable $t1, $t2) : void +function apply(callable $t1, $t2): void { - $t1($t2); + $t1($t2); } /** @@ -22,24 +26,32 @@ function apply(callable $t1, $t2) : void * @param T $t2 * @param callable(T):void $t1 */ -function applyReversed($t2, callable $t1) : void +function applyReversed($t2, callable $t1): void { - $t1($t2); + $t1($t2); } function testApply() { - apply(function(A $_i) : void {}, new A()); - apply(function(B $_i) : void {}, new B()); + apply(function (A $_i): void { + }, new A()); + apply(function (B $_i): void { + }, new B()); - apply(function(A $_i) : void {}, new B()); - apply(function(B $_i) : void {}, new A()); + apply(function (A $_i): void { + }, new B()); + apply(function (B $_i): void { + }, new A()); - applyReversed(new A(), function(A $_i) : void {}); - applyReversed(new B(), function(B $_i) : void {}); + applyReversed(new A(), function (A $_i): void { + }); + applyReversed(new B(), function (B $_i): void { + }); - applyReversed(new B(), function(A $_i) : void {}); - applyReversed(new A(), function(B $_i) : void {}); + applyReversed(new B(), function (A $_i): void { + }); + applyReversed(new A(), function (B $_i): void { + }); } /** @@ -48,9 +60,13 @@ function testApply() * @param callable(callable():T):T $closure * @return T */ -function bar(callable $closure) { throw new \Exception(); } +function bar(callable $closure) +{ + throw new \Exception(); +} /** @param callable(callable():int):string $callable */ -function testBar($callable): void { - bar($callable); +function testBar($callable): void +{ + bar($callable); } diff --git a/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php b/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php index c3dacc310b..5d071d2905 100644 --- a/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php +++ b/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php @@ -1,4 +1,6 @@ -doBar($i); - $this->doBar($j); - $this->doBar($k); - $this->doBar($l); - $this->doBar($m); - } - - public function doBar(int $i) - { - - } - - /** - * @param float|string $a - * @param float|string|null $b - * @param int $c - * @param int|null $d - */ - public function doBaz( - $a, - $b, - int $c, - ?int $d - ) - { - $this->doLorem($a); - $this->doLorem($b); - $this->doLorem($c); - $this->doLorem($d); - $this->doIpsum($a); - $this->doIpsum($b); - $this->doBar(null); - } - - /** - * @param int|resource $a - */ - public function doLorem($a) - { - - } - - /** - * @param float $a - */ - public function doIpsum($a) - { - - } - - /** - * @param int[] $i - * @param float[] $j - * @param (float|string)[] $k - * @param (int|null)[] $l - * @param (int|float)[] $m - */ - public function doFooArray( - array $i, - array $j, - array $k, - array $l, - array $m - ) - { - $this->doBarArray($i); - $this->doBarArray($j); - $this->doBarArray($k); - $this->doBarArray($l); - $this->doBarArray($m); - } - - /** - * @param int[] $i - */ - public function doBarArray(array $i) - { - - } - - public function doBazArray() - { - $ints = [1, 2, 3]; - $floats = [1.1, 2.2, 3.3]; - $floatsAndStrings = [1.1, 2.2]; - $intsAndNulls = [1, 2, 3]; - $intsAndFloats = [1, 2, 3]; - if (rand(0, 1) === 1) { - $floatsAndStrings[] = 'str'; - $intsAndNulls[] = null; - $intsAndFloats[] = 1.1; - } - - $this->doBarArray($ints); - $this->doBarArray($floats); - $this->doBarArray($floatsAndStrings); - $this->doBarArray($intsAndNulls); - $this->doBarArray($intsAndFloats); - } - - /** - * @param int|null $intOrNull - * @param int|float $intOrFloat - */ - public function doBazArrayUnionItemTypes(?int $intOrNull, $intOrFloat) - { - $intsAndNulls = [1, 2, 3, $intOrNull]; - $intsAndFloats = [1, 2, 3, $intOrFloat]; - $this->doBarArray($intsAndNulls); - $this->doBarArray($intsAndFloats); - } - - /** - * @param array $array - */ - public function callableArray( - array $array - ) - { - $this->expectCallable($array); - $this->expectCallable('date'); - $this->expectCallable('nonexistentFunction'); - $this->expectCallable([$this, 'doFoo']); - } - - public function expectCallable(callable $callable) - { - - } - - /** - * @template T of FooCountableInterface - * @param iterable $iterable - * @param mixed[] $array - * @param T $generic - * @param string $string - */ - public function iterableCountable( - iterable $iterable, - array $array, - $generic, - string $string - ) - { - echo count($iterable); - echo count($array); - echo count($generic); - echo count($string); - } - - /** - * @param string[] $strings - */ - public function benevolentUnionNotReported(array $strings) - { - foreach ($strings as $key => $val) { - $this->doBar($key); - } - } - + /** + * @param int $i + * @param float $j + * @param float|string $k + * @param int|null $l + * @param int|float $m + */ + public function doFoo( + int $i, + float $j, + $k, + ?int $l, + $m + ) { + $this->doBar($i); + $this->doBar($j); + $this->doBar($k); + $this->doBar($l); + $this->doBar($m); + } + + public function doBar(int $i) + { + } + + /** + * @param float|string $a + * @param float|string|null $b + * @param int $c + * @param int|null $d + */ + public function doBaz( + $a, + $b, + int $c, + ?int $d + ) { + $this->doLorem($a); + $this->doLorem($b); + $this->doLorem($c); + $this->doLorem($d); + $this->doIpsum($a); + $this->doIpsum($b); + $this->doBar(null); + } + + /** + * @param int|resource $a + */ + public function doLorem($a) + { + } + + /** + * @param float $a + */ + public function doIpsum($a) + { + } + + /** + * @param int[] $i + * @param float[] $j + * @param (float|string)[] $k + * @param (int|null)[] $l + * @param (int|float)[] $m + */ + public function doFooArray( + array $i, + array $j, + array $k, + array $l, + array $m + ) { + $this->doBarArray($i); + $this->doBarArray($j); + $this->doBarArray($k); + $this->doBarArray($l); + $this->doBarArray($m); + } + + /** + * @param int[] $i + */ + public function doBarArray(array $i) + { + } + + public function doBazArray() + { + $ints = [1, 2, 3]; + $floats = [1.1, 2.2, 3.3]; + $floatsAndStrings = [1.1, 2.2]; + $intsAndNulls = [1, 2, 3]; + $intsAndFloats = [1, 2, 3]; + if (rand(0, 1) === 1) { + $floatsAndStrings[] = 'str'; + $intsAndNulls[] = null; + $intsAndFloats[] = 1.1; + } + + $this->doBarArray($ints); + $this->doBarArray($floats); + $this->doBarArray($floatsAndStrings); + $this->doBarArray($intsAndNulls); + $this->doBarArray($intsAndFloats); + } + + /** + * @param int|null $intOrNull + * @param int|float $intOrFloat + */ + public function doBazArrayUnionItemTypes(?int $intOrNull, $intOrFloat) + { + $intsAndNulls = [1, 2, 3, $intOrNull]; + $intsAndFloats = [1, 2, 3, $intOrFloat]; + $this->doBarArray($intsAndNulls); + $this->doBarArray($intsAndFloats); + } + + /** + * @param array $array + */ + public function callableArray( + array $array + ) { + $this->expectCallable($array); + $this->expectCallable('date'); + $this->expectCallable('nonexistentFunction'); + $this->expectCallable([$this, 'doFoo']); + } + + public function expectCallable(callable $callable) + { + } + + /** + * @template T of FooCountableInterface + * @param iterable $iterable + * @param mixed[] $array + * @param T $generic + * @param string $string + */ + public function iterableCountable( + iterable $iterable, + array $array, + $generic, + string $string + ) { + echo count($iterable); + echo count($array); + echo count($generic); + echo count($string); + } + + /** + * @param string[] $strings + */ + public function benevolentUnionNotReported(array $strings) + { + foreach ($strings as $key => $val) { + $this->doBar($key); + } + } } interface ParentFooInterface { - } interface FooInterface extends ParentFooInterface { - } interface FooCountableInterface extends \Countable { - } class FooImpl implements FooInterface { - } class ClosureAccepts { - - public function doFoo(ParentFooInterface $parent) - { - $c = function (FooInterface $x, $y): FooInterface { - return new FooImpl(); - }; - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x): FooInterface { // less parameters - OK - return new FooImpl(); - }; - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x, $y, $z): FooInterface { // more parameters - error - return new FooImpl(); - }; - - $this->doBar($c); - $this->doBaz($c); - - $c = function (ParentFooInterface $x): FooInterface { // parameter contravariance - OK - return new FooImpl(); - }; - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooImpl $x): FooInterface { // parameter covariance - error - return new FooImpl(); - }; - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x): FooImpl { // return type covariance - OK - return new FooImpl(); - }; - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x) use ($parent): ParentFooInterface { // return type contravariance - error - return $parent; - }; - $this->doBar($c); - $this->doBaz($c); - } - - public function doFooUnionClosures(FooInterface $foo, ParentFooInterface $parent) - { - $closure = function () use ($foo): FooInterface { - return $foo; - }; - $c = function (FooInterface $x, $y): FooInterface { - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x): FooInterface { // less parameters - OK - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x, $y, $z): FooInterface { // more parameters - error - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (ParentFooInterface $x): FooInterface { // parameter contravariance - OK - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooImpl $x): FooInterface { // parameter covariance - error - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x): FooImpl { // return type covariance - OK - return new FooImpl(); - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function (FooInterface $x) use ($parent): ParentFooInterface { // return type contravariance - error - return $parent; - }; - if (rand(0, 1) === 0) { - $c = $closure; - } - $this->doBar($c); - $this->doBaz($c); - - $c = function ($mixed) { - return $mixed; - }; - $this->doBar($c); - $this->doBaz($c); - } - - /** - * @param \Closure(FooInterface $x, int $y): FooInterface $closure - */ - public function doBar( - \Closure $closure - ) - { - - } - - /** - * @param callable(FooInterface $x, int $y): FooInterface $callable - */ - public function doBaz( - callable $callable - ) - { - - } - + public function doFoo(ParentFooInterface $parent) + { + $c = function (FooInterface $x, $y): FooInterface { + return new FooImpl(); + }; + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x): FooInterface { // less parameters - OK + return new FooImpl(); + }; + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x, $y, $z): FooInterface { // more parameters - error + return new FooImpl(); + }; + + $this->doBar($c); + $this->doBaz($c); + + $c = function (ParentFooInterface $x): FooInterface { // parameter contravariance - OK + return new FooImpl(); + }; + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooImpl $x): FooInterface { // parameter covariance - error + return new FooImpl(); + }; + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x): FooImpl { // return type covariance - OK + return new FooImpl(); + }; + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x) use ($parent): ParentFooInterface { // return type contravariance - error + return $parent; + }; + $this->doBar($c); + $this->doBaz($c); + } + + public function doFooUnionClosures(FooInterface $foo, ParentFooInterface $parent) + { + $closure = function () use ($foo): FooInterface { + return $foo; + }; + $c = function (FooInterface $x, $y): FooInterface { + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x): FooInterface { // less parameters - OK + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x, $y, $z): FooInterface { // more parameters - error + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (ParentFooInterface $x): FooInterface { // parameter contravariance - OK + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooImpl $x): FooInterface { // parameter covariance - error + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x): FooImpl { // return type covariance - OK + return new FooImpl(); + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function (FooInterface $x) use ($parent): ParentFooInterface { // return type contravariance - error + return $parent; + }; + if (rand(0, 1) === 0) { + $c = $closure; + } + $this->doBar($c); + $this->doBaz($c); + + $c = function ($mixed) { + return $mixed; + }; + $this->doBar($c); + $this->doBaz($c); + } + + /** + * @param \Closure(FooInterface $x, int $y): FooInterface $closure + */ + public function doBar( + \Closure $closure + ) { + } + + /** + * @param callable(FooInterface $x, int $y): FooInterface $callable + */ + public function doBaz( + callable $callable + ) { + } } class Baz { - - public function makeInt(): int - { - return 1; - } - - public function makeFloat(): float - { - return 1.0; - } - - /** - * @return float|string - */ - public function makeFloatOrString() - { - if (rand(0, 1) === 0) { - return 1; - } else { - return 'foo'; - } - } - - /** - * @return float|string|null - */ - public function makeFloatOrStringOrNull() - { - if (rand(0, 1) === 0) { - return 1; - } else { - return 'foo'; - } - } - - public function makeIntOrNull(): ?int - { - if (rand(0, 1) === 0) { - return 1; - } else { - return null; - } - } - - /** - * @return int|float - */ - public function makeIntOrFloat() - { - if (rand(0, 1) === 0) { - return 1; - } else { - return 1.0; - } - } - - public function doFoo() - { - $this->doBar($this->makeInt()); - $this->doBar($this->makeFloat()); - $this->doBar($this->makeFloatOrString()); - $this->doBar($this->makeIntOrNull()); - $this->doBar($this->makeIntOrFloat()); - } - - public function doBar(int $i) - { - - } - - public function doBaz() - { - $this->doLorem($this->makeFloatOrString()); - $this->doLorem($this->makeFloatOrStringOrNull()); - $this->doLorem($this->makeInt()); - $this->doLorem($this->makeIntOrNull()); - $this->doIpsum($this->makeFloatOrString()); - $this->doIpsum($this->makeFloatOrStringOrNull()); - $this->doBar(null); - } - - /** - * @param int|resource $a - */ - public function doLorem($a) - { - - } - - /** - * @param float $a - */ - public function doIpsum($a) - { - - } - - /** - * @return int[] - */ - public function makeIntArray(): array - { - return [1, 2, 3]; - } - - /** - * @return float[] - */ - public function makeFloatArray(): array - { - return [1.0, 2.0, 3.0]; - } - - /** - * @return (float|string)[] - */ - public function makeFloatOrStringArray(): array - { - return [1.0, 2.0, '3.0']; - } - - /** - * @return (int|null)[] - */ - public function makeIntOrNullArray(): array - { - return [1, 2, null]; - } - - /** - * @return (int|float)[] - */ - public function makeIntOrFloatArray(): array - { - return [1.0, 2.0, 3]; - } - - public function doFooArray() - { - $this->doBarArray($this->makeIntArray()); - $this->doBarArray($this->makeFloatArray()); - $this->doBarArray($this->makeFloatOrStringArray()); - $this->doBarArray($this->makeIntOrNullArray()); - $this->doBarArray($this->makeIntOrFloatArray()); - } - - /** - * @param int[] $i - */ - public function doBarArray(array $i) - { - - } - - public function makeFoo(): Foo - { - return new Foo(); - } - - /** - * @return Foo|mixed[] - */ - public function makeFooOrArray() - { - if (rand(0, 1) === 0) { - return new Foo(); - } - - return []; - } - - public function testUnions() - { - $a = []; - if (rand(0, 1) === 0) { - $a = $this->makeFoo(); - } - - $this->requireArray($a); - $this->requireFoo($a); - } - - public function testUnions2() - { - $a = []; - if (rand(0, 1) === 0) { - $a = $this->makeFooOrArray(); - } - - $this->requireArray($a); - $this->requireFoo($a); - } - - /** - * @param mixed[] $array - */ - private function requireArray(array $array) - { - - } - - private function requireFoo(Foo $foo) - { - - } - + public function makeInt(): int + { + return 1; + } + + public function makeFloat(): float + { + return 1.0; + } + + /** + * @return float|string + */ + public function makeFloatOrString() + { + if (rand(0, 1) === 0) { + return 1; + } else { + return 'foo'; + } + } + + /** + * @return float|string|null + */ + public function makeFloatOrStringOrNull() + { + if (rand(0, 1) === 0) { + return 1; + } else { + return 'foo'; + } + } + + public function makeIntOrNull(): ?int + { + if (rand(0, 1) === 0) { + return 1; + } else { + return null; + } + } + + /** + * @return int|float + */ + public function makeIntOrFloat() + { + if (rand(0, 1) === 0) { + return 1; + } else { + return 1.0; + } + } + + public function doFoo() + { + $this->doBar($this->makeInt()); + $this->doBar($this->makeFloat()); + $this->doBar($this->makeFloatOrString()); + $this->doBar($this->makeIntOrNull()); + $this->doBar($this->makeIntOrFloat()); + } + + public function doBar(int $i) + { + } + + public function doBaz() + { + $this->doLorem($this->makeFloatOrString()); + $this->doLorem($this->makeFloatOrStringOrNull()); + $this->doLorem($this->makeInt()); + $this->doLorem($this->makeIntOrNull()); + $this->doIpsum($this->makeFloatOrString()); + $this->doIpsum($this->makeFloatOrStringOrNull()); + $this->doBar(null); + } + + /** + * @param int|resource $a + */ + public function doLorem($a) + { + } + + /** + * @param float $a + */ + public function doIpsum($a) + { + } + + /** + * @return int[] + */ + public function makeIntArray(): array + { + return [1, 2, 3]; + } + + /** + * @return float[] + */ + public function makeFloatArray(): array + { + return [1.0, 2.0, 3.0]; + } + + /** + * @return (float|string)[] + */ + public function makeFloatOrStringArray(): array + { + return [1.0, 2.0, '3.0']; + } + + /** + * @return (int|null)[] + */ + public function makeIntOrNullArray(): array + { + return [1, 2, null]; + } + + /** + * @return (int|float)[] + */ + public function makeIntOrFloatArray(): array + { + return [1.0, 2.0, 3]; + } + + public function doFooArray() + { + $this->doBarArray($this->makeIntArray()); + $this->doBarArray($this->makeFloatArray()); + $this->doBarArray($this->makeFloatOrStringArray()); + $this->doBarArray($this->makeIntOrNullArray()); + $this->doBarArray($this->makeIntOrFloatArray()); + } + + /** + * @param int[] $i + */ + public function doBarArray(array $i) + { + } + + public function makeFoo(): Foo + { + return new Foo(); + } + + /** + * @return Foo|mixed[] + */ + public function makeFooOrArray() + { + if (rand(0, 1) === 0) { + return new Foo(); + } + + return []; + } + + public function testUnions() + { + $a = []; + if (rand(0, 1) === 0) { + $a = $this->makeFoo(); + } + + $this->requireArray($a); + $this->requireFoo($a); + } + + public function testUnions2() + { + $a = []; + if (rand(0, 1) === 0) { + $a = $this->makeFooOrArray(); + } + + $this->requireArray($a); + $this->requireFoo($a); + } + + /** + * @param mixed[] $array + */ + private function requireArray(array $array) + { + } + + private function requireFoo(Foo $foo) + { + } } class ArrayShapes { - - /** - * @param callable[] $callables - * @param string[] $strings - * @param int[] $integers - * @param iterable $iterable - */ - public function doFoo( - array $callables, - array $strings, - array $integers, - iterable $iterable - ) - { - $this->doBar($callables); - $this->doBar($strings); - $this->doBar($integers); - $this->doBar([]); - $this->doBar(['foo' => 'date']); - $this->doBar(['foo' => 1]); - $this->doBar(['foo' => 'date', 'bar' => 'date']); - $this->doBar(['foo' => 'nonexistent']); - $this->doBar(['bar' => 'date']); - - if (array_key_exists('foo', $callables)) { - $this->doBar($callables); - } - - $optional = []; - if (rand(0, 1) === 0) { - $optional['foo'] = 'date'; - } - - $this->doBar($optional); - $this->doBar($iterable); - } - - /** - * @param array{foo:callable} $one - */ - public function doBar( - array $one - ) - { - - } - + /** + * @param callable[] $callables + * @param string[] $strings + * @param int[] $integers + * @param iterable $iterable + */ + public function doFoo( + array $callables, + array $strings, + array $integers, + iterable $iterable + ) { + $this->doBar($callables); + $this->doBar($strings); + $this->doBar($integers); + $this->doBar([]); + $this->doBar(['foo' => 'date']); + $this->doBar(['foo' => 1]); + $this->doBar(['foo' => 'date', 'bar' => 'date']); + $this->doBar(['foo' => 'nonexistent']); + $this->doBar(['bar' => 'date']); + + if (array_key_exists('foo', $callables)) { + $this->doBar($callables); + } + + $optional = []; + if (rand(0, 1) === 0) { + $optional['foo'] = 'date'; + } + + $this->doBar($optional); + $this->doBar($iterable); + } + + /** + * @param array{foo:callable} $one + */ + public function doBar( + array $one + ) { + } } class RequireClassString { - - public function doFoo(string $s): void - { - $this->requireClassString($s); - $this->requireGenericClassString($s); - } - - /** - * @param class-string $s - */ - public function requireClassString(string $s): void - { - - } - - /** - * @param class-string<\stdClass> $s - */ - public function requireGenericClassString(string $s): void - { - - } - + public function doFoo(string $s): void + { + $this->requireClassString($s); + $this->requireGenericClassString($s); + } + + /** + * @param class-string $s + */ + public function requireClassString(string $s): void + { + } + + /** + * @param class-string<\stdClass> $s + */ + public function requireGenericClassString(string $s): void + { + } } class RequireObjectWithoutClassType { - - /** - * @param object $object - */ - public function doFoo($object): void - { - $this->requireStdClass($object); - $this->requireStatic($object); - } - - public function requireStdClass(\stdClass $stdClass): void - { - - } - - /** - * @param static $static - */ - public function requireStatic($static): void - { - - } - + /** + * @param object $object + */ + public function doFoo($object): void + { + $this->requireStdClass($object); + $this->requireStatic($object); + } + + public function requireStdClass(\stdClass $stdClass): void + { + } + + /** + * @param static $static + */ + public function requireStatic($static): void + { + } } class RandomInt { - - public function doThings(): int - { - return random_int(0, -1); - } - - public function doInputMin(int $input): int - { - assert($input > 10); - - return random_int($input, 10); - } - - public function doInputMax(int $input): int - { - assert($input < 340); - - return random_int(340, $input); - } - - public function doStuff(): void - { - random_int(random_int(-1, 1), random_int(0, 1)); - random_int(random_int(-1, 0), random_int(-1, 1)); - random_int(random_int(-1, 1), random_int(-1, 1)); - } - + public function doThings(): int + { + return random_int(0, -1); + } + + public function doInputMin(int $input): int + { + assert($input > 10); + + return random_int($input, 10); + } + + public function doInputMax(int $input): int + { + assert($input < 340); + + return random_int(340, $input); + } + + public function doStuff(): void + { + random_int(random_int(-1, 1), random_int(0, 1)); + random_int(random_int(-1, 0), random_int(-1, 1)); + random_int(random_int(-1, 1), random_int(-1, 1)); + } } class NumericStrings { - - /** - * @param string $string - * @param numeric-string $numericString - */ - public function doFoo(string $string, string $numericString): void - { - $this->doBar('1'); - $this->doBar('foo'); - $this->doBar($string); - $this->doBar($numericString); - } - - /** - * @param numeric-string $numericString - */ - public function doBar(string $numericString): void - { - - } + /** + * @param string $string + * @param numeric-string $numericString + */ + public function doFoo(string $string, string $numericString): void + { + $this->doBar('1'); + $this->doBar('foo'); + $this->doBar($string); + $this->doBar($numericString); + } + + /** + * @param numeric-string $numericString + */ + public function doBar(string $numericString): void + { + } } class AcceptNonEmpty { - - /** - * @param array $array - * @param non-empty-array $nonEmpty - */ - public function doFoo( - array $array, - array $nonEmpty - ): void - { - $this->doBar([]); - $this->doBar([1, 2, 3]); - $this->doBar($array); - $this->doBar($nonEmpty); - } - - /** - * @param non-empty-array $nonEmpty - */ - public function doBar( - array $nonEmpty - ): void - { - - } - + /** + * @param array $array + * @param non-empty-array $nonEmpty + */ + public function doFoo( + array $array, + array $nonEmpty + ): void { + $this->doBar([]); + $this->doBar([1, 2, 3]); + $this->doBar($array); + $this->doBar($nonEmpty); + } + + /** + * @param non-empty-array $nonEmpty + */ + public function doBar( + array $nonEmpty + ): void { + } } diff --git a/tests/PHPStan/Levels/data/arrayAccess.php b/tests/PHPStan/Levels/data/arrayAccess.php index b77266e0f6..5f0359ecfd 100644 --- a/tests/PHPStan/Levels/data/arrayAccess.php +++ b/tests/PHPStan/Levels/data/arrayAccess.php @@ -4,43 +4,37 @@ class Foo { + /** + * @param object $object + */ + public function doFoo( + $object + ) { + $splObjectStorage = new \SplObjectStorage(); + $splObjectStorage[$object] = 1; + } - /** - * @param object $object - */ - public function doFoo( - $object - ) - { - $splObjectStorage = new \SplObjectStorage(); - $splObjectStorage[$object] = 1; - } + /** + * @param object|int $objectOrInt + */ + public function doBar( + $objectOrInt + ) { + $splObjectStorage = new \SplObjectStorage(); + $splObjectStorage[$objectOrInt] = 1; + } - /** - * @param object|int $objectOrInt - */ - public function doBar( - $objectOrInt - ) - { - $splObjectStorage = new \SplObjectStorage(); - $splObjectStorage[$objectOrInt] = 1; - } - - public function doBaz( - int $int - ) - { - $splObjectStorage = new \SplObjectStorage(); - $splObjectStorage[$int] = 1; - } - - public function doLorem( - $mixed - ) - { - $splObjectStorage = new \SplObjectStorage(); - $splObjectStorage[$mixed] = 1; - } + public function doBaz( + int $int + ) { + $splObjectStorage = new \SplObjectStorage(); + $splObjectStorage[$int] = 1; + } + public function doLorem( + $mixed + ) { + $splObjectStorage = new \SplObjectStorage(); + $splObjectStorage[$mixed] = 1; + } } diff --git a/tests/PHPStan/Levels/data/arrayDestructuring.php b/tests/PHPStan/Levels/data/arrayDestructuring.php index 5d7e61ea93..54481dece2 100644 --- a/tests/PHPStan/Levels/data/arrayDestructuring.php +++ b/tests/PHPStan/Levels/data/arrayDestructuring.php @@ -4,30 +4,28 @@ class Foo { + /** + * @param mixed[] $array + * @param mixed[]|null $arrayOrNull + */ + public function doFoo(array $array, ?array $arrayOrNull): void + { + [$a, $b, $c] = $array; + [$a, $b, $c] = $arrayOrNull; + } - /** - * @param mixed[] $array - * @param mixed[]|null $arrayOrNull - */ - public function doFoo(array $array, ?array $arrayOrNull): void - { - [$a, $b, $c] = $array; - [$a, $b, $c] = $arrayOrNull; - } - - /** - * @param iterable $it - */ - public function doBar(iterable $it): void - { - [$a] = $it; - } - - public function doBaz(): void - { - $array = ['a', 'b', 'c']; - [$a] = $array; - [$a, , , $d] = $array; - } + /** + * @param iterable $it + */ + public function doBar(iterable $it): void + { + [$a] = $it; + } + public function doBaz(): void + { + $array = ['a', 'b', 'c']; + [$a] = $array; + [$a, , , $d] = $array; + } } diff --git a/tests/PHPStan/Levels/data/arrayDimFetches.php b/tests/PHPStan/Levels/data/arrayDimFetches.php index effe56842c..b0ebcccc20 100644 --- a/tests/PHPStan/Levels/data/arrayDimFetches.php +++ b/tests/PHPStan/Levels/data/arrayDimFetches.php @@ -4,58 +4,56 @@ class Foo { - - /** - * @param \stdClass $stdClass - * @param mixed[]|null $arrayOrNull - */ - public function doFoo(\stdClass $stdClass, ?array $arrayOrNull) - { - echo $stdClass[1]; - echo $arrayOrNull[0]; - - $arr = [ - 'a' => 1, - ]; - - echo $arr['b']; - - if (rand(0, 1)) { - $arr = $stdClass; - } - - echo $arr['a']; - echo $arr['b']; - } - - public function doBar() - { - $arr = [ - 'a' => 1, - ]; - if (rand(0, 1)) { - $arr['b'] = 1; - } - - echo $arr['b']; - } - - /** - * @param array|false $a - * @param array|null $b - */ - public function doBaz($a, $b): void - { - echo $a[0]; - echo $b[0]; - } - - /** - * @param iterable $iterable - */ - public function iterableOffset($iterable): void - { - unset($iterable['foo']); - } - + /** + * @param \stdClass $stdClass + * @param mixed[]|null $arrayOrNull + */ + public function doFoo(\stdClass $stdClass, ?array $arrayOrNull) + { + echo $stdClass[1]; + echo $arrayOrNull[0]; + + $arr = [ + 'a' => 1, + ]; + + echo $arr['b']; + + if (rand(0, 1)) { + $arr = $stdClass; + } + + echo $arr['a']; + echo $arr['b']; + } + + public function doBar() + { + $arr = [ + 'a' => 1, + ]; + if (rand(0, 1)) { + $arr['b'] = 1; + } + + echo $arr['b']; + } + + /** + * @param array|false $a + * @param array|null $b + */ + public function doBaz($a, $b): void + { + echo $a[0]; + echo $b[0]; + } + + /** + * @param iterable $iterable + */ + public function iterableOffset($iterable): void + { + unset($iterable['foo']); + } } diff --git a/tests/PHPStan/Levels/data/binaryOps.php b/tests/PHPStan/Levels/data/binaryOps.php index 7b04fedbae..c346992d09 100644 --- a/tests/PHPStan/Levels/data/binaryOps.php +++ b/tests/PHPStan/Levels/data/binaryOps.php @@ -4,28 +4,25 @@ class Foo { - - /** - * @param int $int - * @param string $string - * @param int|string $intOrString - * @param string|object $stringOrObject - */ - public function doFoo( - int $int, - string $string, - $intOrString, - $stringOrObject - ) - { - $int + $int; - $int + $intOrString; - $int + $stringOrObject; - $int + $string; - $string + $string; - $intOrString + $stringOrObject; - $intOrString + $string; - $stringOrObject + $stringOrObject; - } - + /** + * @param int $int + * @param string $string + * @param int|string $intOrString + * @param string|object $stringOrObject + */ + public function doFoo( + int $int, + string $string, + $intOrString, + $stringOrObject + ) { + $int + $int; + $int + $intOrString; + $int + $stringOrObject; + $int + $string; + $string + $string; + $intOrString + $stringOrObject; + $intOrString + $string; + $stringOrObject + $stringOrObject; + } } diff --git a/tests/PHPStan/Levels/data/callableCalls.php b/tests/PHPStan/Levels/data/callableCalls.php index 4b22fa723c..47a661e328 100644 --- a/tests/PHPStan/Levels/data/callableCalls.php +++ b/tests/PHPStan/Levels/data/callableCalls.php @@ -4,43 +4,39 @@ class Foo { - - /** - * @param int|string $a - * @param float|string $b - * @param int $c - * @param int|float $d - */ - public function doFoo( - $a, - $b, - $c, - $d - ) - { - $a(); - $b(); - $c(); - $d(); - $f = function (int $i) { - - }; - $f(1); - $f(1.1); - $f($a); - $f($b); - - if (rand(0, 1)) { - $f = null; - } - - $f(); - $f(1); - } - - public function doBar(?int $nullableInt) - { - $nullableInt(); - } - + /** + * @param int|string $a + * @param float|string $b + * @param int $c + * @param int|float $d + */ + public function doFoo( + $a, + $b, + $c, + $d + ) { + $a(); + $b(); + $c(); + $d(); + $f = function (int $i) { + }; + $f(1); + $f(1.1); + $f($a); + $f($b); + + if (rand(0, 1)) { + $f = null; + } + + $f(); + $f(1); + } + + public function doBar(?int $nullableInt) + { + $nullableInt(); + } } diff --git a/tests/PHPStan/Levels/data/callableVariance.php b/tests/PHPStan/Levels/data/callableVariance.php index 3156d9bcb6..ba51b3b6ff 100644 --- a/tests/PHPStan/Levels/data/callableVariance.php +++ b/tests/PHPStan/Levels/data/callableVariance.php @@ -2,18 +2,24 @@ namespace Levels\CallableVariance; -class A {} -class B extends A {} -class C extends B {} +class A +{ +} +class B extends A +{ +} +class C extends B +{ +} /** * @param callable(B): void $cb */ function a(callable $cb): void { - $cb(new A()); - $cb(new B()); - $cb(new C()); + $cb(new A()); + $cb(new B()); + $cb(new C()); } /** @@ -30,13 +36,16 @@ function b(callable $cb): void */ function testB($a, $b, $c): void { - b(function (A $a): void {}); - b(function (B $b): void {}); - b(function (C $c): void {}); + b(function (A $a): void { + }); + b(function (B $b): void { + }); + b(function (C $c): void { + }); - b($a); - b($b); - b($c); + b($a); + b($b); + b($c); } /** @@ -53,13 +62,19 @@ function c(callable $cb): void */ function testC($a, $b, $c): void { - c(function (): A { return new A(); }); - c(function (): B { return new B(); }); - c(function (): C { return new C(); }); + c(function (): A { + return new A(); + }); + c(function (): B { + return new B(); + }); + c(function (): C { + return new C(); + }); - c($a); - c($b); - c($c); + c($a); + c($b); + c($c); } /** @@ -78,9 +93,9 @@ function d(callable $cb) */ function testD($a, $b, $c, $d, $e) { - d($a); - d($b); - d($c); - d($d); - d($e); + d($a); + d($b); + d($c); + d($d); + d($e); } diff --git a/tests/PHPStan/Levels/data/casts.php b/tests/PHPStan/Levels/data/casts.php index 67cf2dacd8..1efcfd22f9 100644 --- a/tests/PHPStan/Levels/data/casts.php +++ b/tests/PHPStan/Levels/data/casts.php @@ -4,21 +4,18 @@ class Foo { - - /** - * @param mixed[] $array - * @param mixed[]|(callable(): mixed) $arrayOrCallable - * @param mixed[]|float|int $arrayOrFloatOrInt - */ - public function doFoo( - array $array, - $arrayOrCallable, - $arrayOrFloatOrInt - ) - { - $test = (int) $array; - $test = (int) $arrayOrCallable; - $test = (string) $arrayOrFloatOrInt; - } - + /** + * @param mixed[] $array + * @param mixed[]|(callable(): mixed) $arrayOrCallable + * @param mixed[]|float|int $arrayOrFloatOrInt + */ + public function doFoo( + array $array, + $arrayOrCallable, + $arrayOrFloatOrInt + ) { + $test = (int) $array; + $test = (int) $arrayOrCallable; + $test = (string) $arrayOrFloatOrInt; + } } diff --git a/tests/PHPStan/Levels/data/clone.php b/tests/PHPStan/Levels/data/clone.php index 6bd72cfe38..3521d84859 100644 --- a/tests/PHPStan/Levels/data/clone.php +++ b/tests/PHPStan/Levels/data/clone.php @@ -4,36 +4,33 @@ class Foo { - - /** - * @param int $int - * @param int|string $intOrString - * @param Foo $foo - * @param Foo|null $nullableFoo - * @param Foo|int $fooOrInt - * @param int|null $nullableInt - * @param Foo|int|null $nullableUnion - * @param mixed $mixed - */ - public function doFoo( - int $int, - $intOrString, - Foo $foo, - ?Foo $nullableFoo, - $fooOrInt, - ?int $nullableInt, - $nullableUnion, - $mixed - ) - { - clone $int; - clone $intOrString; - clone $foo; - clone $nullableFoo; - clone $fooOrInt; - clone $nullableInt; - clone $nullableUnion; - clone $mixed; - } - + /** + * @param int $int + * @param int|string $intOrString + * @param Foo $foo + * @param Foo|null $nullableFoo + * @param Foo|int $fooOrInt + * @param int|null $nullableInt + * @param Foo|int|null $nullableUnion + * @param mixed $mixed + */ + public function doFoo( + int $int, + $intOrString, + Foo $foo, + ?Foo $nullableFoo, + $fooOrInt, + ?int $nullableInt, + $nullableUnion, + $mixed + ) { + clone $int; + clone $intOrString; + clone $foo; + clone $nullableFoo; + clone $fooOrInt; + clone $nullableInt; + clone $nullableUnion; + clone $mixed; + } } diff --git a/tests/PHPStan/Levels/data/coalesce.php b/tests/PHPStan/Levels/data/coalesce.php index 6010637b0a..c51a02c0aa 100644 --- a/tests/PHPStan/Levels/data/coalesce.php +++ b/tests/PHPStan/Levels/data/coalesce.php @@ -1,12 +1,12 @@ bar ?? 'foo'; + echo $bar->bar ?? 'foo'; }; function (\ReflectionClass $ref): void { - echo $ref->name ?? 'foo'; - echo $ref->nonexistent ?? 'bar'; + echo $ref->name ?? 'foo'; + echo $ref->nonexistent ?? 'bar'; }; diff --git a/tests/PHPStan/Levels/data/comparison.php b/tests/PHPStan/Levels/data/comparison.php index d208e6051c..5ac36879ad 100644 --- a/tests/PHPStan/Levels/data/comparison.php +++ b/tests/PHPStan/Levels/data/comparison.php @@ -4,26 +4,24 @@ class Foo { + private const FOO_CONST = 'foo'; - private const FOO_CONST = 'foo'; - - /** + /** * @param \stdClass $object - * @param int $int + * @param int $int * @param float $float - * @param string $string - * @param int|string $intOrString - * @param int|\stdClass $intOrObject - */ - public function doFoo( - \stdClass $object, - int $int, - float $float, - string $string, - $intOrString, + * @param string $string + * @param int|string $intOrString + * @param int|\stdClass $intOrObject + */ + public function doFoo( + \stdClass $object, + int $int, + float $float, + string $string, + $intOrString, $intOrObject - ) - { + ) { $object == $int; $object == $float; $object == $string; @@ -31,11 +29,10 @@ public function doFoo( $object == $intOrObject; self::FOO_CONST === 'bar'; - } - - public function doBar(\ffmpeg_movie $movie): void - { - $movie->getArtist() === 1; - } + } + public function doBar(\ffmpeg_movie $movie): void + { + $movie->getArtist() === 1; + } } diff --git a/tests/PHPStan/Levels/data/constantAccesses.php b/tests/PHPStan/Levels/data/constantAccesses.php index ee810f2d86..de181eb989 100644 --- a/tests/PHPStan/Levels/data/constantAccesses.php +++ b/tests/PHPStan/Levels/data/constantAccesses.php @@ -3,59 +3,53 @@ namespace Levels\ConstantAccesses; function () { - echo UNKNOWN_CONSTANT; + echo UNKNOWN_CONSTANT; }; class Foo { + public const FOO_CONSTANT = 'foo'; - public const FOO_CONSTANT = 'foo'; + public function doFoo() + { + echo Foo::FOO_CONSTANT; + echo Foo::BAR_CONSTANT; + echo Bar::FOO_CONSTANT; - public function doFoo() - { - echo Foo::FOO_CONSTANT; - echo Foo::BAR_CONSTANT; - echo Bar::FOO_CONSTANT; - - echo $this::BAR_CONSTANT; - - $foo = new self(); - echo $foo::BAR_CONSTANT; - } + echo $this::BAR_CONSTANT; + $foo = new self(); + echo $foo::BAR_CONSTANT; + } } class Bar { - } class Baz { - - /** - * @param Foo|Bar $fooOrBar - * @param Foo|null $fooOrNull - * @param Foo|Bar|null $fooOrBarOrNull - * @param Bar|Baz $barOrBaz - */ - public function doBaz( - $fooOrBar, - ?Foo $fooOrNull, - $fooOrBarOrNull, - $barOrBaz - ) - { - echo $fooOrBar::FOO_CONSTANT; - echo $fooOrBar::BAR_CONSTANT; - - echo $fooOrNull::FOO_CONSTANT; - echo $fooOrNull::BAR_CONSTANT; - - echo $fooOrBarOrNull::FOO_CONSTANT; - echo $fooOrBarOrNull::BAR_CONSTANT; - - echo $barOrBaz::FOO_CONSTANT; - } - + /** + * @param Foo|Bar $fooOrBar + * @param Foo|null $fooOrNull + * @param Foo|Bar|null $fooOrBarOrNull + * @param Bar|Baz $barOrBaz + */ + public function doBaz( + $fooOrBar, + ?Foo $fooOrNull, + $fooOrBarOrNull, + $barOrBaz + ) { + echo $fooOrBar::FOO_CONSTANT; + echo $fooOrBar::BAR_CONSTANT; + + echo $fooOrNull::FOO_CONSTANT; + echo $fooOrNull::BAR_CONSTANT; + + echo $fooOrBarOrNull::FOO_CONSTANT; + echo $fooOrBarOrNull::BAR_CONSTANT; + + echo $barOrBaz::FOO_CONSTANT; + } } diff --git a/tests/PHPStan/Levels/data/echo_.php b/tests/PHPStan/Levels/data/echo_.php index ad6925d276..9b8091f6eb 100644 --- a/tests/PHPStan/Levels/data/echo_.php +++ b/tests/PHPStan/Levels/data/echo_.php @@ -6,18 +6,18 @@ class Foo { - /** - * @param mixed[] $array - * @param mixed[]|(callable(): mixed) $arrayOrCallable - * @param mixed[]|float|int $arrayOrFloatOrInt - * @param mixed[]|string $arrayOrString - */ - public function doFoo( - array $array, - $arrayOrCallable, - $arrayOrFloatOrInt, - $arrayOrString - ): void { - echo $array, $arrayOrCallable, $arrayOrFloatOrInt, $arrayOrString; - } + /** + * @param mixed[] $array + * @param mixed[]|(callable(): mixed) $arrayOrCallable + * @param mixed[]|float|int $arrayOrFloatOrInt + * @param mixed[]|string $arrayOrString + */ + public function doFoo( + array $array, + $arrayOrCallable, + $arrayOrFloatOrInt, + $arrayOrString + ): void { + echo $array, $arrayOrCallable, $arrayOrFloatOrInt, $arrayOrString; + } } diff --git a/tests/PHPStan/Levels/data/encapsedString.php b/tests/PHPStan/Levels/data/encapsedString.php index 910076d41e..bffd7f8961 100644 --- a/tests/PHPStan/Levels/data/encapsedString.php +++ b/tests/PHPStan/Levels/data/encapsedString.php @@ -1,32 +1,30 @@ -takesString("foo $array"); + $this->takesString("foo $arrayOrCallable"); + $this->takesString("foo $arrayOrFloatOrInt"); + $this->takesString("foo $arrayOrString"); + } - /** - * @param mixed[] $array - * @param mixed[]|(callable(): mixed) $arrayOrCallable - * @param mixed[]|float|int $arrayOrFloatOrInt - * @param mixed[]|string $arrayOrString - */ - public function doFoo( - array $array, - $arrayOrCallable, - $arrayOrFloatOrInt, - $arrayOrString - ): void - { - $this->takesString("foo $array"); - $this->takesString("foo $arrayOrCallable"); - $this->takesString("foo $arrayOrFloatOrInt"); - $this->takesString("foo $arrayOrString"); - } - - public function takesString(string $str): void - { - - } - + public function takesString(string $str): void + { + } } diff --git a/tests/PHPStan/Levels/data/inferPropertyType.php b/tests/PHPStan/Levels/data/inferPropertyType.php index d6bf2277be..292c9d7844 100644 --- a/tests/PHPStan/Levels/data/inferPropertyType.php +++ b/tests/PHPStan/Levels/data/inferPropertyType.php @@ -1,23 +1,23 @@ -foo = $foo; - $this->bar = $this->bar; - } + private $bar; - public function doFoo() - { - $this->foo->formatt(); - } + public function __construct(\DateTime $foo) + { + $this->foo = $foo; + $this->bar = $this->bar; + } + public function doFoo() + { + $this->foo->formatt(); + } } diff --git a/tests/PHPStan/Levels/data/iterable.php b/tests/PHPStan/Levels/data/iterable.php index 57815aa97c..f720270140 100644 --- a/tests/PHPStan/Levels/data/iterable.php +++ b/tests/PHPStan/Levels/data/iterable.php @@ -4,37 +4,29 @@ class Foo { - - /** - * @param mixed[] $array - * @param mixed[]|null $arrayOrNull - * @param int $int - * @param int|float $intOrFloat - * @param mixed[]|false $arrayOrFalse - */ - public function doFoo( - array $array, - ?array $arrayOrNull, - int $int, - $intOrFloat, - $arrayOrFalse - ) - { - foreach ($array as $val) { - - } - foreach ($arrayOrNull as $val) { - - } - foreach ($int as $val) { - - } - foreach ($intOrFloat as $val) { - - } - foreach ($arrayOrFalse as $val) { - - } - } - + /** + * @param mixed[] $array + * @param mixed[]|null $arrayOrNull + * @param int $int + * @param int|float $intOrFloat + * @param mixed[]|false $arrayOrFalse + */ + public function doFoo( + array $array, + ?array $arrayOrNull, + int $int, + $intOrFloat, + $arrayOrFalse + ) { + foreach ($array as $val) { + } + foreach ($arrayOrNull as $val) { + } + foreach ($int as $val) { + } + foreach ($intOrFloat as $val) { + } + foreach ($arrayOrFalse as $val) { + } + } } diff --git a/tests/PHPStan/Levels/data/methodCalls.php b/tests/PHPStan/Levels/data/methodCalls.php index 06cb63f6ff..e3fd06ff20 100644 --- a/tests/PHPStan/Levels/data/methodCalls.php +++ b/tests/PHPStan/Levels/data/methodCalls.php @@ -4,238 +4,210 @@ class Foo { - - public function doFoo(int $i) - { - $this->doFoo($i); - $this->doFoo(); - $this->doFoo(1.1); - - $foo = new self(); - $foo->doFoo(); - } - + public function doFoo(int $i) + { + $this->doFoo($i); + $this->doFoo(); + $this->doFoo(1.1); + + $foo = new self(); + $foo->doFoo(); + } } class Bar { - - public static function doBar(int $i) - { - Bar::doBar($i); - Bar::doBar(); - Lorem::doBar(); - - $bar = new Bar(); - $bar::doBar($i); - $bar::doBar(); - } - + public static function doBar(int $i) + { + Bar::doBar($i); + Bar::doBar(); + Lorem::doBar(); + + $bar = new Bar(); + $bar::doBar($i); + $bar::doBar(); + } } class Baz { - - /** - * @param Foo|Bar $fooOrBar - * @param Foo|null $fooOrNull - * @param Foo|Bar|null $fooOrBarOrNull - * @param Bar|Baz $barOrBaz - */ - public function doBaz( - $fooOrBar, - ?Foo $fooOrNull, - $fooOrBarOrNull, - $barOrBaz - ) - { - $fooOrBar->doFoo(1); - $fooOrBar->doFoo(); - $fooOrBar->doBaz(); - - $fooOrNull->doFoo(); - $fooOrNull->doFoo(1); - - $fooOrBarOrNull->doFoo(); - $fooOrBarOrNull->doFoo(1); - - $barOrBaz->doFoo(); - } - + /** + * @param Foo|Bar $fooOrBar + * @param Foo|null $fooOrNull + * @param Foo|Bar|null $fooOrBarOrNull + * @param Bar|Baz $barOrBaz + */ + public function doBaz( + $fooOrBar, + ?Foo $fooOrNull, + $fooOrBarOrNull, + $barOrBaz + ) { + $fooOrBar->doFoo(1); + $fooOrBar->doFoo(); + $fooOrBar->doBaz(); + + $fooOrNull->doFoo(); + $fooOrNull->doFoo(1); + + $fooOrBarOrNull->doFoo(); + $fooOrBarOrNull->doFoo(1); + + $barOrBaz->doFoo(); + } } class ClassWithMagicMethod { - - public function doFoo() - { - $this->test(); - } - - /** - * @param string $name - * @param mixed[] $args - */ - public function __call(string $name, array $args) - { - - } - + public function doFoo() + { + $this->test(); + } + + /** + * @param string $name + * @param mixed[] $args + */ + public function __call(string $name, array $args) + { + } } class AnotherClassWithMagicMethod { - - public function doFoo() - { - self::test(); - } - - /** - * @param string $name - * @param mixed[] $args - */ - public static function __callStatic(string $name, array $args) - { - - } - + public function doFoo() + { + self::test(); + } + + /** + * @param string $name + * @param mixed[] $args + */ + public static function __callStatic(string $name, array $args) + { + } } class Ipsum { - - /** - * @return Foo|Bar - */ - private function makeFooOrBar() - { - if (rand(0, 1) === 0) { - return new Foo(); - } else { - return new Bar(); - } - } - - /** - * @return Foo|null - */ - private function makeFooOrNull() - { - if (rand(0, 1) === 0) { - return new Foo(); - } else { - return null; - } - } - - /** - * @return Foo|Bar|null - */ - public function makeFooOrBarOrNull() - { - if (rand(0, 1) === 0) { - return new Foo(); - } elseif (rand(0, 1) === 1) { - return new Bar(); - } else { - return null; - } - } - - /** - * @return Bar|Baz - */ - public function makeBarOrBaz() - { - if (rand(0, 1) === 0) { - return new Bar(); - } else { - return new Baz(); - } - } - - public function doLorem() - { - $fooOrBar = $this->makeFooOrBar(); - $fooOrBar->doFoo(1); - $fooOrBar->doFoo(); - $fooOrBar->doBaz(); - - $fooOrNull = $this->makeFooOrNull(); - $fooOrNull->doFoo(); - $fooOrNull->doFoo(1); - - $fooOrBarOrNull = $this->makeFooOrBarOrNull(); - $fooOrBarOrNull->doFoo(); - $fooOrBarOrNull->doFoo(1); - - $barOrBaz = $this->makeBarOrBaz(); - $barOrBaz->doFoo(); - } - + /** + * @return Foo|Bar + */ + private function makeFooOrBar() + { + if (rand(0, 1) === 0) { + return new Foo(); + } else { + return new Bar(); + } + } + + /** + * @return Foo|null + */ + private function makeFooOrNull() + { + if (rand(0, 1) === 0) { + return new Foo(); + } else { + return null; + } + } + + /** + * @return Foo|Bar|null + */ + public function makeFooOrBarOrNull() + { + if (rand(0, 1) === 0) { + return new Foo(); + } elseif (rand(0, 1) === 1) { + return new Bar(); + } else { + return null; + } + } + + /** + * @return Bar|Baz + */ + public function makeBarOrBaz() + { + if (rand(0, 1) === 0) { + return new Bar(); + } else { + return new Baz(); + } + } + + public function doLorem() + { + $fooOrBar = $this->makeFooOrBar(); + $fooOrBar->doFoo(1); + $fooOrBar->doFoo(); + $fooOrBar->doBaz(); + + $fooOrNull = $this->makeFooOrNull(); + $fooOrNull->doFoo(); + $fooOrNull->doFoo(1); + + $fooOrBarOrNull = $this->makeFooOrBarOrNull(); + $fooOrBarOrNull->doFoo(); + $fooOrBarOrNull->doFoo(1); + + $barOrBaz = $this->makeBarOrBaz(); + $barOrBaz->doFoo(); + } } class FooException extends \Exception { + public function commonMethod() + { + } - public function commonMethod() - { - - } - - public function doFoo() - { - - } - + public function doFoo() + { + } } class BarException extends \Exception { + public function commonMethod() + { + } - public function commonMethod() - { - - } - - public function doBar() - { - - } - + public function doBar() + { + } } class TestExceptions { - - public function doFoo() - { - try { - $this->doBar(); - } catch (FooException | BarException $e) { - $e->commonMethod(); - $e->doFoo(); - $e->doBar(); - $e->doBaz(); - } - } - - public function doBar(): void - { - - } - + public function doFoo() + { + try { + $this->doBar(); + } catch (FooException | BarException $e) { + $e->commonMethod(); + $e->doFoo(); + $e->doBar(); + $e->doBaz(); + } + } + + public function doBar(): void + { + } } class ExtraArguments { - - public function doFoo(int $i) - { - $this->doFoo(); - $this->doFoo(1); - $this->doFoo(1, 2); - } - + public function doFoo(int $i) + { + $this->doFoo(); + $this->doFoo(1); + $this->doFoo(1, 2); + } } diff --git a/tests/PHPStan/Levels/data/missingReturn.php b/tests/PHPStan/Levels/data/missingReturn.php index 2ef27ad3c1..e4b3477b61 100644 --- a/tests/PHPStan/Levels/data/missingReturn.php +++ b/tests/PHPStan/Levels/data/missingReturn.php @@ -4,26 +4,21 @@ class Foo { - - public function doFoo(): int - { - - } - - /** - * @return int - */ - public function doBar() - { - - } - - /** - * @return mixed - */ - public function doBaz() - { - - } - + public function doFoo(): int + { + } + + /** + * @return int + */ + public function doBar() + { + } + + /** + * @return mixed + */ + public function doBaz() + { + } } diff --git a/tests/PHPStan/Levels/data/namedArguments.php b/tests/PHPStan/Levels/data/namedArguments.php index 0380cd24a6..3ad1b59e2f 100644 --- a/tests/PHPStan/Levels/data/namedArguments.php +++ b/tests/PHPStan/Levels/data/namedArguments.php @@ -1,69 +1,68 @@ -= 8.0 += 8.0 namespace NamedArgumentsIntegrationTest; class Foo { + public function doFoo( + int $i, + int $j, + int $k, + ?int $l = null + ): void { + } - public function doFoo( - int $i, - int $j, - int $k, ?int $l = null - ): void - { - - } - - public function doBar(): void - { - $this->doFoo( - i: 1, - 2, - 3 - ); - $this->doFoo( - 1, - j: 3 - ); - $this->doFoo( - 'foo', - j: 3, - k: 4 - ); - $this->doFoo( - i: 'foo', - j: 3, - k: 4 - ); - $this->doFoo(i: 1, ...['j' => 2, 'k' => 3]); - $this->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); - $this->doFoo(...['k' => 3, 'i' => 1, 'str']); - } - - public function doBaz(self $self): void - { - $self->doFoo( - i: 1, - 2, - 3 - ); - $self->doFoo( - 1, - j: 3 - ); - $self->doFoo( - 'foo', - j: 3, - k: 4 - ); - $self->doFoo( - i: 'foo', - j: 3, - k: 4 - ); - $self->doFoo(i: 1, ...['j' => 2, 'k' => 3]); - $self->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); - $self->doFoo(...['k' => 3, 'i' => 1, 'str']); - } + public function doBar(): void + { + $this->doFoo( + i: 1, + 2, + 3 + ); + $this->doFoo( + 1, + j: 3 + ); + $this->doFoo( + 'foo', + j: 3, + k: 4 + ); + $this->doFoo( + i: 'foo', + j: 3, + k: 4 + ); + $this->doFoo(i: 1, ...['j' => 2, 'k' => 3]); + $this->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); + $this->doFoo(...['k' => 3, 'i' => 1, 'str']); + } + public function doBaz(self $self): void + { + $self->doFoo( + i: 1, + 2, + 3 + ); + $self->doFoo( + 1, + j: 3 + ); + $self->doFoo( + 'foo', + j: 3, + k: 4 + ); + $self->doFoo( + i: 'foo', + j: 3, + k: 4 + ); + $self->doFoo(i: 1, ...['j' => 2, 'k' => 3]); + $self->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); + $self->doFoo(...['k' => 3, 'i' => 1, 'str']); + } } diff --git a/tests/PHPStan/Levels/data/object.php b/tests/PHPStan/Levels/data/object.php index 918fc93711..05d9b9335d 100644 --- a/tests/PHPStan/Levels/data/object.php +++ b/tests/PHPStan/Levels/data/object.php @@ -4,41 +4,39 @@ class Foo { - - /** - * @param object $object - */ - public function doFoo($object) - { - $object->foo(); - echo $object->bar; - - $object::baz(); - echo $object::$dolor; - } - - /** - * @param object|null $objectOrNull - */ - public function doBar($objectOrNull) - { - $objectOrNull->foo(); - echo $objectOrNull->bar; - - $objectOrNull::baz(); - echo $objectOrNull::$dolor; - } - - /** - * @param object|int $objectOrInt - */ - public function doBaz($objectOrInt) - { - $objectOrInt->foo(); - echo $objectOrInt->bar; - - $objectOrInt::baz(); - echo $objectOrInt::$dolor; - } - + /** + * @param object $object + */ + public function doFoo($object) + { + $object->foo(); + echo $object->bar; + + $object::baz(); + echo $object::$dolor; + } + + /** + * @param object|null $objectOrNull + */ + public function doBar($objectOrNull) + { + $objectOrNull->foo(); + echo $objectOrNull->bar; + + $objectOrNull::baz(); + echo $objectOrNull::$dolor; + } + + /** + * @param object|int $objectOrInt + */ + public function doBaz($objectOrInt) + { + $objectOrInt->foo(); + echo $objectOrInt->bar; + + $objectOrInt::baz(); + echo $objectOrInt::$dolor; + } } diff --git a/tests/PHPStan/Levels/data/print_.php b/tests/PHPStan/Levels/data/print_.php index ba2fe5265e..5d09da38c8 100644 --- a/tests/PHPStan/Levels/data/print_.php +++ b/tests/PHPStan/Levels/data/print_.php @@ -6,21 +6,21 @@ class Foo { - /** - * @param mixed[] $array - * @param mixed[]|(callable(): mixed) $arrayOrCallable - * @param mixed[]|float|int $arrayOrFloatOrInt - * @param mixed[]|string $arrayOrString - */ - public function doFoo( - array $array, - $arrayOrCallable, - $arrayOrFloatOrInt, - $arrayOrString - ): void { - print $array; - print $arrayOrCallable; - print $arrayOrFloatOrInt; - print $arrayOrString; - } + /** + * @param mixed[] $array + * @param mixed[]|(callable(): mixed) $arrayOrCallable + * @param mixed[]|float|int $arrayOrFloatOrInt + * @param mixed[]|string $arrayOrString + */ + public function doFoo( + array $array, + $arrayOrCallable, + $arrayOrFloatOrInt, + $arrayOrString + ): void { + print $array; + print $arrayOrCallable; + print $arrayOrFloatOrInt; + print $arrayOrString; + } } diff --git a/tests/PHPStan/Levels/data/propertyAccesses.php b/tests/PHPStan/Levels/data/propertyAccesses.php index 72c9d4bcda..aa8b344bfe 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses.php +++ b/tests/PHPStan/Levels/data/propertyAccesses.php @@ -4,173 +4,158 @@ class Foo { - - /** @var self */ - public $foo; - - public function doFoo(int $i) - { - $foo = $this->foo; - echo $this->bar; - - $foo = new self(); - $foo = $foo->foo; - echo $foo->bar; - } - + /** @var self */ + public $foo; + + public function doFoo(int $i) + { + $foo = $this->foo; + echo $this->bar; + + $foo = new self(); + $foo = $foo->foo; + echo $foo->bar; + } } class Bar { - - /** @var self */ - public static $bar; - - public static function doBar(int $i) - { - $bar = Bar::$bar; - echo Lorem::$bar; - - $bar = new Bar(); - $bar = $bar::$bar; - echo $bar::$foo; - } - + /** @var self */ + public static $bar; + + public static function doBar(int $i) + { + $bar = Bar::$bar; + echo Lorem::$bar; + + $bar = new Bar(); + $bar = $bar::$bar; + echo $bar::$foo; + } } class Baz { - - /** - * @param Foo|Bar $fooOrBar - * @param Foo|null $fooOrNull - * @param Foo|Bar|null $fooOrBarOrNull - * @param Bar|Baz $barOrBaz - */ - public function doBaz( - $fooOrBar, - ?Foo $fooOrNull, - $fooOrBarOrNull, - $barOrBaz - ) - { - $foo = $fooOrBar->foo; - $bar =$fooOrBar->bar; - - $foo = $fooOrNull->foo; - $bar = $fooOrNull->bar; - - $foo = $fooOrBarOrNull->foo; - $bar = $fooOrBarOrNull->bar; - - $foo = $barOrBaz->foo; - } - + /** + * @param Foo|Bar $fooOrBar + * @param Foo|null $fooOrNull + * @param Foo|Bar|null $fooOrBarOrNull + * @param Bar|Baz $barOrBaz + */ + public function doBaz( + $fooOrBar, + ?Foo $fooOrNull, + $fooOrBarOrNull, + $barOrBaz + ) { + $foo = $fooOrBar->foo; + $bar =$fooOrBar->bar; + + $foo = $fooOrNull->foo; + $bar = $fooOrNull->bar; + + $foo = $fooOrBarOrNull->foo; + $bar = $fooOrBarOrNull->bar; + + $foo = $barOrBaz->foo; + } } class ClassWithMagicMethod { - - public function doFoo() - { - $this->test = 'test'; - } - - /** - * @param string $name - * @param mixed $value - */ - public function __set(string $name, $value) - { - - } - + public function doFoo() + { + $this->test = 'test'; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set(string $name, $value) + { + } } class AnotherClassWithMagicMethod { - - public function doFoo() - { - echo $this->test; - } - - public function __get(string $name) - { - - } - + public function doFoo() + { + echo $this->test; + } + + public function __get(string $name) + { + } } class Ipsum { - - /** - * @return Foo|Bar - */ - private function makeFooOrBar() - { - if (rand(0, 1) === 0) { - return new Foo(); - } else { - return new Bar(); - } - } - - /** - * @return Foo|null - */ - private function makeFooOrNull() - { - if (rand(0, 1) === 0) { - return new Foo(); - } else { - return null; - } - } - - /** - * @return Foo|Bar|null - */ - public function makeFooOrBarOrNull() - { - if (rand(0, 1) === 0) { - return new Foo(); - } elseif (rand(0, 1) === 1) { - return new Bar(); - } else { - return null; - } - } - - /** - * @return Bar|Baz - */ - public function makeBarOrBaz() - { - if (rand(0, 1) === 0) { - return new Bar(); - } else { - return new Baz(); - } - } - - public function doBaz() - { - $fooOrBar = $this->makeFooOrBar(); - $foo = $fooOrBar->foo; - $bar =$fooOrBar->bar; - - $fooOrNull = $this->makeFooOrNull(); - $foo = $fooOrNull->foo; - $bar = $fooOrNull->bar; - - $fooOrBarOrNull = $this->makeFooOrBarOrNull(); - $foo = $fooOrBarOrNull->foo; - $bar = $fooOrBarOrNull->bar; - - $barOrBaz = $this->makeBarOrBaz(); - $foo = $barOrBaz->foo; - } - + /** + * @return Foo|Bar + */ + private function makeFooOrBar() + { + if (rand(0, 1) === 0) { + return new Foo(); + } else { + return new Bar(); + } + } + + /** + * @return Foo|null + */ + private function makeFooOrNull() + { + if (rand(0, 1) === 0) { + return new Foo(); + } else { + return null; + } + } + + /** + * @return Foo|Bar|null + */ + public function makeFooOrBarOrNull() + { + if (rand(0, 1) === 0) { + return new Foo(); + } elseif (rand(0, 1) === 1) { + return new Bar(); + } else { + return null; + } + } + + /** + * @return Bar|Baz + */ + public function makeBarOrBaz() + { + if (rand(0, 1) === 0) { + return new Bar(); + } else { + return new Baz(); + } + } + + public function doBaz() + { + $fooOrBar = $this->makeFooOrBar(); + $foo = $fooOrBar->foo; + $bar =$fooOrBar->bar; + + $fooOrNull = $this->makeFooOrNull(); + $foo = $fooOrNull->foo; + $bar = $fooOrNull->bar; + + $fooOrBarOrNull = $this->makeFooOrBarOrNull(); + $foo = $fooOrBarOrNull->foo; + $bar = $fooOrBarOrNull->bar; + + $barOrBaz = $this->makeBarOrBaz(); + $foo = $barOrBaz->foo; + } } diff --git a/tests/PHPStan/Levels/data/returnTypes.php b/tests/PHPStan/Levels/data/returnTypes.php index dd5d17a951..0932b92a57 100644 --- a/tests/PHPStan/Levels/data/returnTypes.php +++ b/tests/PHPStan/Levels/data/returnTypes.php @@ -4,88 +4,83 @@ class Foo { + /** + * @param int $i + * @param float $j + * @param int|string $k + * @param float|string $l + * @param int|null $m + * @return int + */ + public function doFoo( + int $i, + float $j, + $k, + $l, + ?int $m + ) { + if (rand(0, 1)) { + return $i; + } + if (rand(0, 1)) { + return $j; + } + if (rand(0, 1)) { + return $k; + } + if (rand(0, 1)) { + return $l; + } + if (rand(0, 1)) { + return $m; + } + if (rand(0, 1)) { + return; + } + } - /** - * @param int $i - * @param float $j - * @param int|string $k - * @param float|string $l - * @param int|null $m - * @return int - */ - public function doFoo( - int $i, - float $j, - $k, - $l, - ?int $m - ) - { - if (rand(0, 1)) { - return $i; - } - if (rand(0, 1)) { - return $j; - } - if (rand(0, 1)) { - return $k; - } - if (rand(0, 1)) { - return $l; - } - if (rand(0, 1)) { - return $m; - } - if (rand(0, 1)) { - return; - } - } - - /** - * @param int $i - * @param float $j - * @param int|string $k - * @param float|string $l - * @param int|null $m - * @return void - */ - public function doBar( - int $i, - float $j, - $k, - $l, - ?int $m - ) - { - if (rand(0, 1)) { - return $i; - } - if (rand(0, 1)) { - return $j; - } - if (rand(0, 1)) { - return $k; - } - if (rand(0, 1)) { - return $l; - } - if (rand(0, 1)) { - return $m; - } - if (rand(0, 1)) { - return; - } - } - - /** - * @param array $array - * @return string[]|null - */ - public function returnArrayOrNull( - $array - ): ?array - { - return $array; - } + /** + * @param int $i + * @param float $j + * @param int|string $k + * @param float|string $l + * @param int|null $m + * @return void + */ + public function doBar( + int $i, + float $j, + $k, + $l, + ?int $m + ) { + if (rand(0, 1)) { + return $i; + } + if (rand(0, 1)) { + return $j; + } + if (rand(0, 1)) { + return $k; + } + if (rand(0, 1)) { + return $l; + } + if (rand(0, 1)) { + return $m; + } + if (rand(0, 1)) { + return; + } + } + /** + * @param array $array + * @return string[]|null + */ + public function returnArrayOrNull( + $array + ): ?array { + return $array; + } } diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess.php b/tests/PHPStan/Levels/data/stringOffsetAccess.php index afed1e9694..efe015ba7a 100644 --- a/tests/PHPStan/Levels/data/stringOffsetAccess.php +++ b/tests/PHPStan/Levels/data/stringOffsetAccess.php @@ -3,50 +3,50 @@ namespace Levels\OffsetAccess; function () { - /** @var int|object $maybeInt */ - $maybeInt = null; + /** @var int|object $maybeInt */ + $maybeInt = null; - $string = 'foo'; - echo $string[0]; + $string = 'foo'; + echo $string[0]; - $string = 'foo'; - echo $string['foo']; + $string = 'foo'; + echo $string['foo']; - $string = 'foo'; - echo $string[12.34]; + $string = 'foo'; + echo $string[12.34]; - $string = 'foo'; - echo $string[$maybeInt]; + $string = 'foo'; + echo $string[$maybeInt]; - /** @var string|array $maybeString */ - $maybeString = []; - echo $maybeString[0]; + /** @var string|array $maybeString */ + $maybeString = []; + echo $maybeString[0]; - /** @var string|array $maybeString */ - $maybeString = []; - echo $maybeString['foo']; + /** @var string|array $maybeString */ + $maybeString = []; + echo $maybeString['foo']; - /** @var string|array $maybeString */ - $maybeString = []; - echo $maybeString[12.34]; + /** @var string|array $maybeString */ + $maybeString = []; + echo $maybeString[12.34]; - /** @var string|array $maybeString */ - $maybeString = []; - echo $maybeString[$maybeInt]; + /** @var string|array $maybeString */ + $maybeString = []; + echo $maybeString[$maybeInt]; - /** @var mixed $mixed */ - $mixed = null; - echo $mixed[0]; + /** @var mixed $mixed */ + $mixed = null; + echo $mixed[0]; - /** @var mixed $mixed */ - $mixed = null; - echo $mixed['foo']; + /** @var mixed $mixed */ + $mixed = null; + echo $mixed['foo']; - /** @var mixed $mixed */ - $mixed = null; - echo $mixed[12.34]; + /** @var mixed $mixed */ + $mixed = null; + echo $mixed[12.34]; - /** @var mixed $mixed */ - $mixed = null; - echo $mixed[$maybeInt]; + /** @var mixed $mixed */ + $mixed = null; + echo $mixed[$maybeInt]; }; diff --git a/tests/PHPStan/Levels/data/stubValidator/stubs.php b/tests/PHPStan/Levels/data/stubValidator/stubs.php index 140ecbb6d1..aa2d3c6c1a 100644 --- a/tests/PHPStan/Levels/data/stubValidator/stubs.php +++ b/tests/PHPStan/Levels/data/stubValidator/stubs.php @@ -6,30 +6,22 @@ class Foo implements Countable { - - public function count(): int - { - return 1; - } - - public function doFoo(array $argument) - { - - } - + public function count(): int + { + return 1; + } + + public function doFoo(array $argument) + { + } } function someFunction(array $argument) { - } -new class () extends \ArrayIterator -{ - - public function doFoo(Foooooooo $foo) - { - - } - +new class() extends \ArrayIterator { + public function doFoo(Foooooooo $foo) + { + } }; diff --git a/tests/PHPStan/Levels/data/stubs-functions.php b/tests/PHPStan/Levels/data/stubs-functions.php index 4eef6a9cb4..6be531261a 100644 --- a/tests/PHPStan/Levels/data/stubs-functions.php +++ b/tests/PHPStan/Levels/data/stubs-functions.php @@ -4,11 +4,11 @@ function foo($i) { - return ''; + return ''; } function () { - foo('test'); - $string = foo(1); - foo($string); + foo('test'); + $string = foo(1); + foo($string); }; diff --git a/tests/PHPStan/Levels/data/stubs-methods.php b/tests/PHPStan/Levels/data/stubs-methods.php index 7c993519c7..bc41cfb8c9 100644 --- a/tests/PHPStan/Levels/data/stubs-methods.php +++ b/tests/PHPStan/Levels/data/stubs-methods.php @@ -4,224 +4,188 @@ class Foo { - - public function doFoo($i) - { - return ''; - } - + public function doFoo($i) + { + return ''; + } } function (Foo $foo) { - $string = $foo->doFoo('test'); - $foo->doFoo($string); + $string = $foo->doFoo('test'); + $foo->doFoo($string); }; class FooChild extends Foo { - - public function doFoo($i) - { - return ''; - } - + public function doFoo($i) + { + return ''; + } } function (FooChild $fooChild) { - $string = $fooChild->doFoo('test'); - $fooChild->doFoo($string); + $string = $fooChild->doFoo('test'); + $fooChild->doFoo($string); }; interface InterfaceWithStubPhpDoc { - - /** - * @return string - */ - public function doFoo(); - + /** + * @return string + */ + public function doFoo(); } -function (InterfaceWithStubPhpDoc $stub): int -{ - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (InterfaceWithStubPhpDoc $stub): int { + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; interface InterfaceExtendingInterfaceWithStubPhpDoc extends InterfaceWithStubPhpDoc { - } -function (InterfaceExtendingInterfaceWithStubPhpDoc $stub): int -{ - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (InterfaceExtendingInterfaceWithStubPhpDoc $stub): int { + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; interface AnotherInterfaceExtendingInterfaceWithStubPhpDoc extends InterfaceWithStubPhpDoc { - - /** - * @return string - */ - public function doFoo(); - + /** + * @return string + */ + public function doFoo(); } -function (AnotherInterfaceExtendingInterfaceWithStubPhpDoc $stub): int -{ - return $stub->doFoo(); // implementation wins - string -> int mismatch reported +function (AnotherInterfaceExtendingInterfaceWithStubPhpDoc $stub): int { + return $stub->doFoo(); // implementation wins - string -> int mismatch reported }; class ClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - - public function doFoo() - { - throw new \Exception(); - } - + public function doFoo() + { + throw new \Exception(); + } } -function (ClassExtendingInterfaceWithStubPhpDoc $stub): int -{ - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (ClassExtendingInterfaceWithStubPhpDoc $stub): int { + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; class AnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - - /** - * @return string - */ - public function doFoo() - { - throw new \Exception(); - } - + /** + * @return string + */ + public function doFoo() + { + throw new \Exception(); + } } -function (AnotherClassExtendingInterfaceWithStubPhpDoc $stub): int -{ - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (AnotherClassExtendingInterfaceWithStubPhpDoc $stub): int { + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; /** This one is missing in the stubs */ class YetAnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - - /** - * @return string - */ - public function doFoo() - { - throw new \Exception(); - } - + /** + * @return string + */ + public function doFoo() + { + throw new \Exception(); + } } -function (YetAnotherClassExtendingInterfaceWithStubPhpDoc $stub): int -{ - return $stub->doFoo(); // implementation wins - string -> int mismatch reported +function (YetAnotherClassExtendingInterfaceWithStubPhpDoc $stub): int { + return $stub->doFoo(); // implementation wins - string -> int mismatch reported }; class YetYetAnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - - public function doFoo() - { - throw new \Exception(); - } - + public function doFoo() + { + throw new \Exception(); + } } -function (YetYetAnotherClassExtendingInterfaceWithStubPhpDoc $stub): int -{ - // return int should be inherited - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (YetYetAnotherClassExtendingInterfaceWithStubPhpDoc $stub): int { + // return int should be inherited + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; interface InterfaceWithStubPhpDoc2 { - - public function doFoo(); - + public function doFoo(); } -function (InterfaceWithStubPhpDoc2 $stub): int -{ - // return int should be inherited - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (InterfaceWithStubPhpDoc2 $stub): int { + // return int should be inherited + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; class YetYetAnotherClassExtendingInterfaceWithStubPhpDoc2 implements InterfaceWithStubPhpDoc2 { - - public function doFoo() - { - throw new \Exception(); - } - + public function doFoo() + { + throw new \Exception(); + } } -function (YetYetAnotherClassExtendingInterfaceWithStubPhpDoc2 $stub): int -{ - // return int should be inherited - $stub->doFoo() === []; - return $stub->doFoo(); // stub wins +function (YetYetAnotherClassExtendingInterfaceWithStubPhpDoc2 $stub): int { + // return int should be inherited + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins }; class AnotherFooChild extends Foo { - - public function doFoo($j) - { - return ''; - } - + public function doFoo($j) + { + return ''; + } } function (AnotherFooChild $foo): void { - $string = $foo->doFoo('test'); - $foo->doFoo($string); + $string = $foo->doFoo('test'); + $foo->doFoo($string); }; class YetAnotherFoo { - - public function doFoo($j) - { - return ''; - } - + public function doFoo($j) + { + return ''; + } } function (YetAnotherFoo $foo): void { - $string = $foo->doFoo('test'); - $foo->doFoo($string); + $string = $foo->doFoo('test'); + $foo->doFoo($string); }; class YetYetAnotherFoo { - - /** - * Deliberately wrong phpDoc - * @param \stdClass $j - * @return \stdClass - */ - public function doFoo($j) - { - return ''; - } - + /** + * Deliberately wrong phpDoc + * @param \stdClass $j + * @return \stdClass + */ + public function doFoo($j) + { + return ''; + } } function (YetYetAnotherFoo $foo): void { - $string = $foo->doFoo('test'); - $foo->doFoo($string); + $string = $foo->doFoo('test'); + $foo->doFoo($string); }; diff --git a/tests/PHPStan/Levels/data/stubs/doctrineCollection.php b/tests/PHPStan/Levels/data/stubs/doctrineCollection.php index 10f3ab8516..879ebd79c1 100644 --- a/tests/PHPStan/Levels/data/stubs/doctrineCollection.php +++ b/tests/PHPStan/Levels/data/stubs/doctrineCollection.php @@ -12,20 +12,18 @@ */ interface Collection extends \Countable, \IteratorAggregate, \ArrayAccess { - - /** - * Partitions this collection in two collections according to a predicate. - * Keys are preserved in the resulting collections. - * - * @param Closure $p The predicate on which to partition. - * - * @return Collection[] An array with two elements. The first element contains the collection - * of elements where the predicate returned TRUE, the second element - * contains the collection of elements where the predicate returned FALSE. - * - * @psalm-param Closure(TKey=, T=):bool $p - * @psalm-return array{0: Collection, 1: Collection} - */ - public function partition(Closure $p); - + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * + * @return Collection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * + * @psalm-param Closure(TKey=, T=):bool $p + * @psalm-return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); } diff --git a/tests/PHPStan/Levels/data/stubs/function.php b/tests/PHPStan/Levels/data/stubs/function.php index 3eae51f885..6dfc4b16af 100644 --- a/tests/PHPStan/Levels/data/stubs/function.php +++ b/tests/PHPStan/Levels/data/stubs/function.php @@ -8,5 +8,5 @@ */ function foo($i) { - return ''; + return ''; } diff --git a/tests/PHPStan/Levels/data/throwValues.php b/tests/PHPStan/Levels/data/throwValues.php index e2d2a530d8..79b2226304 100644 --- a/tests/PHPStan/Levels/data/throwValues.php +++ b/tests/PHPStan/Levels/data/throwValues.php @@ -4,60 +4,55 @@ class InvalidException { - } interface InvalidInterfaceException { - } interface ValidInterfaceException extends \Throwable { - } class Foo { - - /** - * @param ValidInterfaceException $validInterface - * @param InvalidInterfaceException $invalidInterface - * @param \Exception|null $nullableException - * @param \Throwable|int $throwableOrInt - * @param int|string $intOrString - */ - public function doFoo( - ValidInterfaceException $validInterface, - InvalidInterfaceException $invalidInterface, - ?\Exception $nullableException, - $throwableOrInt, - $intOrString - ) { - if (rand(0, 1)) { - throw new \Exception(); - } - if (rand(0, 1)) { - throw $validInterface; - } - if (rand(0, 1)) { - throw 123; - } - if (rand(0, 1)) { - throw new InvalidException(); - } - if (rand(0, 1)) { - throw $invalidInterface; - } - if (rand(0, 1)) { - throw $nullableException; - } - if (rand(0, 1)) { - throw $throwableOrInt; - } - if (rand(0, 1)) { - throw $intOrString; - } - } - + /** + * @param ValidInterfaceException $validInterface + * @param InvalidInterfaceException $invalidInterface + * @param \Exception|null $nullableException + * @param \Throwable|int $throwableOrInt + * @param int|string $intOrString + */ + public function doFoo( + ValidInterfaceException $validInterface, + InvalidInterfaceException $invalidInterface, + ?\Exception $nullableException, + $throwableOrInt, + $intOrString + ) { + if (rand(0, 1)) { + throw new \Exception(); + } + if (rand(0, 1)) { + throw $validInterface; + } + if (rand(0, 1)) { + throw 123; + } + if (rand(0, 1)) { + throw new InvalidException(); + } + if (rand(0, 1)) { + throw $invalidInterface; + } + if (rand(0, 1)) { + throw $nullableException; + } + if (rand(0, 1)) { + throw $throwableOrInt; + } + if (rand(0, 1)) { + throw $intOrString; + } + } } diff --git a/tests/PHPStan/Levels/data/typehints.php b/tests/PHPStan/Levels/data/typehints.php index 30f3849041..c131a92df1 100644 --- a/tests/PHPStan/Levels/data/typehints.php +++ b/tests/PHPStan/Levels/data/typehints.php @@ -4,19 +4,17 @@ class Foo { + public function doFoo(Lorem $lorem): Ipsum + { + return new Ipsum(); + } - public function doFoo(Lorem $lorem): Ipsum - { - return new Ipsum(); - } - - /** - * @param Lorem $lorem - * @return Ipsum - */ - public function doBar($lorem) - { - return new Ipsum(); - } - + /** + * @param Lorem $lorem + * @return Ipsum + */ + public function doBar($lorem) + { + return new Ipsum(); + } } diff --git a/tests/PHPStan/Levels/data/unreachable.php b/tests/PHPStan/Levels/data/unreachable.php index 74c02956da..9c0be0484d 100644 --- a/tests/PHPStan/Levels/data/unreachable.php +++ b/tests/PHPStan/Levels/data/unreachable.php @@ -4,102 +4,86 @@ class Foo { - - public function doStrictComparison() - { - $a = 5; - if ($a === 5) { - - } else { - - } - } - - public function doInstanceOf() - { - if ($this instanceof self) { - - } else { - - } - } - - public function doTypeSpecifyingFunction(string $s) - { - if (is_string($s)) { - - } else { - - } - } - - public function doOtherFunction() - { - if (print_r('foo')) { - - } else { - - } - } - - public function doOtherValue() - { - if (true) { - - } else { - - } - } - - public function doBooleanAnd() - { - $foo = 1; - $bar = 2; - - if ($foo && $bar) { - - } else { - - } - } - + public function doStrictComparison() + { + $a = 5; + if ($a === 5) { + } else { + } + } + + public function doInstanceOf() + { + if ($this instanceof self) { + } else { + } + } + + public function doTypeSpecifyingFunction(string $s) + { + if (is_string($s)) { + } else { + } + } + + public function doOtherFunction() + { + if (print_r('foo')) { + } else { + } + } + + public function doOtherValue() + { + if (true) { + } else { + } + } + + public function doBooleanAnd() + { + $foo = 1; + $bar = 2; + + if ($foo && $bar) { + } else { + } + } } class Bar { - - public function doStrictComparison() - { - $a = 5; - $a === 5 ? 'foo' : 'bar'; - } - - public function doInstanceOf() - { - $this instanceof self ? 'foo' : 'bar'; - } - - public function doTypeSpecifyingFunction(string $s) - { - is_string($s) ? 'foo' : 'bar'; - } - - public function doOtherFunction() - { - print_r('foo') ? 'foo' : 'bar'; - } - - public function doOtherValue() - { - true ? 'foo' : 'bar'; - } - - public function doBooleanAnd() - { - $foo = 1; - $bar = 2; - - $foo && $bar ? 'foo' : 'bar'; - } - + public function doStrictComparison() + { + $a = 5; + $a === 5 ? 'foo' : 'bar'; + } + + public function doInstanceOf() + { + $this instanceof self ? 'foo' : 'bar'; + } + + public function doTypeSpecifyingFunction(string $s) + { + is_string($s) ? 'foo' : 'bar'; + } + + public function doOtherFunction() + { + print_r('foo') ? 'foo' : 'bar'; + } + + public function doOtherValue() + { + true ? 'foo' : 'bar'; + } + + public function doBooleanAnd() + { + $foo = 1; + $bar = 2; + + $foo && $bar ? 'foo' : 'bar'; + } } diff --git a/tests/PHPStan/Levels/data/variables.php b/tests/PHPStan/Levels/data/variables.php index 8b79c1e2dd..0eff19b777 100644 --- a/tests/PHPStan/Levels/data/variables.php +++ b/tests/PHPStan/Levels/data/variables.php @@ -3,23 +3,20 @@ namespace Levels\Variables; function (int $foo) { - echo $foo; - echo $bar; - if (rand(0, 1)) { - $baz = true; - } + echo $foo; + echo $bar; + if (rand(0, 1)) { + $baz = true; + } - echo $baz; + echo $baz; - if (isset($foo)) { + if (isset($foo)) { + } - } + if (isset($bar)) { + } - if (isset($bar)) { - - } - - if (isset($baz)) { - - } + if (isset($baz)) { + } }; diff --git a/tests/PHPStan/Node/FileNodeTest.php b/tests/PHPStan/Node/FileNodeTest.php index 07faa2dc3e..d370863f41 100644 --- a/tests/PHPStan/Node/FileNodeTest.php +++ b/tests/PHPStan/Node/FileNodeTest.php @@ -1,4 +1,6 @@ -getNodes(); - $pathHelper = new SimpleRelativePathHelper(__DIR__ . DIRECTORY_SEPARATOR . 'data'); - if (!isset($nodes[0])) { - return [ - RuleErrorBuilder::message(sprintf('File %s is empty.', $pathHelper->getRelativePath($scope->getFile())))->line(1)->build(), - ]; - } - - return [ - RuleErrorBuilder::message( - sprintf('First node in file %s is: %s', $pathHelper->getRelativePath($scope->getFile()), get_class($nodes[0])) - )->build(), - ]; - } - - }; - } + /** + * @param \PHPStan\Node\FileNode $node + * @param \PHPStan\Analyser\Scope $scope + * @return \PHPStan\Rules\RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + $nodes = $node->getNodes(); + $pathHelper = new SimpleRelativePathHelper(__DIR__ . DIRECTORY_SEPARATOR . 'data'); + if (!isset($nodes[0])) { + return [ + RuleErrorBuilder::message(sprintf('File %s is empty.', $pathHelper->getRelativePath($scope->getFile())))->line(1)->build(), + ]; + } - public function dataRule(): iterable - { - yield [ - __DIR__ . '/data/empty.php', - 'File empty.php is empty.', - 1, - ]; + return [ + RuleErrorBuilder::message( + sprintf('First node in file %s is: %s', $pathHelper->getRelativePath($scope->getFile()), get_class($nodes[0])) + )->build(), + ]; + } + }; + } - yield [ - __DIR__ . '/data/declare.php', - 'First node in file declare.php is: PhpParser\Node\Stmt\Declare_', - 1, - ]; + public function dataRule(): iterable + { + yield [ + __DIR__ . '/data/empty.php', + 'File empty.php is empty.', + 1, + ]; - yield [ - __DIR__ . '/data/namespace.php', - 'First node in file namespace.php is: PhpParser\Node\Stmt\Namespace_', - 3, - ]; - } + yield [ + __DIR__ . '/data/declare.php', + 'First node in file declare.php is: PhpParser\Node\Stmt\Declare_', + 1, + ]; - /** - * @dataProvider dataRule - * @param string $file - * @param string $expectedError - * @param int $line - */ - public function testRule(string $file, string $expectedError, int $line): void - { - $this->analyse([$file], [ - [ - $expectedError, - $line, - ], - ]); - } + yield [ + __DIR__ . '/data/namespace.php', + 'First node in file namespace.php is: PhpParser\Node\Stmt\Namespace_', + 3, + ]; + } + /** + * @dataProvider dataRule + * @param string $file + * @param string $expectedError + * @param int $line + */ + public function testRule(string $file, string $expectedError, int $line): void + { + $this->analyse([$file], [ + [ + $expectedError, + $line, + ], + ]); + } } diff --git a/tests/PHPStan/Node/data/declare.php b/tests/PHPStan/Node/data/declare.php index 3c6b265174..174d7fd709 100644 --- a/tests/PHPStan/Node/data/declare.php +++ b/tests/PHPStan/Node/data/declare.php @@ -1 +1,3 @@ -normalizePath(__DIR__ . '/data/trait-definition.php'); - $this->assertJsonStringEqualsJsonString(Json::encode([ - 'totals' => [ - 'errors' => 0, - 'file_errors' => 3, - ], - 'files' => [ - sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Bar)', $filePath) => [ - 'errors' => 1, - 'messages' => [ - [ - 'message' => 'Method ParallelAnalyserIntegrationTest\\Bar::doFoo() has no return typehint specified.', - 'line' => 8, - 'ignorable' => true, - ], - ], - ], - sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Foo)', $filePath) => [ - 'errors' => 2, - 'messages' => [ - [ - 'message' => 'Method ParallelAnalyserIntegrationTest\\Foo::doFoo() has no return typehint specified.', - 'line' => 8, - 'ignorable' => true, - ], - [ - 'message' => 'Access to an undefined property ParallelAnalyserIntegrationTest\\Foo::$test.', - 'line' => 10, - 'ignorable' => true, - ], - ], - ], - ], - 'errors' => [], - ]), $output); - $this->assertSame(1, $exitCode); - } + exec(sprintf( + '%s %s %s -l 8 -c %s --error-format json --no-progress %s', + escapeshellarg(PHP_BINARY), + escapeshellarg(__DIR__ . '/../../../bin/phpstan'), + $command, + escapeshellarg(__DIR__ . '/parallel-analyser.neon'), + implode(' ', array_map(static function (string $path): string { + return escapeshellarg($path); + }, [ + __DIR__ . '/data/trait-definition.php', + __DIR__ . '/data/traits.php', + ])) + ), $outputLines, $exitCode); + $output = implode("\n", $outputLines); + $fileHelper = new FileHelper(__DIR__); + $filePath = $fileHelper->normalizePath(__DIR__ . '/data/trait-definition.php'); + $this->assertJsonStringEqualsJsonString(Json::encode([ + 'totals' => [ + 'errors' => 0, + 'file_errors' => 3, + ], + 'files' => [ + sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Bar)', $filePath) => [ + 'errors' => 1, + 'messages' => [ + [ + 'message' => 'Method ParallelAnalyserIntegrationTest\\Bar::doFoo() has no return typehint specified.', + 'line' => 8, + 'ignorable' => true, + ], + ], + ], + sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Foo)', $filePath) => [ + 'errors' => 2, + 'messages' => [ + [ + 'message' => 'Method ParallelAnalyserIntegrationTest\\Foo::doFoo() has no return typehint specified.', + 'line' => 8, + 'ignorable' => true, + ], + [ + 'message' => 'Access to an undefined property ParallelAnalyserIntegrationTest\\Foo::$test.', + 'line' => 10, + 'ignorable' => true, + ], + ], + ], + ], + 'errors' => [], + ]), $output); + $this->assertSame(1, $exitCode); + } } diff --git a/tests/PHPStan/Parallel/SchedulerTest.php b/tests/PHPStan/Parallel/SchedulerTest.php index e029e06d9b..06b151d71a 100644 --- a/tests/PHPStan/Parallel/SchedulerTest.php +++ b/tests/PHPStan/Parallel/SchedulerTest.php @@ -1,4 +1,6 @@ - $expectedJobSizes - */ - public function testSchedule( - int $cpuCores, - int $maximumNumberOfProcesses, - int $minimumNumberOfJobsPerProcess, - int $jobSize, - int $numberOfFiles, - int $expectedNumberOfProcesses, - array $expectedJobSizes - ): void - { - $files = array_fill(0, $numberOfFiles, 'file.php'); - $scheduler = new Scheduler($jobSize, $maximumNumberOfProcesses, $minimumNumberOfJobsPerProcess); - $schedule = $scheduler->scheduleWork($cpuCores, $files); - - $this->assertSame($expectedNumberOfProcesses, $schedule->getNumberOfProcesses()); - $jobSizes = array_map(static function (array $job): int { - return count($job); - }, $schedule->getJobs()); - $this->assertSame($expectedJobSizes, $jobSizes); - } + /** + * @dataProvider dataSchedule + * @param int $cpuCores + * @param int $maximumNumberOfProcesses + * @param int $minimumNumberOfJobsPerProcess + * @param int $jobSize + * @param int $numberOfFiles + * @param int $expectedNumberOfProcesses + * @param array $expectedJobSizes + */ + public function testSchedule( + int $cpuCores, + int $maximumNumberOfProcesses, + int $minimumNumberOfJobsPerProcess, + int $jobSize, + int $numberOfFiles, + int $expectedNumberOfProcesses, + array $expectedJobSizes + ): void { + $files = array_fill(0, $numberOfFiles, 'file.php'); + $scheduler = new Scheduler($jobSize, $maximumNumberOfProcesses, $minimumNumberOfJobsPerProcess); + $schedule = $scheduler->scheduleWork($cpuCores, $files); + $this->assertSame($expectedNumberOfProcesses, $schedule->getNumberOfProcesses()); + $jobSizes = array_map(static function (array $job): int { + return count($job); + }, $schedule->getJobs()); + $this->assertSame($expectedJobSizes, $jobSizes); + } } diff --git a/tests/PHPStan/Parallel/data/trait-definition.php b/tests/PHPStan/Parallel/data/trait-definition.php index edf01e73f8..bf7b9bc06e 100644 --- a/tests/PHPStan/Parallel/data/trait-definition.php +++ b/tests/PHPStan/Parallel/data/trait-definition.php @@ -4,10 +4,8 @@ trait FooTrait { - - public function doFoo() - { - $this->test = 1; - } - + public function doFoo() + { + $this->test = 1; + } } diff --git a/tests/PHPStan/Parallel/data/traits.php b/tests/PHPStan/Parallel/data/traits.php index 402a344dd5..2d96f35a85 100644 --- a/tests/PHPStan/Parallel/data/traits.php +++ b/tests/PHPStan/Parallel/data/traits.php @@ -4,17 +4,13 @@ class Foo { - - use FooTrait; - + use FooTrait; } class Bar { + use FooTrait; - use FooTrait; - - /** @var int */ - private $test; - + /** @var int */ + private $test; } diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index a76dd3f97c..2b499a1754 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -1,4 +1,6 @@ -getParserMock(), - $cachedNodesByStringCountMax - ); - - $this->assertEquals( - $cachedNodesByStringCountMax, - $parser->getCachedNodesByStringCountMax() - ); - - // Add strings to cache - for ($i = 0; $i <= $cachedNodesByStringCountMax; $i++) { - $parser->parseString('string' . $i); - } - - $this->assertEquals( - $cachedNodesByStringCountExpected, - $parser->getCachedNodesByStringCount() - ); - - $this->assertCount( - $cachedNodesByStringCountExpected, - $parser->getCachedNodesByString() - ); - } - - public function dataParseFileClearCache(): \Generator - { - yield 'even' => [ - 'cachedNodesByStringCountMax' => 50, - 'cachedNodesByStringCountExpected' => 50, - ]; - - yield 'odd' => [ - 'cachedNodesByStringCountMax' => 51, - 'cachedNodesByStringCountExpected' => 51, - ]; - } - - /** - * @return Parser&\PHPUnit\Framework\MockObject\MockObject - */ - private function getParserMock(): Parser - { - $mock = $this->createMock(Parser::class); - - $mock->method('parseFile')->willReturn([$this->getPhpParserNodeMock()]); - $mock->method('parseString')->willReturn([$this->getPhpParserNodeMock()]); - - return $mock; - } - - /** - * @return \PhpParser\Node&\PHPUnit\Framework\MockObject\MockObject - */ - private function getPhpParserNodeMock(): \PhpParser\Node - { - return $this->createMock(\PhpParser\Node::class); - } - - public function testParseTheSameFileWithDifferentMethod(): void - { - $parser = new CachedParser(self::getContainer()->getService('pathRoutingParser'), 500); - $path = __DIR__ . '/data/test.php'; - $contents = FileReader::read($path); - $stmts = $parser->parseString($contents); - $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertNull($stmts[0]->stmts[0]->getAttribute('parent')); - - $stmts = $parser->parseFile($path); - $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); - - $stmts = $parser->parseString($contents); - $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); - } - + /** + * @dataProvider dataParseFileClearCache + * @param int $cachedNodesByStringCountMax + * @param int $cachedNodesByStringCountExpected + */ + public function testParseFileClearCache( + int $cachedNodesByStringCountMax, + int $cachedNodesByStringCountExpected + ): void { + $parser = new CachedParser( + $this->getParserMock(), + $cachedNodesByStringCountMax + ); + + $this->assertEquals( + $cachedNodesByStringCountMax, + $parser->getCachedNodesByStringCountMax() + ); + + // Add strings to cache + for ($i = 0; $i <= $cachedNodesByStringCountMax; $i++) { + $parser->parseString('string' . $i); + } + + $this->assertEquals( + $cachedNodesByStringCountExpected, + $parser->getCachedNodesByStringCount() + ); + + $this->assertCount( + $cachedNodesByStringCountExpected, + $parser->getCachedNodesByString() + ); + } + + public function dataParseFileClearCache(): \Generator + { + yield 'even' => [ + 'cachedNodesByStringCountMax' => 50, + 'cachedNodesByStringCountExpected' => 50, + ]; + + yield 'odd' => [ + 'cachedNodesByStringCountMax' => 51, + 'cachedNodesByStringCountExpected' => 51, + ]; + } + + /** + * @return Parser&\PHPUnit\Framework\MockObject\MockObject + */ + private function getParserMock(): Parser + { + $mock = $this->createMock(Parser::class); + + $mock->method('parseFile')->willReturn([$this->getPhpParserNodeMock()]); + $mock->method('parseString')->willReturn([$this->getPhpParserNodeMock()]); + + return $mock; + } + + /** + * @return \PhpParser\Node&\PHPUnit\Framework\MockObject\MockObject + */ + private function getPhpParserNodeMock(): \PhpParser\Node + { + return $this->createMock(\PhpParser\Node::class); + } + + public function testParseTheSameFileWithDifferentMethod(): void + { + $parser = new CachedParser(self::getContainer()->getService('pathRoutingParser'), 500); + $path = __DIR__ . '/data/test.php'; + $contents = FileReader::read($path); + $stmts = $parser->parseString($contents); + $this->assertInstanceOf(Namespace_::class, $stmts[0]); + $this->assertNull($stmts[0]->stmts[0]->getAttribute('parent')); + + $stmts = $parser->parseFile($path); + $this->assertInstanceOf(Namespace_::class, $stmts[0]); + $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); + + $stmts = $parser->parseString($contents); + $this->assertInstanceOf(Namespace_::class, $stmts[0]); + $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); + } } diff --git a/tests/PHPStan/Parser/data/test.php b/tests/PHPStan/Parser/data/test.php index a6bee51214..9e78f35af1 100644 --- a/tests/PHPStan/Parser/data/test.php +++ b/tests/PHPStan/Parser/data/test.php @@ -4,5 +4,4 @@ class Foo { - } diff --git a/tests/PHPStan/Php/PhpVersionFactoryTest.php b/tests/PHPStan/Php/PhpVersionFactoryTest.php index ad255411b9..3daec48e5c 100644 --- a/tests/PHPStan/Php/PhpVersionFactoryTest.php +++ b/tests/PHPStan/Php/PhpVersionFactoryTest.php @@ -1,4 +1,6 @@ -create(); - $this->assertSame($expectedVersion, $phpVersion->getVersionId()); - - if ($expectedVersionString === null) { - return; - } + /** + * @dataProvider dataCreate + * @param int|null $versionId + * @param string|null $composerPhpVersion + * @param int $expectedVersion + * @param string|null $expectedVersionString + */ + public function testCreate( + ?int $versionId, + ?string $composerPhpVersion, + int $expectedVersion, + ?string $expectedVersionString + ): void { + $factory = new PhpVersionFactory($versionId, $composerPhpVersion); + $phpVersion = $factory->create(); + $this->assertSame($expectedVersion, $phpVersion->getVersionId()); - $this->assertSame($expectedVersionString, $phpVersion->getVersionString()); - } + if ($expectedVersionString === null) { + return; + } + $this->assertSame($expectedVersionString, $phpVersion->getVersionString()); + } } diff --git a/tests/PHPStan/Process/Runnable/RunnableQueueLoggerStub.php b/tests/PHPStan/Process/Runnable/RunnableQueueLoggerStub.php index 098f2ee4e9..1c6902d312 100644 --- a/tests/PHPStan/Process/Runnable/RunnableQueueLoggerStub.php +++ b/tests/PHPStan/Process/Runnable/RunnableQueueLoggerStub.php @@ -1,24 +1,24 @@ -messages; - } - - public function log(string $message): void - { - $this->messages[] = $message; - } + /** + * @return string[] + */ + public function getMessages(): array + { + return $this->messages; + } + public function log(string $message): void + { + $this->messages[] = $message; + } } diff --git a/tests/PHPStan/Process/Runnable/RunnableQueueTest.php b/tests/PHPStan/Process/Runnable/RunnableQueueTest.php index 06aad9aceb..85187a80a4 100644 --- a/tests/PHPStan/Process/Runnable/RunnableQueueTest.php +++ b/tests/PHPStan/Process/Runnable/RunnableQueueTest.php @@ -1,4 +1,6 @@ -queue($one, 1); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(1, $queue->getRunningSize()); - $one->finish(); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(0, $queue->getRunningSize()); - $this->assertSame([ - 'Queue not full - looking at first item in the queue', - 'Removing top item from queue - new size is 1', - 'Running process 1', - 'Process 1 finished successfully', - 'Queue empty', - ], $logger->getMessages()); - } - - public function testComplexScenario(): void - { - $logger = new RunnableQueueLoggerStub(); - $queue = new RunnableQueue($logger, 8); - - $one = new RunnableStub('1'); - $two = new RunnableStub('2'); - $three = new RunnableStub('3'); - $four = new RunnableStub('4'); - $queue->queue($one, 4); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(4, $queue->getRunningSize()); - - $queue->queue($two, 2); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(6, $queue->getRunningSize()); - $queue->queue($three, 3); - $this->assertSame(3, $queue->getQueueSize()); - $this->assertSame(6, $queue->getRunningSize()); - $queue->queue($four, 4); - $this->assertSame(7, $queue->getQueueSize()); - $this->assertSame(6, $queue->getRunningSize()); - - $one->finish(); - $this->assertSame(4, $queue->getQueueSize()); - $this->assertSame(5, $queue->getRunningSize()); - - $two->finish(); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(7, $queue->getRunningSize()); - - $three->finish(); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(4, $queue->getRunningSize()); - - $four->finish(); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(0, $queue->getRunningSize()); - - $this->assertSame([ - 0 => 'Queue not full - looking at first item in the queue', - 1 => 'Removing top item from queue - new size is 4', - 2 => 'Running process 1', - 3 => 'Queue not full - looking at first item in the queue', - 4 => 'Removing top item from queue - new size is 6', - 5 => 'Running process 2', - 6 => 'Queue not full - looking at first item in the queue', - 7 => 'Canot remote first item from the queue - it has size 3, current queue size is 6, new size would be 9', - 8 => 'Queue not full - looking at first item in the queue', - 9 => 'Canot remote first item from the queue - it has size 3, current queue size is 6, new size would be 9', - 10 => 'Process 1 finished successfully', - 11 => 'Queue not full - looking at first item in the queue', - 12 => 'Removing top item from queue - new size is 5', - 13 => 'Running process 3', - 14 => 'Process 2 finished successfully', - 15 => 'Queue not full - looking at first item in the queue', - 16 => 'Removing top item from queue - new size is 7', - 17 => 'Running process 4', - 18 => 'Process 3 finished successfully', - 19 => 'Queue empty', - 20 => 'Process 4 finished successfully', - 21 => 'Queue empty', - ], $logger->getMessages()); - } - - public function testCancel(): void - { - $logger = new RunnableQueueLoggerStub(); - $queue = new RunnableQueue($logger, 8); - $one = new RunnableStub('1'); - $promise = $queue->queue($one, 4); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(4, $queue->getRunningSize()); - - $promise->then(static function () use ($logger): void { - $logger->log('Should not happen'); - }, static function (\Exception $e) use ($logger): void { - $logger->log(sprintf('Else callback in test called: %s', $e->getMessage())); - }); - $promise->cancel(); - - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(0, $queue->getRunningSize()); - - $this->assertSame([ - 0 => 'Queue not full - looking at first item in the queue', - 1 => 'Removing top item from queue - new size is 4', - 2 => 'Running process 1', - 3 => 'Process 1 finished unsuccessfully: Runnable 1 canceled', - 4 => 'Else callback in test called: Runnable 1 canceled', - 5 => 'Queue empty', - ], $logger->getMessages()); - } - - public function testCancelAll(): void - { - $logger = new RunnableQueueLoggerStub(); - $queue = new RunnableQueue($logger, 6); - $one = new RunnableStub('1'); - $two = new RunnableStub('2'); - $three = new RunnableStub('3'); - $queue->queue($one, 3); - $queue->queue($two, 2); - $queue->queue($three, 3); - - $this->assertSame(3, $queue->getQueueSize()); - $this->assertSame(5, $queue->getRunningSize()); - - $queue->cancelAll(); - $this->assertSame(0, $queue->getQueueSize()); - $this->assertSame(0, $queue->getRunningSize()); - - $this->assertSame([ - 0 => 'Queue not full - looking at first item in the queue', - 1 => 'Removing top item from queue - new size is 3', - 2 => 'Running process 1', - 3 => 'Queue not full - looking at first item in the queue', - 4 => 'Removing top item from queue - new size is 5', - 5 => 'Running process 2', - 6 => 'Queue not full - looking at first item in the queue', - 7 => 'Canot remote first item from the queue - it has size 3, current queue size is 5, new size would be 8', - 8 => 'Process 1 finished unsuccessfully: Runnable 1 canceled', - 9 => 'Queue not full - looking at first item in the queue', - 10 => 'Removing top item from queue - new size is 5', - 11 => 'Running process 3', - 12 => 'Process 3 finished unsuccessfully: Runnable 3 canceled', - 13 => 'Queue empty', - 14 => 'Process 2 finished unsuccessfully: Runnable 2 canceled', - 15 => 'Queue empty', - ], $logger->getMessages()); - } - + public function testQueuedProcessIsRun(): void + { + $logger = new RunnableQueueLoggerStub(); + $queue = new RunnableQueue($logger, 8); + + $one = new RunnableStub('1'); + $queue->queue($one, 1); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(1, $queue->getRunningSize()); + $one->finish(); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(0, $queue->getRunningSize()); + $this->assertSame([ + 'Queue not full - looking at first item in the queue', + 'Removing top item from queue - new size is 1', + 'Running process 1', + 'Process 1 finished successfully', + 'Queue empty', + ], $logger->getMessages()); + } + + public function testComplexScenario(): void + { + $logger = new RunnableQueueLoggerStub(); + $queue = new RunnableQueue($logger, 8); + + $one = new RunnableStub('1'); + $two = new RunnableStub('2'); + $three = new RunnableStub('3'); + $four = new RunnableStub('4'); + $queue->queue($one, 4); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(4, $queue->getRunningSize()); + + $queue->queue($two, 2); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(6, $queue->getRunningSize()); + $queue->queue($three, 3); + $this->assertSame(3, $queue->getQueueSize()); + $this->assertSame(6, $queue->getRunningSize()); + $queue->queue($four, 4); + $this->assertSame(7, $queue->getQueueSize()); + $this->assertSame(6, $queue->getRunningSize()); + + $one->finish(); + $this->assertSame(4, $queue->getQueueSize()); + $this->assertSame(5, $queue->getRunningSize()); + + $two->finish(); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(7, $queue->getRunningSize()); + + $three->finish(); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(4, $queue->getRunningSize()); + + $four->finish(); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(0, $queue->getRunningSize()); + + $this->assertSame([ + 0 => 'Queue not full - looking at first item in the queue', + 1 => 'Removing top item from queue - new size is 4', + 2 => 'Running process 1', + 3 => 'Queue not full - looking at first item in the queue', + 4 => 'Removing top item from queue - new size is 6', + 5 => 'Running process 2', + 6 => 'Queue not full - looking at first item in the queue', + 7 => 'Canot remote first item from the queue - it has size 3, current queue size is 6, new size would be 9', + 8 => 'Queue not full - looking at first item in the queue', + 9 => 'Canot remote first item from the queue - it has size 3, current queue size is 6, new size would be 9', + 10 => 'Process 1 finished successfully', + 11 => 'Queue not full - looking at first item in the queue', + 12 => 'Removing top item from queue - new size is 5', + 13 => 'Running process 3', + 14 => 'Process 2 finished successfully', + 15 => 'Queue not full - looking at first item in the queue', + 16 => 'Removing top item from queue - new size is 7', + 17 => 'Running process 4', + 18 => 'Process 3 finished successfully', + 19 => 'Queue empty', + 20 => 'Process 4 finished successfully', + 21 => 'Queue empty', + ], $logger->getMessages()); + } + + public function testCancel(): void + { + $logger = new RunnableQueueLoggerStub(); + $queue = new RunnableQueue($logger, 8); + $one = new RunnableStub('1'); + $promise = $queue->queue($one, 4); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(4, $queue->getRunningSize()); + + $promise->then(static function () use ($logger): void { + $logger->log('Should not happen'); + }, static function (\Exception $e) use ($logger): void { + $logger->log(sprintf('Else callback in test called: %s', $e->getMessage())); + }); + $promise->cancel(); + + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(0, $queue->getRunningSize()); + + $this->assertSame([ + 0 => 'Queue not full - looking at first item in the queue', + 1 => 'Removing top item from queue - new size is 4', + 2 => 'Running process 1', + 3 => 'Process 1 finished unsuccessfully: Runnable 1 canceled', + 4 => 'Else callback in test called: Runnable 1 canceled', + 5 => 'Queue empty', + ], $logger->getMessages()); + } + + public function testCancelAll(): void + { + $logger = new RunnableQueueLoggerStub(); + $queue = new RunnableQueue($logger, 6); + $one = new RunnableStub('1'); + $two = new RunnableStub('2'); + $three = new RunnableStub('3'); + $queue->queue($one, 3); + $queue->queue($two, 2); + $queue->queue($three, 3); + + $this->assertSame(3, $queue->getQueueSize()); + $this->assertSame(5, $queue->getRunningSize()); + + $queue->cancelAll(); + $this->assertSame(0, $queue->getQueueSize()); + $this->assertSame(0, $queue->getRunningSize()); + + $this->assertSame([ + 0 => 'Queue not full - looking at first item in the queue', + 1 => 'Removing top item from queue - new size is 3', + 2 => 'Running process 1', + 3 => 'Queue not full - looking at first item in the queue', + 4 => 'Removing top item from queue - new size is 5', + 5 => 'Running process 2', + 6 => 'Queue not full - looking at first item in the queue', + 7 => 'Canot remote first item from the queue - it has size 3, current queue size is 5, new size would be 8', + 8 => 'Process 1 finished unsuccessfully: Runnable 1 canceled', + 9 => 'Queue not full - looking at first item in the queue', + 10 => 'Removing top item from queue - new size is 5', + 11 => 'Running process 3', + 12 => 'Process 3 finished unsuccessfully: Runnable 3 canceled', + 13 => 'Queue empty', + 14 => 'Process 2 finished unsuccessfully: Runnable 2 canceled', + 15 => 'Queue empty', + ], $logger->getMessages()); + } } diff --git a/tests/PHPStan/Process/Runnable/RunnableStub.php b/tests/PHPStan/Process/Runnable/RunnableStub.php index d240b3de33..d12275c78f 100644 --- a/tests/PHPStan/Process/Runnable/RunnableStub.php +++ b/tests/PHPStan/Process/Runnable/RunnableStub.php @@ -1,4 +1,6 @@ -name = $name; - $this->deferred = new Deferred(); - } - - public function getName(): string - { - return $this->name; - } - - public function finish(): void - { - $this->deferred->resolve(); - } - - public function run(): CancellablePromiseInterface - { - /** @var CancellablePromiseInterface */ - return $this->deferred->promise(); - } - - public function cancel(): void - { - $this->deferred->reject(new \PHPStan\Process\Runnable\RunnableCanceledException(sprintf('Runnable %s canceled', $this->getName()))); - } - + /** @var string */ + private $name; + + /** @var Deferred */ + private $deferred; + + public function __construct(string $name) + { + $this->name = $name; + $this->deferred = new Deferred(); + } + + public function getName(): string + { + return $this->name; + } + + public function finish(): void + { + $this->deferred->resolve(); + } + + public function run(): CancellablePromiseInterface + { + /** @var CancellablePromiseInterface */ + return $this->deferred->promise(); + } + + public function cancel(): void + { + $this->deferred->reject(new \PHPStan\Process\Runnable\RunnableCanceledException(sprintf('Runnable %s canceled', $this->getName()))); + } } diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php index d585540774..70a755f9c2 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php @@ -1,4 +1,6 @@ - [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomething' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getFooOrBar' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnType' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'mixed', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerStatically' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingStatically' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getFooOrBarStatically' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnTypeStatically' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'static(AnnotationsMethods\Foo)', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getFooOrBarWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnTypeWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'mixed', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getFooOrBarStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnTypeStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'static(AnnotationsMethods\Foo)', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'aStaticMethodThatHasAUniqueReturnTypeInThisClass' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'bool', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'string', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getFooOrBarNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnTypeNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'mixed', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerStaticallyNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingStaticallyNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getFooOrBarStaticallyNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodWithNoReturnTypeStaticallyNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'static(AnnotationsMethods\Foo)', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getFooOrBarWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getIntegerStaticallyWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'int', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingStaticallyWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getFooOrBarStaticallyWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Foo', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'bool|string', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'float|string', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodFromInterface' => [ + 'class' => \AnnotationsMethods\FooInterface::class, + 'returnType' => \AnnotationsMethods\FooInterface::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'publish' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'Aws\Result', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'args', + 'type' => 'array', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + ], + ], + 'rotate' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => 'AnnotationsMethods\Image', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'angle', + 'type' => 'float', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'backgroundColor', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'overridenMethod' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => \AnnotationsMethods\Foo::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'overridenMethodWithAnnotation' => [ + 'class' => \AnnotationsMethods\Foo::class, + 'returnType' => \AnnotationsMethods\Foo::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + ]; + $barMethods = array_merge( + $fooMethods, + [ + 'overridenMethod' => [ + 'class' => \AnnotationsMethods\Bar::class, + 'returnType' => \AnnotationsMethods\Bar::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'overridenMethodWithAnnotation' => [ + 'class' => \AnnotationsMethods\Bar::class, + 'returnType' => \AnnotationsMethods\Bar::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'conflictingMethod' => [ + 'class' => \AnnotationsMethods\Bar::class, + 'returnType' => \AnnotationsMethods\Bar::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + ] + ); + $bazMethods = array_merge( + $barMethods, + [ + 'doSomething' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getIpsum' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'OtherNamespace\Ipsum', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getIpsumStatically' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'OtherNamespace\Ipsum', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getIpsumWithDescription' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'OtherNamespace\Ipsum', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'getIpsumStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'OtherNamespace\Ipsum', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingStatically' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithDescription' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingNoParams' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingStaticallyNoParams' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingStaticallyWithDescriptionNoParams' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => 'void', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'methodFromTrait' => [ + 'class' => \AnnotationsMethods\Baz::class, + 'returnType' => \AnnotationsMethods\BazBaz::class, + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + ] + ); + $bazBazMethods = array_merge( + $bazMethods, + [ + 'getTest' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'OtherNamespace\Test', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getTestStatically' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'OtherNamespace\Test', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getTestWithDescription' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'OtherNamespace\Test', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [], + ], + 'getTestStaticallyWithDescription' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'OtherNamespace\Test', + 'isStatic' => true, + 'isVariadic' => false, + 'parameters' => [], + ], + 'doSomethingWithSpecificScalarParamsWithoutDefault' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'c', + 'type' => 'int', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'd', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithSpecificScalarParamsWithDefault' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'c', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'd', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => true, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithSpecificObjectParamsWithoutDefault' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'OtherNamespace\Ipsum', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'c', + 'type' => 'OtherNamespace\Ipsum', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'd', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithSpecificObjectParamsWithDefault' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'c', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'd', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createCreatesNewVariable(), + 'isOptional' => true, + 'isVariadic' => false, + ], + ], + ], + 'doSomethingWithSpecificVariadicScalarParamsNotNullable' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => true, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => true, + ], + ], + ], + 'doSomethingWithSpecificVariadicScalarParamsNullable' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => true, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'int|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => true, + ], + ], + ], + 'doSomethingWithSpecificVariadicObjectParamsNotNullable' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => true, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'OtherNamespace\Ipsum', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => true, + ], + ], + ], + 'doSomethingWithSpecificVariadicObjectParamsNullable' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => true, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'OtherNamespace\Ipsum|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => true, + ], + ], + ], + 'doSomethingWithComplicatedParameters' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'void', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'a', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'b', + 'type' => 'mixed', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + [ + 'name' => 'c', + 'type' => 'bool|float|int|OtherNamespace\\Test|string', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'd', + 'type' => 'bool|float|int|OtherNamespace\\Test|string|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => true, + 'isVariadic' => false, + ], + ], + ], + 'paramMultipleTypesWithExtraSpaces' => [ + 'class' => \AnnotationsMethods\BazBaz::class, + 'returnType' => 'float|int', + 'isStatic' => false, + 'isVariadic' => false, + 'parameters' => [ + [ + 'name' => 'string', + 'type' => 'string|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + [ + 'name' => 'object', + 'type' => 'OtherNamespace\\Test|null', + 'passedByReference' => PassedByReference::createNo(), + 'isOptional' => false, + 'isVariadic' => false, + ], + ], + ], + ] + ); - public function dataMethods(): array - { - $fooMethods = [ - 'getInteger' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomething' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getFooOrBar' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnType' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'mixed', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerStatically' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingStatically' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getFooOrBarStatically' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnTypeStatically' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'static(AnnotationsMethods\Foo)', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getFooOrBarWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnTypeWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'mixed', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getFooOrBarStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnTypeStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'static(AnnotationsMethods\Foo)', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'aStaticMethodThatHasAUniqueReturnTypeInThisClass' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'bool', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'string', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getFooOrBarNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnTypeNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'mixed', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerStaticallyNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingStaticallyNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getFooOrBarStaticallyNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodWithNoReturnTypeStaticallyNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'static(AnnotationsMethods\Foo)', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getFooOrBarWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getIntegerStaticallyWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'int', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingStaticallyWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getFooOrBarStaticallyWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Foo', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'bool|string', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'float|string', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodFromInterface' => [ - 'class' => \AnnotationsMethods\FooInterface::class, - 'returnType' => \AnnotationsMethods\FooInterface::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'publish' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'Aws\Result', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'args', - 'type' => 'array', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - ], - ], - 'rotate' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => 'AnnotationsMethods\Image', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'angle', - 'type' => 'float', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'backgroundColor', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'overridenMethod' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => \AnnotationsMethods\Foo::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'overridenMethodWithAnnotation' => [ - 'class' => \AnnotationsMethods\Foo::class, - 'returnType' => \AnnotationsMethods\Foo::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - ]; - $barMethods = array_merge( - $fooMethods, - [ - 'overridenMethod' => [ - 'class' => \AnnotationsMethods\Bar::class, - 'returnType' => \AnnotationsMethods\Bar::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'overridenMethodWithAnnotation' => [ - 'class' => \AnnotationsMethods\Bar::class, - 'returnType' => \AnnotationsMethods\Bar::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'conflictingMethod' => [ - 'class' => \AnnotationsMethods\Bar::class, - 'returnType' => \AnnotationsMethods\Bar::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - ] - ); - $bazMethods = array_merge( - $barMethods, - [ - 'doSomething' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getIpsum' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'OtherNamespace\Ipsum', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getIpsumStatically' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'OtherNamespace\Ipsum', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getIpsumWithDescription' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'OtherNamespace\Ipsum', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'getIpsumStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'OtherNamespace\Ipsum', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingStatically' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithDescription' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingNoParams' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingStaticallyNoParams' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingStaticallyWithDescriptionNoParams' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => 'void', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'methodFromTrait' => [ - 'class' => \AnnotationsMethods\Baz::class, - 'returnType' => \AnnotationsMethods\BazBaz::class, - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - ] - ); - $bazBazMethods = array_merge( - $bazMethods, - [ - 'getTest' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'OtherNamespace\Test', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getTestStatically' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'OtherNamespace\Test', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getTestWithDescription' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'OtherNamespace\Test', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [], - ], - 'getTestStaticallyWithDescription' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'OtherNamespace\Test', - 'isStatic' => true, - 'isVariadic' => false, - 'parameters' => [], - ], - 'doSomethingWithSpecificScalarParamsWithoutDefault' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'c', - 'type' => 'int', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'd', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithSpecificScalarParamsWithDefault' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'c', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'd', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => true, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithSpecificObjectParamsWithoutDefault' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'OtherNamespace\Ipsum', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'c', - 'type' => 'OtherNamespace\Ipsum', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'd', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithSpecificObjectParamsWithDefault' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'c', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'd', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createCreatesNewVariable(), - 'isOptional' => true, - 'isVariadic' => false, - ], - ], - ], - 'doSomethingWithSpecificVariadicScalarParamsNotNullable' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => true, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => true, - ], - ], - ], - 'doSomethingWithSpecificVariadicScalarParamsNullable' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => true, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'int|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => true, - ], - ], - ], - 'doSomethingWithSpecificVariadicObjectParamsNotNullable' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => true, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'OtherNamespace\Ipsum', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => true, - ], - ], - ], - 'doSomethingWithSpecificVariadicObjectParamsNullable' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => true, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'OtherNamespace\Ipsum|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => true, - ], - ], - ], - 'doSomethingWithComplicatedParameters' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'void', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'a', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'b', - 'type' => 'mixed', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - [ - 'name' => 'c', - 'type' => 'bool|float|int|OtherNamespace\\Test|string', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'd', - 'type' => 'bool|float|int|OtherNamespace\\Test|string|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => true, - 'isVariadic' => false, - ], - ], - ], - 'paramMultipleTypesWithExtraSpaces' => [ - 'class' => \AnnotationsMethods\BazBaz::class, - 'returnType' => 'float|int', - 'isStatic' => false, - 'isVariadic' => false, - 'parameters' => [ - [ - 'name' => 'string', - 'type' => 'string|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - [ - 'name' => 'object', - 'type' => 'OtherNamespace\\Test|null', - 'passedByReference' => PassedByReference::createNo(), - 'isOptional' => false, - 'isVariadic' => false, - ], - ], - ], - ] - ); - - return [ - [\AnnotationsMethods\Foo::class, $fooMethods], - [\AnnotationsMethods\Bar::class, $barMethods], - [\AnnotationsMethods\Baz::class, $bazMethods], - [\AnnotationsMethods\BazBaz::class, $bazBazMethods], - ]; - } - - /** - * @dataProvider dataMethods - * @param string $className - * @param array $methods - */ - public function testMethods(string $className, array $methods): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - $scope->method('isInClass')->willReturn(true); - $scope->method('getClassReflection')->willReturn($class); - $scope->method('canCallMethod')->willReturn(true); - foreach ($methods as $methodName => $expectedMethodData) { - $this->assertTrue($class->hasMethod($methodName), sprintf('Method %s() not found in class %s.', $methodName, $className)); + return [ + [\AnnotationsMethods\Foo::class, $fooMethods], + [\AnnotationsMethods\Bar::class, $barMethods], + [\AnnotationsMethods\Baz::class, $bazMethods], + [\AnnotationsMethods\BazBaz::class, $bazBazMethods], + ]; + } - $method = $class->getMethod($methodName, $scope); - $selectedParametersAcceptor = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $this->assertSame( - $expectedMethodData['class'], - $method->getDeclaringClass()->getName(), - sprintf('Declaring class of method %s() does not match.', $methodName) - ); - $this->assertSame( - $expectedMethodData['returnType'], - $selectedParametersAcceptor->getReturnType()->describe(VerbosityLevel::precise()), - sprintf('Return type of method %s::%s() does not match', $className, $methodName) - ); - $this->assertSame( - $expectedMethodData['isStatic'], - $method->isStatic(), - sprintf('Scope of method %s::%s() does not match', $className, $methodName) - ); - $this->assertSame( - $expectedMethodData['isVariadic'], - $selectedParametersAcceptor->isVariadic(), - sprintf('Method %s::%s() does not match expected variadicity', $className, $methodName) - ); - $this->assertCount( - count($expectedMethodData['parameters']), - $selectedParametersAcceptor->getParameters(), - sprintf('Method %s::%s() does not match expected count of parameters', $className, $methodName) - ); - foreach ($selectedParametersAcceptor->getParameters() as $i => $parameter) { - $this->assertSame( - $expectedMethodData['parameters'][$i]['name'], - $parameter->getName() - ); - $this->assertSame( - $expectedMethodData['parameters'][$i]['type'], - $parameter->getType()->describe(VerbosityLevel::precise()) - ); - $this->assertTrue( - $expectedMethodData['parameters'][$i]['passedByReference']->equals($parameter->passedByReference()) - ); - $this->assertSame( - $expectedMethodData['parameters'][$i]['isOptional'], - $parameter->isOptional() - ); - $this->assertSame( - $expectedMethodData['parameters'][$i]['isVariadic'], - $parameter->isVariadic() - ); - } - } - } + /** + * @dataProvider dataMethods + * @param string $className + * @param array $methods + */ + public function testMethods(string $className, array $methods): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + $scope->method('isInClass')->willReturn(true); + $scope->method('getClassReflection')->willReturn($class); + $scope->method('canCallMethod')->willReturn(true); + foreach ($methods as $methodName => $expectedMethodData) { + $this->assertTrue($class->hasMethod($methodName), sprintf('Method %s() not found in class %s.', $methodName, $className)); - public function testOverridingNativeMethodsWithAnnotationsDoesNotBreakGetNativeMethod(): void - { - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass(\AnnotationsMethods\Bar::class); - $this->assertTrue($class->hasNativeMethod('overridenMethodWithAnnotation')); - $this->assertInstanceOf(PhpMethodReflection::class, $class->getNativeMethod('overridenMethodWithAnnotation')); - } + $method = $class->getMethod($methodName, $scope); + $selectedParametersAcceptor = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $this->assertSame( + $expectedMethodData['class'], + $method->getDeclaringClass()->getName(), + sprintf('Declaring class of method %s() does not match.', $methodName) + ); + $this->assertSame( + $expectedMethodData['returnType'], + $selectedParametersAcceptor->getReturnType()->describe(VerbosityLevel::precise()), + sprintf('Return type of method %s::%s() does not match', $className, $methodName) + ); + $this->assertSame( + $expectedMethodData['isStatic'], + $method->isStatic(), + sprintf('Scope of method %s::%s() does not match', $className, $methodName) + ); + $this->assertSame( + $expectedMethodData['isVariadic'], + $selectedParametersAcceptor->isVariadic(), + sprintf('Method %s::%s() does not match expected variadicity', $className, $methodName) + ); + $this->assertCount( + count($expectedMethodData['parameters']), + $selectedParametersAcceptor->getParameters(), + sprintf('Method %s::%s() does not match expected count of parameters', $className, $methodName) + ); + foreach ($selectedParametersAcceptor->getParameters() as $i => $parameter) { + $this->assertSame( + $expectedMethodData['parameters'][$i]['name'], + $parameter->getName() + ); + $this->assertSame( + $expectedMethodData['parameters'][$i]['type'], + $parameter->getType()->describe(VerbosityLevel::precise()) + ); + $this->assertTrue( + $expectedMethodData['parameters'][$i]['passedByReference']->equals($parameter->passedByReference()) + ); + $this->assertSame( + $expectedMethodData['parameters'][$i]['isOptional'], + $parameter->isOptional() + ); + $this->assertSame( + $expectedMethodData['parameters'][$i]['isVariadic'], + $parameter->isVariadic() + ); + } + } + } + public function testOverridingNativeMethodsWithAnnotationsDoesNotBreakGetNativeMethod(): void + { + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass(\AnnotationsMethods\Bar::class); + $this->assertTrue($class->hasNativeMethod('overridenMethodWithAnnotation')); + $this->assertInstanceOf(PhpMethodReflection::class, $class->getNativeMethod('overridenMethodWithAnnotation')); + } } diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php index 0291842501..56ba5b3939 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php @@ -1,4 +1,6 @@ - [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Test', + 'writable' => true, + 'readable' => true, + ], + 'otherTestReadOnly' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => false, + 'readable' => true, + ], + 'fooOrBar' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'AnnotationsProperties\Foo', + 'writable' => true, + 'readable' => true, + ], + 'conflictingProperty' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => true, + 'readable' => true, + ], + 'interfaceProperty' => [ + 'class' => \AnnotationsProperties\FooInterface::class, + 'type' => \AnnotationsProperties\FooInterface::class, + 'writable' => true, + 'readable' => true, + ], + 'overridenProperty' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => \AnnotationsProperties\Foo::class, + 'writable' => true, + 'readable' => true, + ], + 'overridenPropertyWithAnnotation' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => \AnnotationsProperties\Foo::class, + 'writable' => true, + 'readable' => true, + ], + ], + ], + [ + \AnnotationsProperties\Bar::class, + [ + 'otherTest' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Test', + 'writable' => true, + 'readable' => true, + ], + 'otherTestReadOnly' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => false, + 'readable' => true, + ], + 'fooOrBar' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'AnnotationsProperties\Foo', + 'writable' => true, + 'readable' => true, + ], + 'conflictingProperty' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => true, + 'readable' => true, + ], + 'overridenProperty' => [ + 'class' => \AnnotationsProperties\Bar::class, + 'type' => \AnnotationsProperties\Bar::class, + 'writable' => true, + 'readable' => true, + ], + 'overridenPropertyWithAnnotation' => [ + 'class' => \AnnotationsProperties\Bar::class, + 'type' => \AnnotationsProperties\Bar::class, + 'writable' => true, + 'readable' => true, + ], + 'conflictingAnnotationProperty' => [ + 'class' => \AnnotationsProperties\Bar::class, + 'type' => \AnnotationsProperties\Bar::class, + 'writable' => true, + 'readable' => true, + ], + ], + ], + [ + \AnnotationsProperties\Baz::class, + [ + 'otherTest' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Test', + 'writable' => true, + 'readable' => true, + ], + 'otherTestReadOnly' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => false, + 'readable' => true, + ], + 'fooOrBar' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'AnnotationsProperties\Foo', + 'writable' => true, + 'readable' => true, + ], + 'conflictingProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Dolor', + 'writable' => true, + 'readable' => true, + ], + 'bazProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Lorem', + 'writable' => true, + 'readable' => true, + ], + 'traitProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\BazBaz', + 'writable' => true, + 'readable' => true, + ], + 'writeOnlyProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Lorem|null', + 'writable' => true, + 'readable' => false, + ], + ], + ], + [ + \AnnotationsProperties\BazBaz::class, + [ + 'otherTest' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Test', + 'writable' => true, + 'readable' => true, + ], + 'otherTestReadOnly' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'OtherNamespace\Ipsum', + 'writable' => false, + 'readable' => true, + ], + 'fooOrBar' => [ + 'class' => \AnnotationsProperties\Foo::class, + 'type' => 'AnnotationsProperties\Foo', + 'writable' => true, + 'readable' => true, + ], + 'conflictingProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Dolor', + 'writable' => true, + 'readable' => true, + ], + 'bazProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Lorem', + 'writable' => true, + 'readable' => true, + ], + 'traitProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\BazBaz', + 'writable' => true, + 'readable' => true, + ], + 'writeOnlyProperty' => [ + 'class' => \AnnotationsProperties\Baz::class, + 'type' => 'AnnotationsProperties\Lorem|null', + 'writable' => true, + 'readable' => false, + ], + 'numericBazBazProperty' => [ + 'class' => \AnnotationsProperties\BazBaz::class, + 'type' => 'float|int', + 'writable' => true, + 'readable' => true, + ], + ], + ], + ]; + } - public function dataProperties(): array - { - return [ - [ - \AnnotationsProperties\Foo::class, - [ - 'otherTest' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Test', - 'writable' => true, - 'readable' => true, - ], - 'otherTestReadOnly' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => false, - 'readable' => true, - ], - 'fooOrBar' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'AnnotationsProperties\Foo', - 'writable' => true, - 'readable' => true, - ], - 'conflictingProperty' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => true, - 'readable' => true, - ], - 'interfaceProperty' => [ - 'class' => \AnnotationsProperties\FooInterface::class, - 'type' => \AnnotationsProperties\FooInterface::class, - 'writable' => true, - 'readable' => true, - ], - 'overridenProperty' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => \AnnotationsProperties\Foo::class, - 'writable' => true, - 'readable' => true, - ], - 'overridenPropertyWithAnnotation' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => \AnnotationsProperties\Foo::class, - 'writable' => true, - 'readable' => true, - ], - ], - ], - [ - \AnnotationsProperties\Bar::class, - [ - 'otherTest' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Test', - 'writable' => true, - 'readable' => true, - ], - 'otherTestReadOnly' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => false, - 'readable' => true, - ], - 'fooOrBar' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'AnnotationsProperties\Foo', - 'writable' => true, - 'readable' => true, - ], - 'conflictingProperty' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => true, - 'readable' => true, - ], - 'overridenProperty' => [ - 'class' => \AnnotationsProperties\Bar::class, - 'type' => \AnnotationsProperties\Bar::class, - 'writable' => true, - 'readable' => true, - ], - 'overridenPropertyWithAnnotation' => [ - 'class' => \AnnotationsProperties\Bar::class, - 'type' => \AnnotationsProperties\Bar::class, - 'writable' => true, - 'readable' => true, - ], - 'conflictingAnnotationProperty' => [ - 'class' => \AnnotationsProperties\Bar::class, - 'type' => \AnnotationsProperties\Bar::class, - 'writable' => true, - 'readable' => true, - ], - ], - ], - [ - \AnnotationsProperties\Baz::class, - [ - 'otherTest' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Test', - 'writable' => true, - 'readable' => true, - ], - 'otherTestReadOnly' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => false, - 'readable' => true, - ], - 'fooOrBar' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'AnnotationsProperties\Foo', - 'writable' => true, - 'readable' => true, - ], - 'conflictingProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Dolor', - 'writable' => true, - 'readable' => true, - ], - 'bazProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Lorem', - 'writable' => true, - 'readable' => true, - ], - 'traitProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\BazBaz', - 'writable' => true, - 'readable' => true, - ], - 'writeOnlyProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Lorem|null', - 'writable' => true, - 'readable' => false, - ], - ], - ], - [ - \AnnotationsProperties\BazBaz::class, - [ - 'otherTest' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Test', - 'writable' => true, - 'readable' => true, - ], - 'otherTestReadOnly' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'OtherNamespace\Ipsum', - 'writable' => false, - 'readable' => true, - ], - 'fooOrBar' => [ - 'class' => \AnnotationsProperties\Foo::class, - 'type' => 'AnnotationsProperties\Foo', - 'writable' => true, - 'readable' => true, - ], - 'conflictingProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Dolor', - 'writable' => true, - 'readable' => true, - ], - 'bazProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Lorem', - 'writable' => true, - 'readable' => true, - ], - 'traitProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\BazBaz', - 'writable' => true, - 'readable' => true, - ], - 'writeOnlyProperty' => [ - 'class' => \AnnotationsProperties\Baz::class, - 'type' => 'AnnotationsProperties\Lorem|null', - 'writable' => true, - 'readable' => false, - ], - 'numericBazBazProperty' => [ - 'class' => \AnnotationsProperties\BazBaz::class, - 'type' => 'float|int', - 'writable' => true, - 'readable' => true, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataProperties - * @param string $className - * @param array $properties - */ - public function testProperties(string $className, array $properties): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - $scope->method('isInClass')->willReturn(true); - $scope->method('getClassReflection')->willReturn($class); - $scope->method('canAccessProperty')->willReturn(true); - foreach ($properties as $propertyName => $expectedPropertyData) { - $this->assertTrue( - $class->hasProperty($propertyName), - sprintf('Class %s does not define property %s.', $className, $propertyName) - ); - - $property = $class->getProperty($propertyName, $scope); - $this->assertSame( - $expectedPropertyData['class'], - $property->getDeclaringClass()->getName(), - sprintf('Declaring class of property $%s does not match.', $propertyName) - ); - $this->assertSame( - $expectedPropertyData['type'], - $property->getReadableType()->describe(VerbosityLevel::precise()), - sprintf('Type of property %s::$%s does not match.', $property->getDeclaringClass()->getName(), $propertyName) - ); - $this->assertSame( - $expectedPropertyData['readable'], - $property->isReadable(), - sprintf('Property %s::$%s readability is not as expected.', $property->getDeclaringClass()->getName(), $propertyName) - ); - $this->assertSame( - $expectedPropertyData['writable'], - $property->isWritable(), - sprintf('Property %s::$%s writability is not as expected.', $property->getDeclaringClass()->getName(), $propertyName) - ); - } - } + /** + * @dataProvider dataProperties + * @param string $className + * @param array $properties + */ + public function testProperties(string $className, array $properties): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + $scope->method('isInClass')->willReturn(true); + $scope->method('getClassReflection')->willReturn($class); + $scope->method('canAccessProperty')->willReturn(true); + foreach ($properties as $propertyName => $expectedPropertyData) { + $this->assertTrue( + $class->hasProperty($propertyName), + sprintf('Class %s does not define property %s.', $className, $propertyName) + ); - public function testOverridingNativePropertiesWithAnnotationsDoesNotBreakGetNativeProperty(): void - { - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass(\AnnotationsProperties\Bar::class); - $this->assertTrue($class->hasNativeProperty('overridenPropertyWithAnnotation')); - $this->assertSame('AnnotationsProperties\Foo', $class->getNativeProperty('overridenPropertyWithAnnotation')->getReadableType()->describe(VerbosityLevel::precise())); - } + $property = $class->getProperty($propertyName, $scope); + $this->assertSame( + $expectedPropertyData['class'], + $property->getDeclaringClass()->getName(), + sprintf('Declaring class of property $%s does not match.', $propertyName) + ); + $this->assertSame( + $expectedPropertyData['type'], + $property->getReadableType()->describe(VerbosityLevel::precise()), + sprintf('Type of property %s::$%s does not match.', $property->getDeclaringClass()->getName(), $propertyName) + ); + $this->assertSame( + $expectedPropertyData['readable'], + $property->isReadable(), + sprintf('Property %s::$%s readability is not as expected.', $property->getDeclaringClass()->getName(), $propertyName) + ); + $this->assertSame( + $expectedPropertyData['writable'], + $property->isWritable(), + sprintf('Property %s::$%s writability is not as expected.', $property->getDeclaringClass()->getName(), $propertyName) + ); + } + } + public function testOverridingNativePropertiesWithAnnotationsDoesNotBreakGetNativeProperty(): void + { + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass(\AnnotationsProperties\Bar::class); + $this->assertTrue($class->hasNativeProperty('overridenPropertyWithAnnotation')); + $this->assertSame('AnnotationsProperties\Foo', $class->getNativeProperty('overridenPropertyWithAnnotation')->getReadableType()->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 6ffdc2fee1..8a8c391780 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -1,4 +1,6 @@ - [ + 'FOO' => null, + ], + 'method' => [ + 'foo' => null, + 'staticFoo' => null, + ], + 'property' => [ + 'foo' => null, + 'staticFoo' => null, + ], + ], + ], + [ + true, + \DeprecatedAnnotations\DeprecatedFoo::class, + 'in 1.0.0.', + [ + 'constant' => [ + 'DEPRECATED_FOO' => 'Deprecated constant.', + ], + 'method' => [ + 'deprecatedFoo' => 'method.', + 'deprecatedStaticFoo' => 'static method.', + ], + 'property' => [ + 'deprecatedFoo' => null, + 'deprecatedStaticFoo' => null, + ], + ], + ], + [ + false, + \DeprecatedAnnotations\FooInterface::class, + null, + [ + 'constant' => [ + 'FOO' => null, + ], + 'method' => [ + 'foo' => null, + 'staticFoo' => null, + ], + ], + ], + [ + true, + \DeprecatedAnnotations\DeprecatedWithMultipleTags::class, + "in Foo 1.1.0 and will be removed in 1.5.0, use\n \\Foo\\Bar\\NotDeprecated instead.", + [ + 'method' => [ + 'deprecatedFoo' => "in Foo 1.1.0, will be removed in Foo 1.5.0, use\n \\Foo\\Bar\\NotDeprecated::replacementFoo() instead.", + ], + ], + ], + ]; + } - public function dataDeprecatedAnnotations(): array - { - return [ - [ - false, - \DeprecatedAnnotations\Foo::class, - null, - [ - 'constant' => [ - 'FOO' => null, - ], - 'method' => [ - 'foo' => null, - 'staticFoo' => null, - ], - 'property' => [ - 'foo' => null, - 'staticFoo' => null, - ], - ], - ], - [ - true, - \DeprecatedAnnotations\DeprecatedFoo::class, - 'in 1.0.0.', - [ - 'constant' => [ - 'DEPRECATED_FOO' => 'Deprecated constant.', - ], - 'method' => [ - 'deprecatedFoo' => 'method.', - 'deprecatedStaticFoo' => 'static method.', - ], - 'property' => [ - 'deprecatedFoo' => null, - 'deprecatedStaticFoo' => null, - ], - ], - ], - [ - false, - \DeprecatedAnnotations\FooInterface::class, - null, - [ - 'constant' => [ - 'FOO' => null, - ], - 'method' => [ - 'foo' => null, - 'staticFoo' => null, - ], - ], - ], - [ - true, - \DeprecatedAnnotations\DeprecatedWithMultipleTags::class, - "in Foo 1.1.0 and will be removed in 1.5.0, use\n \\Foo\\Bar\\NotDeprecated instead.", - [ - 'method' => [ - 'deprecatedFoo' => "in Foo 1.1.0, will be removed in Foo 1.5.0, use\n \\Foo\\Bar\\NotDeprecated::replacementFoo() instead.", - ], - ], - ], - ]; - } - - /** - * @dataProvider dataDeprecatedAnnotations - * @param bool $deprecated - * @param string $className - * @param string|null $classDeprecation - * @param array $deprecatedAnnotations - */ - public function testDeprecatedAnnotations(bool $deprecated, string $className, ?string $classDeprecation, array $deprecatedAnnotations): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - $scope->method('isInClass')->willReturn(true); - $scope->method('getClassReflection')->willReturn($class); - $scope->method('canAccessProperty')->willReturn(true); - - $this->assertSame($deprecated, $class->isDeprecated()); - $this->assertSame($classDeprecation, $class->getDeprecatedDescription()); + /** + * @dataProvider dataDeprecatedAnnotations + * @param bool $deprecated + * @param string $className + * @param string|null $classDeprecation + * @param array $deprecatedAnnotations + */ + public function testDeprecatedAnnotations(bool $deprecated, string $className, ?string $classDeprecation, array $deprecatedAnnotations): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + $scope->method('isInClass')->willReturn(true); + $scope->method('getClassReflection')->willReturn($class); + $scope->method('canAccessProperty')->willReturn(true); - foreach ($deprecatedAnnotations['method'] ?? [] as $methodName => $deprecatedMessage) { - $methodAnnotation = $class->getMethod($methodName, $scope); - $this->assertSame($deprecated, $methodAnnotation->isDeprecated()->yes()); - $this->assertSame($deprecatedMessage, $methodAnnotation->getDeprecatedDescription()); - } + $this->assertSame($deprecated, $class->isDeprecated()); + $this->assertSame($classDeprecation, $class->getDeprecatedDescription()); - foreach ($deprecatedAnnotations['property'] ?? [] as $propertyName => $deprecatedMessage) { - $propertyAnnotation = $class->getProperty($propertyName, $scope); - $this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes()); - $this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription()); - } + foreach ($deprecatedAnnotations['method'] ?? [] as $methodName => $deprecatedMessage) { + $methodAnnotation = $class->getMethod($methodName, $scope); + $this->assertSame($deprecated, $methodAnnotation->isDeprecated()->yes()); + $this->assertSame($deprecatedMessage, $methodAnnotation->getDeprecatedDescription()); + } - foreach ($deprecatedAnnotations['constant'] ?? [] as $constantName => $deprecatedMessage) { - $constantAnnotation = $class->getConstant($constantName); - $this->assertSame($deprecated, $constantAnnotation->isDeprecated()->yes()); - $this->assertSame($deprecatedMessage, $constantAnnotation->getDeprecatedDescription()); - } - } + foreach ($deprecatedAnnotations['property'] ?? [] as $propertyName => $deprecatedMessage) { + $propertyAnnotation = $class->getProperty($propertyName, $scope); + $this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes()); + $this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription()); + } - public function testDeprecatedUserFunctions(): void - { - require_once __DIR__ . '/data/annotations-deprecated.php'; + foreach ($deprecatedAnnotations['constant'] ?? [] as $constantName => $deprecatedMessage) { + $constantAnnotation = $class->getConstant($constantName); + $this->assertSame($deprecated, $constantAnnotation->isDeprecated()->yes()); + $this->assertSame($deprecatedMessage, $constantAnnotation->getDeprecatedDescription()); + } + } - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + public function testDeprecatedUserFunctions(): void + { + require_once __DIR__ . '/data/annotations-deprecated.php'; - $this->assertFalse($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\foo'), null)->isDeprecated()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\deprecatedFoo'), null)->isDeprecated()->yes()); - } + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); - public function testNonDeprecatedNativeFunctions(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $this->assertFalse($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\foo'), null)->isDeprecated()->yes()); + $this->assertTrue($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\deprecatedFoo'), null)->isDeprecated()->yes()); + } - $this->assertFalse($broker->getFunction(new Name('str_replace'), null)->isDeprecated()->yes()); - $this->assertFalse($broker->getFunction(new Name('get_class'), null)->isDeprecated()->yes()); - $this->assertFalse($broker->getFunction(new Name('function_exists'), null)->isDeprecated()->yes()); - } + public function testNonDeprecatedNativeFunctions(): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $this->assertFalse($broker->getFunction(new Name('str_replace'), null)->isDeprecated()->yes()); + $this->assertFalse($broker->getFunction(new Name('get_class'), null)->isDeprecated()->yes()); + $this->assertFalse($broker->getFunction(new Name('function_exists'), null)->isDeprecated()->yes()); + } } diff --git a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php index d876b4765f..6955bf0ebe 100644 --- a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php @@ -1,4 +1,6 @@ - [ + 'foo', + 'staticFoo', + ], + ], + ], + [ + true, + \FinalAnnotations\FinalFoo::class, + [ + 'method' => [ + 'finalFoo', + 'finalStaticFoo', + ], + ], + ], + ]; + } - public function dataFinalAnnotations(): array - { - return [ - [ - false, - \FinalAnnotations\Foo::class, - [ - 'method' => [ - 'foo', - 'staticFoo', - ], - ], - ], - [ - true, - \FinalAnnotations\FinalFoo::class, - [ - 'method' => [ - 'finalFoo', - 'finalStaticFoo', - ], - ], - ], - ]; - } - - /** - * @dataProvider dataFinalAnnotations - * @param bool $final - * @param string $className - * @param array $finalAnnotations - */ - public function testFinalAnnotations(bool $final, string $className, array $finalAnnotations): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - $scope->method('isInClass')->willReturn(true); - $scope->method('getClassReflection')->willReturn($class); - $scope->method('canAccessProperty')->willReturn(true); - - $this->assertSame($final, $class->isFinal()); + /** + * @dataProvider dataFinalAnnotations + * @param bool $final + * @param string $className + * @param array $finalAnnotations + */ + public function testFinalAnnotations(bool $final, string $className, array $finalAnnotations): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + $scope->method('isInClass')->willReturn(true); + $scope->method('getClassReflection')->willReturn($class); + $scope->method('canAccessProperty')->willReturn(true); - foreach ($finalAnnotations['method'] ?? [] as $methodName) { - $methodAnnotation = $class->getMethod($methodName, $scope); - $this->assertSame($final, $methodAnnotation->isFinal()->yes()); - } - } + $this->assertSame($final, $class->isFinal()); - public function testFinalUserFunctions(): void - { - require_once __DIR__ . '/data/annotations-final.php'; + foreach ($finalAnnotations['method'] ?? [] as $methodName) { + $methodAnnotation = $class->getMethod($methodName, $scope); + $this->assertSame($final, $methodAnnotation->isFinal()->yes()); + } + } - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + public function testFinalUserFunctions(): void + { + require_once __DIR__ . '/data/annotations-final.php'; - $this->assertFalse($broker->getFunction(new Name\FullyQualified('FinalAnnotations\foo'), null)->isFinal()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('FinalAnnotations\finalFoo'), null)->isFinal()->yes()); - } + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $this->assertFalse($broker->getFunction(new Name\FullyQualified('FinalAnnotations\foo'), null)->isFinal()->yes()); + $this->assertTrue($broker->getFunction(new Name\FullyQualified('FinalAnnotations\finalFoo'), null)->isFinal()->yes()); + } } diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index 095001f84b..dcf0422e32 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -1,4 +1,6 @@ - [ + 'FOO', + ], + 'method' => [ + 'foo', + 'staticFoo', + ], + 'property' => [ + 'foo', + 'staticFoo', + ], + ], + ], + [ + true, + \InternalAnnotations\InternalFoo::class, + [ + 'constant' => [ + 'INTERNAL_FOO', + ], + 'method' => [ + 'internalFoo', + 'internalStaticFoo', + ], + 'property' => [ + 'internalFoo', + 'internalStaticFoo', + ], + ], + ], + [ + false, + \InternalAnnotations\FooInterface::class, + [ + 'constant' => [ + 'FOO', + ], + 'method' => [ + 'foo', + 'staticFoo', + ], + ], + ], + [ + true, + \InternalAnnotations\InternalFooInterface::class, + [ + 'constant' => [ + 'INTERNAL_FOO', + ], + 'method' => [ + 'internalFoo', + 'internalStaticFoo', + ], + ], + ], + [ + false, + \InternalAnnotations\FooTrait::class, + [ + 'method' => [ + 'foo', + 'staticFoo', + ], + 'property' => [ + 'foo', + 'staticFoo', + ], + ], + ], + [ + true, + \InternalAnnotations\InternalFooTrait::class, + [ + 'method' => [ + 'internalFoo', + 'internalStaticFoo', + ], + 'property' => [ + 'internalFoo', + 'internalStaticFoo', + ], + ], + ], + ]; + } - public function dataInternalAnnotations(): array - { - return [ - [ - false, - \InternalAnnotations\Foo::class, - [ - 'constant' => [ - 'FOO', - ], - 'method' => [ - 'foo', - 'staticFoo', - ], - 'property' => [ - 'foo', - 'staticFoo', - ], - ], - ], - [ - true, - \InternalAnnotations\InternalFoo::class, - [ - 'constant' => [ - 'INTERNAL_FOO', - ], - 'method' => [ - 'internalFoo', - 'internalStaticFoo', - ], - 'property' => [ - 'internalFoo', - 'internalStaticFoo', - ], - ], - ], - [ - false, - \InternalAnnotations\FooInterface::class, - [ - 'constant' => [ - 'FOO', - ], - 'method' => [ - 'foo', - 'staticFoo', - ], - ], - ], - [ - true, - \InternalAnnotations\InternalFooInterface::class, - [ - 'constant' => [ - 'INTERNAL_FOO', - ], - 'method' => [ - 'internalFoo', - 'internalStaticFoo', - ], - ], - ], - [ - false, - \InternalAnnotations\FooTrait::class, - [ - 'method' => [ - 'foo', - 'staticFoo', - ], - 'property' => [ - 'foo', - 'staticFoo', - ], - ], - ], - [ - true, - \InternalAnnotations\InternalFooTrait::class, - [ - 'method' => [ - 'internalFoo', - 'internalStaticFoo', - ], - 'property' => [ - 'internalFoo', - 'internalStaticFoo', - ], - ], - ], - ]; - } - - /** - * @dataProvider dataInternalAnnotations - * @param bool $internal - * @param string $className - * @param array $internalAnnotations - */ - public function testInternalAnnotations(bool $internal, string $className, array $internalAnnotations): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - $scope->method('isInClass')->willReturn(true); - $scope->method('getClassReflection')->willReturn($class); - $scope->method('canAccessProperty')->willReturn(true); - - $this->assertSame($internal, $class->isInternal()); + /** + * @dataProvider dataInternalAnnotations + * @param bool $internal + * @param string $className + * @param array $internalAnnotations + */ + public function testInternalAnnotations(bool $internal, string $className, array $internalAnnotations): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + $scope->method('isInClass')->willReturn(true); + $scope->method('getClassReflection')->willReturn($class); + $scope->method('canAccessProperty')->willReturn(true); - foreach ($internalAnnotations['method'] ?? [] as $methodName) { - $methodAnnotation = $class->getMethod($methodName, $scope); - $this->assertSame($internal, $methodAnnotation->isInternal()->yes()); - } + $this->assertSame($internal, $class->isInternal()); - foreach ($internalAnnotations['property'] ?? [] as $propertyName) { - $propertyAnnotation = $class->getProperty($propertyName, $scope); - $this->assertSame($internal, $propertyAnnotation->isInternal()->yes()); - } + foreach ($internalAnnotations['method'] ?? [] as $methodName) { + $methodAnnotation = $class->getMethod($methodName, $scope); + $this->assertSame($internal, $methodAnnotation->isInternal()->yes()); + } - foreach ($internalAnnotations['constant'] ?? [] as $constantName) { - $constantAnnotation = $class->getConstant($constantName); - $this->assertSame($internal, $constantAnnotation->isInternal()->yes()); - } - } + foreach ($internalAnnotations['property'] ?? [] as $propertyName) { + $propertyAnnotation = $class->getProperty($propertyName, $scope); + $this->assertSame($internal, $propertyAnnotation->isInternal()->yes()); + } - public function testInternalUserFunctions(): void - { - require_once __DIR__ . '/data/annotations-internal.php'; + foreach ($internalAnnotations['constant'] ?? [] as $constantName) { + $constantAnnotation = $class->getConstant($constantName); + $this->assertSame($internal, $constantAnnotation->isInternal()->yes()); + } + } - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + public function testInternalUserFunctions(): void + { + require_once __DIR__ . '/data/annotations-internal.php'; - $this->assertFalse($broker->getFunction(new Name\FullyQualified('InternalAnnotations\foo'), null)->isInternal()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('InternalAnnotations\internalFoo'), null)->isInternal()->yes()); - } + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $this->assertFalse($broker->getFunction(new Name\FullyQualified('InternalAnnotations\foo'), null)->isInternal()->yes()); + $this->assertTrue($broker->getFunction(new Name\FullyQualified('InternalAnnotations\internalFoo'), null)->isInternal()->yes()); + } } diff --git a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php index 4e1de7a891..81c71b9005 100644 --- a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php @@ -1,4 +1,6 @@ - null, - 'throwsRuntime' => \RuntimeException::class, - 'staticThrowsRuntime' => \RuntimeException::class, - - ], - ], - [ - \ThrowsAnnotations\PhpstanFoo::class, - [ - 'withoutThrows' => 'void', - 'throwsRuntime' => \RuntimeException::class, - 'staticThrowsRuntime' => \RuntimeException::class, - - ], - ], - [ - \ThrowsAnnotations\FooInterface::class, - [ - 'withoutThrows' => null, - 'throwsRuntime' => \RuntimeException::class, - 'staticThrowsRuntime' => \RuntimeException::class, - - ], - ], - [ - \ThrowsAnnotations\FooTrait::class, - [ - 'withoutThrows' => null, - 'throwsRuntime' => \RuntimeException::class, - 'staticThrowsRuntime' => \RuntimeException::class, - - ], - ], - [ - \ThrowsAnnotations\BarTrait::class, - [ - 'withoutThrows' => null, - 'throwsRuntime' => \RuntimeException::class, - 'staticThrowsRuntime' => \RuntimeException::class, - - ], - ], - ]; - } - - /** - * @dataProvider dataThrowsAnnotations - * @param string $className - * @param array $throwsAnnotations - */ - public function testThrowsAnnotations(string $className, array $throwsAnnotations): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); - $scope = $this->createMock(Scope::class); - - foreach ($throwsAnnotations as $methodName => $type) { - $methodAnnotation = $class->getMethod($methodName, $scope); - $throwType = $methodAnnotation->getThrowType(); - $this->assertSame($type, $throwType !== null ? $throwType->describe(VerbosityLevel::typeOnly()) : null); - } - } - - public function testThrowsOnUserFunctions(): void - { - require_once __DIR__ . '/data/annotations-throws.php'; - - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - - $this->assertNull($broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\withoutThrows'), null)->getThrowType()); - - $throwType = $broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\throwsRuntime'), null)->getThrowType(); - $this->assertNotNull($throwType); - $this->assertSame(\RuntimeException::class, $throwType->describe(VerbosityLevel::typeOnly())); - } - + public function dataThrowsAnnotations(): array + { + return [ + [ + \ThrowsAnnotations\Foo::class, + [ + 'withoutThrows' => null, + 'throwsRuntime' => \RuntimeException::class, + 'staticThrowsRuntime' => \RuntimeException::class, + + ], + ], + [ + \ThrowsAnnotations\PhpstanFoo::class, + [ + 'withoutThrows' => 'void', + 'throwsRuntime' => \RuntimeException::class, + 'staticThrowsRuntime' => \RuntimeException::class, + + ], + ], + [ + \ThrowsAnnotations\FooInterface::class, + [ + 'withoutThrows' => null, + 'throwsRuntime' => \RuntimeException::class, + 'staticThrowsRuntime' => \RuntimeException::class, + + ], + ], + [ + \ThrowsAnnotations\FooTrait::class, + [ + 'withoutThrows' => null, + 'throwsRuntime' => \RuntimeException::class, + 'staticThrowsRuntime' => \RuntimeException::class, + + ], + ], + [ + \ThrowsAnnotations\BarTrait::class, + [ + 'withoutThrows' => null, + 'throwsRuntime' => \RuntimeException::class, + 'staticThrowsRuntime' => \RuntimeException::class, + + ], + ], + ]; + } + + /** + * @dataProvider dataThrowsAnnotations + * @param string $className + * @param array $throwsAnnotations + */ + public function testThrowsAnnotations(string $className, array $throwsAnnotations): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + $class = $broker->getClass($className); + $scope = $this->createMock(Scope::class); + + foreach ($throwsAnnotations as $methodName => $type) { + $methodAnnotation = $class->getMethod($methodName, $scope); + $throwType = $methodAnnotation->getThrowType(); + $this->assertSame($type, $throwType !== null ? $throwType->describe(VerbosityLevel::typeOnly()) : null); + } + } + + public function testThrowsOnUserFunctions(): void + { + require_once __DIR__ . '/data/annotations-throws.php'; + + /** @var Broker $broker */ + $broker = self::getContainer()->getByType(Broker::class); + + $this->assertNull($broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\withoutThrows'), null)->getThrowType()); + + $throwType = $broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\throwsRuntime'), null)->getThrowType(); + $this->assertNotNull($throwType); + $this->assertSame(\RuntimeException::class, $throwType->describe(VerbosityLevel::typeOnly())); + } } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php b/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php index 0d4db04b06..efeb27e7a3 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php @@ -4,7 +4,6 @@ function foo() { - } /** @@ -12,28 +11,23 @@ function foo() */ function deprecatedFoo() { - } class Foo { + public const FOO = 'foo'; - const FOO = 'foo'; - - public $foo; - - public static $staticFoo; - - public function foo() - { + public $foo; - } + public static $staticFoo; - public static function staticFoo() - { - - } + public function foo() + { + } + public static function staticFoo() + { + } } /** @@ -41,49 +35,43 @@ public static function staticFoo() */ class DeprecatedFoo { - - /** - * @deprecated Deprecated constant. - */ - const DEPRECATED_FOO = 'deprecated_foo'; - - /** - * @deprecated - */ - public $deprecatedFoo; - - /** - * @deprecated - */ - public static $deprecatedStaticFoo; - - /** - * @deprecated method. - */ - public function deprecatedFoo() - { - - } - - /** - * @deprecated static method. - */ - public static function deprecatedStaticFoo() - { - - } - + /** + * @deprecated Deprecated constant. + */ + public const DEPRECATED_FOO = 'deprecated_foo'; + + /** + * @deprecated + */ + public $deprecatedFoo; + + /** + * @deprecated + */ + public static $deprecatedStaticFoo; + + /** + * @deprecated method. + */ + public function deprecatedFoo() + { + } + + /** + * @deprecated static method. + */ + public static function deprecatedStaticFoo() + { + } } interface FooInterface { + public const FOO = 'foo'; - const FOO = 'foo'; - - public function foo(); - - public static function staticFoo(); + public function foo(); + public static function staticFoo(); } /** @@ -91,41 +79,35 @@ public static function staticFoo(); */ interface DeprecatedFooInterface { - - /** - * @deprecated Deprecated constant. - */ - const DEPRECATED_FOO = 'deprecated_foo'; - - /** - * @deprecated Deprecated method. - */ - public function deprecatedFoo(); - - /** - * @deprecated Deprecated static method. - */ - public static function deprecatedStaticFoo(); - + /** + * @deprecated Deprecated constant. + */ + public const DEPRECATED_FOO = 'deprecated_foo'; + + /** + * @deprecated Deprecated method. + */ + public function deprecatedFoo(); + + /** + * @deprecated Deprecated static method. + */ + public static function deprecatedStaticFoo(); } trait FooTrait { + public $foo; - public $foo; - - public static $staticFoo; - - public function foo() - { + public static $staticFoo; - } - - public static function staticFoo() - { - - } + public function foo() + { + } + public static function staticFoo() + { + } } /** @@ -133,33 +115,29 @@ public static function staticFoo() */ trait DeprecatedFooTrait { - - /** - * @deprecated Deprecated trait property. - */ - public $deprecatedFoo; - - /** - * @deprecated Deprecated static trait property. - */ - public static $deprecatedStaticFoo; - - /** - * @deprecated Deprecated trait method. - */ - public function deprecatedFoo() - { - - } - - /** - * @deprecated Deprecated static trait method. - */ - public static function deprecatedStaticFoo() - { - - } - + /** + * @deprecated Deprecated trait property. + */ + public $deprecatedFoo; + + /** + * @deprecated Deprecated static trait property. + */ + public static $deprecatedStaticFoo; + + /** + * @deprecated Deprecated trait method. + */ + public function deprecatedFoo() + { + } + + /** + * @deprecated Deprecated static trait method. + */ + public static function deprecatedStaticFoo() + { + } } /** @@ -172,18 +150,16 @@ public static function deprecatedStaticFoo() */ class DeprecatedWithMultipleTags { - - /** - * Method documentation. - * - * @return string - * Returns a string - * - * @deprecated in Foo 1.1.0, will be removed in Foo 1.5.0, use - * \Foo\Bar\NotDeprecated::replacementFoo() instead. - */ - public function deprecatedFoo() { - - } - + /** + * Method documentation. + * + * @return string + * Returns a string + * + * @deprecated in Foo 1.1.0, will be removed in Foo 1.5.0, use + * \Foo\Bar\NotDeprecated::replacementFoo() instead. + */ + public function deprecatedFoo() + { + } } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-final.php b/tests/PHPStan/Reflection/Annotations/data/annotations-final.php index cb3932500f..d784665cd3 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-final.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-final.php @@ -4,7 +4,6 @@ function foo() { - } /** @@ -12,22 +11,17 @@ function foo() */ function finalFoo() { - } class Foo { + public function foo() + { + } - public function foo() - { - - } - - public static function staticFoo() - { - - } - + public static function staticFoo() + { + } } /** @@ -35,21 +29,17 @@ public static function staticFoo() */ class FinalFoo { - - /** - * @final - */ - public function finalFoo() - { - - } - - /** - * @final - */ - public static function finalStaticFoo() - { - - } - + /** + * @final + */ + public function finalFoo() + { + } + + /** + * @final + */ + public static function finalStaticFoo() + { + } } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-internal.php b/tests/PHPStan/Reflection/Annotations/data/annotations-internal.php index c4c0dd642b..24747da048 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-internal.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-internal.php @@ -4,7 +4,6 @@ function foo() { - } /** @@ -12,28 +11,23 @@ function foo() */ function internalFoo() { - } class Foo { + public const FOO = 'foo'; - const FOO = 'foo'; - - public $foo; - - public static $staticFoo; + public $foo; - public function foo() - { + public static $staticFoo; - } - - public static function staticFoo() - { - - } + public function foo() + { + } + public static function staticFoo() + { + } } /** @@ -41,49 +35,43 @@ public static function staticFoo() */ class InternalFoo { - - /** - * @internal - */ - const INTERNAL_FOO = 'internal_foo'; - - /** - * @internal - */ - public $internalFoo; - - /** - * @internal - */ - public static $internalStaticFoo; - - /** - * @internal - */ - public function internalFoo() - { - - } - - /** - * @internal - */ - public static function internalStaticFoo() - { - - } - + /** + * @internal + */ + public const INTERNAL_FOO = 'internal_foo'; + + /** + * @internal + */ + public $internalFoo; + + /** + * @internal + */ + public static $internalStaticFoo; + + /** + * @internal + */ + public function internalFoo() + { + } + + /** + * @internal + */ + public static function internalStaticFoo() + { + } } interface FooInterface { + public const FOO = 'foo'; - const FOO = 'foo'; - - public function foo(); - - public static function staticFoo(); + public function foo(); + public static function staticFoo(); } /** @@ -91,41 +79,35 @@ public static function staticFoo(); */ interface InternalFooInterface { - - /** - * @internal - */ - const INTERNAL_FOO = 'internal_foo'; - - /** - * @internal - */ - public function internalFoo(); - - /** - * @internal - */ - public static function internalStaticFoo(); - + /** + * @internal + */ + public const INTERNAL_FOO = 'internal_foo'; + + /** + * @internal + */ + public function internalFoo(); + + /** + * @internal + */ + public static function internalStaticFoo(); } trait FooTrait { + public $foo; - public $foo; - - public static $staticFoo; - - public function foo() - { - - } - - public static function staticFoo() - { + public static $staticFoo; - } + public function foo() + { + } + public static function staticFoo() + { + } } /** @@ -133,31 +115,27 @@ public static function staticFoo() */ trait InternalFooTrait { - - /** - * @internal - */ - public $internalFoo; - - /** - * @internal - */ - public static $internalStaticFoo; - - /** - * @internal - */ - public function internalFoo() - { - - } - - /** - * @internal - */ - public static function internalStaticFoo() - { - - } - + /** + * @internal + */ + public $internalFoo; + + /** + * @internal + */ + public static $internalStaticFoo; + + /** + * @internal + */ + public function internalFoo() + { + } + + /** + * @internal + */ + public static function internalStaticFoo() + { + } } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-methods.php b/tests/PHPStan/Reflection/Annotations/data/annotations-methods.php index effb233be9..f45056b2a5 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-methods.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-methods.php @@ -53,12 +53,9 @@ */ class Foo implements FooInterface { - - public function overridenMethodWithAnnotation(): Foo - { - - } - + public function overridenMethodWithAnnotation(): Foo + { + } } /** @@ -67,17 +64,13 @@ public function overridenMethodWithAnnotation(): Foo */ class Bar extends Foo { + public function overridenMethod(): Bar + { + } - public function overridenMethod(): Bar - { - - } - - public function conflictingMethod(): Bar - { - - } - + public function conflictingMethod(): Bar + { + } } /** @@ -100,9 +93,7 @@ public function conflictingMethod(): Bar */ class Baz extends Bar { - - use FooTrait; - + use FooTrait; } /** @@ -129,7 +120,6 @@ class Baz extends Bar */ class BazBaz extends Baz { - } /** @@ -137,7 +127,6 @@ class BazBaz extends Baz */ interface FooInterface { - } /** @@ -145,5 +134,4 @@ interface FooInterface */ trait FooTrait { - } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-properties.php b/tests/PHPStan/Reflection/Annotations/data/annotations-properties.php index fc7363ccfc..6fca94b19f 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-properties.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-properties.php @@ -14,10 +14,8 @@ */ class Foo implements FooInterface { - - /** @var Foo */ - public $overridenPropertyWithAnnotation; - + /** @var Foo */ + public $overridenPropertyWithAnnotation; } /** @@ -26,13 +24,11 @@ class Foo implements FooInterface */ class Bar extends Foo { + /** @var Bar */ + public $overridenProperty; - /** @var Bar */ - public $overridenProperty; - - /** @var Bar */ - public $conflictingAnnotationProperty; - + /** @var Bar */ + public $conflictingAnnotationProperty; } /** @@ -42,9 +38,7 @@ class Bar extends Foo */ class Baz extends Bar { - - use FooTrait; - + use FooTrait; } /** @@ -52,7 +46,6 @@ class Baz extends Bar */ class BazBaz extends Baz { - } /** @@ -60,7 +53,6 @@ class BazBaz extends Baz */ interface FooInterface { - } /** @@ -68,5 +60,4 @@ interface FooInterface */ trait FooTrait { - } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-throws.php b/tests/PHPStan/Reflection/Annotations/data/annotations-throws.php index 779e27840c..34c6b54b24 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-throws.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-throws.php @@ -4,7 +4,6 @@ function withoutThrows() { - } /** @@ -12,115 +11,96 @@ function withoutThrows() */ function throwsRuntime() { - } class Foo { - - public function withoutThrows() - { - - } - - /** - * @throws \RuntimeException - */ - public function throwsRuntime() - { - - } - - /** - * @throws \RuntimeException - */ - public static function staticThrowsRuntime() - { - - } - + public function withoutThrows() + { + } + + /** + * @throws \RuntimeException + */ + public function throwsRuntime() + { + } + + /** + * @throws \RuntimeException + */ + public static function staticThrowsRuntime() + { + } } class PhpstanFoo { - /** - * @throws \RuntimeException - * - * @phpstan-throws void - */ - public function withoutThrows() - { - - } - - /** - * @throws \Exception - * - * @phpstan-throws \RuntimeException - */ - public function throwsRuntime() - { - - } - - /** - * @throws \Exception - * - * @phpstan-throws \RuntimeException - */ - public static function staticThrowsRuntime() - { - - } - + /** + * @throws \RuntimeException + * + * @phpstan-throws void + */ + public function withoutThrows() + { + } + + /** + * @throws \Exception + * + * @phpstan-throws \RuntimeException + */ + public function throwsRuntime() + { + } + + /** + * @throws \Exception + * + * @phpstan-throws \RuntimeException + */ + public static function staticThrowsRuntime() + { + } } interface FooInterface { + public function withoutThrows(); - public function withoutThrows(); - - /** - * @throws \RuntimeException - */ - public function throwsRuntime(); - - /** - * @throws \RuntimeException - */ - public static function staticThrowsRuntime(); + /** + * @throws \RuntimeException + */ + public function throwsRuntime(); + /** + * @throws \RuntimeException + */ + public static function staticThrowsRuntime(); } trait FooTrait { - - public function withoutThrows() - { - - } - - /** - * @throws \RuntimeException - */ - public function throwsRuntime() - { - - } - - /** - * @throws \RuntimeException - */ - public static function staticThrowsRuntime() - { - - } - + public function withoutThrows() + { + } + + /** + * @throws \RuntimeException + */ + public function throwsRuntime() + { + } + + /** + * @throws \RuntimeException + */ + public static function staticThrowsRuntime() + { + } } trait BarTrait { - - use FooTrait; - + use FooTrait; } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php index 6d37cd64fd..b6a8cd1406 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php @@ -1,4 +1,6 @@ -getByType(FileNodesFetcher::class)); - $classReflector = new ClassReflector($locator); - $functionReflector = new FunctionReflector($locator, $classReflector); - $constantReflector = new ConstantReflector($locator, $classReflector); - $aFoo = $classReflector->reflect(AFoo::class); - $this->assertNotNull($aFoo->getFileName()); - $this->assertSame('a.php', basename($aFoo->getFileName())); - - $testFunctionReflection = $functionReflector->reflect('PHPStan\\Reflection\\BetterReflection\\SourceLocator\testFunctionForLocator'); - $this->assertSame(str_replace('\\', '/', __FILE__), $testFunctionReflection->getFileName()); - - $someConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\SOME_CONSTANT'); - $this->assertNotNull($someConstant->getFileName()); - $this->assertSame('a.php', basename($someConstant->getFileName())); - $this->assertSame(1, $someConstant->getValue()); - - $anotherConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\ANOTHER_CONSTANT'); - $this->assertNotNull($anotherConstant->getFileName()); - $this->assertSame('a.php', basename($anotherConstant->getFileName())); - $this->assertSame(2, $anotherConstant->getValue()); - - $doFooFunctionReflection = $functionReflector->reflect('TestSingleFileSourceLocator\\doFoo'); - $this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName()); - $this->assertNotNull($doFooFunctionReflection->getFileName()); - $this->assertSame('a.php', basename($doFooFunctionReflection->getFileName())); - - class_exists(InCondition::class); - $classInCondition = $classReflector->reflect(InCondition::class); - $classInConditionFilename = $classInCondition->getFileName(); - $this->assertNotNull($classInConditionFilename); - $this->assertSame('a.php', basename($classInConditionFilename)); - $this->assertSame(InCondition::class, $classInCondition->getName()); - $this->assertSame(25, $classInCondition->getStartLine()); - $this->assertInstanceOf(ReflectionClass::class, $classInCondition->getParentClass()); - $this->assertSame(AFoo::class, $classInCondition->getParentClass()->getName()); - } - + public function testAutoloadEverythingInFile(): void + { + /** @var FunctionReflector $functionReflector */ + $functionReflector = null; + $locator = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class)); + $classReflector = new ClassReflector($locator); + $functionReflector = new FunctionReflector($locator, $classReflector); + $constantReflector = new ConstantReflector($locator, $classReflector); + $aFoo = $classReflector->reflect(AFoo::class); + $this->assertNotNull($aFoo->getFileName()); + $this->assertSame('a.php', basename($aFoo->getFileName())); + + $testFunctionReflection = $functionReflector->reflect('PHPStan\\Reflection\\BetterReflection\\SourceLocator\testFunctionForLocator'); + $this->assertSame(str_replace('\\', '/', __FILE__), $testFunctionReflection->getFileName()); + + $someConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\SOME_CONSTANT'); + $this->assertNotNull($someConstant->getFileName()); + $this->assertSame('a.php', basename($someConstant->getFileName())); + $this->assertSame(1, $someConstant->getValue()); + + $anotherConstant = $constantReflector->reflect('TestSingleFileSourceLocator\\ANOTHER_CONSTANT'); + $this->assertNotNull($anotherConstant->getFileName()); + $this->assertSame('a.php', basename($anotherConstant->getFileName())); + $this->assertSame(2, $anotherConstant->getValue()); + + $doFooFunctionReflection = $functionReflector->reflect('TestSingleFileSourceLocator\\doFoo'); + $this->assertSame('TestSingleFileSourceLocator\\doFoo', $doFooFunctionReflection->getName()); + $this->assertNotNull($doFooFunctionReflection->getFileName()); + $this->assertSame('a.php', basename($doFooFunctionReflection->getFileName())); + + class_exists(InCondition::class); + $classInCondition = $classReflector->reflect(InCondition::class); + $classInConditionFilename = $classInCondition->getFileName(); + $this->assertNotNull($classInConditionFilename); + $this->assertSame('a.php', basename($classInConditionFilename)); + $this->assertSame(InCondition::class, $classInCondition->getName()); + $this->assertSame(25, $classInCondition->getStartLine()); + $this->assertInstanceOf(ReflectionClass::class, $classInCondition->getParentClass()); + $this->assertSame(AFoo::class, $classInCondition->getParentClass()->getName()); + } } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index cce937f43d..250e456249 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -1,4 +1,6 @@ -getByType(OptimizedDirectorySourceLocatorFactory::class); - $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); - $classReflector = new ClassReflector($locator); - $classReflection = $classReflector->reflect($className); - $this->assertSame($expectedClassName, $classReflection->getName()); - $this->assertNotNull($classReflection->getFileName()); - $this->assertSame($file, basename($classReflection->getFileName())); - } - - public function dataFunctionExists(): array - { - return [ - [ - 'TestDirectorySourceLocator\\doLorem', - 'TestDirectorySourceLocator\\doLorem', - 'a.php', - ], - [ - 'testdirectorysourcelocator\\doLorem', - 'TestDirectorySourceLocator\\doLorem', - 'a.php', - ], - [ - 'doBar', - 'doBar', - 'b.php', - ], - [ - 'doBaz', - 'doBaz', - 'b.php', - ], - [ - 'dobaz', - 'doBaz', - 'b.php', - ], - [ - 'get_smarty', - 'get_smarty', - 'b.php', - ], - [ - 'get_smarty2', - 'get_smarty2', - 'b.php', - ], - ]; - } + /** + * @dataProvider dataClass + * @param string $className + * @param string $file + */ + public function testClass(string $className, string $expectedClassName, string $file): void + { + $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); + $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); + $classReflector = new ClassReflector($locator); + $classReflection = $classReflector->reflect($className); + $this->assertSame($expectedClassName, $classReflection->getName()); + $this->assertNotNull($classReflection->getFileName()); + $this->assertSame($file, basename($classReflection->getFileName())); + } - /** - * @dataProvider dataFunctionExists - * @param string $functionName - * @param string $expectedFunctionName - * @param string $file - */ - public function testFunctionExists(string $functionName, string $expectedFunctionName, string $file): void - { - $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); - $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); - $classReflector = new ClassReflector($locator); - $functionReflector = new FunctionReflector($locator, $classReflector); - $functionReflection = $functionReflector->reflect($functionName); - $this->assertSame($expectedFunctionName, $functionReflection->getName()); - $this->assertNotNull($functionReflection->getFileName()); - $this->assertSame($file, basename($functionReflection->getFileName())); - } + public function dataFunctionExists(): array + { + return [ + [ + 'TestDirectorySourceLocator\\doLorem', + 'TestDirectorySourceLocator\\doLorem', + 'a.php', + ], + [ + 'testdirectorysourcelocator\\doLorem', + 'TestDirectorySourceLocator\\doLorem', + 'a.php', + ], + [ + 'doBar', + 'doBar', + 'b.php', + ], + [ + 'doBaz', + 'doBaz', + 'b.php', + ], + [ + 'dobaz', + 'doBaz', + 'b.php', + ], + [ + 'get_smarty', + 'get_smarty', + 'b.php', + ], + [ + 'get_smarty2', + 'get_smarty2', + 'b.php', + ], + ]; + } - public function dataFunctionDoesNotExist(): array - { - return [ - ['doFoo'], - ['TestDirectorySourceLocator\\doFoo'], - ]; - } + /** + * @dataProvider dataFunctionExists + * @param string $functionName + * @param string $expectedFunctionName + * @param string $file + */ + public function testFunctionExists(string $functionName, string $expectedFunctionName, string $file): void + { + $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); + $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); + $classReflector = new ClassReflector($locator); + $functionReflector = new FunctionReflector($locator, $classReflector); + $functionReflection = $functionReflector->reflect($functionName); + $this->assertSame($expectedFunctionName, $functionReflection->getName()); + $this->assertNotNull($functionReflection->getFileName()); + $this->assertSame($file, basename($functionReflection->getFileName())); + } - /** - * @dataProvider dataFunctionDoesNotExist - * @param string $functionName - */ - public function testFunctionDoesNotExist(string $functionName): void - { - $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); - $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); - $classReflector = new ClassReflector($locator); - $functionReflector = new FunctionReflector($locator, $classReflector); + public function dataFunctionDoesNotExist(): array + { + return [ + ['doFoo'], + ['TestDirectorySourceLocator\\doFoo'], + ]; + } - $this->expectException(IdentifierNotFound::class); - $functionReflector->reflect($functionName); - } + /** + * @dataProvider dataFunctionDoesNotExist + * @param string $functionName + */ + public function testFunctionDoesNotExist(string $functionName): void + { + $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); + $locator = $factory->createByDirectory(__DIR__ . '/data/directory'); + $classReflector = new ClassReflector($locator); + $functionReflector = new FunctionReflector($locator, $classReflector); + $this->expectException(IdentifierNotFound::class); + $functionReflector->reflect($functionName); + } } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php index 38185ccfc8..847a39617f 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php @@ -1,4 +1,6 @@ -getByType(OptimizedSingleFileSourceLocatorFactory::class); - $locator = $factory->create($file); - $classReflector = new ClassReflector($locator); - $classReflection = $classReflector->reflect($className); - $this->assertSame($expectedClassName, $classReflection->getName()); - } - - public function dataFunction(): array - { - return [ - [ - 'TestSingleFileSourceLocator\\doFoo', - 'TestSingleFileSourceLocator\\doFoo', - __DIR__ . '/data/a.php', - ], - [ - 'testSingleFilesourcelocatOR\\dofoo', - 'TestSingleFileSourceLocator\\doFoo', - __DIR__ . '/data/a.php', - ], - [ - 'singleFileSourceLocatorTestFunction', - 'singleFileSourceLocatorTestFunction', - __DIR__ . '/data/b.php', - ], - [ - 'singlefileSourceLocatORTestfunCTion', - 'singleFileSourceLocatorTestFunction', - __DIR__ . '/data/b.php', - ], - ]; - } + /** + * @dataProvider dataClass + * @param string $className + * @param string $expectedClassName + * @param string $file + */ + public function testClass(string $className, string $expectedClassName, string $file): void + { + $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); + $locator = $factory->create($file); + $classReflector = new ClassReflector($locator); + $classReflection = $classReflector->reflect($className); + $this->assertSame($expectedClassName, $classReflection->getName()); + } - /** - * @dataProvider dataFunction - * @param string $functionName - * @param string $expectedFunctionName - * @param string $file - */ - public function testFunction(string $functionName, string $expectedFunctionName, string $file): void - { - $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); - $locator = $factory->create($file); - $classReflector = new ClassReflector($locator); - $functionReflector = new FunctionReflector($locator, $classReflector); - $functionReflection = $functionReflector->reflect($functionName); - $this->assertSame($expectedFunctionName, $functionReflection->getName()); - } + public function dataFunction(): array + { + return [ + [ + 'TestSingleFileSourceLocator\\doFoo', + 'TestSingleFileSourceLocator\\doFoo', + __DIR__ . '/data/a.php', + ], + [ + 'testSingleFilesourcelocatOR\\dofoo', + 'TestSingleFileSourceLocator\\doFoo', + __DIR__ . '/data/a.php', + ], + [ + 'singleFileSourceLocatorTestFunction', + 'singleFileSourceLocatorTestFunction', + __DIR__ . '/data/b.php', + ], + [ + 'singlefileSourceLocatORTestfunCTion', + 'singleFileSourceLocatorTestFunction', + __DIR__ . '/data/b.php', + ], + ]; + } - public function dataConst(): array - { - return [ - [ - 'ConstFile\\TABLE_NAME', - 'resized_images', - ], - [ - 'ANOTHER_NAME', - 'foo_images', - ], - [ - 'ConstFile\\ANOTHER_NAME', - 'bar_images', - ], - [ - 'const_with_dir_const', - str_replace('\\', '/', __DIR__ . '/data'), - ], - ]; - } + /** + * @dataProvider dataFunction + * @param string $functionName + * @param string $expectedFunctionName + * @param string $file + */ + public function testFunction(string $functionName, string $expectedFunctionName, string $file): void + { + $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); + $locator = $factory->create($file); + $classReflector = new ClassReflector($locator); + $functionReflector = new FunctionReflector($locator, $classReflector); + $functionReflection = $functionReflector->reflect($functionName); + $this->assertSame($expectedFunctionName, $functionReflection->getName()); + } - /** - * @dataProvider dataConst - * @param string $constantName - * @param mixed $value - */ - public function testConst(string $constantName, $value): void - { - $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); - $locator = $factory->create(__DIR__ . '/data/const.php'); - $classReflector = new ClassReflector($locator); - $constantReflector = new ConstantReflector($locator, $classReflector); - $constant = $constantReflector->reflect($constantName); - $this->assertSame($constantName, $constant->getName()); - $this->assertSame($value, $constant->getValue()); - } + public function dataConst(): array + { + return [ + [ + 'ConstFile\\TABLE_NAME', + 'resized_images', + ], + [ + 'ANOTHER_NAME', + 'foo_images', + ], + [ + 'ConstFile\\ANOTHER_NAME', + 'bar_images', + ], + [ + 'const_with_dir_const', + str_replace('\\', '/', __DIR__ . '/data'), + ], + ]; + } - public function dataConstUnknown(): array - { - return [ - ['TEST_VARIABLE'], - ]; - } + /** + * @dataProvider dataConst + * @param string $constantName + * @param mixed $value + */ + public function testConst(string $constantName, $value): void + { + $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); + $locator = $factory->create(__DIR__ . '/data/const.php'); + $classReflector = new ClassReflector($locator); + $constantReflector = new ConstantReflector($locator, $classReflector); + $constant = $constantReflector->reflect($constantName); + $this->assertSame($constantName, $constant->getName()); + $this->assertSame($value, $constant->getValue()); + } - /** - * @dataProvider dataConstUnknown - * @param string $constantName - */ - public function testConstUnknown(string $constantName): void - { - $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); - $locator = $factory->create(__DIR__ . '/data/const.php'); - $classReflector = new ClassReflector($locator); - $constantReflector = new ConstantReflector($locator, $classReflector); - $this->expectException(IdentifierNotFound::class); - $constantReflector->reflect($constantName); - } + public function dataConstUnknown(): array + { + return [ + ['TEST_VARIABLE'], + ]; + } + /** + * @dataProvider dataConstUnknown + * @param string $constantName + */ + public function testConstUnknown(string $constantName): void + { + $factory = self::getContainer()->getByType(OptimizedSingleFileSourceLocatorFactory::class); + $locator = $factory->create(__DIR__ . '/data/const.php'); + $classReflector = new ClassReflector($locator); + $constantReflector = new ConstantReflector($locator, $classReflector); + $this->expectException(IdentifierNotFound::class); + $constantReflector->reflect($constantName); + } } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php index aadc4a67ea..5b7b7b57c9 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/a.php @@ -4,12 +4,10 @@ class AFoo { - } function doFoo() { - } define('TestSingleFileSourceLocator\\SOME_CONSTANT', 1); @@ -17,18 +15,15 @@ function doFoo() const ANOTHER_CONSTANT = 2; if (false) { - class InCondition - { - - } + class InCondition + { + } } elseif (true) { - class InCondition extends AFoo - { - - } + class InCondition extends AFoo + { + } } else { - class InCondition extends \stdClass - { - - } + class InCondition extends \stdClass + { + } } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/b.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/b.php index 9e486b6503..2f8e16b12d 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/b.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/b.php @@ -2,10 +2,8 @@ class SingleFileSourceLocatorTestClass { - } function singleFileSourceLocatorTestFunction() { - } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php index eba1d99941..c11b48ee9e 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php @@ -1,4 +1,6 @@ -getReflectors(); - $reflection = $classReflector->reflect(\Throwable::class); - $this->assertSame(\Throwable::class, $reflection->getName()); - } - - public function testFunction(): void - { - /** @var FunctionReflector $functionReflector */ - [, $functionReflector] = $this->getReflectors(); - $reflection = $functionReflector->reflect('htmlspecialchars'); - $this->assertSame('htmlspecialchars', $reflection->getName()); - } - - /** - * @return array{ClassReflector, FunctionReflector} - */ - private function getReflectors(): array - { - // memoizing parser screws things up so we need to create the universe from the start - $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Emulative([ - 'usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'], - ])); - /** @var FunctionReflector $functionReflector */ - $functionReflector = null; - $astLocator = new Locator($parser, static function () use (&$functionReflector): FunctionReflector { - return $functionReflector; - }); - $sourceStubber = new Php8StubsSourceStubber(); - $phpInternalSourceLocator = new PhpInternalSourceLocator( - $astLocator, - $sourceStubber - ); - $classReflector = new ClassReflector($phpInternalSourceLocator); - $functionReflector = new FunctionReflector($phpInternalSourceLocator, $classReflector); - - return [$classReflector, $functionReflector]; - } - + public function testClass(): void + { + /** @var ClassReflector $classReflector */ + [$classReflector] = $this->getReflectors(); + $reflection = $classReflector->reflect(\Throwable::class); + $this->assertSame(\Throwable::class, $reflection->getName()); + } + + public function testFunction(): void + { + /** @var FunctionReflector $functionReflector */ + [, $functionReflector] = $this->getReflectors(); + $reflection = $functionReflector->reflect('htmlspecialchars'); + $this->assertSame('htmlspecialchars', $reflection->getName()); + } + + /** + * @return array{ClassReflector, FunctionReflector} + */ + private function getReflectors(): array + { + // memoizing parser screws things up so we need to create the universe from the start + $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Emulative([ + 'usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'], + ])); + /** @var FunctionReflector $functionReflector */ + $functionReflector = null; + $astLocator = new Locator($parser, static function () use (&$functionReflector): FunctionReflector { + return $functionReflector; + }); + $sourceStubber = new Php8StubsSourceStubber(); + $phpInternalSourceLocator = new PhpInternalSourceLocator( + $astLocator, + $sourceStubber + ); + $classReflector = new ClassReflector($phpInternalSourceLocator); + $functionReflector = new FunctionReflector($phpInternalSourceLocator, $classReflector); + + return [$classReflector, $functionReflector]; + } } diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 25251fc4cf..7f0b8f7d4e 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -1,4 +1,6 @@ -createMock(Broker::class); - $fileTypeMapper = $this->createMock(FileTypeMapper::class); - $classReflection = new ClassReflection($broker, $fileTypeMapper, new PhpVersion(PHP_VERSION_ID), [], [], $className, new \ReflectionClass($className), null, null, null); - $this->assertSame($has, $classReflection->hasTraitUse(\HasTraitUse\FooTrait::class)); - } - - public function dataClassHierarchyDistances(): array - { - return [ - [ - \HierarchyDistances\Lorem::class, - [ - \HierarchyDistances\Lorem::class => 0, - \HierarchyDistances\TraitTwo::class => 1, - \HierarchyDistances\TraitThree::class => 2, - \HierarchyDistances\FirstLoremInterface::class => 3, - \HierarchyDistances\SecondLoremInterface::class => 4, - ], - ], - [ - \HierarchyDistances\Ipsum::class, - PHP_VERSION_ID < 70400 ? - [ - \HierarchyDistances\Ipsum::class => 0, - \HierarchyDistances\TraitOne::class => 1, - \HierarchyDistances\Lorem::class => 2, - \HierarchyDistances\TraitTwo::class => 3, - \HierarchyDistances\TraitThree::class => 4, - \HierarchyDistances\SecondLoremInterface::class => 5, - \HierarchyDistances\FirstLoremInterface::class => 6, - \HierarchyDistances\FirstIpsumInterface::class => 7, - \HierarchyDistances\ExtendedIpsumInterface::class => 8, - \HierarchyDistances\SecondIpsumInterface::class => 9, - \HierarchyDistances\ThirdIpsumInterface::class => 10, - ] - : - [ - \HierarchyDistances\Ipsum::class => 0, - \HierarchyDistances\TraitOne::class => 1, - \HierarchyDistances\Lorem::class => 2, - \HierarchyDistances\TraitTwo::class => 3, - \HierarchyDistances\TraitThree::class => 4, - \HierarchyDistances\FirstLoremInterface::class => 5, - \HierarchyDistances\SecondLoremInterface::class => 6, - \HierarchyDistances\FirstIpsumInterface::class => 7, - \HierarchyDistances\SecondIpsumInterface::class => 8, - \HierarchyDistances\ThirdIpsumInterface::class => 9, - \HierarchyDistances\ExtendedIpsumInterface::class => 10, - ], - ], - ]; - } - - /** - * @dataProvider dataClassHierarchyDistances - * @param class-string $class - * @param int[] $expectedDistances - */ - public function testClassHierarchyDistances( - string $class, - array $expectedDistances - ): void - { - $broker = $this->createReflectionProvider(); - $fileTypeMapper = $this->createMock(FileTypeMapper::class); - - $classReflection = new ClassReflection( - $broker, - $fileTypeMapper, - new PhpVersion(PHP_VERSION_ID), - [], - [], - $class, - new \ReflectionClass($class), - null, - null, - null - ); - $this->assertSame( - $expectedDistances, - $classReflection->getClassHierarchyDistances() - ); - } - - public function testVariadicTraitMethod(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $fooReflection = $broker->getClass(\HasTraitUse\Foo::class); - $variadicMethod = $fooReflection->getNativeMethod('variadicMethod'); - $methodVariant = ParametersAcceptorSelector::selectSingle($variadicMethod->getVariants()); - $this->assertTrue($methodVariant->isVariadic()); - } - - public function testGenericInheritance(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\GenericInheritance\C::class); - - $this->assertSame('GenericInheritance\\C', $reflection->getDisplayName()); - - $parent = $reflection->getParentClass(); - $this->assertNotFalse($parent); - - $this->assertSame('GenericInheritance\\C0', $parent->getDisplayName()); - - $this->assertSame([ - 'GenericInheritance\\I0', - 'GenericInheritance\\I1', - 'GenericInheritance\\I', - ], array_map(static function (ClassReflection $r): string { - return $r->getDisplayName(); - }, array_values($reflection->getInterfaces()))); - } - - public function testGenericInheritanceOverride(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\GenericInheritance\Override::class); - - $this->assertSame([ - 'GenericInheritance\\I0', - 'GenericInheritance\\I1', - 'GenericInheritance\\I', - ], array_map(static function (ClassReflection $r): string { - return $r->getDisplayName(); - }, array_values($reflection->getInterfaces()))); - } - - public function testIsGenericWithStubPhpDoc(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\ReflectionClass::class); - $this->assertTrue($reflection->isGeneric()); - } - - public function dataIsAttributeClass(): array - { - return [ - [ - IsNotAttribute::class, - false, - ], - [ - IsAttribute::class, - true, - ], - [ - IsAttribute2::class, - true, - \Attribute::IS_REPEATABLE, - ], - [ - IsAttribute3::class, - true, - \Attribute::IS_REPEATABLE | \Attribute::TARGET_PROPERTY, - ], - ]; - } - - /** - * @dataProvider dataIsAttributeClass - * @param string $className - * @param bool $expected - */ - public function testIsAttributeClass(string $className, bool $expected, int $expectedFlags = \Attribute::TARGET_ALL): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - $reflectionProvider = $this->createBroker(); - $reflection = $reflectionProvider->getClass($className); - $this->assertSame($expected, $reflection->isAttributeClass()); - if (!$expected) { - return; - } - $this->assertSame($expectedFlags, $reflection->getAttributeClassFlags()); - } - - public function testDeprecatedConstantFromAnotherFile(): void - { - $reflectionProvider = $this->createBroker(); - $reflection = $reflectionProvider->getClass(SecuredRouter::class); - $constant = $reflection->getConstant('SECURED'); - $this->assertTrue($constant->isDeprecated()->yes()); - } - + public function dataHasTraitUse(): array + { + return [ + [\HasTraitUse\Foo::class, true], + [\HasTraitUse\Bar::class, true], + [\HasTraitUse\Baz::class, false], + ]; + } + + /** + * @dataProvider dataHasTraitUse + * @param class-string $className + * @param bool $has + */ + public function testHasTraitUse(string $className, bool $has): void + { + $broker = $this->createMock(Broker::class); + $fileTypeMapper = $this->createMock(FileTypeMapper::class); + $classReflection = new ClassReflection($broker, $fileTypeMapper, new PhpVersion(PHP_VERSION_ID), [], [], $className, new \ReflectionClass($className), null, null, null); + $this->assertSame($has, $classReflection->hasTraitUse(\HasTraitUse\FooTrait::class)); + } + + public function dataClassHierarchyDistances(): array + { + return [ + [ + \HierarchyDistances\Lorem::class, + [ + \HierarchyDistances\Lorem::class => 0, + \HierarchyDistances\TraitTwo::class => 1, + \HierarchyDistances\TraitThree::class => 2, + \HierarchyDistances\FirstLoremInterface::class => 3, + \HierarchyDistances\SecondLoremInterface::class => 4, + ], + ], + [ + \HierarchyDistances\Ipsum::class, + PHP_VERSION_ID < 70400 ? + [ + \HierarchyDistances\Ipsum::class => 0, + \HierarchyDistances\TraitOne::class => 1, + \HierarchyDistances\Lorem::class => 2, + \HierarchyDistances\TraitTwo::class => 3, + \HierarchyDistances\TraitThree::class => 4, + \HierarchyDistances\SecondLoremInterface::class => 5, + \HierarchyDistances\FirstLoremInterface::class => 6, + \HierarchyDistances\FirstIpsumInterface::class => 7, + \HierarchyDistances\ExtendedIpsumInterface::class => 8, + \HierarchyDistances\SecondIpsumInterface::class => 9, + \HierarchyDistances\ThirdIpsumInterface::class => 10, + ] + : + [ + \HierarchyDistances\Ipsum::class => 0, + \HierarchyDistances\TraitOne::class => 1, + \HierarchyDistances\Lorem::class => 2, + \HierarchyDistances\TraitTwo::class => 3, + \HierarchyDistances\TraitThree::class => 4, + \HierarchyDistances\FirstLoremInterface::class => 5, + \HierarchyDistances\SecondLoremInterface::class => 6, + \HierarchyDistances\FirstIpsumInterface::class => 7, + \HierarchyDistances\SecondIpsumInterface::class => 8, + \HierarchyDistances\ThirdIpsumInterface::class => 9, + \HierarchyDistances\ExtendedIpsumInterface::class => 10, + ], + ], + ]; + } + + /** + * @dataProvider dataClassHierarchyDistances + * @param class-string $class + * @param int[] $expectedDistances + */ + public function testClassHierarchyDistances( + string $class, + array $expectedDistances + ): void { + $broker = $this->createReflectionProvider(); + $fileTypeMapper = $this->createMock(FileTypeMapper::class); + + $classReflection = new ClassReflection( + $broker, + $fileTypeMapper, + new PhpVersion(PHP_VERSION_ID), + [], + [], + $class, + new \ReflectionClass($class), + null, + null, + null + ); + $this->assertSame( + $expectedDistances, + $classReflection->getClassHierarchyDistances() + ); + } + + public function testVariadicTraitMethod(): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getService('broker'); + $fooReflection = $broker->getClass(\HasTraitUse\Foo::class); + $variadicMethod = $fooReflection->getNativeMethod('variadicMethod'); + $methodVariant = ParametersAcceptorSelector::selectSingle($variadicMethod->getVariants()); + $this->assertTrue($methodVariant->isVariadic()); + } + + public function testGenericInheritance(): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getService('broker'); + $reflection = $broker->getClass(\GenericInheritance\C::class); + + $this->assertSame('GenericInheritance\\C', $reflection->getDisplayName()); + + $parent = $reflection->getParentClass(); + $this->assertNotFalse($parent); + + $this->assertSame('GenericInheritance\\C0', $parent->getDisplayName()); + + $this->assertSame([ + 'GenericInheritance\\I0', + 'GenericInheritance\\I1', + 'GenericInheritance\\I', + ], array_map(static function (ClassReflection $r): string { + return $r->getDisplayName(); + }, array_values($reflection->getInterfaces()))); + } + + public function testGenericInheritanceOverride(): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getService('broker'); + $reflection = $broker->getClass(\GenericInheritance\Override::class); + + $this->assertSame([ + 'GenericInheritance\\I0', + 'GenericInheritance\\I1', + 'GenericInheritance\\I', + ], array_map(static function (ClassReflection $r): string { + return $r->getDisplayName(); + }, array_values($reflection->getInterfaces()))); + } + + public function testIsGenericWithStubPhpDoc(): void + { + /** @var Broker $broker */ + $broker = self::getContainer()->getService('broker'); + $reflection = $broker->getClass(\ReflectionClass::class); + $this->assertTrue($reflection->isGeneric()); + } + + public function dataIsAttributeClass(): array + { + return [ + [ + IsNotAttribute::class, + false, + ], + [ + IsAttribute::class, + true, + ], + [ + IsAttribute2::class, + true, + \Attribute::IS_REPEATABLE, + ], + [ + IsAttribute3::class, + true, + \Attribute::IS_REPEATABLE | \Attribute::TARGET_PROPERTY, + ], + ]; + } + + /** + * @dataProvider dataIsAttributeClass + * @param string $className + * @param bool $expected + */ + public function testIsAttributeClass(string $className, bool $expected, int $expectedFlags = \Attribute::TARGET_ALL): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $reflectionProvider = $this->createBroker(); + $reflection = $reflectionProvider->getClass($className); + $this->assertSame($expected, $reflection->isAttributeClass()); + if (!$expected) { + return; + } + $this->assertSame($expectedFlags, $reflection->getAttributeClassFlags()); + } + + public function testDeprecatedConstantFromAnotherFile(): void + { + $reflectionProvider = $this->createBroker(); + $reflection = $reflectionProvider->getClass(SecuredRouter::class); + $constant = $reflection->getConstant('SECURED'); + $this->assertTrue($constant->isDeprecated()->yes()); + } } diff --git a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php index 909057cfb5..416d017cd9 100644 --- a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php +++ b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php @@ -1,4 +1,6 @@ - + */ + public function dataResolve(): array + { + $templateType = static function (string $name, ?Type $type = null): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + $type, + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @return array - */ - public function dataResolve(): array - { - $templateType = static function (string $name, ?Type $type = null): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - $type, - TemplateTypeVariance::createInvariant() - ); - }; - - return [ - 'one param, one arg' => [ - [ - new ObjectType('DateTime'), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - ]), - null, - [ - new DummyParameter( - 'a', - new ObjectType('DateTime'), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ], - 'two params, two args, return type' => [ - [ - new ObjectType('DateTime'), - new IntegerType(), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - $templateType('U'), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - $templateType('U') - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - new ObjectType('DateTime'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - new IntegerType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new IntegerType() - ), - ], - 'mixed types' => [ - [ - new ObjectType('DateTime'), - new IntegerType(), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - $templateType('T') - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - ]), - null, - [ - new DummyParameter( - 'a', - new UnionType([ - new ObjectType('DateTime'), - new IntegerType(), - ]), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - new UnionType([ - new ObjectType('DateTime'), - new IntegerType(), - ]), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new UnionType([ - new ObjectType('DateTime'), - new IntegerType(), - ]) - ), - ], - 'parameter default value' => [ - [ - new ObjectType('DateTime'), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - $templateType('U'), - true, - PassedByReference::createNo(), - false, - new IntegerType() - ), - ], - false, - new NullType() - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - new ObjectType('DateTime'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - new IntegerType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ], - 'variadic parameter' => [ - [ - new ObjectType('DateTime'), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - $templateType('U'), - false, - PassedByReference::createNo(), - true, - null - ), - ], - true, - $templateType('U') - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - new ObjectType('DateTime'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - new IntegerType(), - false, - PassedByReference::createNo(), - true, - null - ), - ], - false, - new IntegerType() - ), - ], - 'missing args' => [ - [ - new ObjectType('DateTime'), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - $templateType('T'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - $templateType('U'), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - 'U' => $templateType('U'), - ]), - null, - [ - new DummyParameter( - 'a', - new ObjectType('DateTime'), - false, - PassedByReference::createNo(), - false, - null - ), - new DummyParameter( - 'b', - new MixedType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ], - 'constant string arg resolved to constant string' => [ - [ - new ConstantStringType('foooooo'), - ], - new FunctionVariant( - new TemplateTypeMap([ - 'T' => $templateType('T'), - ]), - null, - [ - new DummyParameter('str', $templateType('T'), false, null, false, null), - ], - false, - $templateType('T') - ), - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter('str', new StringType(), false, null, false, null), - ], - false, - new StringType() - ), - ], - ]; - } - - /** - * @dataProvider dataResolve - * @param \PHPStan\Type\Type[] $argTypes - */ - public function testResolve(array $argTypes, ParametersAcceptor $parametersAcceptor, ParametersAcceptor $expectedResult): void - { - $result = GenericParametersAcceptorResolver::resolve( - $argTypes, - $parametersAcceptor - ); + return [ + 'one param, one arg' => [ + [ + new ObjectType('DateTime'), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + ]), + null, + [ + new DummyParameter( + 'a', + new ObjectType('DateTime'), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ], + 'two params, two args, return type' => [ + [ + new ObjectType('DateTime'), + new IntegerType(), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + $templateType('U'), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + $templateType('U') + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + new ObjectType('DateTime'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + new IntegerType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new IntegerType() + ), + ], + 'mixed types' => [ + [ + new ObjectType('DateTime'), + new IntegerType(), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + $templateType('T') + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + ]), + null, + [ + new DummyParameter( + 'a', + new UnionType([ + new ObjectType('DateTime'), + new IntegerType(), + ]), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + new UnionType([ + new ObjectType('DateTime'), + new IntegerType(), + ]), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new UnionType([ + new ObjectType('DateTime'), + new IntegerType(), + ]) + ), + ], + 'parameter default value' => [ + [ + new ObjectType('DateTime'), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + $templateType('U'), + true, + PassedByReference::createNo(), + false, + new IntegerType() + ), + ], + false, + new NullType() + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + new ObjectType('DateTime'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + new IntegerType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ], + 'variadic parameter' => [ + [ + new ObjectType('DateTime'), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + $templateType('U'), + false, + PassedByReference::createNo(), + true, + null + ), + ], + true, + $templateType('U') + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + new ObjectType('DateTime'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + new IntegerType(), + false, + PassedByReference::createNo(), + true, + null + ), + ], + false, + new IntegerType() + ), + ], + 'missing args' => [ + [ + new ObjectType('DateTime'), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + $templateType('T'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + $templateType('U'), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + 'U' => $templateType('U'), + ]), + null, + [ + new DummyParameter( + 'a', + new ObjectType('DateTime'), + false, + PassedByReference::createNo(), + false, + null + ), + new DummyParameter( + 'b', + new MixedType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ], + 'constant string arg resolved to constant string' => [ + [ + new ConstantStringType('foooooo'), + ], + new FunctionVariant( + new TemplateTypeMap([ + 'T' => $templateType('T'), + ]), + null, + [ + new DummyParameter('str', $templateType('T'), false, null, false, null), + ], + false, + $templateType('T') + ), + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter('str', new StringType(), false, null, false, null), + ], + false, + new StringType() + ), + ], + ]; + } - $this->assertInstanceOf( - get_class($expectedResult->getReturnType()), - $result->getReturnType(), - 'Unexpected return type' - ); - $this->assertSame( - $expectedResult->getReturnType()->describe(VerbosityLevel::precise()), - $result->getReturnType()->describe(VerbosityLevel::precise()), - 'Unexpected return type' - ); + /** + * @dataProvider dataResolve + * @param \PHPStan\Type\Type[] $argTypes + */ + public function testResolve(array $argTypes, ParametersAcceptor $parametersAcceptor, ParametersAcceptor $expectedResult): void + { + $result = GenericParametersAcceptorResolver::resolve( + $argTypes, + $parametersAcceptor + ); - $resultParameters = $result->getParameters(); - $expectedParameters = $expectedResult->getParameters(); + $this->assertInstanceOf( + get_class($expectedResult->getReturnType()), + $result->getReturnType(), + 'Unexpected return type' + ); + $this->assertSame( + $expectedResult->getReturnType()->describe(VerbosityLevel::precise()), + $result->getReturnType()->describe(VerbosityLevel::precise()), + 'Unexpected return type' + ); - $this->assertCount(count($expectedParameters), $resultParameters); + $resultParameters = $result->getParameters(); + $expectedParameters = $expectedResult->getParameters(); - foreach ($expectedParameters as $i => $param) { - $this->assertInstanceOf( - get_class($param->getType()), - $resultParameters[$i]->getType(), - sprintf('Unexpected parameter %d', $i + 1) - ); - $this->assertSame( - $param->getType()->describe(VerbosityLevel::precise()), - $resultParameters[$i]->getType()->describe(VerbosityLevel::precise()), - sprintf('Unexpected parameter %d', $i + 1) - ); - } - } + $this->assertCount(count($expectedParameters), $resultParameters); + foreach ($expectedParameters as $i => $param) { + $this->assertInstanceOf( + get_class($param->getType()), + $resultParameters[$i]->getType(), + sprintf('Unexpected parameter %d', $i + 1) + ); + $this->assertSame( + $param->getType()->describe(VerbosityLevel::precise()), + $resultParameters[$i]->getType()->describe(VerbosityLevel::precise()), + sprintf('Unexpected parameter %d', $i + 1) + ); + } + } } diff --git a/tests/PHPStan/Reflection/MixedTypeTest.php b/tests/PHPStan/Reflection/MixedTypeTest.php index edc0370428..2bff9d6e70 100644 --- a/tests/PHPStan/Reflection/MixedTypeTest.php +++ b/tests/PHPStan/Reflection/MixedTypeTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0'); - } - - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass(Foo::class); - $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); - $this->assertInstanceOf(MixedType::class, $propertyType); - $this->assertTrue($propertyType->isExplicitMixed()); - - $method = $class->getNativeMethod('doFoo'); - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodReturnType = $methodVariant->getReturnType(); - $this->assertInstanceOf(MixedType::class, $methodReturnType); - $this->assertTrue($methodReturnType->isExplicitMixed()); - - $methodParameterType = $methodVariant->getParameters()[0]->getType(); - $this->assertInstanceOf(MixedType::class, $methodParameterType); - $this->assertTrue($methodParameterType->isExplicitMixed()); - - $function = $reflectionProvider->getFunction(new Name('NativeMixedType\doFoo'), null); - $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); - $functionReturnType = $functionVariant->getReturnType(); - $this->assertInstanceOf(MixedType::class, $functionReturnType); - $this->assertTrue($functionReturnType->isExplicitMixed()); - - $functionParameterType = $functionVariant->getParameters()[0]->getType(); - $this->assertInstanceOf(MixedType::class, $functionParameterType); - $this->assertTrue($functionParameterType->isExplicitMixed()); - } - + public function testMixedType(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $reflectionProvider = $this->createBroker(); + $class = $reflectionProvider->getClass(Foo::class); + $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); + $this->assertInstanceOf(MixedType::class, $propertyType); + $this->assertTrue($propertyType->isExplicitMixed()); + + $method = $class->getNativeMethod('doFoo'); + $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodReturnType = $methodVariant->getReturnType(); + $this->assertInstanceOf(MixedType::class, $methodReturnType); + $this->assertTrue($methodReturnType->isExplicitMixed()); + + $methodParameterType = $methodVariant->getParameters()[0]->getType(); + $this->assertInstanceOf(MixedType::class, $methodParameterType); + $this->assertTrue($methodParameterType->isExplicitMixed()); + + $function = $reflectionProvider->getFunction(new Name('NativeMixedType\doFoo'), null); + $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); + $functionReturnType = $functionVariant->getReturnType(); + $this->assertInstanceOf(MixedType::class, $functionReturnType); + $this->assertTrue($functionReturnType->isExplicitMixed()); + + $functionParameterType = $functionVariant->getParameters()[0]->getType(); + $this->assertInstanceOf(MixedType::class, $functionParameterType); + $this->assertTrue($functionParameterType->isExplicitMixed()); + } } diff --git a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php index ecf59410e3..450f2220dc 100644 --- a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php +++ b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php @@ -1,4 +1,6 @@ -createBroker(); - public function dataSelectFromTypes(): \Generator - { - require_once __DIR__ . '/data/function-definitions.php'; - $broker = $this->createBroker(); - - $arrayRandVariants = $broker->getFunction(new Name('array_rand'), null)->getVariants(); - yield [ - [ - new ArrayType(new MixedType(), new MixedType()), - new IntegerType(), - ], - $arrayRandVariants, - false, - $arrayRandVariants[0], - ]; - - yield [ - [ - new ArrayType(new MixedType(), new MixedType()), - ], - $arrayRandVariants, - false, - $arrayRandVariants[1], - ]; + $arrayRandVariants = $broker->getFunction(new Name('array_rand'), null)->getVariants(); + yield [ + [ + new ArrayType(new MixedType(), new MixedType()), + new IntegerType(), + ], + $arrayRandVariants, + false, + $arrayRandVariants[0], + ]; - $datePeriodConstructorVariants = $broker->getClass('DatePeriod')->getNativeMethod('__construct')->getVariants(); - yield [ - [ - new ObjectType(\DateTimeInterface::class), - new ObjectType(\DateInterval::class), - new IntegerType(), - new IntegerType(), - ], - $datePeriodConstructorVariants, - false, - $datePeriodConstructorVariants[0], - ]; - yield [ - [ - new ObjectType(\DateTimeInterface::class), - new ObjectType(\DateInterval::class), - new ObjectType(\DateTimeInterface::class), - new IntegerType(), - ], - $datePeriodConstructorVariants, - false, - $datePeriodConstructorVariants[1], - ]; - yield [ - [ - new StringType(), - new IntegerType(), - ], - $datePeriodConstructorVariants, - false, - $datePeriodConstructorVariants[2], - ]; + yield [ + [ + new ArrayType(new MixedType(), new MixedType()), + ], + $arrayRandVariants, + false, + $arrayRandVariants[1], + ]; - $ibaseWaitEventVariants = $broker->getFunction(new Name('ibase_wait_event'), null)->getVariants(); - yield [ - [ - new ResourceType(), - ], - $ibaseWaitEventVariants, - false, - $ibaseWaitEventVariants[0], - ]; - yield [ - [ - new StringType(), - ], - $ibaseWaitEventVariants, - false, - $ibaseWaitEventVariants[1], - ]; - yield [ - [ - new StringType(), - new StringType(), - new StringType(), - new StringType(), - new StringType(), - ], - $ibaseWaitEventVariants, - false, - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new NativeParameterReflection( - 'link_identifier|event', - false, - new MixedType(), - PassedByReference::createNo(), - false, - null - ), - new NativeParameterReflection( - 'event|args', - true, - new MixedType(), - PassedByReference::createNo(), - true, - null - ), - ], - true, - new StringType() - ), - ]; + $datePeriodConstructorVariants = $broker->getClass('DatePeriod')->getNativeMethod('__construct')->getVariants(); + yield [ + [ + new ObjectType(\DateTimeInterface::class), + new ObjectType(\DateInterval::class), + new IntegerType(), + new IntegerType(), + ], + $datePeriodConstructorVariants, + false, + $datePeriodConstructorVariants[0], + ]; + yield [ + [ + new ObjectType(\DateTimeInterface::class), + new ObjectType(\DateInterval::class), + new ObjectType(\DateTimeInterface::class), + new IntegerType(), + ], + $datePeriodConstructorVariants, + false, + $datePeriodConstructorVariants[1], + ]; + yield [ + [ + new StringType(), + new IntegerType(), + ], + $datePeriodConstructorVariants, + false, + $datePeriodConstructorVariants[2], + ]; - $absVariants = $broker->getFunction(new Name('abs'), null)->getVariants(); - yield [ - [ - new FloatType(), - new FloatType(), - ], - $absVariants, - false, - ParametersAcceptorSelector::combineAcceptors($absVariants), - ]; - yield [ - [ - new FloatType(), - new IntegerType(), - new StringType(), - ], - $absVariants, - false, - ParametersAcceptorSelector::combineAcceptors($absVariants), - ]; - yield [ - [ - new StringType(), - ], - $absVariants, - false, - $absVariants[2], - ]; + $ibaseWaitEventVariants = $broker->getFunction(new Name('ibase_wait_event'), null)->getVariants(); + yield [ + [ + new ResourceType(), + ], + $ibaseWaitEventVariants, + false, + $ibaseWaitEventVariants[0], + ]; + yield [ + [ + new StringType(), + ], + $ibaseWaitEventVariants, + false, + $ibaseWaitEventVariants[1], + ]; + yield [ + [ + new StringType(), + new StringType(), + new StringType(), + new StringType(), + new StringType(), + ], + $ibaseWaitEventVariants, + false, + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new NativeParameterReflection( + 'link_identifier|event', + false, + new MixedType(), + PassedByReference::createNo(), + false, + null + ), + new NativeParameterReflection( + 'event|args', + true, + new MixedType(), + PassedByReference::createNo(), + true, + null + ), + ], + true, + new StringType() + ), + ]; - $strtokVariants = $broker->getFunction(new Name('strtok'), null)->getVariants(); - yield [ - [], - $strtokVariants, - false, - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new NativeParameterReflection( - 'str|token', - false, - new StringType(), - PassedByReference::createNo(), - false, - null - ), - new NativeParameterReflection( - 'token', - true, - new StringType(), - PassedByReference::createNo(), - false, - null - ), - ], - false, - new UnionType([new StringType(), new ConstantBooleanType(false)]) - ), - ]; - yield [ - [ - new StringType(), - ], - $strtokVariants, - true, - ParametersAcceptorSelector::combineAcceptors($strtokVariants), - ]; + $absVariants = $broker->getFunction(new Name('abs'), null)->getVariants(); + yield [ + [ + new FloatType(), + new FloatType(), + ], + $absVariants, + false, + ParametersAcceptorSelector::combineAcceptors($absVariants), + ]; + yield [ + [ + new FloatType(), + new IntegerType(), + new StringType(), + ], + $absVariants, + false, + ParametersAcceptorSelector::combineAcceptors($absVariants), + ]; + yield [ + [ + new StringType(), + ], + $absVariants, + false, + $absVariants[2], + ]; - $variadicVariants = [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new NativeParameterReflection( - 'int', - false, - new IntegerType(), - PassedByReference::createNo(), - false, - null - ), - new NativeParameterReflection( - 'intVariadic', - true, - new IntegerType(), - PassedByReference::createNo(), - true, - null - ), - ], - true, - new IntegerType() - ), - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new NativeParameterReflection( - 'int', - false, - new IntegerType(), - PassedByReference::createNo(), - false, - null - ), - new NativeParameterReflection( - 'floatVariadic', - true, - new FloatType(), - PassedByReference::createNo(), - true, - null - ), - ], - true, - new IntegerType() - ), - ]; + $strtokVariants = $broker->getFunction(new Name('strtok'), null)->getVariants(); + yield [ + [], + $strtokVariants, + false, + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new NativeParameterReflection( + 'str|token', + false, + new StringType(), + PassedByReference::createNo(), + false, + null + ), + new NativeParameterReflection( + 'token', + true, + new StringType(), + PassedByReference::createNo(), + false, + null + ), + ], + false, + new UnionType([new StringType(), new ConstantBooleanType(false)]) + ), + ]; + yield [ + [ + new StringType(), + ], + $strtokVariants, + true, + ParametersAcceptorSelector::combineAcceptors($strtokVariants), + ]; - yield [ - [ - new IntegerType(), - ], - $variadicVariants, - true, - $variadicVariants[0], - ]; + $variadicVariants = [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new NativeParameterReflection( + 'int', + false, + new IntegerType(), + PassedByReference::createNo(), + false, + null + ), + new NativeParameterReflection( + 'intVariadic', + true, + new IntegerType(), + PassedByReference::createNo(), + true, + null + ), + ], + true, + new IntegerType() + ), + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new NativeParameterReflection( + 'int', + false, + new IntegerType(), + PassedByReference::createNo(), + false, + null + ), + new NativeParameterReflection( + 'floatVariadic', + true, + new FloatType(), + PassedByReference::createNo(), + true, + null + ), + ], + true, + new IntegerType() + ), + ]; - yield [ - [ - new IntegerType(), - ], - $variadicVariants, - false, - ParametersAcceptorSelector::combineAcceptors($variadicVariants), - ]; + yield [ + [ + new IntegerType(), + ], + $variadicVariants, + true, + $variadicVariants[0], + ]; - $defaultValuesVariants1 = [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - new ConstantIntegerType(1) - ), - ], - false, - new NullType() - ), - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - new ConstantIntegerType(2) - ), - ], - false, - new NullType() - ), - ]; + yield [ + [ + new IntegerType(), + ], + $variadicVariants, + false, + ParametersAcceptorSelector::combineAcceptors($variadicVariants), + ]; - yield [ - [ - new IntegerType(), - ], - $defaultValuesVariants1, - true, - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - new UnionType([ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ]) - ), - ], - false, - new NullType() - ), - ]; + $defaultValuesVariants1 = [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + new ConstantIntegerType(1) + ), + ], + false, + new NullType() + ), + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + new ConstantIntegerType(2) + ), + ], + false, + new NullType() + ), + ]; - $defaultValuesVariants2 = [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - new ConstantIntegerType(1) - ), - ], - false, - new NullType() - ), - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ]; + yield [ + [ + new IntegerType(), + ], + $defaultValuesVariants1, + true, + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + new UnionType([ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ]) + ), + ], + false, + new NullType() + ), + ]; - yield [ - [ - new IntegerType(), - ], - $defaultValuesVariants2, - true, - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new MixedType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ]; + $defaultValuesVariants2 = [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + new ConstantIntegerType(1) + ), + ], + false, + new NullType() + ), + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ]; - $genericVariants = [ - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ]; + yield [ + [ + new IntegerType(), + ], + $defaultValuesVariants2, + true, + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new MixedType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ]; - yield [ - [ - new IntegerType(), - ], - $genericVariants, - true, - new FunctionVariant( - TemplateTypeMap::createEmpty(), - null, - [ - new DummyParameter( - 'a', - new IntegerType(), - false, - PassedByReference::createNo(), - false, - null - ), - ], - false, - new NullType() - ), - ]; - } + $genericVariants = [ + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ]; - /** - * @dataProvider dataSelectFromTypes - * @param \PHPStan\Type\Type[] $types - * @param ParametersAcceptor[] $variants - * @param bool $unpack - * @param ParametersAcceptor $expected - */ - public function testSelectFromTypes( - array $types, - array $variants, - bool $unpack, - ParametersAcceptor $expected - ): void - { - $selectedAcceptor = ParametersAcceptorSelector::selectFromTypes($types, $variants, $unpack); - $this->assertCount(count($expected->getParameters()), $selectedAcceptor->getParameters()); - foreach ($selectedAcceptor->getParameters() as $i => $parameter) { - $expectedParameter = $expected->getParameters()[$i]; - $this->assertSame( - $expectedParameter->getName(), - $parameter->getName() - ); - $this->assertSame( - $expectedParameter->isOptional(), - $parameter->isOptional() - ); - $this->assertSame( - $expectedParameter->getType()->describe(VerbosityLevel::precise()), - $parameter->getType()->describe(VerbosityLevel::precise()) - ); - $this->assertTrue( - $expectedParameter->passedByReference()->equals($parameter->passedByReference()) - ); - $this->assertSame( - $expectedParameter->isVariadic(), - $parameter->isVariadic() - ); - if ($expectedParameter->getDefaultValue() === null) { - $this->assertNull($parameter->getDefaultValue()); - } else { - $this->assertSame( - $expectedParameter->getDefaultValue()->describe(VerbosityLevel::precise()), - $parameter->getDefaultValue() !== null ? $parameter->getDefaultValue()->describe(VerbosityLevel::precise()) : null - ); - } - } + yield [ + [ + new IntegerType(), + ], + $genericVariants, + true, + new FunctionVariant( + TemplateTypeMap::createEmpty(), + null, + [ + new DummyParameter( + 'a', + new IntegerType(), + false, + PassedByReference::createNo(), + false, + null + ), + ], + false, + new NullType() + ), + ]; + } - $this->assertSame( - $expected->getReturnType()->describe(VerbosityLevel::precise()), - $selectedAcceptor->getReturnType()->describe(VerbosityLevel::precise()) - ); - $this->assertSame($expected->isVariadic(), $selectedAcceptor->isVariadic()); - } + /** + * @dataProvider dataSelectFromTypes + * @param \PHPStan\Type\Type[] $types + * @param ParametersAcceptor[] $variants + * @param bool $unpack + * @param ParametersAcceptor $expected + */ + public function testSelectFromTypes( + array $types, + array $variants, + bool $unpack, + ParametersAcceptor $expected + ): void { + $selectedAcceptor = ParametersAcceptorSelector::selectFromTypes($types, $variants, $unpack); + $this->assertCount(count($expected->getParameters()), $selectedAcceptor->getParameters()); + foreach ($selectedAcceptor->getParameters() as $i => $parameter) { + $expectedParameter = $expected->getParameters()[$i]; + $this->assertSame( + $expectedParameter->getName(), + $parameter->getName() + ); + $this->assertSame( + $expectedParameter->isOptional(), + $parameter->isOptional() + ); + $this->assertSame( + $expectedParameter->getType()->describe(VerbosityLevel::precise()), + $parameter->getType()->describe(VerbosityLevel::precise()) + ); + $this->assertTrue( + $expectedParameter->passedByReference()->equals($parameter->passedByReference()) + ); + $this->assertSame( + $expectedParameter->isVariadic(), + $parameter->isVariadic() + ); + if ($expectedParameter->getDefaultValue() === null) { + $this->assertNull($parameter->getDefaultValue()); + } else { + $this->assertSame( + $expectedParameter->getDefaultValue()->describe(VerbosityLevel::precise()), + $parameter->getDefaultValue() !== null ? $parameter->getDefaultValue()->describe(VerbosityLevel::precise()) : null + ); + } + } + $this->assertSame( + $expected->getReturnType()->describe(VerbosityLevel::precise()), + $selectedAcceptor->getReturnType()->describe(VerbosityLevel::precise()) + ); + $this->assertSame($expected->isVariadic(), $selectedAcceptor->isVariadic()); + } } diff --git a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php index 0c5c5d812b..e00e9d1bf2 100644 --- a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php @@ -1,4 +1,6 @@ -getByType(Broker::class); - $extension = new UniversalObjectCratesClassReflectionExtension([ - 'NonexistentClass', - 'stdClass', - ]); - $extension->setBroker($broker); - $this->assertTrue($extension->hasProperty($broker->getClass(\stdClass::class), 'foo')); - } - - public function testDifferentGetSetType(): void - { - require_once __DIR__ . '/data/universal-object-crates.php'; - - $broker = self::getContainer()->getByType(Broker::class); - $extension = new UniversalObjectCratesClassReflectionExtension([ - 'UniversalObjectCreates\DifferentGetSetTypes', - ]); - $extension->setBroker($broker); - - $this->assertEquals( - new ObjectType('UniversalObjectCreates\DifferentGetSetTypesValue'), - $extension - ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') - ->getReadableType() - ); - $this->assertEquals( - new StringType(), - $extension - ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') - ->getWritableType() - ); - } - + public function testNonexistentClass(): void + { + $broker = self::getContainer()->getByType(Broker::class); + $extension = new UniversalObjectCratesClassReflectionExtension([ + 'NonexistentClass', + 'stdClass', + ]); + $extension->setBroker($broker); + $this->assertTrue($extension->hasProperty($broker->getClass(\stdClass::class), 'foo')); + } + + public function testDifferentGetSetType(): void + { + require_once __DIR__ . '/data/universal-object-crates.php'; + + $broker = self::getContainer()->getByType(Broker::class); + $extension = new UniversalObjectCratesClassReflectionExtension([ + 'UniversalObjectCreates\DifferentGetSetTypes', + ]); + $extension->setBroker($broker); + + $this->assertEquals( + new ObjectType('UniversalObjectCreates\DifferentGetSetTypesValue'), + $extension + ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') + ->getReadableType() + ); + $this->assertEquals( + new StringType(), + $extension + ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') + ->getWritableType() + ); + } } diff --git a/tests/PHPStan/Reflection/Php/data/universal-object-crates.php b/tests/PHPStan/Reflection/Php/data/universal-object-crates.php index bf3b410611..b559b0456e 100644 --- a/tests/PHPStan/Reflection/Php/data/universal-object-crates.php +++ b/tests/PHPStan/Reflection/Php/data/universal-object-crates.php @@ -4,23 +4,23 @@ class DifferentGetSetTypes { - private $values = []; + private $values = []; - public function __get($name): DifferentGetSetTypesValue - { - $this->values[$name] ?: new DifferentGetSetTypesValue(); - } + public function __get($name): DifferentGetSetTypesValue + { + $this->values[$name] ?: new DifferentGetSetTypesValue(); + } - public function __set($name, string $value): void - { - $newValue = new DifferentGetSetTypesValue(); - $newValue->value = $value; + public function __set($name, string $value): void + { + $newValue = new DifferentGetSetTypesValue(); + $newValue->value = $value; - $this->values[$name] = $newValue; - } + $this->values[$name] = $newValue; + } } class DifferentGetSetTypesValue { - public $value = null; + public $value = null; } diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 431214c141..5b2029ca57 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -1,4 +1,6 @@ -= 70200) { - yield [ - 'sodium_crypto_kx_keypair', - new ObjectType('SodiumException'), - ]; - } - - yield [ - 'bcdiv', - new ObjectType('DivisionByZeroError'), - ]; + if (PHP_VERSION_ID >= 70200) { + yield [ + 'sodium_crypto_kx_keypair', + new ObjectType('SodiumException'), + ]; + } - yield [ - 'GEOSRelateMatch', - new ObjectType('Exception'), - ]; + yield [ + 'bcdiv', + new ObjectType('DivisionByZeroError'), + ]; - yield [ - 'random_int', - new ObjectType('Exception'), - ]; - } + yield [ + 'GEOSRelateMatch', + new ObjectType('Exception'), + ]; - /** - * @dataProvider dataFunctionThrowType - * @param string $functionName - * @param ?Type $expectedThrowType - */ - public function testFunctionThrowType(string $functionName, ?Type $expectedThrowType): void - { - $reflectionProvider = $this->createReflectionProvider(); - $function = $reflectionProvider->getFunction(new Name($functionName), null); - $throwType = $function->getThrowType(); - if ($expectedThrowType === null) { - $this->assertNull($throwType); - return; - } - $this->assertNotNull($throwType); - $this->assertSame( - $expectedThrowType->describe(VerbosityLevel::precise()), - $throwType->describe(VerbosityLevel::precise()) - ); - } + yield [ + 'random_int', + new ObjectType('Exception'), + ]; + } - public function dataMethodThrowType(): array - { - return [ - [ - \DateTime::class, - '__construct', - new ObjectType('Exception'), - ], - [ - \DateTime::class, - 'format', - null, - ], - ]; - } + /** + * @dataProvider dataFunctionThrowType + * @param string $functionName + * @param ?Type $expectedThrowType + */ + public function testFunctionThrowType(string $functionName, ?Type $expectedThrowType): void + { + $reflectionProvider = $this->createReflectionProvider(); + $function = $reflectionProvider->getFunction(new Name($functionName), null); + $throwType = $function->getThrowType(); + if ($expectedThrowType === null) { + $this->assertNull($throwType); + return; + } + $this->assertNotNull($throwType); + $this->assertSame( + $expectedThrowType->describe(VerbosityLevel::precise()), + $throwType->describe(VerbosityLevel::precise()) + ); + } - /** - * @dataProvider dataMethodThrowType - * @param string $className - * @param string $methodName - * @param ?Type $expectedThrowType - */ - public function testMethodThrowType(string $className, string $methodName, ?Type $expectedThrowType): void - { - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass($className); - $method = $class->getNativeMethod($methodName); - $throwType = $method->getThrowType(); - if ($expectedThrowType === null) { - $this->assertNull($throwType); - return; - } - $this->assertNotNull($throwType); - $this->assertSame( - $expectedThrowType->describe(VerbosityLevel::precise()), - $throwType->describe(VerbosityLevel::precise()) - ); - } + public function dataMethodThrowType(): array + { + return [ + [ + \DateTime::class, + '__construct', + new ObjectType('Exception'), + ], + [ + \DateTime::class, + 'format', + null, + ], + ]; + } + /** + * @dataProvider dataMethodThrowType + * @param string $className + * @param string $methodName + * @param ?Type $expectedThrowType + */ + public function testMethodThrowType(string $className, string $methodName, ?Type $expectedThrowType): void + { + $reflectionProvider = $this->createBroker(); + $class = $reflectionProvider->getClass($className); + $method = $class->getNativeMethod($methodName); + $throwType = $method->getThrowType(); + if ($expectedThrowType === null) { + $this->assertNull($throwType); + return; + } + $this->assertNotNull($throwType); + $this->assertSame( + $expectedThrowType->describe(VerbosityLevel::precise()), + $throwType->describe(VerbosityLevel::precise()) + ); + } } diff --git a/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php b/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php index ac5feca510..8684a9deb2 100644 --- a/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php @@ -1,4 +1,6 @@ -assertIsArray($data); - public function testSchema(): void - { - $data = require __DIR__ . '/../../../../resources/functionMetadata.php'; - $this->assertIsArray($data); - - $processor = new Processor(); - $processor->process(Expect::arrayOf( - Expect::structure([ - 'hasSideEffects' => Expect::bool()->required(), - ])->required() - )->required(), $data); - } - + $processor = new Processor(); + $processor->process(Expect::arrayOf( + Expect::structure([ + 'hasSideEffects' => Expect::bool()->required(), + ])->required() + )->required(), $data); + } } diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index db00ba439a..32f901106f 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -1,4 +1,6 @@ - 'url', + 'optional' => true, + 'type' => new UnionType([ + new StringType(), + new NullType(), + ]), + 'nativeType' => new UnionType([ + new StringType(), + new NullType(), + ]), + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + ], + new UnionType([ + new ObjectType('CurlHandle'), + new ConstantBooleanType(false), + ]), + new UnionType([ + new ObjectType('CurlHandle'), + new ConstantBooleanType(false), + ]), + false, + ], + [ + 'curl_exec', + [ + [ + 'name' => 'handle', + 'optional' => false, + 'type' => new ObjectType('CurlHandle'), + 'nativeType' => new ObjectType('CurlHandle'), + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + ], + new UnionType([new StringType(), new BooleanType()]), + new UnionType([new StringType(), new BooleanType()]), + false, + ], + [ + 'date_get_last_errors', + [], + new UnionType([ + new ConstantBooleanType(false), + new ConstantArrayType([ + new ConstantStringType('warning_count'), + new ConstantStringType('warnings'), + new ConstantStringType('error_count'), + new ConstantStringType('errors'), + ], [ + new IntegerType(), + new ArrayType(new IntegerType(), new StringType()), + new IntegerType(), + new ArrayType(new IntegerType(), new StringType()), + ]), + ]), + new UnionType([ + new ConstantBooleanType(false), + new ArrayType(new MixedType(true), new MixedType(true)), + ]), + false, + ], + [ + 'end', + [ + [ + 'name' => 'array', + 'optional' => false, + 'type' => new ArrayType(new MixedType(), new MixedType()), + 'nativeType' => new ArrayType(new MixedType(), new MixedType()), + 'passedByReference' => PassedByReference::createReadsArgument(), + 'variadic' => false, + ], + ], + new MixedType(true), + new MixedType(true), + false, + ], + ]; + } - public function dataFunctions(): array - { - return [ - [ - 'curl_init', - [ - [ - 'name' => 'url', - 'optional' => true, - 'type' => new UnionType([ - new StringType(), - new NullType(), - ]), - 'nativeType' => new UnionType([ - new StringType(), - new NullType(), - ]), - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - ], - new UnionType([ - new ObjectType('CurlHandle'), - new ConstantBooleanType(false), - ]), - new UnionType([ - new ObjectType('CurlHandle'), - new ConstantBooleanType(false), - ]), - false, - ], - [ - 'curl_exec', - [ - [ - 'name' => 'handle', - 'optional' => false, - 'type' => new ObjectType('CurlHandle'), - 'nativeType' => new ObjectType('CurlHandle'), - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - ], - new UnionType([new StringType(), new BooleanType()]), - new UnionType([new StringType(), new BooleanType()]), - false, - ], - [ - 'date_get_last_errors', - [], - new UnionType([ - new ConstantBooleanType(false), - new ConstantArrayType([ - new ConstantStringType('warning_count'), - new ConstantStringType('warnings'), - new ConstantStringType('error_count'), - new ConstantStringType('errors'), - ], [ - new IntegerType(), - new ArrayType(new IntegerType(), new StringType()), - new IntegerType(), - new ArrayType(new IntegerType(), new StringType()), - ]), - ]), - new UnionType([ - new ConstantBooleanType(false), - new ArrayType(new MixedType(true), new MixedType(true)), - ]), - false, - ], - [ - 'end', - [ - [ - 'name' => 'array', - 'optional' => false, - 'type' => new ArrayType(new MixedType(), new MixedType()), - 'nativeType' => new ArrayType(new MixedType(), new MixedType()), - 'passedByReference' => PassedByReference::createReadsArgument(), - 'variadic' => false, - ], - ], - new MixedType(true), - new MixedType(true), - false, - ], - ]; - } - - /** - * @dataProvider dataFunctions - * @param mixed[] $parameters - */ - public function testFunctions( - string $functionName, - array $parameters, - Type $returnType, - Type $nativeReturnType, - bool $variadic - ): void - { - $provider = $this->createProvider(); - $signature = $provider->getFunctionSignature($functionName, null); - $this->assertSignature($parameters, $returnType, $nativeReturnType, $variadic, $signature); - } - - private function createProvider(): Php8SignatureMapProvider - { - return new Php8SignatureMapProvider( - new FunctionSignatureMapProvider( - self::getContainer()->getByType(SignatureMapParser::class), - new PhpVersion(80000) - ), - self::getContainer()->getByType(FileNodesFetcher::class), - self::getContainer()->getByType(FileTypeMapper::class) - ); - } + /** + * @dataProvider dataFunctions + * @param mixed[] $parameters + */ + public function testFunctions( + string $functionName, + array $parameters, + Type $returnType, + Type $nativeReturnType, + bool $variadic + ): void { + $provider = $this->createProvider(); + $signature = $provider->getFunctionSignature($functionName, null); + $this->assertSignature($parameters, $returnType, $nativeReturnType, $variadic, $signature); + } - public function dataMethods(): array - { - return [ - [ - 'Closure', - 'bindTo', - [ - [ - 'name' => 'newThis', - 'optional' => false, - 'type' => new UnionType([ - new ObjectWithoutClassType(), - new NullType(), - ]), - 'nativeType' => new UnionType([ - new ObjectWithoutClassType(), - new NullType(), - ]), - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - [ - 'name' => 'newScope', - 'optional' => true, - 'type' => new UnionType([ - new ObjectWithoutClassType(), - new StringType(), - new NullType(), - ]), - 'nativeType' => new UnionType([ - new ObjectWithoutClassType(), - new StringType(), - new NullType(), - ]), - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - ], - new UnionType([ - new ObjectType('Closure'), - new NullType(), - ]), - new UnionType([ - new ObjectType('Closure'), - new NullType(), - ]), - false, - ], - [ - 'ArrayIterator', - 'uasort', - [ - [ - 'name' => 'callback', - 'optional' => false, - 'type' => new CallableType([ - new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), - new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), - ], new IntegerType(), false), - 'nativeType' => new CallableType(), - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - ], - new VoidType(), - new MixedType(), - false, - ], - [ - 'RecursiveArrayIterator', - 'uasort', - [ - [ - 'name' => 'cmp_function', - 'optional' => false, - 'type' => new CallableType([ - new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), - new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), - ], new IntegerType(), false), - 'nativeType' => new MixedType(), // todo - because uasort is not found in file with RecursiveArrayIterator - 'passedByReference' => PassedByReference::createNo(), - 'variadic' => false, - ], - ], - new VoidType(), - new MixedType(), // todo - because uasort is not found in file with RecursiveArrayIterator - false, - ], - ]; - } + private function createProvider(): Php8SignatureMapProvider + { + return new Php8SignatureMapProvider( + new FunctionSignatureMapProvider( + self::getContainer()->getByType(SignatureMapParser::class), + new PhpVersion(80000) + ), + self::getContainer()->getByType(FileNodesFetcher::class), + self::getContainer()->getByType(FileTypeMapper::class) + ); + } - /** - * @dataProvider dataMethods - * @param mixed[] $parameters - */ - public function testMethods( - string $className, - string $methodName, - array $parameters, - Type $returnType, - Type $nativeReturnType, - bool $variadic - ): void - { - $provider = $this->createProvider(); - $signature = $provider->getMethodSignature($className, $methodName, null); - $this->assertSignature($parameters, $returnType, $nativeReturnType, $variadic, $signature); - } + public function dataMethods(): array + { + return [ + [ + 'Closure', + 'bindTo', + [ + [ + 'name' => 'newThis', + 'optional' => false, + 'type' => new UnionType([ + new ObjectWithoutClassType(), + new NullType(), + ]), + 'nativeType' => new UnionType([ + new ObjectWithoutClassType(), + new NullType(), + ]), + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + [ + 'name' => 'newScope', + 'optional' => true, + 'type' => new UnionType([ + new ObjectWithoutClassType(), + new StringType(), + new NullType(), + ]), + 'nativeType' => new UnionType([ + new ObjectWithoutClassType(), + new StringType(), + new NullType(), + ]), + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + ], + new UnionType([ + new ObjectType('Closure'), + new NullType(), + ]), + new UnionType([ + new ObjectType('Closure'), + new NullType(), + ]), + false, + ], + [ + 'ArrayIterator', + 'uasort', + [ + [ + 'name' => 'callback', + 'optional' => false, + 'type' => new CallableType([ + new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), + new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), + ], new IntegerType(), false), + 'nativeType' => new CallableType(), + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + ], + new VoidType(), + new MixedType(), + false, + ], + [ + 'RecursiveArrayIterator', + 'uasort', + [ + [ + 'name' => 'cmp_function', + 'optional' => false, + 'type' => new CallableType([ + new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), + new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), + ], new IntegerType(), false), + 'nativeType' => new MixedType(), // todo - because uasort is not found in file with RecursiveArrayIterator + 'passedByReference' => PassedByReference::createNo(), + 'variadic' => false, + ], + ], + new VoidType(), + new MixedType(), // todo - because uasort is not found in file with RecursiveArrayIterator + false, + ], + ]; + } - /** - * @param mixed[] $expectedParameters - * @param Type $expectedReturnType - * @param Type $expectedNativeReturnType - * @param bool $expectedVariadic - * @param FunctionSignature $actualSignature - */ - private function assertSignature( - array $expectedParameters, - Type $expectedReturnType, - Type $expectedNativeReturnType, - bool $expectedVariadic, - FunctionSignature $actualSignature - ): void - { - $this->assertCount(count($expectedParameters), $actualSignature->getParameters()); - foreach ($expectedParameters as $i => $expectedParameter) { - $actualParameter = $actualSignature->getParameters()[$i]; - $this->assertSame($expectedParameter['name'], $actualParameter->getName()); - $this->assertSame($expectedParameter['optional'], $actualParameter->isOptional()); - $this->assertSame($expectedParameter['type']->describe(VerbosityLevel::precise()), $actualParameter->getType()->describe(VerbosityLevel::precise())); - $this->assertSame($expectedParameter['nativeType']->describe(VerbosityLevel::precise()), $actualParameter->getNativeType()->describe(VerbosityLevel::precise())); - $this->assertTrue($expectedParameter['passedByReference']->equals($actualParameter->passedByReference())); - $this->assertSame($expectedParameter['variadic'], $actualParameter->isVariadic()); - } + /** + * @dataProvider dataMethods + * @param mixed[] $parameters + */ + public function testMethods( + string $className, + string $methodName, + array $parameters, + Type $returnType, + Type $nativeReturnType, + bool $variadic + ): void { + $provider = $this->createProvider(); + $signature = $provider->getMethodSignature($className, $methodName, null); + $this->assertSignature($parameters, $returnType, $nativeReturnType, $variadic, $signature); + } - $this->assertSame($expectedReturnType->describe(VerbosityLevel::precise()), $actualSignature->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertSame($expectedNativeReturnType->describe(VerbosityLevel::precise()), $actualSignature->getNativeReturnType()->describe(VerbosityLevel::precise())); - $this->assertSame($expectedVariadic, $actualSignature->isVariadic()); - } + /** + * @param mixed[] $expectedParameters + * @param Type $expectedReturnType + * @param Type $expectedNativeReturnType + * @param bool $expectedVariadic + * @param FunctionSignature $actualSignature + */ + private function assertSignature( + array $expectedParameters, + Type $expectedReturnType, + Type $expectedNativeReturnType, + bool $expectedVariadic, + FunctionSignature $actualSignature + ): void { + $this->assertCount(count($expectedParameters), $actualSignature->getParameters()); + foreach ($expectedParameters as $i => $expectedParameter) { + $actualParameter = $actualSignature->getParameters()[$i]; + $this->assertSame($expectedParameter['name'], $actualParameter->getName()); + $this->assertSame($expectedParameter['optional'], $actualParameter->isOptional()); + $this->assertSame($expectedParameter['type']->describe(VerbosityLevel::precise()), $actualParameter->getType()->describe(VerbosityLevel::precise())); + $this->assertSame($expectedParameter['nativeType']->describe(VerbosityLevel::precise()), $actualParameter->getNativeType()->describe(VerbosityLevel::precise())); + $this->assertTrue($expectedParameter['passedByReference']->equals($actualParameter->passedByReference())); + $this->assertSame($expectedParameter['variadic'], $actualParameter->isVariadic()); + } - public function dataParseAll(): array - { - return array_map(static function (string $file): array { - return [__DIR__ . '/../../../../vendor/phpstan/php-8-stubs/' . $file]; - }, array_merge(Php8StubsMap::CLASSES, Php8StubsMap::FUNCTIONS)); - } + $this->assertSame($expectedReturnType->describe(VerbosityLevel::precise()), $actualSignature->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertSame($expectedNativeReturnType->describe(VerbosityLevel::precise()), $actualSignature->getNativeReturnType()->describe(VerbosityLevel::precise())); + $this->assertSame($expectedVariadic, $actualSignature->isVariadic()); + } - /** - * @dataProvider dataParseAll - * @param string $stubFile - */ - public function testParseAll(string $stubFile): void - { - $parser = $this->getParser(); - $parser->parseFile($stubFile); - $this->expectNotToPerformAssertions(); - } + public function dataParseAll(): array + { + return array_map(static function (string $file): array { + return [__DIR__ . '/../../../../vendor/phpstan/php-8-stubs/' . $file]; + }, array_merge(Php8StubsMap::CLASSES, Php8StubsMap::FUNCTIONS)); + } + /** + * @dataProvider dataParseAll + * @param string $stubFile + */ + public function testParseAll(string $stubFile): void + { + $parser = $this->getParser(); + $parser->parseFile($stubFile); + $this->expectNotToPerformAssertions(); + } } diff --git a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php index a0b85a89ce..5d9ff1da51 100644 --- a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php @@ -1,4 +1,6 @@ - 'resource', 'fields' => 'array', 'delimiter=' => 'string', 'enclosure=' => 'string', 'escape_char=' => 'string'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'fp', + false, + new ResourceType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'fields', + false, + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'delimiter', + true, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'enclosure', + true, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'escape_char', + true, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + ], + new IntegerType(), + new MixedType(), + false + ), + ], + [ + ['bool', 'fp' => 'resource'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'fp', + false, + new ResourceType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + ], + new BooleanType(), + new MixedType(), + false + ), + ], + [ + ['bool', '&rw_array_arg' => 'array'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'array_arg', + false, + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + PassedByReference::createReadsArgument(), + false + ), + ], + new BooleanType(), + new MixedType(), + false + ), + ], + [ + ['bool', 'csr' => 'string|resource', '&w_out' => 'string', 'notext=' => 'bool'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'csr', + false, + new UnionType([ + new StringType(), + new ResourceType(), + ]), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'out', + false, + new StringType(), + new MixedType(), + PassedByReference::createCreatesNewVariable(), + false + ), + new ParameterSignature( + 'notext', + true, + new BooleanType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + ], + new BooleanType(), + new MixedType(), + false + ), + ], + [ + ['(?Throwable)|(?Foo)'], + null, + new FunctionSignature( + [], + new UnionType([ + new ObjectType(\Throwable::class), + new ObjectType('Foo'), + new NullType(), + ]), + new MixedType(), + false + ), + ], + [ + [''], + null, + new FunctionSignature( + [], + new MixedType(), + new MixedType(), + false + ), + ], + [ + ['array', 'arr1' => 'array', 'arr2' => 'array', '...=' => 'array'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'arr1', + false, + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'arr2', + false, + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + '...', + true, + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + PassedByReference::createNo(), + true + ), + ], + new ArrayType(new MixedType(), new MixedType()), + new MixedType(), + true + ), + ], + [ + ['resource', 'callback' => 'callable', 'event' => 'string', '...' => ''], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'callback', + false, + new CallableType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'event', + false, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + '...', + true, + new MixedType(), + new MixedType(), + PassedByReference::createNo(), + true + ), + ], + new ResourceType(), + new MixedType(), + true + ), + ], + [ + ['string', 'format' => 'string', '...args=' => ''], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'format', + false, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'args', + true, + new MixedType(), + new MixedType(), + PassedByReference::createNo(), + true + ), + ], + new StringType(), + new MixedType(), + true + ), + ], + [ + ['string', 'format' => 'string', '...args' => ''], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'format', + false, + new StringType(), + new MixedType(), + PassedByReference::createNo(), + false + ), + new ParameterSignature( + 'args', + true, + new MixedType(), + new MixedType(), + PassedByReference::createNo(), + true + ), + ], + new StringType(), + new MixedType(), + true + ), + ], + [ + ['array'], + null, + new FunctionSignature( + [], + new ArrayType(new IntegerType(), new ObjectType(\ReflectionParameter::class)), + new MixedType(), + false + ), + ], + [ + ['static', 'interval' => 'DateInterval'], + \DateTime::class, + new FunctionSignature( + [ + new ParameterSignature( + 'interval', + false, + new ObjectType(\DateInterval::class), + new MixedType(), + PassedByReference::createNo(), + false + ), + ], + new StaticType(\DateTime::class), + new MixedType(), + false + ), + ], + [ + ['bool', '&rw_string' => 'string', '&...rw_strings=' => 'string'], + null, + new FunctionSignature( + [ + new ParameterSignature( + 'string', + false, + new StringType(), + new MixedType(), + PassedByReference::createReadsArgument(), + false + ), + new ParameterSignature( + 'strings', + true, + new StringType(), + new MixedType(), + PassedByReference::createReadsArgument(), + true + ), + ], + new BooleanType(), + new MixedType(), + true + ), + ], + ]; + } - public function dataGetFunctions(): array - { - return [ - [ - ['int', 'fp' => 'resource', 'fields' => 'array', 'delimiter=' => 'string', 'enclosure=' => 'string', 'escape_char=' => 'string'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'fp', - false, - new ResourceType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'fields', - false, - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'delimiter', - true, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'enclosure', - true, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'escape_char', - true, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - ], - new IntegerType(), - new MixedType(), - false - ), - ], - [ - ['bool', 'fp' => 'resource'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'fp', - false, - new ResourceType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - ], - new BooleanType(), - new MixedType(), - false - ), - ], - [ - ['bool', '&rw_array_arg' => 'array'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'array_arg', - false, - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - PassedByReference::createReadsArgument(), - false - ), - ], - new BooleanType(), - new MixedType(), - false - ), - ], - [ - ['bool', 'csr' => 'string|resource', '&w_out' => 'string', 'notext=' => 'bool'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'csr', - false, - new UnionType([ - new StringType(), - new ResourceType(), - ]), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'out', - false, - new StringType(), - new MixedType(), - PassedByReference::createCreatesNewVariable(), - false - ), - new ParameterSignature( - 'notext', - true, - new BooleanType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - ], - new BooleanType(), - new MixedType(), - false - ), - ], - [ - ['(?Throwable)|(?Foo)'], - null, - new FunctionSignature( - [], - new UnionType([ - new ObjectType(\Throwable::class), - new ObjectType('Foo'), - new NullType(), - ]), - new MixedType(), - false - ), - ], - [ - [''], - null, - new FunctionSignature( - [], - new MixedType(), - new MixedType(), - false - ), - ], - [ - ['array', 'arr1' => 'array', 'arr2' => 'array', '...=' => 'array'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'arr1', - false, - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'arr2', - false, - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - '...', - true, - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - PassedByReference::createNo(), - true - ), - ], - new ArrayType(new MixedType(), new MixedType()), - new MixedType(), - true - ), - ], - [ - ['resource', 'callback' => 'callable', 'event' => 'string', '...' => ''], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'callback', - false, - new CallableType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'event', - false, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - '...', - true, - new MixedType(), - new MixedType(), - PassedByReference::createNo(), - true - ), - ], - new ResourceType(), - new MixedType(), - true - ), - ], - [ - ['string', 'format' => 'string', '...args=' => ''], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'format', - false, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'args', - true, - new MixedType(), - new MixedType(), - PassedByReference::createNo(), - true - ), - ], - new StringType(), - new MixedType(), - true - ), - ], - [ - ['string', 'format' => 'string', '...args' => ''], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'format', - false, - new StringType(), - new MixedType(), - PassedByReference::createNo(), - false - ), - new ParameterSignature( - 'args', - true, - new MixedType(), - new MixedType(), - PassedByReference::createNo(), - true - ), - ], - new StringType(), - new MixedType(), - true - ), - ], - [ - ['array'], - null, - new FunctionSignature( - [], - new ArrayType(new IntegerType(), new ObjectType(\ReflectionParameter::class)), - new MixedType(), - false - ), - ], - [ - ['static', 'interval' => 'DateInterval'], - \DateTime::class, - new FunctionSignature( - [ - new ParameterSignature( - 'interval', - false, - new ObjectType(\DateInterval::class), - new MixedType(), - PassedByReference::createNo(), - false - ), - ], - new StaticType(\DateTime::class), - new MixedType(), - false - ), - ], - [ - ['bool', '&rw_string' => 'string', '&...rw_strings=' => 'string'], - null, - new FunctionSignature( - [ - new ParameterSignature( - 'string', - false, - new StringType(), - new MixedType(), - PassedByReference::createReadsArgument(), - false - ), - new ParameterSignature( - 'strings', - true, - new StringType(), - new MixedType(), - PassedByReference::createReadsArgument(), - true - ), - ], - new BooleanType(), - new MixedType(), - true - ), - ], - ]; - } - - /** - * @dataProvider dataGetFunctions - * @param mixed[] $map - * @param string|null $className - * @param \PHPStan\Reflection\SignatureMap\FunctionSignature $expectedSignature - */ - public function testGetFunctions( - array $map, - ?string $className, - FunctionSignature $expectedSignature - ): void - { - /** @var SignatureMapParser $parser */ - $parser = self::getContainer()->getByType(SignatureMapParser::class); - $functionSignature = $parser->getFunctionSignature($map, $className); - $this->assertCount( - count($expectedSignature->getParameters()), - $functionSignature->getParameters(), - 'Number of parameters does not match.' - ); - - foreach ($functionSignature->getParameters() as $i => $parameterSignature) { - $expectedParameterSignature = $expectedSignature->getParameters()[$i]; - $this->assertSame( - $expectedParameterSignature->getName(), - $parameterSignature->getName(), - sprintf('Name of parameter #%d does not match.', $i) - ); - $this->assertSame( - $expectedParameterSignature->isOptional(), - $parameterSignature->isOptional(), - sprintf('Optionality of parameter $%s does not match.', $parameterSignature->getName()) - ); - $this->assertSame( - $expectedParameterSignature->getType()->describe(VerbosityLevel::precise()), - $parameterSignature->getType()->describe(VerbosityLevel::precise()), - sprintf('Type of parameter $%s does not match.', $parameterSignature->getName()) - ); - $this->assertTrue( - $expectedParameterSignature->passedByReference()->equals($parameterSignature->passedByReference()), - sprintf('Passed-by-reference of parameter $%s does not match.', $parameterSignature->getName()) - ); - $this->assertSame( - $expectedParameterSignature->isVariadic(), - $parameterSignature->isVariadic(), - sprintf('Variadicity of parameter $%s does not match.', $parameterSignature->getName()) - ); - } + /** + * @dataProvider dataGetFunctions + * @param mixed[] $map + * @param string|null $className + * @param \PHPStan\Reflection\SignatureMap\FunctionSignature $expectedSignature + */ + public function testGetFunctions( + array $map, + ?string $className, + FunctionSignature $expectedSignature + ): void { + /** @var SignatureMapParser $parser */ + $parser = self::getContainer()->getByType(SignatureMapParser::class); + $functionSignature = $parser->getFunctionSignature($map, $className); + $this->assertCount( + count($expectedSignature->getParameters()), + $functionSignature->getParameters(), + 'Number of parameters does not match.' + ); - $this->assertSame( - $expectedSignature->getReturnType()->describe(VerbosityLevel::precise()), - $functionSignature->getReturnType()->describe(VerbosityLevel::precise()), - 'Return type does not match.' - ); - $this->assertSame( - $expectedSignature->isVariadic(), - $functionSignature->isVariadic(), - 'Variadicity does not match.' - ); - } + foreach ($functionSignature->getParameters() as $i => $parameterSignature) { + $expectedParameterSignature = $expectedSignature->getParameters()[$i]; + $this->assertSame( + $expectedParameterSignature->getName(), + $parameterSignature->getName(), + sprintf('Name of parameter #%d does not match.', $i) + ); + $this->assertSame( + $expectedParameterSignature->isOptional(), + $parameterSignature->isOptional(), + sprintf('Optionality of parameter $%s does not match.', $parameterSignature->getName()) + ); + $this->assertSame( + $expectedParameterSignature->getType()->describe(VerbosityLevel::precise()), + $parameterSignature->getType()->describe(VerbosityLevel::precise()), + sprintf('Type of parameter $%s does not match.', $parameterSignature->getName()) + ); + $this->assertTrue( + $expectedParameterSignature->passedByReference()->equals($parameterSignature->passedByReference()), + sprintf('Passed-by-reference of parameter $%s does not match.', $parameterSignature->getName()) + ); + $this->assertSame( + $expectedParameterSignature->isVariadic(), + $parameterSignature->isVariadic(), + sprintf('Variadicity of parameter $%s does not match.', $parameterSignature->getName()) + ); + } - public function dataParseAll(): array - { - return [ - [70400], - [80000], - ]; - } + $this->assertSame( + $expectedSignature->getReturnType()->describe(VerbosityLevel::precise()), + $functionSignature->getReturnType()->describe(VerbosityLevel::precise()), + 'Return type does not match.' + ); + $this->assertSame( + $expectedSignature->isVariadic(), + $functionSignature->isVariadic(), + 'Variadicity does not match.' + ); + } - /** - * @dataProvider dataParseAll - * @param int $phpVersionId - */ - public function testParseAll(int $phpVersionId): void - { - $parser = self::getContainer()->getByType(SignatureMapParser::class); - $provider = new FunctionSignatureMapProvider($parser, new PhpVersion($phpVersionId)); - $signatureMap = $provider->getSignatureMap(); + public function dataParseAll(): array + { + return [ + [70400], + [80000], + ]; + } - $count = 0; - foreach (array_keys($signatureMap) as $functionName) { - $className = null; - if (strpos($functionName, '::') !== false) { - $parts = explode('::', $functionName); - $className = $parts[0]; - } + /** + * @dataProvider dataParseAll + * @param int $phpVersionId + */ + public function testParseAll(int $phpVersionId): void + { + $parser = self::getContainer()->getByType(SignatureMapParser::class); + $provider = new FunctionSignatureMapProvider($parser, new PhpVersion($phpVersionId)); + $signatureMap = $provider->getSignatureMap(); - try { - $signature = $provider->getFunctionSignature($functionName, $className); - $count++; - } catch (\PHPStan\PhpDocParser\Parser\ParserException $e) { - $this->fail(sprintf('Could not parse %s: %s.', $functionName, $e->getMessage())); - } + $count = 0; + foreach (array_keys($signatureMap) as $functionName) { + $className = null; + if (strpos($functionName, '::') !== false) { + $parts = explode('::', $functionName); + $className = $parts[0]; + } - self::assertNotInstanceOf(ErrorType::class, $signature->getReturnType(), $functionName); - $optionalOcurred = false; - foreach ($signature->getParameters() as $parameter) { - if ($parameter->isOptional()) { - $optionalOcurred = true; - } elseif ($optionalOcurred) { - $this->fail(sprintf('%s contains required parameter after optional.', $functionName)); - } - self::assertNotInstanceOf(ErrorType::class, $parameter->getType(), sprintf('%s (parameter %s)', $functionName, $parameter->getName())); - } - } + try { + $signature = $provider->getFunctionSignature($functionName, $className); + $count++; + } catch (\PHPStan\PhpDocParser\Parser\ParserException $e) { + $this->fail(sprintf('Could not parse %s: %s.', $functionName, $e->getMessage())); + } - $this->assertGreaterThan(0, $count); - } + self::assertNotInstanceOf(ErrorType::class, $signature->getReturnType(), $functionName); + $optionalOcurred = false; + foreach ($signature->getParameters() as $parameter) { + if ($parameter->isOptional()) { + $optionalOcurred = true; + } elseif ($optionalOcurred) { + $this->fail(sprintf('%s contains required parameter after optional.', $functionName)); + } + self::assertNotInstanceOf(ErrorType::class, $parameter->getType(), sprintf('%s (parameter %s)', $functionName, $parameter->getName())); + } + } + $this->assertGreaterThan(0, $count); + } } diff --git a/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php b/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php index cbd42d5f9c..06d64a9f90 100644 --- a/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php +++ b/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php @@ -1,4 +1,6 @@ -createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated'), - $this->createDeprecatedMethod(TrinaryLogic::createMaybe(), 'Maybe deprecated'), - $this->createDeprecatedMethod(TrinaryLogic::createNo(), 'Not deprecated'), - ] - ); - - $this->assertSame('Deprecated', $reflection->getDeprecatedDescription()); - } - - public function testMultipleDeprecationsAreJoined(): void - { - $reflection = new IntersectionTypeMethodReflection( - 'foo', - [ - $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #1'), - $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #2'), - ] - ); - - $this->assertSame('Deprecated #1 Deprecated #2', $reflection->getDeprecatedDescription()); - } - - private function createDeprecatedMethod(TrinaryLogic $deprecated, ?string $deprecationText): MethodReflection - { - $method = $this->createMock(MethodReflection::class); - $method->method('isDeprecated')->willReturn($deprecated); - $method->method('getDeprecatedDescription')->willReturn($deprecationText); - return $method; - } - + public function testCollectsDeprecatedMessages(): void + { + $reflection = new IntersectionTypeMethodReflection( + 'foo', + [ + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated'), + $this->createDeprecatedMethod(TrinaryLogic::createMaybe(), 'Maybe deprecated'), + $this->createDeprecatedMethod(TrinaryLogic::createNo(), 'Not deprecated'), + ] + ); + + $this->assertSame('Deprecated', $reflection->getDeprecatedDescription()); + } + + public function testMultipleDeprecationsAreJoined(): void + { + $reflection = new IntersectionTypeMethodReflection( + 'foo', + [ + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #1'), + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #2'), + ] + ); + + $this->assertSame('Deprecated #1 Deprecated #2', $reflection->getDeprecatedDescription()); + } + + private function createDeprecatedMethod(TrinaryLogic $deprecated, ?string $deprecationText): MethodReflection + { + $method = $this->createMock(MethodReflection::class); + $method->method('isDeprecated')->willReturn($deprecated); + $method->method('getDeprecatedDescription')->willReturn($deprecationText); + return $method; + } } diff --git a/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php b/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php index 93a168e43e..75a8dab5f0 100644 --- a/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php +++ b/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php @@ -1,4 +1,6 @@ -createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated'), - $this->createDeprecatedMethod(TrinaryLogic::createMaybe(), 'Maybe deprecated'), - $this->createDeprecatedMethod(TrinaryLogic::createNo(), 'Not deprecated'), - ] - ); - - $this->assertSame('Deprecated', $reflection->getDeprecatedDescription()); - } - - public function testMultipleDeprecationsAreJoined(): void - { - $reflection = new UnionTypeMethodReflection( - 'foo', - [ - $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #1'), - $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #2'), - ] - ); - - $this->assertSame('Deprecated #1 Deprecated #2', $reflection->getDeprecatedDescription()); - } - - private function createDeprecatedMethod(TrinaryLogic $deprecated, ?string $deprecationText): MethodReflection - { - $method = $this->createMock(MethodReflection::class); - $method->method('isDeprecated')->willReturn($deprecated); - $method->method('getDeprecatedDescription')->willReturn($deprecationText); - return $method; - } - + public function testCollectsDeprecatedMessages(): void + { + $reflection = new UnionTypeMethodReflection( + 'foo', + [ + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated'), + $this->createDeprecatedMethod(TrinaryLogic::createMaybe(), 'Maybe deprecated'), + $this->createDeprecatedMethod(TrinaryLogic::createNo(), 'Not deprecated'), + ] + ); + + $this->assertSame('Deprecated', $reflection->getDeprecatedDescription()); + } + + public function testMultipleDeprecationsAreJoined(): void + { + $reflection = new UnionTypeMethodReflection( + 'foo', + [ + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #1'), + $this->createDeprecatedMethod(TrinaryLogic::createYes(), 'Deprecated #2'), + ] + ); + + $this->assertSame('Deprecated #1 Deprecated #2', $reflection->getDeprecatedDescription()); + } + + private function createDeprecatedMethod(TrinaryLogic $deprecated, ?string $deprecationText): MethodReflection + { + $method = $this->createMock(MethodReflection::class); + $method->method('isDeprecated')->willReturn($deprecated); + $method->method('getDeprecatedDescription')->willReturn($deprecationText); + return $method; + } } diff --git a/tests/PHPStan/Reflection/UnionTypesTest.php b/tests/PHPStan/Reflection/UnionTypesTest.php index 692b4f31e1..c1369cef07 100644 --- a/tests/PHPStan/Reflection/UnionTypesTest.php +++ b/tests/PHPStan/Reflection/UnionTypesTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0'); - } - - require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'; - - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass(Foo::class); - $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); - $this->assertInstanceOf(UnionType::class, $propertyType); - $this->assertSame('bool|int', $propertyType->describe(VerbosityLevel::precise())); - - $method = $class->getNativeMethod('doFoo'); - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodReturnType = $methodVariant->getReturnType(); - $this->assertInstanceOf(UnionType::class, $methodReturnType); - $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $methodReturnType->describe(VerbosityLevel::precise())); - - $methodParameterType = $methodVariant->getParameters()[0]->getType(); - $this->assertInstanceOf(UnionType::class, $methodParameterType); - $this->assertSame('bool|int', $methodParameterType->describe(VerbosityLevel::precise())); - - $function = $reflectionProvider->getFunction(new Name('NativeUnionTypes\doFoo'), null); - $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); - $functionReturnType = $functionVariant->getReturnType(); - $this->assertInstanceOf(UnionType::class, $functionReturnType); - $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $functionReturnType->describe(VerbosityLevel::precise())); - - $functionParameterType = $functionVariant->getParameters()[0]->getType(); - $this->assertInstanceOf(UnionType::class, $functionParameterType); - $this->assertSame('bool|int', $functionParameterType->describe(VerbosityLevel::precise())); - } - + public function testUnionTypes(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'; + + $reflectionProvider = $this->createBroker(); + $class = $reflectionProvider->getClass(Foo::class); + $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); + $this->assertInstanceOf(UnionType::class, $propertyType); + $this->assertSame('bool|int', $propertyType->describe(VerbosityLevel::precise())); + + $method = $class->getNativeMethod('doFoo'); + $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodReturnType = $methodVariant->getReturnType(); + $this->assertInstanceOf(UnionType::class, $methodReturnType); + $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $methodReturnType->describe(VerbosityLevel::precise())); + + $methodParameterType = $methodVariant->getParameters()[0]->getType(); + $this->assertInstanceOf(UnionType::class, $methodParameterType); + $this->assertSame('bool|int', $methodParameterType->describe(VerbosityLevel::precise())); + + $function = $reflectionProvider->getFunction(new Name('NativeUnionTypes\doFoo'), null); + $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); + $functionReturnType = $functionVariant->getReturnType(); + $this->assertInstanceOf(UnionType::class, $functionReturnType); + $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $functionReturnType->describe(VerbosityLevel::precise())); + + $functionParameterType = $functionVariant->getParameters()[0]->getType(); + $this->assertInstanceOf(UnionType::class, $functionParameterType); + $this->assertSame('bool|int', $functionParameterType->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Reflection/data/Bar.php b/tests/PHPStan/Reflection/data/Bar.php index daaafa7675..6494b977de 100644 --- a/tests/PHPStan/Reflection/data/Bar.php +++ b/tests/PHPStan/Reflection/data/Bar.php @@ -1,8 +1,9 @@ - * @extends I1 */ -interface I extends I0, I1 { +interface I extends I0, I1 +{ } /** @@ -36,7 +41,8 @@ interface I extends I0, I1 { * * @implements I */ -class C0 implements I { +class C0 implements I +{ } /** @@ -44,12 +50,14 @@ class C0 implements I { * * @extends C0<\DateTime> */ -class C extends C0 { +class C extends C0 +{ } /** * @implements I<\DateTimeInterface> */ -class Override extends C { +class Override extends C +{ } diff --git a/tests/PHPStan/Reflection/data/IRouter.php b/tests/PHPStan/Reflection/data/IRouter.php index 184e235b4e..06e987545d 100644 --- a/tests/PHPStan/Reflection/data/IRouter.php +++ b/tests/PHPStan/Reflection/data/IRouter.php @@ -4,8 +4,6 @@ interface IRouter { - - /** @deprecated */ - const SECURED = 's'; - + /** @deprecated */ + public const SECURED = 's'; } diff --git a/tests/PHPStan/Reflection/data/IsAttribute.php b/tests/PHPStan/Reflection/data/IsAttribute.php index 687ddfd6a3..d51c81807d 100644 --- a/tests/PHPStan/Reflection/data/IsAttribute.php +++ b/tests/PHPStan/Reflection/data/IsAttribute.php @@ -5,17 +5,14 @@ #[\Attribute] class IsAttribute { - } #[\Attribute(\Attribute::IS_REPEATABLE)] class IsAttribute2 { - } #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_PROPERTY)] class IsAttribute3 { - } diff --git a/tests/PHPStan/Reflection/data/IsNotAttribute.php b/tests/PHPStan/Reflection/data/IsNotAttribute.php index fe52a6fbaa..29e62c0bb8 100644 --- a/tests/PHPStan/Reflection/data/IsNotAttribute.php +++ b/tests/PHPStan/Reflection/data/IsNotAttribute.php @@ -4,5 +4,4 @@ class IsNotAttribute { - } diff --git a/tests/PHPStan/Reflection/data/SecuredRouter.php b/tests/PHPStan/Reflection/data/SecuredRouter.php index bcf261f444..5298fde642 100644 --- a/tests/PHPStan/Reflection/data/SecuredRouter.php +++ b/tests/PHPStan/Reflection/data/SecuredRouter.php @@ -4,5 +4,4 @@ class SecuredRouter implements IRouter { - } diff --git a/tests/PHPStan/Reflection/data/distances/ExtendedIpsumInterface.php b/tests/PHPStan/Reflection/data/distances/ExtendedIpsumInterface.php index a8155a627e..b85a05d56c 100644 --- a/tests/PHPStan/Reflection/data/distances/ExtendedIpsumInterface.php +++ b/tests/PHPStan/Reflection/data/distances/ExtendedIpsumInterface.php @@ -4,5 +4,4 @@ interface ExtendedIpsumInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/FirstIpsumInterface.php b/tests/PHPStan/Reflection/data/distances/FirstIpsumInterface.php index 7fe6ebd790..3501fa7c64 100644 --- a/tests/PHPStan/Reflection/data/distances/FirstIpsumInterface.php +++ b/tests/PHPStan/Reflection/data/distances/FirstIpsumInterface.php @@ -4,5 +4,4 @@ interface FirstIpsumInterface extends ExtendedIpsumInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/FirstLoremInterface.php b/tests/PHPStan/Reflection/data/distances/FirstLoremInterface.php index e2147a2b2e..b4e79eae3b 100644 --- a/tests/PHPStan/Reflection/data/distances/FirstLoremInterface.php +++ b/tests/PHPStan/Reflection/data/distances/FirstLoremInterface.php @@ -4,5 +4,4 @@ interface FirstLoremInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/Ipsum.php b/tests/PHPStan/Reflection/data/distances/Ipsum.php index 9d0842ab10..8ede1b0793 100644 --- a/tests/PHPStan/Reflection/data/distances/Ipsum.php +++ b/tests/PHPStan/Reflection/data/distances/Ipsum.php @@ -4,7 +4,5 @@ class Ipsum extends Lorem implements FirstIpsumInterface, SecondIpsumInterface, ThirdIpsumInterface { - - use TraitOne; - + use TraitOne; } diff --git a/tests/PHPStan/Reflection/data/distances/Lorem.php b/tests/PHPStan/Reflection/data/distances/Lorem.php index 38da2cef8b..126b22d622 100644 --- a/tests/PHPStan/Reflection/data/distances/Lorem.php +++ b/tests/PHPStan/Reflection/data/distances/Lorem.php @@ -4,7 +4,5 @@ class Lorem implements FirstLoremInterface, SecondLoremInterface { - - use TraitTwo; - + use TraitTwo; } diff --git a/tests/PHPStan/Reflection/data/distances/SecondIpsumInterface.php b/tests/PHPStan/Reflection/data/distances/SecondIpsumInterface.php index f0dfc77a0b..4a58c2daf4 100644 --- a/tests/PHPStan/Reflection/data/distances/SecondIpsumInterface.php +++ b/tests/PHPStan/Reflection/data/distances/SecondIpsumInterface.php @@ -4,5 +4,4 @@ interface SecondIpsumInterface extends ExtendedIpsumInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/SecondLoremInterface.php b/tests/PHPStan/Reflection/data/distances/SecondLoremInterface.php index 629778dc84..f8a82c8f11 100644 --- a/tests/PHPStan/Reflection/data/distances/SecondLoremInterface.php +++ b/tests/PHPStan/Reflection/data/distances/SecondLoremInterface.php @@ -4,5 +4,4 @@ interface SecondLoremInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/ThirdIpsumInterface.php b/tests/PHPStan/Reflection/data/distances/ThirdIpsumInterface.php index 13a9ed17c1..4edef350e3 100644 --- a/tests/PHPStan/Reflection/data/distances/ThirdIpsumInterface.php +++ b/tests/PHPStan/Reflection/data/distances/ThirdIpsumInterface.php @@ -4,5 +4,4 @@ interface ThirdIpsumInterface extends ExtendedIpsumInterface { - } diff --git a/tests/PHPStan/Reflection/data/distances/TraitOne.php b/tests/PHPStan/Reflection/data/distances/TraitOne.php index 4cf546e764..938c567444 100644 --- a/tests/PHPStan/Reflection/data/distances/TraitOne.php +++ b/tests/PHPStan/Reflection/data/distances/TraitOne.php @@ -4,5 +4,4 @@ trait TraitOne { - } diff --git a/tests/PHPStan/Reflection/data/distances/TraitThree.php b/tests/PHPStan/Reflection/data/distances/TraitThree.php index e45a96a317..b73faa3498 100644 --- a/tests/PHPStan/Reflection/data/distances/TraitThree.php +++ b/tests/PHPStan/Reflection/data/distances/TraitThree.php @@ -4,5 +4,4 @@ trait TraitThree { - } diff --git a/tests/PHPStan/Reflection/data/distances/TraitTwo.php b/tests/PHPStan/Reflection/data/distances/TraitTwo.php index ab59672160..4859a49307 100644 --- a/tests/PHPStan/Reflection/data/distances/TraitTwo.php +++ b/tests/PHPStan/Reflection/data/distances/TraitTwo.php @@ -4,7 +4,5 @@ trait TraitTwo { - - use TraitThree; - + use TraitThree; } diff --git a/tests/PHPStan/Reflection/data/function-definitions.php b/tests/PHPStan/Reflection/data/function-definitions.php index 497c3dbe71..e85911af11 100644 --- a/tests/PHPStan/Reflection/data/function-definitions.php +++ b/tests/PHPStan/Reflection/data/function-definitions.php @@ -1,8 +1,7 @@ = 8.0 += 8.0 namespace NativeMixedType; @@ -6,37 +8,34 @@ class Foo { + public mixed $fooProp; - public mixed $fooProp; - - public function doFoo(mixed $foo): mixed - { - assertType('mixed', $foo); - assertType('mixed', $this->fooProp); - } - + public function doFoo(mixed $foo): mixed + { + assertType('mixed', $foo); + assertType('mixed', $this->fooProp); + } } class Bar { - } function doFoo(mixed $foo): mixed { - assertType('mixed', $foo); + assertType('mixed', $foo); } function (Foo $foo): void { - assertType('mixed', $foo->fooProp); - assertType('mixed', $foo->doFoo(1)); - assertType('mixed', doFoo(1)); + assertType('mixed', $foo->fooProp); + assertType('mixed', $foo->doFoo(1)); + assertType('mixed', doFoo(1)); }; function (): void { - $f = function (mixed $foo): mixed { - assertType('mixed', $foo); - }; + $f = function (mixed $foo): mixed { + assertType('mixed', $foo); + }; - assertType('void', $f(1)); + assertType('void', $f(1)); }; diff --git a/tests/PHPStan/Reflection/data/staticReturnType.php b/tests/PHPStan/Reflection/data/staticReturnType.php index 66aa5f0b03..a8bc1e6110 100644 --- a/tests/PHPStan/Reflection/data/staticReturnType.php +++ b/tests/PHPStan/Reflection/data/staticReturnType.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace NativeStaticReturnType; @@ -6,50 +8,47 @@ class Foo { - - public function doFoo(): static - { - return new static(); - } - - public function doBar(): void - { - assertType('static(NativeStaticReturnType\Foo)', $this->doFoo()); - } - - /** - * @return callable(): static - */ - public function doBaz(): callable - { - $f = function (): static { - return new static(); - }; - - assertType('static(NativeStaticReturnType\Foo)', $f()); - - return $f; - } - + public function doFoo(): static + { + return new static(); + } + + public function doBar(): void + { + assertType('static(NativeStaticReturnType\Foo)', $this->doFoo()); + } + + /** + * @return callable(): static + */ + public function doBaz(): callable + { + $f = function (): static { + return new static(); + }; + + assertType('static(NativeStaticReturnType\Foo)', $f()); + + return $f; + } } class Bar extends Foo { - } function (Foo $foo): void { - assertType('NativeStaticReturnType\Foo', $foo->doFoo()); + assertType('NativeStaticReturnType\Foo', $foo->doFoo()); - $callable = $foo->doBaz(); - assertType('callable(): NativeStaticReturnType\Foo', $callable); - assertType('NativeStaticReturnType\Foo', $callable()); + $callable = $foo->doBaz(); + assertType('callable(): NativeStaticReturnType\Foo', $callable); + assertType('NativeStaticReturnType\Foo', $callable()); }; function (Bar $bar): void { - assertType('NativeStaticReturnType\Bar', $bar->doFoo()); + assertType('NativeStaticReturnType\Bar', $bar->doFoo()); - $callable = $bar->doBaz(); - assertType('callable(): NativeStaticReturnType\Bar', $callable); - assertType('NativeStaticReturnType\Bar', $callable()); + $callable = $bar->doBaz(); + assertType('callable(): NativeStaticReturnType\Bar', $callable); + assertType('NativeStaticReturnType\Bar', $callable()); }; diff --git a/tests/PHPStan/Reflection/data/unionTypes.php b/tests/PHPStan/Reflection/data/unionTypes.php index e4b24b0acd..c0efc64af7 100644 --- a/tests/PHPStan/Reflection/data/unionTypes.php +++ b/tests/PHPStan/Reflection/data/unionTypes.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace NativeUnionTypes; @@ -7,87 +9,79 @@ class Foo { - - public int|bool $fooProp; - - public function doFoo(int|bool $foo): self|Bar - { - assertType('bool|int', $foo); - assertType('bool|int', $this->fooProp); - assertNativeType('bool|int', $foo); - } - + public int|bool $fooProp; + + public function doFoo(int|bool $foo): self|Bar + { + assertType('bool|int', $foo); + assertType('bool|int', $this->fooProp); + assertNativeType('bool|int', $foo); + } } class Bar { - } function doFoo(int|bool $foo): Foo|Bar { - assertType('bool|int', $foo); - assertNativeType('bool|int', $foo); + assertType('bool|int', $foo); + assertNativeType('bool|int', $foo); } function (Foo $foo): void { - assertType('bool|int', $foo->fooProp); - assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $foo->doFoo(1)); - assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', doFoo(1)); + assertType('bool|int', $foo->fooProp); + assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $foo->doFoo(1)); + assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', doFoo(1)); }; function (): void { - $f = function (int|bool $foo): Foo|Bar { - assertType('bool|int', $foo); - }; + $f = function (int|bool $foo): Foo|Bar { + assertType('bool|int', $foo); + }; - assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $f(1)); + assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $f(1)); }; class Baz { - - public function doFoo(array|false $foo): void - { - assertType('array|false', $foo); - assertNativeType('array|false', $foo); - assertType('array|false', $this->doBar()); - } - - public function doBar(): array|false - { - - } - - /** - * @param array $foo - */ - public function doBaz(array|false $foo): void - { - assertType('array|false', $foo); - assertNativeType('array|false', $foo); - - assertType('array|false', $this->doLorem()); - } - - /** - * @return array - */ - public function doLorem(): array|false - { - - } - - public function doIpsum(int|string|null $nullable): void - { - assertType('int|string|null', $nullable); - assertNativeType('int|string|null', $nullable); - assertType('int|string|null', $this->doDolor()); - } - - public function doDolor(): int|string|null - { - - } - + public function doFoo(array|false $foo): void + { + assertType('array|false', $foo); + assertNativeType('array|false', $foo); + assertType('array|false', $this->doBar()); + } + + public function doBar(): array|false + { + } + + /** + * @param array $foo + */ + public function doBaz(array|false $foo): void + { + assertType('array|false', $foo); + assertNativeType('array|false', $foo); + + assertType('array|false', $this->doLorem()); + } + + /** + * @return array + */ + public function doLorem(): array|false + { + } + + public function doIpsum(int|string|null $nullable): void + { + assertType('int|string|null', $nullable); + assertNativeType('int|string|null', $nullable); + assertType('int|string|null', $this->doDolor()); + } + + public function doDolor(): int|string|null + { + } } diff --git a/tests/PHPStan/Rules/AlwaysFailRule.php b/tests/PHPStan/Rules/AlwaysFailRule.php index af3809d23f..b449b81114 100644 --- a/tests/PHPStan/Rules/AlwaysFailRule.php +++ b/tests/PHPStan/Rules/AlwaysFailRule.php @@ -1,4 +1,6 @@ -name instanceof Node\Name) { - return []; - } - - if ($node->name->toLowerString() !== 'fail') { - return []; - } - - return ['Fail.']; - } - + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + if ($node->name->toLowerString() !== 'fail') { + return []; + } + + return ['Fail.']; + } } diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php index d6c27680be..d5aa8058fb 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new AppendedArrayItemTypeRule( - new PropertyReflectionFinder(), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testAppendedArrayItemType(): void - { - $this->analyse( - [__DIR__ . '/data/appended-array-item.php'], - [ - [ - 'Array (array) does not accept string.', - 18, - ], - [ - 'Array (array) does not accept array(1, 2, 3).', - 20, - ], - [ - 'Array (array) does not accept array(\'AppendedArrayItem\\\\Foo\', \'classMethod\').', - 23, - ], - [ - 'Array (array) does not accept array(\'Foo\', \'Hello world\').', - 25, - ], - [ - 'Array (array) does not accept string.', - 27, - ], - [ - 'Array (array) does not accept string.', - 32, - ], - [ - 'Array (array) does not accept Closure(): 1.', - 45, - ], - [ - 'Array (array) does not accept AppendedArrayItem\Baz.', - 79, - ], - ] - ); - } - + public function testAppendedArrayItemType(): void + { + $this->analyse( + [__DIR__ . '/data/appended-array-item.php'], + [ + [ + 'Array (array) does not accept string.', + 18, + ], + [ + 'Array (array) does not accept array(1, 2, 3).', + 20, + ], + [ + 'Array (array) does not accept array(\'AppendedArrayItem\\\\Foo\', \'classMethod\').', + 23, + ], + [ + 'Array (array) does not accept array(\'Foo\', \'Hello world\').', + 25, + ], + [ + 'Array (array) does not accept string.', + 27, + ], + [ + 'Array (array) does not accept string.', + 32, + ], + [ + 'Array (array) does not accept Closure(): 1.', + 45, + ], + [ + 'Array (array) does not accept AppendedArrayItem\Baz.', + 79, + ], + ] + ); + } } diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php index 502df29bc4..d4b674ce39 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/appended-array-key.php'], [ - [ - 'Array (array) does not accept key int|string.', - 28, - ], - [ - 'Array (array) does not accept key string.', - 30, - ], - [ - 'Array (array) does not accept key int.', - 31, - ], - [ - 'Array (array) does not accept key int|string.', - 33, - ], - [ - 'Array (array) does not accept key 0.', - 38, - ], - [ - 'Array (array) does not accept key 1.', - 46, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/appended-array-key.php'], [ + [ + 'Array (array) does not accept key int|string.', + 28, + ], + [ + 'Array (array) does not accept key string.', + 30, + ], + [ + 'Array (array) does not accept key int.', + 31, + ], + [ + 'Array (array) does not accept key int|string.', + 33, + ], + [ + 'Array (array) does not accept key 0.', + 38, + ], + [ + 'Array (array) does not accept key 1.', + 46, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php index 195dbf6e94..57ee9ed654 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false); - protected function getRule(): Rule - { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); - - return new ArrayDestructuringRule( - $ruleLevelHelper, - new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/array-destructuring.php'], [ - [ - 'Cannot use array destructuring on array|null.', - 11, - ], - [ - 'Offset 0 does not exist on array().', - 12, - ], - [ - 'Cannot use array destructuring on stdClass.', - 13, - ], - [ - 'Offset 2 does not exist on array(1, 2).', - 15, - ], - [ - 'Offset \'a\' does not exist on array(\'b\' => 1).', - 22, - ], - ]); - } + return new ArrayDestructuringRule( + $ruleLevelHelper, + new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true) + ); + } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/array-destructuring.php'], [ + [ + 'Cannot use array destructuring on array|null.', + 11, + ], + [ + 'Offset 0 does not exist on array().', + 12, + ], + [ + 'Cannot use array destructuring on stdClass.', + 13, + ], + [ + 'Offset 2 does not exist on array(1, 2).', + 15, + ], + [ + 'Offset \'a\' does not exist on array(\'b\' => 1).', + 22, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php index d8b999e8e6..6b2ec3a1e8 100644 --- a/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/dead-foreach.php'], [ - [ - 'Empty array passed to foreach.', - 16, - ], - [ - 'Empty array passed to foreach.', - 30, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/dead-foreach.php'], [ + [ + 'Empty array passed to foreach.', + 16, + ], + [ + 'Empty array passed to foreach.', + 30, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php index 28e7898337..b8ddca4e68 100644 --- a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/duplicate-keys.php'], [ - [ - 'Array has 2 duplicate keys with value \'\' (null, NULL).', - 15, - ], - [ - 'Array has 4 duplicate keys with value 1 (1, 1, 1.0, true).', - 17, - ], - [ - 'Array has 3 duplicate keys with value 0 (false, 0, PHPSTAN_DUPLICATE_KEY).', - 23, - ], - [ - 'Array has 2 duplicate keys with value \'=\' (self::EQ, self::IS).', - 32, - ], - [ - 'Array has 2 duplicate keys with value 2 ($idx, $idx).', - 55, - ], - ]); - } - + public function testDuplicateKeys(): void + { + define('PHPSTAN_DUPLICATE_KEY', 0); + $this->analyse([__DIR__ . '/data/duplicate-keys.php'], [ + [ + 'Array has 2 duplicate keys with value \'\' (null, NULL).', + 15, + ], + [ + 'Array has 4 duplicate keys with value 1 (1, 1, 1.0, true).', + 17, + ], + [ + 'Array has 3 duplicate keys with value 0 (false, 0, PHPSTAN_DUPLICATE_KEY).', + 23, + ], + [ + 'Array has 2 duplicate keys with value \'=\' (self::EQ, self::IS).', + 32, + ], + [ + 'Array has 2 duplicate keys with value 2 ($idx, $idx).', + 55, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php index 51ba629e16..85c12dd063 100644 --- a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/empty-array-item.php'], [ - [ - 'Literal array contains empty item.', - 5, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/empty-array-item.php'], [ + [ + 'Literal array contains empty item.', + 5, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index e1cc3685d0..6d849d8acf 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [ - [ - 'Invalid array key type DateTimeImmutable.', - 7, - ], - [ - 'Invalid array key type array.', - 8, - ], - [ - 'Possibly invalid array key type stdClass|string.', - 24, - ], - [ - 'Invalid array key type DateTimeImmutable.', - 31, - ], - ]); - } - + public function testInvalidKey(): void + { + $this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [ + [ + 'Invalid array key type DateTimeImmutable.', + 7, + ], + [ + 'Invalid array key type array.', + 8, + ], + [ + 'Possibly invalid array key type stdClass|string.', + 24, + ], + [ + 'Invalid array key type DateTimeImmutable.', + 31, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php index 7bb10e9e47..318179901a 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/invalid-key-array-item.php'], [ - [ - 'Invalid array key type DateTimeImmutable.', - 13, - ], - [ - 'Invalid array key type array.', - 14, - ], - [ - 'Possibly invalid array key type stdClass|string.', - 15, - ], - ]); - } - - public function testInvalidKeyInList(): void - { - $this->analyse([__DIR__ . '/data/invalid-key-list.php'], [ - [ - 'Invalid array key type DateTimeImmutable.', - 7, - ], - [ - 'Invalid array key type array.', - 8, - ], - ]); - } + public function testInvalidKey(): void + { + $this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [ + [ + 'Invalid array key type DateTimeImmutable.', + 13, + ], + [ + 'Invalid array key type array.', + 14, + ], + [ + 'Possibly invalid array key type stdClass|string.', + 15, + ], + ]); + } - public function testInvalidKeyShortArray(): void - { - $this->analyse([__DIR__ . '/data/invalid-key-short-array.php'], [ - [ - 'Invalid array key type DateTimeImmutable.', - 7, - ], - [ - 'Invalid array key type array.', - 8, - ], - ]); - } + public function testInvalidKeyInList(): void + { + $this->analyse([__DIR__ . '/data/invalid-key-list.php'], [ + [ + 'Invalid array key type DateTimeImmutable.', + 7, + ], + [ + 'Invalid array key type array.', + 8, + ], + ]); + } + public function testInvalidKeyShortArray(): void + { + $this->analyse([__DIR__ . '/data/invalid-key-short-array.php'], [ + [ + 'Invalid array key type DateTimeImmutable.', + 7, + ], + [ + 'Invalid array key type array.', + 8, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index 6025adb242..a80abee593 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testCheckWithMaybes(): void - { - $this->analyse([__DIR__ . '/data/foreach-iterable.php'], [ - [ - 'Argument of an invalid type string supplied for foreach, only iterables are supported.', - 10, - ], - [ - 'Argument of an invalid type array|false supplied for foreach, only iterables are supported.', - 19, - ], - [ - 'Iterating over an object of an unknown class IterablesInForeach\Bar.', - 47, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } - + public function testCheckWithMaybes(): void + { + $this->analyse([__DIR__ . '/data/foreach-iterable.php'], [ + [ + 'Argument of an invalid type string supplied for foreach, only iterables are supported.', + 10, + ], + [ + 'Argument of an invalid type array|false supplied for foreach, only iterables are supported.', + 19, + ], + [ + 'Iterating over an object of an unknown class IterablesInForeach\Bar.', + 47, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index cbf0a48f5d..5faf6df50c 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false); - protected function getRule(): \PHPStan\Rules\Rule - { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); - - return new NonexistentOffsetInArrayDimFetchRule( - $ruleLevelHelper, - new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true), - true - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/nonexistent-offset.php'], [ - [ - 'Offset \'b\' does not exist on array(\'a\' => stdClass, 0 => 2).', - 17, - ], - [ - 'Offset 1 does not exist on array(\'a\' => stdClass, 0 => 2).', - 18, - ], - [ - 'Offset \'a\' does not exist on array(\'b\' => 1).', - 55, - ], - [ - 'Access to offset \'bar\' on an unknown class NonexistentOffset\Bar.', - 101, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an offset on an unknown class NonexistentOffset\Bar.', - 102, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Offset 0 does not exist on array.', - 111, - ], - [ - 'Offset \'0\' does not exist on array.', - 112, - ], - [ - 'Offset int does not exist on array.', - 114, - ], - [ - 'Offset \'test\' does not exist on null.', - 126, - ], - [ - 'Cannot access offset 42 on int.', - 142, - ], - [ - 'Cannot access offset 42 on float.', - 143, - ], - [ - 'Cannot access offset 42 on bool.', - 144, - ], - [ - 'Cannot access offset 42 on resource.', - 145, - ], - [ - 'Offset \'c\' does not exist on array(\'c\' => bool)|array(\'e\' => true).', - 171, - ], - [ - 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', - 190, - ], - [ - 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', - 193, - ], - [ - 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', - 225, - ], - [ - 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', - 228, - ], - [ - 'Offset string does not exist on array.', - 240, - ], - [ - 'Cannot access offset \'a\' on Closure(): void.', - 253, - ], - [ - 'Cannot access offset \'a\' on array(\'a\' => 1, \'b\' => 1)|(Closure(): void).', - 258, - ], - [ - 'Offset string does not exist on array.', - 308, - ], - [ - 'Offset null does not exist on array.', - 310, - ], - [ - 'Offset int does not exist on array.', - 312, - ], - [ - 'Offset \'baz\' does not exist on array(\'bar\' => 1, ?\'baz\' => 2).', - 344, - ], - [ - 'Offset \'foo\' does not exist on ArrayAccess.', - 411, - ], - [ - 'Cannot access offset \'foo\' on stdClass.', - 423, - ], - [ - 'Cannot access offset \'foo\' on true.', - 426, - ], - [ - 'Cannot access offset \'foo\' on false.', - 429, - ], - [ - 'Cannot access offset \'foo\' on resource.', - 433, - ], - [ - 'Cannot access offset \'foo\' on 42.', - 436, - ], - [ - 'Cannot access offset \'foo\' on 4.141.', - 439, - ], - [ - 'Cannot access offset \'foo\' on array|int.', - 443, - ], - ]); - } + return new NonexistentOffsetInArrayDimFetchRule( + $ruleLevelHelper, + new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true), + true + ); + } - public function testStrings(): void - { - $this->analyse([__DIR__ . '/data/strings-offset-access.php'], [ - [ - 'Offset \'foo\' does not exist on \'foo\'.', - 10, - ], - [ - 'Offset 12.34 does not exist on \'foo\'.', - 13, - ], - [ - 'Offset \'foo\' does not exist on array|string.', - 24, - ], - [ - 'Offset 12.34 does not exist on array|string.', - 28, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/nonexistent-offset.php'], [ + [ + 'Offset \'b\' does not exist on array(\'a\' => stdClass, 0 => 2).', + 17, + ], + [ + 'Offset 1 does not exist on array(\'a\' => stdClass, 0 => 2).', + 18, + ], + [ + 'Offset \'a\' does not exist on array(\'b\' => 1).', + 55, + ], + [ + 'Access to offset \'bar\' on an unknown class NonexistentOffset\Bar.', + 101, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an offset on an unknown class NonexistentOffset\Bar.', + 102, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Offset 0 does not exist on array.', + 111, + ], + [ + 'Offset \'0\' does not exist on array.', + 112, + ], + [ + 'Offset int does not exist on array.', + 114, + ], + [ + 'Offset \'test\' does not exist on null.', + 126, + ], + [ + 'Cannot access offset 42 on int.', + 142, + ], + [ + 'Cannot access offset 42 on float.', + 143, + ], + [ + 'Cannot access offset 42 on bool.', + 144, + ], + [ + 'Cannot access offset 42 on resource.', + 145, + ], + [ + 'Offset \'c\' does not exist on array(\'c\' => bool)|array(\'e\' => true).', + 171, + ], + [ + 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', + 190, + ], + [ + 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', + 193, + ], + [ + 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', + 225, + ], + [ + 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', + 228, + ], + [ + 'Offset string does not exist on array.', + 240, + ], + [ + 'Cannot access offset \'a\' on Closure(): void.', + 253, + ], + [ + 'Cannot access offset \'a\' on array(\'a\' => 1, \'b\' => 1)|(Closure(): void).', + 258, + ], + [ + 'Offset string does not exist on array.', + 308, + ], + [ + 'Offset null does not exist on array.', + 310, + ], + [ + 'Offset int does not exist on array.', + 312, + ], + [ + 'Offset \'baz\' does not exist on array(\'bar\' => 1, ?\'baz\' => 2).', + 344, + ], + [ + 'Offset \'foo\' does not exist on ArrayAccess.', + 411, + ], + [ + 'Cannot access offset \'foo\' on stdClass.', + 423, + ], + [ + 'Cannot access offset \'foo\' on true.', + 426, + ], + [ + 'Cannot access offset \'foo\' on false.', + 429, + ], + [ + 'Cannot access offset \'foo\' on resource.', + 433, + ], + [ + 'Cannot access offset \'foo\' on 42.', + 436, + ], + [ + 'Cannot access offset \'foo\' on 4.141.', + 439, + ], + [ + 'Cannot access offset \'foo\' on array|int.', + 443, + ], + ]); + } - public function testAssignOp(): void - { - $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], [ - [ - 'Offset \'foo\' does not exist on array().', - 4, - ], - [ - 'Offset \'foo\' does not exist on \'Foo\'.', - 10, - ], - [ - 'Cannot access offset \'foo\' on stdClass.', - 13, - ], - [ - 'Cannot access offset \'foo\' on true.', - 16, - ], - [ - 'Cannot access offset \'foo\' on false.', - 19, - ], - [ - 'Cannot access offset \'foo\' on resource.', - 23, - ], - [ - 'Cannot access offset \'foo\' on 4.141.', - 26, - ], - [ - 'Cannot access offset \'foo\' on array|int.', - 30, - ], - [ - 'Cannot access offset \'foo\' on 42.', - 33, - ], - ]); - } + public function testStrings(): void + { + $this->analyse([__DIR__ . '/data/strings-offset-access.php'], [ + [ + 'Offset \'foo\' does not exist on \'foo\'.', + 10, + ], + [ + 'Offset 12.34 does not exist on \'foo\'.', + 13, + ], + [ + 'Offset \'foo\' does not exist on array|string.', + 24, + ], + [ + 'Offset 12.34 does not exist on array|string.', + 28, + ], + ]); + } - public function testCoalesceAssign(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/nonexistent-offset-coalesce-assign.php'], []); - } + public function testAssignOp(): void + { + $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], [ + [ + 'Offset \'foo\' does not exist on array().', + 4, + ], + [ + 'Offset \'foo\' does not exist on \'Foo\'.', + 10, + ], + [ + 'Cannot access offset \'foo\' on stdClass.', + 13, + ], + [ + 'Cannot access offset \'foo\' on true.', + 16, + ], + [ + 'Cannot access offset \'foo\' on false.', + 19, + ], + [ + 'Cannot access offset \'foo\' on resource.', + 23, + ], + [ + 'Cannot access offset \'foo\' on 4.141.', + 26, + ], + [ + 'Cannot access offset \'foo\' on array|int.', + 30, + ], + [ + 'Cannot access offset \'foo\' on 42.', + 33, + ], + ]); + } - public function testIntersection(): void - { - $this->analyse([__DIR__ . '/data/nonexistent-offset-intersection.php'], []); - } + public function testCoalesceAssign(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/nonexistent-offset-coalesce-assign.php'], []); + } - public function testBug3782(): void - { - $this->analyse([__DIR__ . '/data/bug-3782.php'], [ - [ - 'Cannot access offset (int|string) on Bug3782\HelloWorld.', - 11, - ], - ]); - } + public function testIntersection(): void + { + $this->analyse([__DIR__ . '/data/nonexistent-offset-intersection.php'], []); + } - public function testBug4432(): void - { - $this->analyse([__DIR__ . '/data/bug-4432.php'], []); - } + public function testBug3782(): void + { + $this->analyse([__DIR__ . '/data/bug-3782.php'], [ + [ + 'Cannot access offset (int|string) on Bug3782\HelloWorld.', + 11, + ], + ]); + } - public function testBug1664(): void - { - $this->analyse([__DIR__ . '/data/bug-1664.php'], []); - } + public function testBug4432(): void + { + $this->analyse([__DIR__ . '/data/bug-4432.php'], []); + } - public function testBug2689(): void - { - $this->analyse([__DIR__ . '/data/bug-2689.php'], [ - [ - 'Cannot access an offset on callable.', - 14, - ], - ]); - } + public function testBug1664(): void + { + $this->analyse([__DIR__ . '/data/bug-1664.php'], []); + } + public function testBug2689(): void + { + $this->analyse([__DIR__ . '/data/bug-2689.php'], [ + [ + 'Cannot access an offset on callable.', + 14, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php index 624b446d91..c1a5090f9e 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, $this->checkUnions, false); - return new OffsetAccessAssignOpRule($ruleLevelHelper); - } - - public function testRule(): void - { - $this->checkUnions = true; - $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], [ - [ - 'Cannot assign offset \'foo\' to array|int.', - 30, - ], - ]); - } - - public function testRuleWithoutUnions(): void - { - $this->checkUnions = false; - $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], []); - } - + /** @var bool */ + private $checkUnions; + + protected function getRule(): Rule + { + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false); + return new OffsetAccessAssignOpRule($ruleLevelHelper); + } + + public function testRule(): void + { + $this->checkUnions = true; + $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], [ + [ + 'Cannot assign offset \'foo\' to array|int.', + 30, + ], + ]); + } + + public function testRuleWithoutUnions(): void + { + $this->checkUnions = false; + $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], []); + } } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index 88f38c8ee1..e36ce08dfd 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, $this->checkUnionTypes, false); - return new OffsetAccessAssignmentRule($ruleLevelHelper); - } - - public function testOffsetAccessAssignmentToScalar(): void - { - $this->checkUnionTypes = true; - $this->analyse( - [__DIR__ . '/data/offset-access-assignment-to-scalar.php'], - [ - [ - 'Cannot assign offset \'foo\' to string.', - 14, - ], - [ - 'Cannot assign new offset to string.', - 17, - ], - [ - 'Cannot assign offset 12.34 to string.', - 20, - ], - [ - 'Cannot assign offset \'foo\' to array|string.', - 28, - ], - [ - 'Cannot assign offset int|object to array|string.', - 35, - ], - [ - 'Cannot assign offset int|object to string.', - 38, - ], - [ - 'Cannot assign offset false to string.', - 66, - ], - [ - 'Cannot assign offset stdClass to string.', - 68, - ], - [ - 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', - 72, - ], - [ - 'Cannot assign offset false to OffsetAccessAssignment\ObjectWithOffsetAccess.', - 75, - ], - [ - 'Cannot assign new offset to OffsetAccessAssignment\ObjectWithOffsetAccess.', - 81, - ], - ] - ); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, false); + return new OffsetAccessAssignmentRule($ruleLevelHelper); + } - public function testOffsetAccessAssignmentToScalarWithoutMaybes(): void - { - $this->checkUnionTypes = false; - $this->analyse( - [__DIR__ . '/data/offset-access-assignment-to-scalar.php'], - [ - [ - 'Cannot assign offset \'foo\' to string.', - 14, - ], - [ - 'Cannot assign new offset to string.', - 17, - ], - [ - 'Cannot assign offset 12.34 to string.', - 20, - ], - [ - 'Cannot assign offset false to string.', - 66, - ], - [ - 'Cannot assign offset stdClass to string.', - 68, - ], - [ - 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', - 72, - ], - [ - 'Cannot assign offset false to OffsetAccessAssignment\ObjectWithOffsetAccess.', - 75, - ], - [ - 'Cannot assign new offset to OffsetAccessAssignment\ObjectWithOffsetAccess.', - 81, - ], - ] - ); - } + public function testOffsetAccessAssignmentToScalar(): void + { + $this->checkUnionTypes = true; + $this->analyse( + [__DIR__ . '/data/offset-access-assignment-to-scalar.php'], + [ + [ + 'Cannot assign offset \'foo\' to string.', + 14, + ], + [ + 'Cannot assign new offset to string.', + 17, + ], + [ + 'Cannot assign offset 12.34 to string.', + 20, + ], + [ + 'Cannot assign offset \'foo\' to array|string.', + 28, + ], + [ + 'Cannot assign offset int|object to array|string.', + 35, + ], + [ + 'Cannot assign offset int|object to string.', + 38, + ], + [ + 'Cannot assign offset false to string.', + 66, + ], + [ + 'Cannot assign offset stdClass to string.', + 68, + ], + [ + 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', + 72, + ], + [ + 'Cannot assign offset false to OffsetAccessAssignment\ObjectWithOffsetAccess.', + 75, + ], + [ + 'Cannot assign new offset to OffsetAccessAssignment\ObjectWithOffsetAccess.', + 81, + ], + ] + ); + } - public function testInheritDocTemplateTypeResolution(): void - { - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/inherit-doc-template-type-resolution.php'], []); - } + public function testOffsetAccessAssignmentToScalarWithoutMaybes(): void + { + $this->checkUnionTypes = false; + $this->analyse( + [__DIR__ . '/data/offset-access-assignment-to-scalar.php'], + [ + [ + 'Cannot assign offset \'foo\' to string.', + 14, + ], + [ + 'Cannot assign new offset to string.', + 17, + ], + [ + 'Cannot assign offset 12.34 to string.', + 20, + ], + [ + 'Cannot assign offset false to string.', + 66, + ], + [ + 'Cannot assign offset stdClass to string.', + 68, + ], + [ + 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', + 72, + ], + [ + 'Cannot assign offset false to OffsetAccessAssignment\ObjectWithOffsetAccess.', + 75, + ], + [ + 'Cannot assign new offset to OffsetAccessAssignment\ObjectWithOffsetAccess.', + 81, + ], + ] + ); + } - public function testAssignNewOffsetToStubbedClass(): void - { - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/new-offset-stub.php'], []); - } + public function testInheritDocTemplateTypeResolution(): void + { + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/inherit-doc-template-type-resolution.php'], []); + } + public function testAssignNewOffsetToStubbedClass(): void + { + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/new-offset-stub.php'], []); + } } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php index 366937c226..4fa844e8a3 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): Rule - { - return new OffsetAccessValueAssignmentRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/offset-access-value-assignment.php'], [ - [ - 'ArrayAccess does not accept string.', - 13, - ], - [ - 'ArrayAccess does not accept string.', - 15, - ], - [ - 'ArrayAccess does not accept string.', - 20, - ], - [ - 'ArrayAccess does not accept array.', - 21, - ], - [ - 'ArrayAccess does not accept string.', - 24, - ], - [ - 'ArrayAccess does not accept float.', - 38, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/offset-access-value-assignment.php'], [ + [ + 'ArrayAccess does not accept string.', + 13, + ], + [ + 'ArrayAccess does not accept string.', + 15, + ], + [ + 'ArrayAccess does not accept string.', + 20, + ], + [ + 'ArrayAccess does not accept array.', + 21, + ], + [ + 'ArrayAccess does not accept string.', + 24, + ], + [ + 'ArrayAccess does not accept float.', + 38, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessWithoutDimForReadingRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessWithoutDimForReadingRuleTest.php index 027f8c420f..94ca94df5d 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessWithoutDimForReadingRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessWithoutDimForReadingRuleTest.php @@ -1,4 +1,6 @@ -analyse( - [__DIR__ . '/data/offset-access-without-dim-for-reading.php'], - [ - [ - 'Cannot use [] for reading.', - 7, - ], - [ - 'Cannot use [] for reading.', - 8, - ], - [ - 'Cannot use [] for reading.', - 9, - ], - [ - 'Cannot use [] for reading.', - 12, - ], - [ - 'Cannot use [] for reading.', - 13, - ], - [ - 'Cannot use [] for reading.', - 14, - ], - [ - 'Cannot use [] for reading.', - 14, - ], - [ - 'Cannot use [] for reading.', - 17, - ], - [ - 'Cannot use [] for reading.', - 21, - ], - [ - 'Cannot use [] for reading.', - 22, - ], - [ - 'Cannot use [] for reading.', - 23, - ], - [ - 'Cannot use [] for reading.', - 24, - ], - [ - 'Cannot use [] for reading.', - 27, - ], - [ - 'Cannot use [] for reading.', - 28, - ], - [ - 'Cannot use [] for reading.', - 29, - ], - [ - 'Cannot use [] for reading.', - 30, - ], - ] - ); - } - + public function testOffsetAccessWithoutDimForReading(): void + { + $this->analyse( + [__DIR__ . '/data/offset-access-without-dim-for-reading.php'], + [ + [ + 'Cannot use [] for reading.', + 7, + ], + [ + 'Cannot use [] for reading.', + 8, + ], + [ + 'Cannot use [] for reading.', + 9, + ], + [ + 'Cannot use [] for reading.', + 12, + ], + [ + 'Cannot use [] for reading.', + 13, + ], + [ + 'Cannot use [] for reading.', + 14, + ], + [ + 'Cannot use [] for reading.', + 14, + ], + [ + 'Cannot use [] for reading.', + 17, + ], + [ + 'Cannot use [] for reading.', + 21, + ], + [ + 'Cannot use [] for reading.', + 22, + ], + [ + 'Cannot use [] for reading.', + 23, + ], + [ + 'Cannot use [] for reading.', + 24, + ], + [ + 'Cannot use [] for reading.', + 27, + ], + [ + 'Cannot use [] for reading.', + 28, + ], + [ + 'Cannot use [] for reading.', + 29, + ], + [ + 'Cannot use [] for reading.', + 30, + ], + ] + ); + } } diff --git a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php index 833f4d6b78..9bb3e0a499 100644 --- a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): Rule - { - return new UnpackIterableInArrayRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/unpack-iterable.php'], [ - [ - 'Only iterables can be unpacked, array|null given.', - 21, - ], - [ - 'Only iterables can be unpacked, int given.', - 22, - ], - [ - 'Only iterables can be unpacked, string given.', - 23, - ], - ]); - } - + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/unpack-iterable.php'], [ + [ + 'Only iterables can be unpacked, array|null given.', + 21, + ], + [ + 'Only iterables can be unpacked, int given.', + 22, + ], + [ + 'Only iterables can be unpacked, string given.', + 23, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Arrays/data/appended-array-item.php b/tests/PHPStan/Rules/Arrays/data/appended-array-item.php index 4d77299b03..ab666b0214 100644 --- a/tests/PHPStan/Rules/Arrays/data/appended-array-item.php +++ b/tests/PHPStan/Rules/Arrays/data/appended-array-item.php @@ -4,77 +4,68 @@ class Foo { - - /** @var int[] */ - private $integers; - - /** @var callable[] */ - private $callables; - - public function doFoo() - { - $this->integers[] = 4; - $this->integers['foo'] = 5; - $this->integers[] = 'foo'; - $this->callables[] = [$this, 'doFoo']; - $this->callables[] = [1, 2, 3]; - $this->callables[] = ['Closure', 'bind']; - $this->callables[] = 'strpos'; - $this->callables[] = [__CLASS__, 'classMethod']; - $world = 'world'; - $this->callables[] = ['Foo', "Hello $world"]; - - $this->integers[] = &$world; - } - - public function assignOp() - { - $this->integers[0] .= 'foo'; - } - + /** @var int[] */ + private $integers; + + /** @var callable[] */ + private $callables; + + public function doFoo() + { + $this->integers[] = 4; + $this->integers['foo'] = 5; + $this->integers[] = 'foo'; + $this->callables[] = [$this, 'doFoo']; + $this->callables[] = [1, 2, 3]; + $this->callables[] = ['Closure', 'bind']; + $this->callables[] = 'strpos'; + $this->callables[] = [__CLASS__, 'classMethod']; + $world = 'world'; + $this->callables[] = ['Foo', "Hello $world"]; + + $this->integers[] = &$world; + } + + public function assignOp() + { + $this->integers[0] .= 'foo'; + } } class Bar { - - /** @var (callable(): string)[] */ - private $stringCallables; - - public function doFoo() - { - $this->stringCallables[] = function (): int { - return 1; - }; - } - - public function doBar() - { - $this->stringCallables[] = function (): string { - return 1; - }; - } - + /** @var (callable(): string)[] */ + private $stringCallables; + + public function doFoo() + { + $this->stringCallables[] = function (): int { + return 1; + }; + } + + public function doBar() + { + $this->stringCallables[] = function (): string { + return 1; + }; + } } class Baz { - - /** @var array */ - public $staticProperty; - + /** @var array */ + public $staticProperty; } class Lorem extends Baz { - } -function (Lorem $lorem) -{ - $lorem->staticProperty[] = new Lorem(); +function (Lorem $lorem) { + $lorem->staticProperty[] = new Lorem(); }; -function (Lorem $lorem) -{ - $lorem->staticProperty[] = new Baz(); +function (Lorem $lorem) { + $lorem->staticProperty[] = new Baz(); }; diff --git a/tests/PHPStan/Rules/Arrays/data/appended-array-key.php b/tests/PHPStan/Rules/Arrays/data/appended-array-key.php index 3381fabab9..f40d721413 100644 --- a/tests/PHPStan/Rules/Arrays/data/appended-array-key.php +++ b/tests/PHPStan/Rules/Arrays/data/appended-array-key.php @@ -4,67 +4,62 @@ class Foo { + /** @var array */ + private $intArray; - /** @var array */ - private $intArray; + /** @var array */ + private $stringArray; - /** @var array */ - private $stringArray; + /** @var array */ + private $bothArray; - /** @var array */ - private $bothArray; + /** + * @param int|string $intOrString + */ + public function doFoo( + int $int, + string $string, + $intOrString, + ?string $stringOrNull + ) { + $this->intArray[new \DateTimeImmutable()] = 1; + $this->intArray[$intOrString] = 1; + $this->intArray[$int] = 1; + $this->intArray[$string] = 1; + $this->stringArray[$int] = 1; + $this->stringArray[$string] = 1; + $this->stringArray[$intOrString] = 1; + $this->bothArray[$int] = 1; + $this->bothArray[$intOrString] = 1; + $this->bothArray[$string] = 1; + $this->stringArray[$stringOrNull] = 1; // will be cast to string + $this->stringArray['0'] = 1; + } - /** - * @param int|string $intOrString - */ - public function doFoo( - int $int, - string $string, - $intOrString, - ?string $stringOrNull - ) - { - $this->intArray[new \DateTimeImmutable()] = 1; - $this->intArray[$intOrString] = 1; - $this->intArray[$int] = 1; - $this->intArray[$string] = 1; - $this->stringArray[$int] = 1; - $this->stringArray[$string] = 1; - $this->stringArray[$intOrString] = 1; - $this->bothArray[$int] = 1; - $this->bothArray[$intOrString] = 1; - $this->bothArray[$string] = 1; - $this->stringArray[$stringOrNull] = 1; // will be cast to string - $this->stringArray['0'] = 1; - } - - public function checkRewrittenArray() - { - $this->stringArray = []; - $integerKey = (int)'1'; - - $this->stringArray[$integerKey] = false; - } + public function checkRewrittenArray() + { + $this->stringArray = []; + $integerKey = (int)'1'; + $this->stringArray[$integerKey] = false; + } } class Bar { + /** @var array, true> */ + private $classStringKey; - /** @var array, true> */ - private $classStringKey; - - /** - * @param class-string<\stdClass> $s - */ - public function doFoo(string $s) - { - $this->classStringKey[$s] = true; - } - - public function doBar() - { - $this->classStringKey[\stdClass::class] = true; - } + /** + * @param class-string<\stdClass> $s + */ + public function doFoo(string $s) + { + $this->classStringKey[$s] = true; + } + public function doBar() + { + $this->classStringKey[\stdClass::class] = true; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/array-destructuring.php b/tests/PHPStan/Rules/Arrays/data/array-destructuring.php index bfe0ff5421..348029a1ea 100644 --- a/tests/PHPStan/Rules/Arrays/data/array-destructuring.php +++ b/tests/PHPStan/Rules/Arrays/data/array-destructuring.php @@ -4,22 +4,20 @@ class Foo { + public function doFoo(?array $arrayOrNull): void + { + [$a] = [0, 1, 2]; + [$a] = $arrayOrNull; + [$a] = []; + [[$a]] = [new \stdClass()]; - public function doFoo(?array $arrayOrNull): void - { - [$a] = [0, 1, 2]; - [$a] = $arrayOrNull; - [$a] = []; - [[$a]] = [new \stdClass()]; + [[$a, $b, $c]] = [[1, 2]]; + } - [[$a, $b, $c]] = [[1, 2]]; - } - - public function doBar(): void - { - ['a' => $a] = ['a' => 1]; - - ['a' => $a] = ['b' => 1]; - } + public function doBar(): void + { + ['a' => $a] = ['a' => 1]; + ['a' => $a] = ['b' => 1]; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-1664.php b/tests/PHPStan/Rules/Arrays/data/bug-1664.php index 64fb492a76..acda34d0df 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-1664.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-1664.php @@ -4,14 +4,14 @@ class A { - public function a() - { - $responses = [ - 'foo', - 42, - 'bar', - ]; + public function a() + { + $responses = [ + 'foo', + 42, + 'bar', + ]; - return $responses[array_rand($responses)]; - } + return $responses[array_rand($responses)]; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-2689.php b/tests/PHPStan/Rules/Arrays/data/bug-2689.php index 394e3c83c1..30900ecb56 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-2689.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-2689.php @@ -4,13 +4,13 @@ class HelloWorld { - /** - * @var callable[] - */ - private $listeners; + /** + * @var callable[] + */ + private $listeners; - public function addListener(string $name, callable $callback): void - { - $this->listeners[$name][] = $callback; - } + public function addListener(string $name, callable $callback): void + { + $this->listeners[$name][] = $callback; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-3782.php b/tests/PHPStan/Rules/Arrays/data/bug-3782.php index e3871d9827..dee0f1e39e 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-3782.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-3782.php @@ -4,11 +4,11 @@ class HelloWorld { - /** @param mixed[] $data */ - public function sayHello(array $data): void - { - foreach($data as $key => $value){ - $this[$key] = $value; - } - } + /** @param mixed[] $data */ + public function sayHello(array $data): void + { + foreach ($data as $key => $value) { + $this[$key] = $value; + } + } } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-4432.php b/tests/PHPStan/Rules/Arrays/data/bug-4432.php index 8cc909faf0..b5a0e3aa4c 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-4432.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-4432.php @@ -1,18 +1,20 @@ - true, - NULL => false, - 1 => 'aaa', - 2 => 'bbb', - 3 => 'ccc', - 1 => 'aaa', - 1.0 => 'aaa', - true => 'aaa', - false => 'aaa', - 0 => 'aaa', - PHPSTAN_DUPLICATE_KEY => 'aaa', - ]; - } - - public function doBar() - { - $array = [ - self::EQ => '= %s', - self::IS => '= %s', - self::NEQ => '!= %s', - ]; - } - - public function doIncrement() - { - $idx = 0; - - $foo = [ - $idx++ => 'test', - $idx++ => 'presto', - ]; - } - - public function doIncrement2() - { - $idx = 0; - - $foo = [ - $idx++ => 'test', - $idx++ => 'presto', - $idx => 'lorem', - $idx => 'ipsum', - ]; - } - + private const EQ = '='; + private const IS = '='; + private const NEQ = '!='; + + public function doFoo() + { + $a = [ + null => true, + null => false, + 1 => 'aaa', + 2 => 'bbb', + 3 => 'ccc', + 1 => 'aaa', + 1.0 => 'aaa', + true => 'aaa', + false => 'aaa', + 0 => 'aaa', + PHPSTAN_DUPLICATE_KEY => 'aaa', + ]; + } + + public function doBar() + { + $array = [ + self::EQ => '= %s', + self::IS => '= %s', + self::NEQ => '!= %s', + ]; + } + + public function doIncrement() + { + $idx = 0; + + $foo = [ + $idx++ => 'test', + $idx++ => 'presto', + ]; + } + + public function doIncrement2() + { + $idx = 0; + + $foo = [ + $idx++ => 'test', + $idx++ => 'presto', + $idx => 'lorem', + $idx => 'ipsum', + ]; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/foreach-iterable.php b/tests/PHPStan/Rules/Arrays/data/foreach-iterable.php index c3b8bfcf52..ec5bb2863f 100644 --- a/tests/PHPStan/Rules/Arrays/data/foreach-iterable.php +++ b/tests/PHPStan/Rules/Arrays/data/foreach-iterable.php @@ -3,48 +3,40 @@ namespace IterablesInForeach; foreach ([1, 2, 3] as $x) { - } $string = 'foo'; foreach ($string as $x) { - } $arrayOrFalse = [1, 2, 3]; if (doFoo()) { - $arrayOrFalse = false; + $arrayOrFalse = false; } foreach ($arrayOrFalse as $val) { - } $arrayOrNull = []; if (doFoo()) { - $arrayOrNull = null; + $arrayOrNull = null; } if (empty($arrayOrNull)) { - } elseif (empty($arrayOrFalse)) { } else { - foreach ($arrayOrNull as $val) { - - } - foreach ($arrayOrFalse as $vla) { - - } + foreach ($arrayOrNull as $val) { + } + foreach ($arrayOrFalse as $vla) { + } } /** @var mixed $mixed */ $mixed = doFoo(); foreach ($mixed as $val) { - } foreach ( - new Bar() as $val + new Bar() as $val ) { - } diff --git a/tests/PHPStan/Rules/Arrays/data/inherit-doc-template-type-resolution.php b/tests/PHPStan/Rules/Arrays/data/inherit-doc-template-type-resolution.php index fb019ba628..32a4cc421a 100644 --- a/tests/PHPStan/Rules/Arrays/data/inherit-doc-template-type-resolution.php +++ b/tests/PHPStan/Rules/Arrays/data/inherit-doc-template-type-resolution.php @@ -4,11 +4,8 @@ class Foo extends \SimpleXMLElement { - - public function removeThis(): void - { - unset($this[0]); - - } - + public function removeThis(): void + { + unset($this[0]); + } } diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php index 6824050420..8588ccf16c 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php @@ -25,7 +25,7 @@ $constantArray = ['a' => 1]; if (doFoo()) { - $constantArray['b'] = 2; + $constantArray['b'] = 2; } $constantArray[new \DateTimeImmutable()] = 1; @@ -33,5 +33,5 @@ /** @var string[] $array */ $array = doFoo(); foreach ($array as $i => $val) { - echo $array[$i]; + echo $array[$i]; } diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php index cde86133df..b613445f18 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php @@ -6,11 +6,11 @@ $stringOrObject = doFoo(); $a = [ - 'foo', - 1 => 'aaa', - '1' => 'aaa', - null => 'aaa', - new \DateTimeImmutable() => 'aaa', - [] => 'bbb', - $stringOrObject => 'aaa', + 'foo', + 1 => 'aaa', + '1' => 'aaa', + null => 'aaa', + new \DateTimeImmutable() => 'aaa', + [] => 'bbb', + $stringOrObject => 'aaa', ]; diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-list.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-list.php index a5f2199c00..ca6d992917 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-list.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-list.php @@ -3,7 +3,7 @@ namespace InvalidKeyList; list( - 'test' => $b, - new \DateTimeImmutable => $c, - [] => $d, + 'test' => $b, + new \DateTimeImmutable() => $c, + [] => $d, ) = $arr; diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-short-array.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-short-array.php index 2d8766755b..f1d4f07f4c 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-short-array.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-short-array.php @@ -3,7 +3,7 @@ namespace InvalidKeyShortArray; [ - 'test' => $b, - new \DateTimeImmutable => $c, - [] => $d, + 'test' => $b, + new \DateTimeImmutable() => $c, + [] => $d, ] = $a; diff --git a/tests/PHPStan/Rules/Arrays/data/new-offset-stub.php b/tests/PHPStan/Rules/Arrays/data/new-offset-stub.php index 6d7584aa4b..6e4299307a 100644 --- a/tests/PHPStan/Rules/Arrays/data/new-offset-stub.php +++ b/tests/PHPStan/Rules/Arrays/data/new-offset-stub.php @@ -7,9 +7,8 @@ */ abstract class Foo implements \ArrayAccess { - } function (Foo $foo): void { - $foo[] = new \stdClass(); + $foo[] = new \stdClass(); }; diff --git a/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-coalesce-assign.php b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-coalesce-assign.php index 627263097d..475fac1763 100644 --- a/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-coalesce-assign.php +++ b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-coalesce-assign.php @@ -1,29 +1,29 @@ -= 7.4 += 7.4 namespace NonexistentOffsetCoalesceAssign; class Foo { + public function doFoo() + { + $a = []; + $a['foo'] ??= 'foo'; + } - public function doFoo() - { - $a = []; - $a['foo'] ??= 'foo'; - } - - public function doBar() - { - $a = []; - if (rand(0, 1)) { - $a['foo'] = 'foo'; - } - $a['foo'] ??= 'foo'; - } - - public function doBaz() - { - $a = ['foo' => 'foo']; - $a['foo'] ??= 'bar'; - } + public function doBar() + { + $a = []; + if (rand(0, 1)) { + $a['foo'] = 'foo'; + } + $a['foo'] ??= 'foo'; + } + public function doBaz() + { + $a = ['foo' => 'foo']; + $a['foo'] ??= 'bar'; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-intersection.php b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-intersection.php index 999252ea9c..f560b6af9a 100644 --- a/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-intersection.php +++ b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-intersection.php @@ -1,4 +1,6 @@ - new \stdClass(), - 2, - ]; - - echo $array['a']; - echo $array[0]; - echo $array['b']; - echo $array[1]; - } - - public function assigningNewKeyToLiteralArray() - { - $array = []; - $array[] = 0; - $array['aaa'] = 1; - - /** @var string $key */ - $key = doFoo(); - $array[$key] = 2; - } - - public function assigningToNullable() - { - $null = null; - $null[] = 'test'; - - /** @var mixed[]|null $nullable */ - $nullable = doFoo(); - $nullable['test'] = 0; - echo $nullable['testt']; - } - - public function unsetOffset() - { - $array = [ - 'a' => new \stdClass(), - 'b' => 1, - ]; - - echo $array['a']; - echo $array['b']; - - unset($array['a']); - - echo $array['a']; - echo $array['b']; - } - - public function arrayAfterForeaches() - { - $result = [ - 'id' => 'blabla', // string - 'allowedRoomCounter' => 0, - 'roomCounter' => 0, - ]; - - foreach ([1, 2] as $x) { - $result['allowedRoomCounter'] += $x; - } - - foreach ([3, 4] as $x) { - $result['roomCounter'] += $x; - } - } - - public function errorType() - { - $array = [ - 'foo' => NONEXISTENT_CONSTANT, - ]; - echo $array['foo']; - } - - public function cumulative() - { - $arr = [1, 1, 1, 1, 2, 5, 3, 2]; - /** @var (string|int)[] */ - $cumulative = []; - - foreach ($arr as $val) { - if (!isset($cumulative[$val])) { - $cumulative[$val] = 0; - } - - $cumulative[$val] = $cumulative[$val] + 1; - } - } - - public function classDoesNotExist(Bar $foo) - { - echo $foo['bar']; - $foo[] = 'test'; - } - - /** - * @param array $array - * @param int $i - */ - public function trickyArrayCasting(array $array, int $i) - { - echo $array[0]; - echo $array['0']; - echo $array['foo']; - echo $array[$i]; - } - - public function assigningToNull() - { - $null = null; - $null['test'] = 'foo'; - } - - public function readingNull() - { - $null = null; - echo $null['test']; - } - - /** - * @param int $int - * @param float $float - * @param bool $bool - * @param resource $resource - */ - public function offsetAccessibleOnPrimitiveTypes( - int $int, - float $float, - bool $bool, - $resource - ) - { - $int[42]; - $float[42]; - $bool[42]; - $resource[42]; - } - - public function offsetExistsOnArrayAccess( - \ArrayAccess $access - ) - { - echo $access['name']; - } - - public function issetProblem(string $s) - { - $a = [ - 'b' => ['c' => false], - 'c' => ['c' => true], - 'd' => ['e' => true] - ]; - if (isset($a[$s]['c'])) { - echo $a[$s]; - echo $a[$s]['c']; - } - if (isset($a['b']['c'])) { - echo $a['b']; - echo $a['b']['c']; - } - - echo $a[$s]['c']; - } - - public function issetProblem2(float $amount, int $bar) - { - if ($amount > 0) { - $map = [ - 1 => 1, - 2 => 2, - ]; - } elseif ($amount < 0) { - $map = [ - 3 => 3, - 4 => 4, - ]; - } else { - $map = []; - } - - echo $map[$bar]; - - if (!isset($map[$bar])) { - echo $map[$bar]; - throw new \Exception(); - } - - return $map[$bar]; - } - - private $propertyThatWillBeSetToArray; - - public function assignmentToProperty() - { - $this->propertyThatWillBeSetToArray = []; - $this->propertyThatWillBeSetToArray['foo'] = 1; - echo $this->propertyThatWillBeSetToArray['foo']; - } - - public function offsetAccessArrayMaybe(array $strings) - { - echo $strings[0]; - - if (isset($strings['foo'])) { - echo $strings['bar']; - } - } - - public function constantStringStillUndefinedInGeneralStringIsset(string $s) - { - $a = [ - 'a' => 'blabla', - ]; - - echo $a[$s]; - echo $a['b']; - if (isset($a[$s])) { - echo $a[$s]; - echo $a['b']; - } - } - - /** - * @param array $array - */ - public function generalArrayHasOffsetOfDifferentType( - array $array, - string $s - ) - { - echo $array[$s]; - if (isset($array[$s])) { - echo $array[$s]; - } - } - - public function issetEliminatesOffsetInaccessibleType() - { - $a = ['a' => 1, 'b' => 1]; - if (rand(0, 1) === 1) { - $a = function () { - - }; - if (isset($a['a'])) { - - } - } - - if (isset($a['a'])) { - echo $a['a']; - echo $a['b']; - } - } - - public function accessOnString(string $s) - { - echo $s[1]; - } - - /** - * @param self[] $array - * @param array $intKeys - */ - public function benevolentUnionType(array $array, array $intKeys) - { - foreach ($array as $key => $foo) { - echo $intKeys[$key]; - } - } - - public function castToArrayKeyType() - { - $array = [ - '1' => [ - 'foo' => 'bar', - ], - ]; - return $array['1']; - } - - /** - * @param array $intArray - * @param array $stringArray - * @param array $intOrStringArray - * @param int $int - * @param string $string - * @param int|null $intOrNull - */ - public function arraysWithNull( - array $intArray, - array $stringArray, - array $intOrStringArray, - int $int, - string $string, - $intOrNull - ) - { - echo $intArray[$int]; - echo $intArray[$string]; - echo $intArray[$intOrNull]; - echo $intArray[null]; - - echo $stringArray[$int]; - echo $stringArray[$string]; - echo $stringArray[$intOrNull]; - echo $stringArray[null]; - - echo $intOrStringArray[$int]; - echo $intOrStringArray[$string]; - echo $intOrStringArray[$intOrNull]; - echo $intOrStringArray[null]; - } - - public function simpleXMLElementArrayAccess(\SimpleXMLElement $xml) - { - echo $xml['asdf']; - } - - public function simpleXMLElementSubclassArrayAccess(SubClassSimpleXMLElement $xml) - { - echo $xml['asdf']; - } - - public function arrayWithMultipleKeysAfterForeaches(int $i, int $j) - { - // Must fail - $array = []; - - $array[$i]['bar'] = 1; - if ((bool) rand(0, 1)) { - $array[$i]['baz'] = 2; - } - - echo $array[$i]['bar']; - echo $array[$i]['baz']; - - // Must work - $array = []; - - $array['bar'] = 1; - $array['baz'] = 2; - - echo $array['bar']; - echo $array['baz']; - - $array = []; - - $array[$i]['bar'] = 1; - $array[$i]['baz'] = 2; - - echo $array[$i]['bar']; - echo $array[$i]['baz']; - - $array = []; - - $array[$i][$j]['bar'] = 1; - $array[$i][$j]['baz'] = 2; - - echo $array[$i][$j]['bar']; - echo $array[$i][$j]['baz']; - } + public function nonexistentOffsetOnArray() + { + $array = [ + 'a' => new \stdClass(), + 2, + ]; + + echo $array['a']; + echo $array[0]; + echo $array['b']; + echo $array[1]; + } + + public function assigningNewKeyToLiteralArray() + { + $array = []; + $array[] = 0; + $array['aaa'] = 1; + + /** @var string $key */ + $key = doFoo(); + $array[$key] = 2; + } + + public function assigningToNullable() + { + $null = null; + $null[] = 'test'; + + /** @var mixed[]|null $nullable */ + $nullable = doFoo(); + $nullable['test'] = 0; + echo $nullable['testt']; + } + + public function unsetOffset() + { + $array = [ + 'a' => new \stdClass(), + 'b' => 1, + ]; + + echo $array['a']; + echo $array['b']; + + unset($array['a']); + + echo $array['a']; + echo $array['b']; + } + + public function arrayAfterForeaches() + { + $result = [ + 'id' => 'blabla', // string + 'allowedRoomCounter' => 0, + 'roomCounter' => 0, + ]; + + foreach ([1, 2] as $x) { + $result['allowedRoomCounter'] += $x; + } + + foreach ([3, 4] as $x) { + $result['roomCounter'] += $x; + } + } + + public function errorType() + { + $array = [ + 'foo' => NONEXISTENT_CONSTANT, + ]; + echo $array['foo']; + } + + public function cumulative() + { + $arr = [1, 1, 1, 1, 2, 5, 3, 2]; + /** @var (string|int)[] */ + $cumulative = []; + + foreach ($arr as $val) { + if (!isset($cumulative[$val])) { + $cumulative[$val] = 0; + } + + $cumulative[$val] = $cumulative[$val] + 1; + } + } + + public function classDoesNotExist(Bar $foo) + { + echo $foo['bar']; + $foo[] = 'test'; + } + + /** + * @param array $array + * @param int $i + */ + public function trickyArrayCasting(array $array, int $i) + { + echo $array[0]; + echo $array['0']; + echo $array['foo']; + echo $array[$i]; + } + + public function assigningToNull() + { + $null = null; + $null['test'] = 'foo'; + } + + public function readingNull() + { + $null = null; + echo $null['test']; + } + + /** + * @param int $int + * @param float $float + * @param bool $bool + * @param resource $resource + */ + public function offsetAccessibleOnPrimitiveTypes( + int $int, + float $float, + bool $bool, + $resource + ) { + $int[42]; + $float[42]; + $bool[42]; + $resource[42]; + } + + public function offsetExistsOnArrayAccess( + \ArrayAccess $access + ) { + echo $access['name']; + } + + public function issetProblem(string $s) + { + $a = [ + 'b' => ['c' => false], + 'c' => ['c' => true], + 'd' => ['e' => true] + ]; + if (isset($a[$s]['c'])) { + echo $a[$s]; + echo $a[$s]['c']; + } + if (isset($a['b']['c'])) { + echo $a['b']; + echo $a['b']['c']; + } + + echo $a[$s]['c']; + } + + public function issetProblem2(float $amount, int $bar) + { + if ($amount > 0) { + $map = [ + 1 => 1, + 2 => 2, + ]; + } elseif ($amount < 0) { + $map = [ + 3 => 3, + 4 => 4, + ]; + } else { + $map = []; + } + + echo $map[$bar]; + + if (!isset($map[$bar])) { + echo $map[$bar]; + throw new \Exception(); + } + + return $map[$bar]; + } + + private $propertyThatWillBeSetToArray; + + public function assignmentToProperty() + { + $this->propertyThatWillBeSetToArray = []; + $this->propertyThatWillBeSetToArray['foo'] = 1; + echo $this->propertyThatWillBeSetToArray['foo']; + } + + public function offsetAccessArrayMaybe(array $strings) + { + echo $strings[0]; + + if (isset($strings['foo'])) { + echo $strings['bar']; + } + } + + public function constantStringStillUndefinedInGeneralStringIsset(string $s) + { + $a = [ + 'a' => 'blabla', + ]; + + echo $a[$s]; + echo $a['b']; + if (isset($a[$s])) { + echo $a[$s]; + echo $a['b']; + } + } + + /** + * @param array $array + */ + public function generalArrayHasOffsetOfDifferentType( + array $array, + string $s + ) { + echo $array[$s]; + if (isset($array[$s])) { + echo $array[$s]; + } + } + + public function issetEliminatesOffsetInaccessibleType() + { + $a = ['a' => 1, 'b' => 1]; + if (rand(0, 1) === 1) { + $a = function () { + }; + if (isset($a['a'])) { + } + } + + if (isset($a['a'])) { + echo $a['a']; + echo $a['b']; + } + } + + public function accessOnString(string $s) + { + echo $s[1]; + } + + /** + * @param self[] $array + * @param array $intKeys + */ + public function benevolentUnionType(array $array, array $intKeys) + { + foreach ($array as $key => $foo) { + echo $intKeys[$key]; + } + } + + public function castToArrayKeyType() + { + $array = [ + '1' => [ + 'foo' => 'bar', + ], + ]; + return $array['1']; + } + + /** + * @param array $intArray + * @param array $stringArray + * @param array $intOrStringArray + * @param int $int + * @param string $string + * @param int|null $intOrNull + */ + public function arraysWithNull( + array $intArray, + array $stringArray, + array $intOrStringArray, + int $int, + string $string, + $intOrNull + ) { + echo $intArray[$int]; + echo $intArray[$string]; + echo $intArray[$intOrNull]; + echo $intArray[null]; + + echo $stringArray[$int]; + echo $stringArray[$string]; + echo $stringArray[$intOrNull]; + echo $stringArray[null]; + + echo $intOrStringArray[$int]; + echo $intOrStringArray[$string]; + echo $intOrStringArray[$intOrNull]; + echo $intOrStringArray[null]; + } + + public function simpleXMLElementArrayAccess(\SimpleXMLElement $xml) + { + echo $xml['asdf']; + } + + public function simpleXMLElementSubclassArrayAccess(SubClassSimpleXMLElement $xml) + { + echo $xml['asdf']; + } + + public function arrayWithMultipleKeysAfterForeaches(int $i, int $j) + { + // Must fail + $array = []; + + $array[$i]['bar'] = 1; + if ((bool) rand(0, 1)) { + $array[$i]['baz'] = 2; + } + + echo $array[$i]['bar']; + echo $array[$i]['baz']; + + // Must work + $array = []; + + $array['bar'] = 1; + $array['baz'] = 2; + + echo $array['bar']; + echo $array['baz']; + + $array = []; + + $array[$i]['bar'] = 1; + $array[$i]['baz'] = 2; + + echo $array[$i]['bar']; + echo $array[$i]['baz']; + + $array = []; + + $array[$i][$j]['bar'] = 1; + $array[$i][$j]['baz'] = 2; + + echo $array[$i][$j]['bar']; + echo $array[$i][$j]['baz']; + } } class SubClassSimpleXMLElement extends \SimpleXMLElement @@ -376,86 +369,78 @@ class SubClassSimpleXMLElement extends \SimpleXMLElement class OffsetAfterForLoop { - - public function doFoo(int $x) - { - $tags = []; - for ($i = 0; $i < 10; $i ++) { - $tags[$i] = $x; - } - - $tags[1] === $tags[1]; - } - + public function doFoo(int $x) + { + $tags = []; + for ($i = 0; $i < 10; $i ++) { + $tags[$i] = $x; + } + + $tags[1] === $tags[1]; + } } class Coalesce { - - public function doFoo() - { - $a = []; - echo $a['foo'] ?? 'foo'; - } - + public function doFoo() + { + $a = []; + echo $a['foo'] ?? 'foo'; + } } class GenericArrayAccess { - - /** - * @param \ArrayAccess $ac - */ - public function doFoo(\ArrayAccess $ac): void - { - $ac['foo']; - $ac[1]; - } - + /** + * @param \ArrayAccess $ac + */ + public function doFoo(\ArrayAccess $ac): void + { + $ac['foo']; + $ac[1]; + } } class Scalars { + public function doFoo(): void + { + $value = new \stdClass(); + $value['foo'] = null; - public function doFoo(): void - { - $value = new \stdClass(); - $value['foo'] = null; - - $value = true; - $value['foo'] = null; - - $value = false; - $value['foo'] = null; + $value = true; + $value['foo'] = null; - /** @var resource $value */ - $value = null; - $value['foo'] = null; + $value = false; + $value['foo'] = null; - $value = 42; - $value['foo'] = null; + /** @var resource $value */ + $value = null; + $value['foo'] = null; - $value = 4.141; - $value['foo'] = null; + $value = 42; + $value['foo'] = null; - /** @var array|int $value */ - $value = []; - $value['foo'] = null; - } + $value = 4.141; + $value['foo'] = null; + /** @var array|int $value */ + $value = []; + $value['foo'] = null; + } } class Bug3282 { - /** - * @phpstan-param array{event: string, msg?: array{ts?: int}} $array - */ - public function foo(array $array): int - { - if (isset($array['msg']['ts'])) { - return 1; - } - - return 0; - } + /** + * @phpstan-param array{event: string, msg?: array{ts?: int}} $array + */ + public function foo(array $array): int + { + if (isset($array['msg']['ts'])) { + return 1; + } + + return 0; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php index 3818fd4ceb..cab62556ea 100644 --- a/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php @@ -4,131 +4,125 @@ class Test1 { + public function test() + { + $value = []; + $value['foo'] = null; - public function test() - { - $value = []; - $value['foo'] = null; + $value = 'Foo'; + $value['foo'] = null; - $value = 'Foo'; - $value['foo'] = null; + $value = 'Foo'; + $value[] = 'test'; - $value = 'Foo'; - $value[] = 'test'; + $value = 'Foo'; + $value[12.34] = 'test'; - $value = 'Foo'; - $value[12.34] = 'test'; + /** @var string|array $maybeString */ + $maybeString = []; + $maybeString[0] = 'foo'; - /** @var string|array $maybeString */ - $maybeString = []; - $maybeString[0] = 'foo'; + /** @var string|array $maybeString */ + $maybeString = []; + $maybeString['foo'] = 'foo'; - /** @var string|array $maybeString */ - $maybeString = []; - $maybeString['foo'] = 'foo'; + /** @var int|object $maybeInt */ + $maybeInt = null; - /** @var int|object $maybeInt */ - $maybeInt = null; + /** @var string|array $maybeString */ + $maybeString = []; + $maybeString[$maybeInt] = 'foo'; - /** @var string|array $maybeString */ - $maybeString = []; - $maybeString[$maybeInt] = 'foo'; + $value = 'Foo'; + $value[$maybeInt] = 'foo'; - $value = 'Foo'; - $value[$maybeInt] = 'foo'; + $value = new \stdClass(); + $value['foo'] = null; - $value = new \stdClass(); - $value['foo'] = null; + $value = true; + $value['foo'] = null; - $value = true; - $value['foo'] = null; + $value = false; + $value['foo'] = null; - $value = false; - $value['foo'] = null; + /** @var resource $value */ + $value = null; + $value['foo'] = null; - /** @var resource $value */ - $value = null; - $value['foo'] = null; + $value = 42; + $value['foo'] = null; - $value = 42; - $value['foo'] = null; + $value = 4.141; + $value['foo'] = null; - $value = 4.141; - $value['foo'] = null; + /** @var array|int $value */ + $value = []; + $value['foo'] = null; - /** @var array|int $value */ - $value = []; - $value['foo'] = null; + $string1 = 'Foo'; + $string1[999] = 'B'; + $string2 = 'Foo'; + $string2[false] = 'C'; + $string3 = 'Foo'; + $string3[new \stdClass()] = 'E'; - $string1 = 'Foo'; - $string1[999] = 'B'; - $string2 = 'Foo'; - $string2[false] = 'C'; - $string3 = 'Foo'; - $string3[new \stdClass()] = 'E'; + $key = [1, 2, 3]; + $storage = new \SplObjectStorage(); + $storage[$key] = 'test'; - $key = [1, 2, 3]; - $storage = new \SplObjectStorage(); - $storage[$key] = 'test'; + $obj1 = new ObjectWithOffsetAccess(); + $obj1[false] = 'invalid key, valid value'; - $obj1 = new ObjectWithOffsetAccess(); - $obj1[false] = 'invalid key, valid value'; - - $obj2 = new ObjectWithOffsetAccess(); - $obj2['valid key'] = ['invalid value']; - - $obj3 = new ObjectWithOffsetAccess(); - $obj3[] = 'null key'; - } + $obj2 = new ObjectWithOffsetAccess(); + $obj2['valid key'] = ['invalid value']; + $obj3 = new ObjectWithOffsetAccess(); + $obj3[] = 'null key'; + } } class ObjectWithOffsetAccess implements \ArrayAccess { - - /** - * @param string $offset - * @return bool - */ - public function offsetExists($offset) - { - return true; - } - - /** - * @param string $offset - * @return int - */ - public function offsetGet($offset) - { - return 0; - } - - /** - * @param string $offset - * @param int $value - * @return void - */ - public function offsetSet($offset, $value) - { - } - - /** - * @param string $offset - * @return void - */ - public function offsetUnset($offset) - { - } - + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return true; + } + + /** + * @param string $offset + * @return int + */ + public function offsetGet($offset) + { + return 0; + } + + /** + * @param string $offset + * @param int $value + * @return void + */ + public function offsetSet($offset, $value) + { + } + + /** + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + } } class AssignOnInt { - - public function doFoo(int $i) - { - $i['foo'] = 'bar'; - } - + public function doFoo(int $i) + { + $i['foo'] = 'bar'; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment.php b/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment.php index 9ce7827d02..3e9b040778 100644 --- a/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment.php +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment.php @@ -4,43 +4,41 @@ class Foo { - - /** - * @param \ArrayAccess $arrayAccess - */ - public function doFoo(\ArrayAccess $arrayAccess): void - { - $arrayAccess[] = 'foo'; - $arrayAccess[] = 1; - $arrayAccess[2] = 'bar'; - - $i = 1; - $arrayAccess[] = $i; - - $arrayAccess[] = 'baz'; - $arrayAccess[] = ['foo']; - - $s = 'foo'; - $arrayAccess[] = &$s; - } - - public function doBar(int $test): void - { - $test[2] = 'foo'; - } - - /** - * @param \ArrayAccess $arrayAccess - */ - public function doBaz(\ArrayAccess $arrayAccess): void - { - $arrayAccess[1] += 1; - $arrayAccess[1] += 2.5; - } - - public function doLorem(string $str): void - { - $str[3] = 'bar'; - } - + /** + * @param \ArrayAccess $arrayAccess + */ + public function doFoo(\ArrayAccess $arrayAccess): void + { + $arrayAccess[] = 'foo'; + $arrayAccess[] = 1; + $arrayAccess[2] = 'bar'; + + $i = 1; + $arrayAccess[] = $i; + + $arrayAccess[] = 'baz'; + $arrayAccess[] = ['foo']; + + $s = 'foo'; + $arrayAccess[] = &$s; + } + + public function doBar(int $test): void + { + $test[2] = 'foo'; + } + + /** + * @param \ArrayAccess $arrayAccess + */ + public function doBaz(\ArrayAccess $arrayAccess): void + { + $arrayAccess[1] += 1; + $arrayAccess[1] += 2.5; + } + + public function doLorem(string $str): void + { + $str[3] = 'bar'; + } } diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php b/tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php index 3ff679e20f..372bda6435 100644 --- a/tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php @@ -6,7 +6,8 @@ $array[][] = 10; $array[]; var_dump($array[]); -while ($foo = $array[]) {} +while ($foo = $array[]) { +} $array['foo']['bar']; $array['foo'][]; @@ -14,7 +15,8 @@ $array[][]; $firstElement = &$array[]; -(function ($ref) {})($array[]); +(function ($ref) { +})($array[]); //(function (&$ref) {})($array[]); // Should work but doesn't // Technically works but makes no sense diff --git a/tests/PHPStan/Rules/Arrays/data/unpack-iterable.php b/tests/PHPStan/Rules/Arrays/data/unpack-iterable.php index 84a24f9421..68d988d44a 100644 --- a/tests/PHPStan/Rules/Arrays/data/unpack-iterable.php +++ b/tests/PHPStan/Rules/Arrays/data/unpack-iterable.php @@ -1,27 +1,26 @@ -= 7.4 += 7.4 namespace UnpackIterable; class Foo { - - /** - * @param int[] $integers - * @param int[]|null $integersOrNull - */ - public function doFoo( - array $integers, - ?array $integersOrNull, - string $str - ) - { - $foo = [ - ...[1, 2, 3], - ...$integers, - ...$integersOrNull, - ...2, - ...$str - ]; - } - + /** + * @param int[] $integers + * @param int[]|null $integersOrNull + */ + public function doFoo( + array $integers, + ?array $integersOrNull, + string $str + ) { + $foo = [ + ...[1, 2, 3], + ...$integers, + ...$integersOrNull, + ...2, + ...$str + ]; + } } diff --git a/tests/PHPStan/Rules/Cast/EchoRuleTest.php b/tests/PHPStan/Rules/Cast/EchoRuleTest.php index 81621e9f19..d61461c405 100644 --- a/tests/PHPStan/Rules/Cast/EchoRuleTest.php +++ b/tests/PHPStan/Rules/Cast/EchoRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): Rule - { - return new EchoRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testEchoRule(): void - { - $this->analyse([__DIR__ . '/data/echo.php'], [ - [ - 'Parameter #1 (array()) of echo cannot be converted to string.', - 7, - ], - [ - 'Parameter #1 (stdClass) of echo cannot be converted to string.', - 9, - ], - [ - 'Parameter #1 (array()) of echo cannot be converted to string.', - 11, - ], - [ - 'Parameter #2 (stdClass) of echo cannot be converted to string.', - 11, - ], - [ - 'Parameter #1 (Closure(): void) of echo cannot be converted to string.', - 13, - ], - [ - 'Parameter #1 (\'string\'|array(\'string\')) of echo cannot be converted to string.', - 17, - ], - ]); - } - + public function testEchoRule(): void + { + $this->analyse([__DIR__ . '/data/echo.php'], [ + [ + 'Parameter #1 (array()) of echo cannot be converted to string.', + 7, + ], + [ + 'Parameter #1 (stdClass) of echo cannot be converted to string.', + 9, + ], + [ + 'Parameter #1 (array()) of echo cannot be converted to string.', + 11, + ], + [ + 'Parameter #2 (stdClass) of echo cannot be converted to string.', + 11, + ], + [ + 'Parameter #1 (Closure(): void) of echo cannot be converted to string.', + 13, + ], + [ + 'Parameter #1 (\'string\'|array(\'string\')) of echo cannot be converted to string.', + 17, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index e0a0fd1e57..1c51d58e47 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, false)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, false)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-cast.php'], [ - [ - 'Cannot cast stdClass to string.', - 7, - ], - [ - 'Cannot cast array() to int.', - 16, - ], - [ - 'Cannot cast \'blabla\' to int.', - 21, - ], - [ - 'Cannot cast stdClass to int.', - 23, - ], - [ - 'Cannot cast stdClass to float.', - 24, - ], - [ - 'Cannot cast Test\\Foo to string.', - 41, - ], - [ - 'Cannot cast array|float|int to string.', - 48, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-cast.php'], [ + [ + 'Cannot cast stdClass to string.', + 7, + ], + [ + 'Cannot cast array() to int.', + 16, + ], + [ + 'Cannot cast \'blabla\' to int.', + 21, + ], + [ + 'Cannot cast stdClass to int.', + 23, + ], + [ + 'Cannot cast stdClass to float.', + 24, + ], + [ + 'Cannot cast Test\\Foo to string.', + 41, + ], + [ + 'Cannot cast array|float|int to string.', + 48, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php index 0429ec48ee..0bc6d15faf 100644 --- a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidPartOfEncapsedStringRule( - new \PhpParser\PrettyPrinter\Standard(), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-encapsed-part.php'], [ - [ - 'Part $std (stdClass) of encapsed string cannot be cast to string.', - 8, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-encapsed-part.php'], [ + [ + 'Part $std (stdClass) of encapsed string cannot be cast to string.', + 8, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Cast/PrintRuleTest.php b/tests/PHPStan/Rules/Cast/PrintRuleTest.php index fa12ad4424..7e618a1821 100644 --- a/tests/PHPStan/Rules/Cast/PrintRuleTest.php +++ b/tests/PHPStan/Rules/Cast/PrintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): Rule - { - return new PrintRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testPrintRule(): void - { - $this->analyse([__DIR__ . '/data/print.php'], [ - [ - 'Parameter array() of print cannot be converted to string.', - 5, - ], - [ - 'Parameter stdClass of print cannot be converted to string.', - 7, - ], - [ - 'Parameter Closure(): void of print cannot be converted to string.', - 9, - ], - [ - 'Parameter array() of print cannot be converted to string.', - 13, - ], - [ - 'Parameter stdClass of print cannot be converted to string.', - 15, - ], - [ - 'Parameter Closure(): void of print cannot be converted to string.', - 17, - ], - [ - 'Parameter \'string\'|array(\'string\') of print cannot be converted to string.', - 21, - ], - ]); - } - + public function testPrintRule(): void + { + $this->analyse([__DIR__ . '/data/print.php'], [ + [ + 'Parameter array() of print cannot be converted to string.', + 5, + ], + [ + 'Parameter stdClass of print cannot be converted to string.', + 7, + ], + [ + 'Parameter Closure(): void of print cannot be converted to string.', + 9, + ], + [ + 'Parameter array() of print cannot be converted to string.', + 13, + ], + [ + 'Parameter stdClass of print cannot be converted to string.', + 15, + ], + [ + 'Parameter Closure(): void of print cannot be converted to string.', + 17, + ], + [ + 'Parameter \'string\'|array(\'string\') of print cannot be converted to string.', + 21, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Cast/UnsetCastRuleTest.php b/tests/PHPStan/Rules/Cast/UnsetCastRuleTest.php index d9000d4378..93f2d2cd37 100644 --- a/tests/PHPStan/Rules/Cast/UnsetCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/UnsetCastRuleTest.php @@ -1,4 +1,6 @@ -phpVersion)); - } - - public function dataRule(): array - { - return [ - [ - 70400, - [], - ], - [ - 80000, - [ - [ - 'The (unset) cast is no longer supported in PHP 8.0 and later.', - 6, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataRule - * @param int $phpVersion - * @param mixed[] $errors - */ - public function testRule(int $phpVersion, array $errors): void - { - $this->phpVersion = $phpVersion; - $this->analyse([__DIR__ . '/data/unset-cast.php'], $errors); - } - + /** @var int */ + private $phpVersion; + + protected function getRule(): Rule + { + return new UnsetCastRule(new PhpVersion($this->phpVersion)); + } + + public function dataRule(): array + { + return [ + [ + 70400, + [], + ], + [ + 80000, + [ + [ + 'The (unset) cast is no longer supported in PHP 8.0 and later.', + 6, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testRule(int $phpVersion, array $errors): void + { + $this->phpVersion = $phpVersion; + $this->analyse([__DIR__ . '/data/unset-cast.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Cast/data/echo.php b/tests/PHPStan/Rules/Cast/data/echo.php index 0235559328..e5e4d5e7f9 100644 --- a/tests/PHPStan/Rules/Cast/data/echo.php +++ b/tests/PHPStan/Rules/Cast/data/echo.php @@ -2,24 +2,22 @@ declare(strict_types=1); -function () -{ - echo []; +function () { + echo []; - echo new stdClass(); + echo new stdClass(); - echo [], new stdClass(); + echo [], new stdClass(); - echo function () {}; + echo function () { + }; - echo 13132, 'string'; - - echo random_int(0, 1) ? ['string'] : 'string'; + echo 13132, 'string'; + echo random_int(0, 1) ? ['string'] : 'string'; }; -function (array $test) -{ - /** @var string $test */ - echo $test; +function (array $test) { + /** @var string $test */ + echo $test; }; diff --git a/tests/PHPStan/Rules/Cast/data/invalid-cast.php b/tests/PHPStan/Rules/Cast/data/invalid-cast.php index 4c48e6acfa..681b6b8129 100644 --- a/tests/PHPStan/Rules/Cast/data/invalid-cast.php +++ b/tests/PHPStan/Rules/Cast/data/invalid-cast.php @@ -1,69 +1,68 @@ createReflectionProvider(); + return new ClassAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new ClassAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/class-attributes.php'], [ - [ - 'Attribute class ClassAttributes\Nonexistent does not exist.', - 22, - ], - [ - 'Class ClassAttributes\Foo is not an Attribute class.', - 28, - ], - [ - 'Class ClassAttributes\Bar referenced with incorrect case: ClassAttributes\baR.', - 34, - ], - [ - 'Attribute class ClassAttributes\Baz does not have the class target.', - 46, - ], - [ - 'Attribute class ClassAttributes\Bar is not repeatable but is already present above the class.', - 59, - ], - [ - 'Attribute class self does not exist.', - 65, - ], - [ - 'Attribute class ClassAttributes\AbstractAttribute is abstract.', - 77, - ], - [ - 'Attribute class ClassAttributes\Bar does not have a constructor and must be instantiated without any parameters.', - 83, - ], - [ - 'Constructor of attribute class ClassAttributes\NonPublicConstructor is not public.', - 100, - ], - [ - 'Attribute class ClassAttributes\AttributeWithConstructor constructor invoked with 0 parameters, 2 required.', - 118, - ], - [ - 'Attribute class ClassAttributes\AttributeWithConstructor constructor invoked with 1 parameter, 2 required.', - 119, - ], - [ - 'Unknown parameter $r in call to ClassAttributes\AttributeWithConstructor constructor.', - 120, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/class-attributes.php'], [ + [ + 'Attribute class ClassAttributes\Nonexistent does not exist.', + 22, + ], + [ + 'Class ClassAttributes\Foo is not an Attribute class.', + 28, + ], + [ + 'Class ClassAttributes\Bar referenced with incorrect case: ClassAttributes\baR.', + 34, + ], + [ + 'Attribute class ClassAttributes\Baz does not have the class target.', + 46, + ], + [ + 'Attribute class ClassAttributes\Bar is not repeatable but is already present above the class.', + 59, + ], + [ + 'Attribute class self does not exist.', + 65, + ], + [ + 'Attribute class ClassAttributes\AbstractAttribute is abstract.', + 77, + ], + [ + 'Attribute class ClassAttributes\Bar does not have a constructor and must be instantiated without any parameters.', + 83, + ], + [ + 'Constructor of attribute class ClassAttributes\NonPublicConstructor is not public.', + 100, + ], + [ + 'Attribute class ClassAttributes\AttributeWithConstructor constructor invoked with 0 parameters, 2 required.', + 118, + ], + [ + 'Attribute class ClassAttributes\AttributeWithConstructor constructor invoked with 1 parameter, 2 required.', + 119, + ], + [ + 'Unknown parameter $r in call to ClassAttributes\AttributeWithConstructor constructor.', + 120, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 623b23448b..e4ca319372 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ClassConstantAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new ClassConstantAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/class-constant-attributes.php'], [ - [ - 'Attribute class ClassConstantAttributes\Foo does not have the class constant target.', - 26, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/class-constant-attributes.php'], [ + [ + 'Attribute class ClassConstantAttributes\Foo does not have the class constant target.', + 26, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 838397f882..3749cb6d01 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ClassConstantRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersion)); - } - - public function testClassConstant(): void - { - $this->phpVersion = PHP_VERSION_ID; - $this->analyse( - [ - __DIR__ . '/data/class-constant.php', - __DIR__ . '/data/class-constant-defined.php', - ], - [ - [ - 'Class ClassConstantNamespace\Bar not found.', - 6, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Using self outside of class scope.', - 7, - ], - [ - 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', - 10, - ], - [ - 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', - 16, - ], - [ - 'Using static outside of class scope.', - 18, - ], - [ - 'Using parent outside of class scope.', - 19, - ], - [ - 'Access to constant FOO on an unknown class ClassConstantNamespace\UnknownClass.', - 21, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', - 26, - ], - [ - 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', - 27, - ], - [ - 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', - 27, - ], - [ - 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', - 28, - ], - [ - 'Access to undefined constant ClassConstantNamespace\Foo|string::DOLOR.', - 33, - ], - ] - ); - } + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new ClassConstantRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersion)); + } - public function testClassConstantVisibility(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); - } + public function testClassConstant(): void + { + $this->phpVersion = PHP_VERSION_ID; + $this->analyse( + [ + __DIR__ . '/data/class-constant.php', + __DIR__ . '/data/class-constant-defined.php', + ], + [ + [ + 'Class ClassConstantNamespace\Bar not found.', + 6, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Using self outside of class scope.', + 7, + ], + [ + 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', + 10, + ], + [ + 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', + 16, + ], + [ + 'Using static outside of class scope.', + 18, + ], + [ + 'Using parent outside of class scope.', + 19, + ], + [ + 'Access to constant FOO on an unknown class ClassConstantNamespace\UnknownClass.', + 21, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', + 26, + ], + [ + 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', + 27, + ], + [ + 'Access to undefined constant ClassConstantNamespace\Foo::DOLOR.', + 27, + ], + [ + 'Class ClassConstantNamespace\Foo referenced with incorrect case: ClassConstantNamespace\FOO.', + 28, + ], + [ + 'Access to undefined constant ClassConstantNamespace\Foo|string::DOLOR.', + 33, + ], + ] + ); + } - $this->phpVersion = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/class-constant-visibility.php'], [ - [ - 'Access to private constant PRIVATE_BAR of class ClassConstantVisibility\Bar.', - 25, - ], - [ - 'Access to parent::BAZ but ClassConstantVisibility\Foo does not extend any class.', - 27, - ], - [ - 'Access to undefined constant ClassConstantVisibility\Bar::PRIVATE_FOO.', - 45, - ], - [ - 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', - 46, - ], - [ - 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', - 47, - ], - [ - 'Access to undefined constant ClassConstantVisibility\Bar::PRIVATE_FOO.', - 60, - ], - [ - 'Access to protected constant PROTECTED_FOO of class ClassConstantVisibility\Foo.', - 71, - ], - [ - 'Access to undefined constant ClassConstantVisibility\WithFooAndBarConstant&ClassConstantVisibility\WithFooConstant::BAZ.', - 106, - ], - [ - 'Access to undefined constant ClassConstantVisibility\WithFooAndBarConstant|ClassConstantVisibility\WithFooConstant::BAR.', - 110, - ], - [ - 'Access to constant FOO on an unknown class ClassConstantVisibility\UnknownClassFirst.', - 112, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to constant FOO on an unknown class ClassConstantVisibility\UnknownClassSecond.', - 112, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Cannot access constant FOO on int|string.', - 116, - ], - [ - 'Class ClassConstantVisibility\Foo referenced with incorrect case: ClassConstantVisibility\FOO.', - 122, - ], - [ - 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', - 122, - ], - ]); - } + public function testClassConstantVisibility(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); + } - public function testClassExists(): void - { - $this->phpVersion = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/class-exists.php'], [ - [ - 'Class UnknownClass\Bar not found.', - 24, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class UnknownClass\Foo not found.', - 26, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class UnknownClass\Foo not found.', - 29, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + $this->phpVersion = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/class-constant-visibility.php'], [ + [ + 'Access to private constant PRIVATE_BAR of class ClassConstantVisibility\Bar.', + 25, + ], + [ + 'Access to parent::BAZ but ClassConstantVisibility\Foo does not extend any class.', + 27, + ], + [ + 'Access to undefined constant ClassConstantVisibility\Bar::PRIVATE_FOO.', + 45, + ], + [ + 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', + 46, + ], + [ + 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', + 47, + ], + [ + 'Access to undefined constant ClassConstantVisibility\Bar::PRIVATE_FOO.', + 60, + ], + [ + 'Access to protected constant PROTECTED_FOO of class ClassConstantVisibility\Foo.', + 71, + ], + [ + 'Access to undefined constant ClassConstantVisibility\WithFooAndBarConstant&ClassConstantVisibility\WithFooConstant::BAZ.', + 106, + ], + [ + 'Access to undefined constant ClassConstantVisibility\WithFooAndBarConstant|ClassConstantVisibility\WithFooConstant::BAR.', + 110, + ], + [ + 'Access to constant FOO on an unknown class ClassConstantVisibility\UnknownClassFirst.', + 112, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to constant FOO on an unknown class ClassConstantVisibility\UnknownClassSecond.', + 112, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Cannot access constant FOO on int|string.', + 116, + ], + [ + 'Class ClassConstantVisibility\Foo referenced with incorrect case: ClassConstantVisibility\FOO.', + 122, + ], + [ + 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', + 122, + ], + ]); + } - public function dataClassConstantOnExpression(): array - { - return [ - [ - 70400, - [ - [ - 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', - 15, - ], - [ - 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', - 16, - ], - [ - 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', - 17, - ], - [ - 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', - 18, - ], - ], - ], - [ - 80000, - [ - [ - 'Accessing ::class constant on a dynamic string is not supported in PHP.', - 16, - ], - [ - 'Cannot access constant class on stdClass|null.', - 17, - ], - [ - 'Cannot access constant class on string|null.', - 18, - ], - ], - ], - ]; - } + public function testClassExists(): void + { + $this->phpVersion = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/class-exists.php'], [ + [ + 'Class UnknownClass\Bar not found.', + 24, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class UnknownClass\Foo not found.', + 26, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class UnknownClass\Foo not found.', + 29, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - /** - * @dataProvider dataClassConstantOnExpression - * @param int $phpVersion - * @param mixed[] $errors - */ - public function testClassConstantOnExpression(int $phpVersion, array $errors): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection'); - } - $this->phpVersion = $phpVersion; - $this->analyse([__DIR__ . '/data/class-constant-on-expr.php'], $errors); - } + public function dataClassConstantOnExpression(): array + { + return [ + [ + 70400, + [ + [ + 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', + 15, + ], + [ + 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', + 16, + ], + [ + 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', + 17, + ], + [ + 'Accessing ::class constant on an expression is supported only on PHP 8.0 and later.', + 18, + ], + ], + ], + [ + 80000, + [ + [ + 'Accessing ::class constant on a dynamic string is not supported in PHP.', + 16, + ], + [ + 'Cannot access constant class on stdClass|null.', + 17, + ], + [ + 'Cannot access constant class on string|null.', + 18, + ], + ], + ], + ]; + } - public function testAttributes(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + /** + * @dataProvider dataClassConstantOnExpression + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testClassConstantOnExpression(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection'); + } + $this->phpVersion = $phpVersion; + $this->analyse([__DIR__ . '/data/class-constant-on-expr.php'], $errors); + } - $this->phpVersion = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/class-constant-attribute.php'], [ - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 5, - ], - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 9, - ], - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 12, - ], - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 15, - ], - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 17, - ], - [ - 'Access to private constant FOO of class ClassConstantAttribute\Foo.', - 26, - ], - [ - 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', - 26, - ], - ]); - } + public function testAttributes(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersion = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/class-constant-attribute.php'], [ + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 5, + ], + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 9, + ], + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 12, + ], + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 15, + ], + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 17, + ], + [ + 'Access to private constant FOO of class ClassConstantAttribute\Foo.', + 26, + ], + [ + 'Access to undefined constant ClassConstantAttribute\Foo::BAR.', + 26, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php index 5ea6be4bd8..233214b979 100644 --- a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('This test needs static reflection'); - } - - $this->analyse( - [ - __DIR__ . '/data/duplicate-declarations.php', - ], - [ - [ - 'Cannot redeclare constant DuplicateDeclarations\Foo::CONST1.', - 8, - ], - [ - 'Cannot redeclare constant DuplicateDeclarations\Foo::CONST2.', - 10, - ], - [ - 'Cannot redeclare property DuplicateDeclarations\Foo::$prop1.', - 17, - ], - [ - 'Cannot redeclare property DuplicateDeclarations\Foo::$prop2.', - 20, - ], - [ - 'Cannot redeclare method DuplicateDeclarations\Foo::func1().', - 27, - ], - [ - 'Cannot redeclare method DuplicateDeclarations\Foo::Func1().', - 35, - ], - ] - ); - } + public function testDuplicateDeclarations(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } - public function testDuplicatePromotedProperty(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('This test needs static reflection'); - } + $this->analyse( + [ + __DIR__ . '/data/duplicate-declarations.php', + ], + [ + [ + 'Cannot redeclare constant DuplicateDeclarations\Foo::CONST1.', + 8, + ], + [ + 'Cannot redeclare constant DuplicateDeclarations\Foo::CONST2.', + 10, + ], + [ + 'Cannot redeclare property DuplicateDeclarations\Foo::$prop1.', + 17, + ], + [ + 'Cannot redeclare property DuplicateDeclarations\Foo::$prop2.', + 20, + ], + [ + 'Cannot redeclare method DuplicateDeclarations\Foo::func1().', + 27, + ], + [ + 'Cannot redeclare method DuplicateDeclarations\Foo::Func1().', + 35, + ], + ] + ); + } - $this->analyse([__DIR__ . '/data/duplicate-promoted-property.php'], [ - [ - 'Cannot redeclare property DuplicatedPromotedProperty\Foo::$foo.', - 11, - ], - [ - 'Cannot redeclare property DuplicatedPromotedProperty\Foo::$bar.', - 13, - ], - ]); - } + public function testDuplicatePromotedProperty(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + $this->analyse([__DIR__ . '/data/duplicate-promoted-property.php'], [ + [ + 'Cannot redeclare property DuplicatedPromotedProperty\Foo::$foo.', + 11, + ], + [ + 'Cannot redeclare property DuplicatedPromotedProperty\Foo::$bar.', + 13, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index 836894513f..6d6d6b6a4a 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingClassInClassExtendsRule( + new ClassCaseSensitivityCheck($broker), + $broker + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingClassInClassExtendsRule( - new ClassCaseSensitivityCheck($broker), - $broker - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/extends-implements.php'], [ - [ - 'Class ExtendsImplements\Foo referenced with incorrect case: ExtendsImplements\FOO.', - 15, - ], - ]); - } - - public function testRuleExtendsError(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('This test needs static reflection'); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/extends-implements.php'], [ + [ + 'Class ExtendsImplements\Foo referenced with incorrect case: ExtendsImplements\FOO.', + 15, + ], + ]); + } - $this->analyse([__DIR__ . '/data/extends-error.php'], [ - [ - 'Class ExtendsError\Foo extends unknown class ExtendsError\Bar.', - 5, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class ExtendsError\Lorem extends interface ExtendsError\BazInterface.', - 15, - ], - [ - 'Class ExtendsError\Ipsum extends trait ExtendsError\DolorTrait.', - 25, - ], - [ - 'Anonymous class extends trait ExtendsError\DolorTrait.', - 30, - ], - [ - 'Class ExtendsError\Sit extends final class ExtendsError\FinalFoo.', - 39, - ], - ]); - } + public function testRuleExtendsError(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + $this->analyse([__DIR__ . '/data/extends-error.php'], [ + [ + 'Class ExtendsError\Foo extends unknown class ExtendsError\Bar.', + 5, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class ExtendsError\Lorem extends interface ExtendsError\BazInterface.', + 15, + ], + [ + 'Class ExtendsError\Ipsum extends trait ExtendsError\DolorTrait.', + 25, + ], + [ + 'Anonymous class extends trait ExtendsError\DolorTrait.', + 30, + ], + [ + 'Class ExtendsError\Sit extends final class ExtendsError\FinalFoo.', + 39, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 18d571f3e8..fd35bd10a5 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingClassInInstanceOfRule( + $broker, + new ClassCaseSensitivityCheck($broker), + true + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingClassInInstanceOfRule( - $broker, - new ClassCaseSensitivityCheck($broker), - true - ); - } - - public function testClassDoesNotExist(): void - { - $this->analyse( - [ - __DIR__ . '/data/instanceof.php', - __DIR__ . '/data/instanceof-defined.php', - ], - [ - [ - 'Class InstanceOfNamespace\Bar not found.', - 7, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Using self outside of class scope.', - 9, - ], - [ - 'Class InstanceOfNamespace\Foo referenced with incorrect case: InstanceOfNamespace\FOO.', - 13, - ], - [ - 'Using parent outside of class scope.', - 15, - ], - [ - 'Using self outside of class scope.', - 17, - ], - ] - ); - } - - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/instanceof-class-exists.php'], []); - } + public function testClassDoesNotExist(): void + { + $this->analyse( + [ + __DIR__ . '/data/instanceof.php', + __DIR__ . '/data/instanceof-defined.php', + ], + [ + [ + 'Class InstanceOfNamespace\Bar not found.', + 7, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Using self outside of class scope.', + 9, + ], + [ + 'Class InstanceOfNamespace\Foo referenced with incorrect case: InstanceOfNamespace\FOO.', + 13, + ], + [ + 'Using parent outside of class scope.', + 15, + ], + [ + 'Using self outside of class scope.', + 17, + ], + ] + ); + } + public function testClassExists(): void + { + $this->analyse([__DIR__ . '/data/instanceof-class-exists.php'], []); + } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php index 826094c52a..c6ab6268ed 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingClassInTraitUseRule( + new ClassCaseSensitivityCheck($broker), + $broker + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingClassInTraitUseRule( - new ClassCaseSensitivityCheck($broker), - $broker - ); - } - - public function testClassWithWrongCase(): void - { - $this->analyse([__DIR__ . '/data/trait-use.php'], [ - [ - 'Trait TraitUseCase\FooTrait referenced with incorrect case: TraitUseCase\FOOTrait.', - 13, - ], - ]); - } - - public function testTraitUseError(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('This test needs static reflection'); - } + public function testClassWithWrongCase(): void + { + $this->analyse([__DIR__ . '/data/trait-use.php'], [ + [ + 'Trait TraitUseCase\FooTrait referenced with incorrect case: TraitUseCase\FOOTrait.', + 13, + ], + ]); + } - $this->analyse([__DIR__ . '/data/trait-use-error.php'], [ - [ - 'Class TraitUseError\Foo uses unknown trait TraitUseError\FooTrait.', - 8, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - /*[ - 'Trait TraitUseError\BarTrait uses class TraitUseError\Foo.', - 15, - ], - [ - 'Trait TraitUseError\BarTrait uses unknown trait TraitUseError\FooTrait. ', - 15, - ],*/ - [ - 'Interface TraitUseError\Baz uses trait TraitUseError\BarTrait.', - 22, - ], - [ - 'Anonymous class uses unknown trait TraitUseError\FooTrait.', - 27, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Anonymous class uses interface TraitUseError\Baz.', - 28, - ], - ]); - } + public function testTraitUseError(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + $this->analyse([__DIR__ . '/data/trait-use-error.php'], [ + [ + 'Class TraitUseError\Foo uses unknown trait TraitUseError\FooTrait.', + 8, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + /*[ + 'Trait TraitUseError\BarTrait uses class TraitUseError\Foo.', + 15, + ], + [ + 'Trait TraitUseError\BarTrait uses unknown trait TraitUseError\FooTrait. ', + 15, + ],*/ + [ + 'Interface TraitUseError\Baz uses trait TraitUseError\BarTrait.', + 22, + ], + [ + 'Anonymous class uses unknown trait TraitUseError\FooTrait.', + 27, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Anonymous class uses interface TraitUseError\Baz.', + 28, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php index 72f58b2369..2c5f0c387e 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInClassImplementsRule( - new ClassCaseSensitivityCheck($broker), - $broker - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/extends-implements.php'], [ - [ - 'Interface ExtendsImplements\FooInterface referenced with incorrect case: ExtendsImplements\FOOInterface.', - 15, - ], - ]); - } - - public function testRuleImplementsError(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('This test needs static reflection'); - } - - $this->analyse([__DIR__ . '/data/implements-error.php'], [ - [ - 'Class ImplementsError\Foo implements unknown interface ImplementsError\Bar.', - 5, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class ImplementsError\Lorem implements class ImplementsError\Foo.', - 10, - ], - [ - 'Class ImplementsError\Ipsum implements trait ImplementsError\DolorTrait.', - 20, - ], - [ - 'Anonymous class implements trait ImplementsError\DolorTrait.', - 25, - ], - ]); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInClassImplementsRule( + new ClassCaseSensitivityCheck($broker), + $broker + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/extends-implements.php'], [ + [ + 'Interface ExtendsImplements\FooInterface referenced with incorrect case: ExtendsImplements\FOOInterface.', + 15, + ], + ]); + } + + public function testRuleImplementsError(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + + $this->analyse([__DIR__ . '/data/implements-error.php'], [ + [ + 'Class ImplementsError\Foo implements unknown interface ImplementsError\Bar.', + 5, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class ImplementsError\Lorem implements class ImplementsError\Foo.', + 10, + ], + [ + 'Class ImplementsError\Ipsum implements trait ImplementsError\DolorTrait.', + 20, + ], + [ + 'Anonymous class implements trait ImplementsError\DolorTrait.', + 25, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php index 196edc0421..7efc386e4e 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInInterfaceExtendsRule( - new ClassCaseSensitivityCheck($broker), - $broker - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/extends-implements.php'], [ - [ - 'Interface ExtendsImplements\FooInterface referenced with incorrect case: ExtendsImplements\FOOInterface.', - 30, - ], - ]); - } - - public function testRuleExtendsError(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('This test needs static reflection'); - } - - $this->analyse([__DIR__ . '/data/interface-extends-error.php'], [ - [ - 'Interface InterfaceExtendsError\Foo extends unknown interface InterfaceExtendsError\Bar.', - 5, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Interface InterfaceExtendsError\Lorem extends class InterfaceExtendsError\BazClass.', - 15, - ], - [ - 'Interface InterfaceExtendsError\Ipsum extends trait InterfaceExtendsError\DolorTrait.', - 25, - ], - ]); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInInterfaceExtendsRule( + new ClassCaseSensitivityCheck($broker), + $broker + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/extends-implements.php'], [ + [ + 'Interface ExtendsImplements\FooInterface referenced with incorrect case: ExtendsImplements\FOOInterface.', + 30, + ], + ]); + } + + public function testRuleExtendsError(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + + $this->analyse([__DIR__ . '/data/interface-extends-error.php'], [ + [ + 'Interface InterfaceExtendsError\Foo extends unknown interface InterfaceExtendsError\Bar.', + 5, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Interface InterfaceExtendsError\Lorem extends class InterfaceExtendsError\BazClass.', + 15, + ], + [ + 'Interface InterfaceExtendsError\Ipsum extends trait InterfaceExtendsError\DolorTrait.', + 25, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 8f6c11818f..1261054316 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -1,4 +1,6 @@ -checkAlwaysTrueInstanceOf, $this->treatPhpDocTypesAsCertain); - } + /** @var bool */ + private $treatPhpDocTypesAsCertain; - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new ImpossibleInstanceOfRule($this->checkAlwaysTrueInstanceOf, $this->treatPhpDocTypesAsCertain); + } - public function testInstanceof(): void - { - $this->checkAlwaysTrueInstanceOf = true; - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse( - [__DIR__ . '/data/impossible-instanceof.php'], - [ - [ - 'Instanceof between ImpossibleInstanceOf\Lorem and ImpossibleInstanceOf\Lorem will always evaluate to true.', - 59, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Ipsum and ImpossibleInstanceOf\Lorem will always evaluate to true.', - 65, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Ipsum and ImpossibleInstanceOf\Ipsum will always evaluate to true.', - 68, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 71, - ], - [ - 'Instanceof between ImpossibleInstanceOf\FooImpl and ImpossibleInstanceOf\Foo will always evaluate to true.', - 74, - ], - [ - 'Instanceof between ImpossibleInstanceOf\BarChild and ImpossibleInstanceOf\Bar will always evaluate to true.', - 77, - ], - [ - 'Instanceof between string and ImpossibleInstanceOf\Foo will always evaluate to false.', - 94, - ], - [ - 'Instanceof between string and string will always evaluate to false.', - 98, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', - 107, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 119, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', - 124, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 137, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', - 142, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 155, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', - 160, - ], - [ - 'Instanceof between callable and ImpossibleInstanceOf\FinalClassWithoutInvoke will always evaluate to false.', - 204, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Dolor will always evaluate to true.', - 226, - $tipText, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 228, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', - 232, - $tipText, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Bar will always evaluate to true.', - 232, - $tipText, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Foo will always evaluate to false.', - 234, - $tipText, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', - 238, - //$tipText, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Bar will always evaluate to false.', - 240, - //$tipText, - ], - [ - 'Instanceof between object and Exception will always evaluate to false.', - 303, - ], - [ - 'Instanceof between object and InvalidArgumentException will always evaluate to false.', - 307, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarChild will always evaluate to false.', - 318, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', - 322, - ], - [ - 'Instanceof between mixed and int results in an error.', - 353, - ], - [ - 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', - 362, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', - 388, - $tipText, - ], - [ - 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', - 418, - $tipText, - ], - [ - 'Instanceof between class-string and class-string will always evaluate to false.', - 419, - ], - [ - 'Instanceof between class-string and string will always evaluate to false.', - 432, - $tipText, - ], - [ - 'Instanceof between DateTimeInterface and string will always evaluate to true.', - 433, - $tipText, - ], - ] - ); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testInstanceofWithoutAlwaysTrue(): void - { - $this->checkAlwaysTrueInstanceOf = false; - $this->treatPhpDocTypesAsCertain = true; + public function testInstanceof(): void + { + $this->checkAlwaysTrueInstanceOf = true; + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse( + [__DIR__ . '/data/impossible-instanceof.php'], + [ + [ + 'Instanceof between ImpossibleInstanceOf\Lorem and ImpossibleInstanceOf\Lorem will always evaluate to true.', + 59, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Ipsum and ImpossibleInstanceOf\Lorem will always evaluate to true.', + 65, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Ipsum and ImpossibleInstanceOf\Ipsum will always evaluate to true.', + 68, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 71, + ], + [ + 'Instanceof between ImpossibleInstanceOf\FooImpl and ImpossibleInstanceOf\Foo will always evaluate to true.', + 74, + ], + [ + 'Instanceof between ImpossibleInstanceOf\BarChild and ImpossibleInstanceOf\Bar will always evaluate to true.', + 77, + ], + [ + 'Instanceof between string and ImpossibleInstanceOf\Foo will always evaluate to false.', + 94, + ], + [ + 'Instanceof between string and string will always evaluate to false.', + 98, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', + 107, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 119, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', + 124, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 137, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', + 142, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 155, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test and ImpossibleInstanceOf\Test will always evaluate to true.', + 160, + ], + [ + 'Instanceof between callable and ImpossibleInstanceOf\FinalClassWithoutInvoke will always evaluate to false.', + 204, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Dolor will always evaluate to true.', + 226, + $tipText, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 228, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', + 232, + $tipText, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Bar will always evaluate to true.', + 232, + $tipText, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Foo will always evaluate to false.', + 234, + $tipText, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar&ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', + 238, + //$tipText, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Bar will always evaluate to false.', + 240, + //$tipText, + ], + [ + 'Instanceof between object and Exception will always evaluate to false.', + 303, + ], + [ + 'Instanceof between object and InvalidArgumentException will always evaluate to false.', + 307, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarChild will always evaluate to false.', + 318, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', + 322, + ], + [ + 'Instanceof between mixed and int results in an error.', + 353, + ], + [ + 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', + 362, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', + 388, + $tipText, + ], + [ + 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', + 418, + $tipText, + ], + [ + 'Instanceof between class-string and class-string will always evaluate to false.', + 419, + ], + [ + 'Instanceof between class-string and string will always evaluate to false.', + 432, + $tipText, + ], + [ + 'Instanceof between DateTimeInterface and string will always evaluate to true.', + 433, + $tipText, + ], + ] + ); + } - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse( - [__DIR__ . '/data/impossible-instanceof.php'], - [ - [ - 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 71, - ], - [ - 'Instanceof between string and ImpossibleInstanceOf\Foo will always evaluate to false.', - 94, - ], - [ - 'Instanceof between string and string will always evaluate to false.', - 98, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 119, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 137, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 155, - ], - [ - 'Instanceof between callable and ImpossibleInstanceOf\FinalClassWithoutInvoke will always evaluate to false.', - 204, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 228, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Foo will always evaluate to false.', - 234, - $tipText, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Bar will always evaluate to false.', - 240, - //$tipText, - ], - [ - 'Instanceof between object and Exception will always evaluate to false.', - 303, - ], - [ - 'Instanceof between object and InvalidArgumentException will always evaluate to false.', - 307, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarChild will always evaluate to false.', - 318, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', - 322, - ], - [ - 'Instanceof between mixed and int results in an error.', - 353, - ], - [ - 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', - 362, - ], - [ - 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', - 418, - $tipText, - ], - [ - 'Instanceof between class-string and class-string will always evaluate to false.', - 419, - ], - [ - 'Instanceof between class-string and string will always evaluate to false.', - 432, - $tipText, - ], - ] - ); - } + public function testInstanceofWithoutAlwaysTrue(): void + { + $this->checkAlwaysTrueInstanceOf = false; + $this->treatPhpDocTypesAsCertain = true; - public function testDoNotReportTypesFromPhpDocs(): void - { - $this->checkAlwaysTrueInstanceOf = true; - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ - [ - 'Instanceof between stdClass and stdClass will always evaluate to true.', - 12, - ], - [ - 'Instanceof between stdClass and Exception will always evaluate to false.', - 15, - ], - [ - 'Instanceof between DateTimeImmutable and DateTimeInterface will always evaluate to true.', - 27, - ], - [ - 'Instanceof between DateTimeImmutable and ImpossibleInstanceofNotPhpDoc\SomeFinalClass will always evaluate to false.', - 30, - ], - ]); - } + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse( + [__DIR__ . '/data/impossible-instanceof.php'], + [ + [ + 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 71, + ], + [ + 'Instanceof between string and ImpossibleInstanceOf\Foo will always evaluate to false.', + 94, + ], + [ + 'Instanceof between string and string will always evaluate to false.', + 98, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 119, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 137, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 155, + ], + [ + 'Instanceof between callable and ImpossibleInstanceOf\FinalClassWithoutInvoke will always evaluate to false.', + 204, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Lorem will always evaluate to false.', + 228, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Foo will always evaluate to false.', + 234, + $tipText, + ], + [ + 'Instanceof between *NEVER* and ImpossibleInstanceOf\Bar will always evaluate to false.', + 240, + //$tipText, + ], + [ + 'Instanceof between object and Exception will always evaluate to false.', + 303, + ], + [ + 'Instanceof between object and InvalidArgumentException will always evaluate to false.', + 307, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarChild will always evaluate to false.', + 318, + ], + [ + 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', + 322, + ], + [ + 'Instanceof between mixed and int results in an error.', + 353, + ], + [ + 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', + 362, + ], + [ + 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', + 418, + $tipText, + ], + [ + 'Instanceof between class-string and class-string will always evaluate to false.', + 419, + ], + [ + 'Instanceof between class-string and string will always evaluate to false.', + 432, + $tipText, + ], + ] + ); + } - public function testReportTypesFromPhpDocs(): void - { - $this->checkAlwaysTrueInstanceOf = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ - [ - 'Instanceof between stdClass and stdClass will always evaluate to true.', - 12, - ], - [ - 'Instanceof between stdClass and Exception will always evaluate to false.', - 15, - ], - [ - 'Instanceof between DateTimeImmutable and DateTimeInterface will always evaluate to true.', - 27, - ], - [ - 'Instanceof between DateTimeImmutable and ImpossibleInstanceofNotPhpDoc\SomeFinalClass will always evaluate to false.', - 30, - ], - [ - 'Instanceof between DateTimeImmutable and DateTimeImmutable will always evaluate to true.', - 33, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Instanceof between DateTimeImmutable and DateTime will always evaluate to false.', - 36, - //'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportTypesFromPhpDocs(): void + { + $this->checkAlwaysTrueInstanceOf = true; + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ + [ + 'Instanceof between stdClass and stdClass will always evaluate to true.', + 12, + ], + [ + 'Instanceof between stdClass and Exception will always evaluate to false.', + 15, + ], + [ + 'Instanceof between DateTimeImmutable and DateTimeInterface will always evaluate to true.', + 27, + ], + [ + 'Instanceof between DateTimeImmutable and ImpossibleInstanceofNotPhpDoc\SomeFinalClass will always evaluate to false.', + 30, + ], + ]); + } - public function testBug3096(): void - { - $this->checkAlwaysTrueInstanceOf = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-3096.php'], []); - } + public function testReportTypesFromPhpDocs(): void + { + $this->checkAlwaysTrueInstanceOf = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ + [ + 'Instanceof between stdClass and stdClass will always evaluate to true.', + 12, + ], + [ + 'Instanceof between stdClass and Exception will always evaluate to false.', + 15, + ], + [ + 'Instanceof between DateTimeImmutable and DateTimeInterface will always evaluate to true.', + 27, + ], + [ + 'Instanceof between DateTimeImmutable and ImpossibleInstanceofNotPhpDoc\SomeFinalClass will always evaluate to false.', + 30, + ], + [ + 'Instanceof between DateTimeImmutable and DateTimeImmutable will always evaluate to true.', + 33, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Instanceof between DateTimeImmutable and DateTime will always evaluate to false.', + 36, + //'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + public function testBug3096(): void + { + $this->checkAlwaysTrueInstanceOf = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-3096.php'], []); + } } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 47e51ea7ed..c4ff905be0 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new InstantiationRule( + $broker, + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true), + new ClassCaseSensitivityCheck($broker) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new InstantiationRule( - $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true), - new ClassCaseSensitivityCheck($broker) - ); - } - - public function testInstantiation(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); - } - $this->analyse( - [__DIR__ . '/data/instantiation.php'], - [ - [ - 'Class TestInstantiation\InstantiatingClass constructor invoked with 0 parameters, 1 required.', - 15, - ], - [ - 'TestInstantiation\InstantiatingClass::doFoo() calls new parent but TestInstantiation\InstantiatingClass does not extend any class.', - 18, - ], - [ - 'Class TestInstantiation\FooInstantiation does not have a constructor and must be instantiated without any parameters.', - 26, - ], - [ - 'Instantiated class TestInstantiation\FooBarInstantiation not found.', - 27, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class TestInstantiation\BarInstantiation constructor invoked with 0 parameters, 1 required.', - 28, - ], - [ - 'Instantiated class TestInstantiation\LoremInstantiation is abstract.', - 29, - ], - [ - 'Cannot instantiate interface TestInstantiation\IpsumInstantiation.', - 30, - ], - [ - 'Instantiated class Test not found.', - 33, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class DatePeriod constructor invoked with 0 parameters, 1-4 required.', - 36, - ], - [ - 'Using self outside of class scope.', - 39, - ], - [ - 'Using static outside of class scope.', - 40, - ], - [ - 'Using parent outside of class scope.', - 41, - ], - [ - 'Class TestInstantiation\InstantiatingClass constructor invoked with 0 parameters, 1 required.', - 57, - ], - [ - 'Class TestInstantiation\FooInstantiation referenced with incorrect case: TestInstantiation\FOOInstantiation.', - 64, - ], - [ - 'Class TestInstantiation\FooInstantiation does not have a constructor and must be instantiated without any parameters.', - 64, - ], - [ - 'Class TestInstantiation\BarInstantiation referenced with incorrect case: TestInstantiation\BARInstantiation.', - 65, - ], - [ - 'Class TestInstantiation\BarInstantiation constructor invoked with 0 parameters, 1 required.', - 65, - ], - [ - 'Class TestInstantiation\BarInstantiation referenced with incorrect case: TestInstantiation\BARInstantiation.', - 66, - ], - [ - 'Class TestInstantiation\ClassExtendsProtectedConstructorClass constructor invoked with 0 parameters, 1 required.', - 94, - ], - [ - 'Cannot instantiate class TestInstantiation\ExtendsPrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', - 104, - ], - [ - 'Class TestInstantiation\ExtendsPrivateConstructorClass constructor invoked with 0 parameters, 1 required.', - 104, - ], - [ - 'Cannot instantiate class TestInstantiation\PrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', - 110, - ], - [ - 'Cannot instantiate class TestInstantiation\ProtectedConstructorClass via protected constructor TestInstantiation\ProtectedConstructorClass::__construct().', - 111, - ], - [ - 'Cannot instantiate class TestInstantiation\ClassExtendsProtectedConstructorClass via protected constructor TestInstantiation\ProtectedConstructorClass::__construct().', - 112, - ], - [ - 'Cannot instantiate class TestInstantiation\ExtendsPrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', - 113, - ], - [ - 'Parameter #1 $message of class Exception constructor expects string, int given.', - 117, - ], - [ - 'Parameter #2 $code of class Exception constructor expects int, string given.', - 117, - ], - [ - 'Class TestInstantiation\NoConstructor referenced with incorrect case: TestInstantiation\NOCONSTRUCTOR.', - 127, - ], - [ - 'Class class@anonymous/tests/PHPStan/Rules/Classes/data/instantiation.php:137 constructor invoked with 3 parameters, 1 required.', - 137, - ], - [ - 'Instantiated class UndefinedClass1 not found.', - 169, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Instantiated class UndefinedClass2 not found.', - 172, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Instantiated class UndefinedClass3 not found.', - 179, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class TestInstantiation\FinalClass does not have a constructor and must be instantiated without any parameters.', - 190, - ], - [ - 'Class TestInstantiation\ClassWithFinalConstructor constructor invoked with 0 parameters, 1 required.', - 206, - ], - [ - 'Class TestInstantiation\ConstructorComingFromAnInterface constructor invoked with 0 parameters, 1 required.', - 229, - ], - [ - 'Class TestInstantiation\AbstractClassWithFinalConstructor constructor invoked with 1 parameter, 0 required.', - 245, - ], - [ - 'Class TestInstantiation\AbstractConstructor constructor invoked with 0 parameters, 1 required.', - 257, - ], - [ - 'Class TestInstantiation\ClassExtendingAbstractConstructor constructor invoked with 0 parameters, 1 required.', - 273, - ], - ] - ); - } - - public function testSoap(): void - { - $this->analyse( - [__DIR__ . '/data/instantiation-soap.php'], - [ - [ - 'Parameter #2 $string of class SoapFault constructor expects string, int given.', - 6, - ], - ] - ); - } + public function testInstantiation(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); + } + $this->analyse( + [__DIR__ . '/data/instantiation.php'], + [ + [ + 'Class TestInstantiation\InstantiatingClass constructor invoked with 0 parameters, 1 required.', + 15, + ], + [ + 'TestInstantiation\InstantiatingClass::doFoo() calls new parent but TestInstantiation\InstantiatingClass does not extend any class.', + 18, + ], + [ + 'Class TestInstantiation\FooInstantiation does not have a constructor and must be instantiated without any parameters.', + 26, + ], + [ + 'Instantiated class TestInstantiation\FooBarInstantiation not found.', + 27, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class TestInstantiation\BarInstantiation constructor invoked with 0 parameters, 1 required.', + 28, + ], + [ + 'Instantiated class TestInstantiation\LoremInstantiation is abstract.', + 29, + ], + [ + 'Cannot instantiate interface TestInstantiation\IpsumInstantiation.', + 30, + ], + [ + 'Instantiated class Test not found.', + 33, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class DatePeriod constructor invoked with 0 parameters, 1-4 required.', + 36, + ], + [ + 'Using self outside of class scope.', + 39, + ], + [ + 'Using static outside of class scope.', + 40, + ], + [ + 'Using parent outside of class scope.', + 41, + ], + [ + 'Class TestInstantiation\InstantiatingClass constructor invoked with 0 parameters, 1 required.', + 57, + ], + [ + 'Class TestInstantiation\FooInstantiation referenced with incorrect case: TestInstantiation\FOOInstantiation.', + 64, + ], + [ + 'Class TestInstantiation\FooInstantiation does not have a constructor and must be instantiated without any parameters.', + 64, + ], + [ + 'Class TestInstantiation\BarInstantiation referenced with incorrect case: TestInstantiation\BARInstantiation.', + 65, + ], + [ + 'Class TestInstantiation\BarInstantiation constructor invoked with 0 parameters, 1 required.', + 65, + ], + [ + 'Class TestInstantiation\BarInstantiation referenced with incorrect case: TestInstantiation\BARInstantiation.', + 66, + ], + [ + 'Class TestInstantiation\ClassExtendsProtectedConstructorClass constructor invoked with 0 parameters, 1 required.', + 94, + ], + [ + 'Cannot instantiate class TestInstantiation\ExtendsPrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', + 104, + ], + [ + 'Class TestInstantiation\ExtendsPrivateConstructorClass constructor invoked with 0 parameters, 1 required.', + 104, + ], + [ + 'Cannot instantiate class TestInstantiation\PrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', + 110, + ], + [ + 'Cannot instantiate class TestInstantiation\ProtectedConstructorClass via protected constructor TestInstantiation\ProtectedConstructorClass::__construct().', + 111, + ], + [ + 'Cannot instantiate class TestInstantiation\ClassExtendsProtectedConstructorClass via protected constructor TestInstantiation\ProtectedConstructorClass::__construct().', + 112, + ], + [ + 'Cannot instantiate class TestInstantiation\ExtendsPrivateConstructorClass via private constructor TestInstantiation\PrivateConstructorClass::__construct().', + 113, + ], + [ + 'Parameter #1 $message of class Exception constructor expects string, int given.', + 117, + ], + [ + 'Parameter #2 $code of class Exception constructor expects int, string given.', + 117, + ], + [ + 'Class TestInstantiation\NoConstructor referenced with incorrect case: TestInstantiation\NOCONSTRUCTOR.', + 127, + ], + [ + 'Class class@anonymous/tests/PHPStan/Rules/Classes/data/instantiation.php:137 constructor invoked with 3 parameters, 1 required.', + 137, + ], + [ + 'Instantiated class UndefinedClass1 not found.', + 169, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Instantiated class UndefinedClass2 not found.', + 172, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Instantiated class UndefinedClass3 not found.', + 179, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class TestInstantiation\FinalClass does not have a constructor and must be instantiated without any parameters.', + 190, + ], + [ + 'Class TestInstantiation\ClassWithFinalConstructor constructor invoked with 0 parameters, 1 required.', + 206, + ], + [ + 'Class TestInstantiation\ConstructorComingFromAnInterface constructor invoked with 0 parameters, 1 required.', + 229, + ], + [ + 'Class TestInstantiation\AbstractClassWithFinalConstructor constructor invoked with 1 parameter, 0 required.', + 245, + ], + [ + 'Class TestInstantiation\AbstractConstructor constructor invoked with 0 parameters, 1 required.', + 257, + ], + [ + 'Class TestInstantiation\ClassExtendingAbstractConstructor constructor invoked with 0 parameters, 1 required.', + 273, + ], + ] + ); + } - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/instantiation-class-exists.php'], []); - } + public function testSoap(): void + { + $this->analyse( + [__DIR__ . '/data/instantiation-soap.php'], + [ + [ + 'Parameter #2 $string of class SoapFault constructor expects string, int given.', + 6, + ], + ] + ); + } - public function testBug3404(): void - { - $this->analyse([__DIR__ . '/data/bug-3404.php'], [ - [ - 'Class finfo constructor invoked with 3 parameters, 0-2 required.', - 7, - ], - ]); - } + public function testClassExists(): void + { + $this->analyse([__DIR__ . '/data/instantiation-class-exists.php'], []); + } - public function testOldStyleConstructorOnPhp8(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0'); - } + public function testBug3404(): void + { + $this->analyse([__DIR__ . '/data/bug-3404.php'], [ + [ + 'Class finfo constructor invoked with 3 parameters, 0-2 required.', + 7, + ], + ]); + } - $this->analyse([__DIR__ . '/data/php80-constructor.php'], [ - [ - 'Class OldStyleConstructorOnPhp8 does not have a constructor and must be instantiated without any parameters.', - 13, - ], - [ - 'Class OldStyleConstructorOnPhp8 does not have a constructor and must be instantiated without any parameters.', - 20, - ], - ]); - } + public function testOldStyleConstructorOnPhp8(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } - public function testOldStyleConstructorOnPhp7(): void - { - if (PHP_VERSION_ID >= 80000) { - $this->markTestSkipped('Test requires PHP 7.x'); - } + $this->analyse([__DIR__ . '/data/php80-constructor.php'], [ + [ + 'Class OldStyleConstructorOnPhp8 does not have a constructor and must be instantiated without any parameters.', + 13, + ], + [ + 'Class OldStyleConstructorOnPhp8 does not have a constructor and must be instantiated without any parameters.', + 20, + ], + ]); + } - $errors = [ - [ - 'Class OldStyleConstructorOnPhp8 constructor invoked with 0 parameters, 1 required.', - 19, - ], - ]; + public function testOldStyleConstructorOnPhp7(): void + { + if (PHP_VERSION_ID >= 80000) { + $this->markTestSkipped('Test requires PHP 7.x'); + } - if (!self::$useStaticReflectionProvider) { - $errors[] = [ - 'Methods with the same name as their class will not be constructors in a future version of PHP; OldStyleConstructorOnPhp8 has a deprecated constructor', - 3, - ]; - } + $errors = [ + [ + 'Class OldStyleConstructorOnPhp8 constructor invoked with 0 parameters, 1 required.', + 19, + ], + ]; - $this->analyse([__DIR__ . '/data/php80-constructor.php'], $errors); - } + if (!self::$useStaticReflectionProvider) { + $errors[] = [ + 'Methods with the same name as their class will not be constructors in a future version of PHP; OldStyleConstructorOnPhp8 has a deprecated constructor', + 3, + ]; + } - public function testBug4030(): void - { - $this->analyse([__DIR__ . '/data/bug-4030.php'], []); - } + $this->analyse([__DIR__ . '/data/php80-constructor.php'], $errors); + } - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testBug4030(): void + { + $this->analyse([__DIR__ . '/data/bug-4030.php'], []); + } - $this->analyse([__DIR__ . '/data/instantiation-promoted-properties.php'], [ - [ - 'Parameter #2 $bar of class InstantiationPromotedProperties\Foo constructor expects array, array given.', - 30, - ], - [ - 'Parameter #2 $bar of class InstantiationPromotedProperties\Bar constructor expects array, array given.', - 33, - ], - ]); - } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function testBug4056(): void - { - $this->analyse([__DIR__ . '/data/bug-4056.php'], []); - } + $this->analyse([__DIR__ . '/data/instantiation-promoted-properties.php'], [ + [ + 'Parameter #2 $bar of class InstantiationPromotedProperties\Foo constructor expects array, array given.', + 30, + ], + [ + 'Parameter #2 $bar of class InstantiationPromotedProperties\Bar constructor expects array, array given.', + 33, + ], + ]); + } - public function testNamedArguments(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testBug4056(): void + { + $this->analyse([__DIR__ . '/data/bug-4056.php'], []); + } - $this->analyse([__DIR__ . '/data/instantiation-named-arguments.php'], [ - [ - 'Missing parameter $j (int) in call to InstantiationNamedArguments\Foo constructor.', - 15, - ], - [ - 'Unknown parameter $z in call to InstantiationNamedArguments\Foo constructor.', - 16, - ], - ]); - } + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function testBug4471(): void - { - $this->analyse([__DIR__ . '/data/bug-4471.php'], [ - [ - 'Instantiated class Bug4471\Baz not found.', - 19, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Instantiated class Bug4471\Foo is abstract.', - 24, - ], - [ - 'Cannot instantiate interface Bug4471\Bar.', - 27, - ], - ]); - } + $this->analyse([__DIR__ . '/data/instantiation-named-arguments.php'], [ + [ + 'Missing parameter $j (int) in call to InstantiationNamedArguments\Foo constructor.', + 15, + ], + [ + 'Unknown parameter $z in call to InstantiationNamedArguments\Foo constructor.', + 16, + ], + ]); + } - public function testBug1711(): void - { - $this->analyse([__DIR__ . '/data/bug-1711.php'], []); - } + public function testBug4471(): void + { + $this->analyse([__DIR__ . '/data/bug-4471.php'], [ + [ + 'Instantiated class Bug4471\Baz not found.', + 19, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Instantiated class Bug4471\Foo is abstract.', + 24, + ], + [ + 'Cannot instantiate interface Bug4471\Bar.', + 27, + ], + ]); + } - public function testBug3425(): void - { - $this->analyse([__DIR__ . '/data/bug-3425.php'], [ - [ - 'Parameter #1 $iterator of class RecursiveIteratorIterator constructor expects T of IteratorAggregate|RecursiveIterator, Generator given.', - 5, - ], - ]); - } + public function testBug1711(): void + { + $this->analyse([__DIR__ . '/data/bug-1711.php'], []); + } + public function testBug3425(): void + { + $this->analyse([__DIR__ . '/data/bug-3425.php'], [ + [ + 'Parameter #1 $iterator of class RecursiveIteratorIterator constructor expects T of IteratorAggregate|RecursiveIterator, Generator given.', + 5, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php index 9023c74faa..bcb7a3983d 100644 --- a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php @@ -1,4 +1,6 @@ -phpVersion)); - } - - public function testNotSupportedOnPhp7(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->phpVersion = 70400; - $this->analyse([__DIR__ . '/data/invalid-promoted-properties.php'], [ - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 8, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 10, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 17, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 21, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 23, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 31, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 38, - ], - [ - 'Promoted properties are supported only on PHP 8.0 and later.', - 45, - ], - ]); - } + protected function getRule(): Rule + { + return new InvalidPromotedPropertiesRule(new PhpVersion($this->phpVersion)); + } - public function testSupportedOnPhp8(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->phpVersion = 80000; - $this->analyse([__DIR__ . '/data/invalid-promoted-properties.php'], [ - [ - 'Promoted properties can be in constructor only.', - 10, - ], - [ - 'Promoted properties can be in constructor only.', - 17, - ], - [ - 'Promoted properties can be in constructor only.', - 21, - ], - [ - 'Promoted properties can be in constructor only.', - 23, - ], - [ - 'Promoted properties are not allowed in abstract constructors.', - 31, - ], - [ - 'Promoted properties are not allowed in abstract constructors.', - 38, - ], - [ - 'Promoted property parameter $i can not be variadic.', - 45, - ], - ]); - } + public function testNotSupportedOnPhp7(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->phpVersion = 70400; + $this->analyse([__DIR__ . '/data/invalid-promoted-properties.php'], [ + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 8, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 10, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 17, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 21, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 23, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 31, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 38, + ], + [ + 'Promoted properties are supported only on PHP 8.0 and later.', + 45, + ], + ]); + } + public function testSupportedOnPhp8(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->phpVersion = 80000; + $this->analyse([__DIR__ . '/data/invalid-promoted-properties.php'], [ + [ + 'Promoted properties can be in constructor only.', + 10, + ], + [ + 'Promoted properties can be in constructor only.', + 17, + ], + [ + 'Promoted properties can be in constructor only.', + 21, + ], + [ + 'Promoted properties can be in constructor only.', + 23, + ], + [ + 'Promoted properties are not allowed in abstract constructors.', + 31, + ], + [ + 'Promoted properties are not allowed in abstract constructors.', + 38, + ], + [ + 'Promoted property parameter $i can not be variadic.', + 45, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 11262e5187..900b025e71 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -1,4 +1,6 @@ - 'int|string'], + $this->createReflectionProvider(), + self::getContainer()->getByType(TypeNodeResolver::class) + ); + } - protected function getRule(): Rule - { - return new LocalTypeAliasesRule( - ['GlobalTypeAlias' => 'int|string'], - $this->createReflectionProvider(), - self::getContainer()->getByType(TypeNodeResolver::class) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/local-type-aliases.php'], [ - [ - 'Type alias ExistingClassAlias already exists as a class in scope of LocalTypeAliases\Bar.', - 23, - ], - [ - 'Type alias GlobalTypeAlias already exists as a global type alias.', - 23, - ], - [ - 'Type alias has an invalid name: int.', - 23, - ], - [ - 'Circular definition detected in type alias RecursiveTypeAlias.', - 23, - ], - [ - 'Circular definition detected in type alias CircularTypeAlias1.', - 23, - ], - [ - 'Circular definition detected in type alias CircularTypeAlias2.', - 23, - ], - [ - 'Cannot import type alias ImportedAliasFromNonClass: class LocalTypeAliases\int does not exist.', - 39, - ], - [ - 'Cannot import type alias ImportedAliasFromUnknownClass: class LocalTypeAliases\UnknownClass does not exist.', - 39, - ], - [ - 'Cannot import type alias ImportedUnknownAlias: type alias does not exist in LocalTypeAliases\Foo.', - 39, - ], - [ - 'Type alias ExistingClassAlias already exists as a class in scope of LocalTypeAliases\Baz.', - 39, - ], - [ - 'Type alias GlobalTypeAlias already exists as a global type alias.', - 39, - ], - [ - 'Imported type alias ExportedTypeAlias has an invalid name: int.', - 39, - ], - [ - 'Type alias OverwrittenTypeAlias overwrites an imported type alias of the same name.', - 39, - ], - [ - 'Circular definition detected in type alias CircularTypeAliasImport2.', - 39, - ], - [ - 'Circular definition detected in type alias CircularTypeAliasImport1.', - 47, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/local-type-aliases.php'], [ + [ + 'Type alias ExistingClassAlias already exists as a class in scope of LocalTypeAliases\Bar.', + 23, + ], + [ + 'Type alias GlobalTypeAlias already exists as a global type alias.', + 23, + ], + [ + 'Type alias has an invalid name: int.', + 23, + ], + [ + 'Circular definition detected in type alias RecursiveTypeAlias.', + 23, + ], + [ + 'Circular definition detected in type alias CircularTypeAlias1.', + 23, + ], + [ + 'Circular definition detected in type alias CircularTypeAlias2.', + 23, + ], + [ + 'Cannot import type alias ImportedAliasFromNonClass: class LocalTypeAliases\int does not exist.', + 39, + ], + [ + 'Cannot import type alias ImportedAliasFromUnknownClass: class LocalTypeAliases\UnknownClass does not exist.', + 39, + ], + [ + 'Cannot import type alias ImportedUnknownAlias: type alias does not exist in LocalTypeAliases\Foo.', + 39, + ], + [ + 'Type alias ExistingClassAlias already exists as a class in scope of LocalTypeAliases\Baz.', + 39, + ], + [ + 'Type alias GlobalTypeAlias already exists as a global type alias.', + 39, + ], + [ + 'Imported type alias ExportedTypeAlias has an invalid name: int.', + 39, + ], + [ + 'Type alias OverwrittenTypeAlias overwrites an imported type alias of the same name.', + 39, + ], + [ + 'Circular definition detected in type alias CircularTypeAliasImport2.', + 39, + ], + [ + 'Circular definition detected in type alias CircularTypeAliasImport1.', + 47, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index 739e847fa9..0b4cdfc152 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - - return new MixinRule( - self::getContainer()->getByType(FileTypeMapper::class), - $reflectionProvider, - new ClassCaseSensitivityCheck($reflectionProvider), - new GenericObjectTypeCheck(), - new MissingTypehintCheck($reflectionProvider, true, true, true), - true - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/mixin.php'], [ - [ - 'PHPDoc tag @mixin contains non-object type int.', - 16, - ], - [ - 'PHPDoc tag @mixin contains unresolvable type.', - 24, - ], - [ - 'PHPDoc tag @mixin contains generic type Exception but class Exception is not generic.', - 34, - ], - [ - 'Generic type Traversable in PHPDoc tag @mixin specifies 3 template types, but class Traversable supports only 2: TKey, TValue', - 34, - ], - [ - 'Type string in generic type ReflectionClass in PHPDoc tag @mixin is not subtype of template type T of object of class ReflectionClass.', - 34, - ], - [ - 'PHPDoc tag @mixin contains generic class ReflectionClass but does not specify its types: T', - 50, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'PHPDoc tag @mixin contains unknown class MixinRule\UnknownestClass.', - 50, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @mixin contains invalid type MixinRule\FooTrait.', - 50, - ], - [ - 'PHPDoc tag @mixin contains unknown class MixinRule\U.', - 59, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Generic type MixinRule\Consecteur in PHPDoc tag @mixin does not specify all template types of class MixinRule\Consecteur: T, U', - 76, - ], - [ - 'Class MixinRule\Foo referenced with incorrect case: MixinRule\foo.', - 84, - ], - ]); - } + return new MixinRule( + self::getContainer()->getByType(FileTypeMapper::class), + $reflectionProvider, + new ClassCaseSensitivityCheck($reflectionProvider), + new GenericObjectTypeCheck(), + new MissingTypehintCheck($reflectionProvider, true, true, true), + true + ); + } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/mixin.php'], [ + [ + 'PHPDoc tag @mixin contains non-object type int.', + 16, + ], + [ + 'PHPDoc tag @mixin contains unresolvable type.', + 24, + ], + [ + 'PHPDoc tag @mixin contains generic type Exception but class Exception is not generic.', + 34, + ], + [ + 'Generic type Traversable in PHPDoc tag @mixin specifies 3 template types, but class Traversable supports only 2: TKey, TValue', + 34, + ], + [ + 'Type string in generic type ReflectionClass in PHPDoc tag @mixin is not subtype of template type T of object of class ReflectionClass.', + 34, + ], + [ + 'PHPDoc tag @mixin contains generic class ReflectionClass but does not specify its types: T', + 50, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'PHPDoc tag @mixin contains unknown class MixinRule\UnknownestClass.', + 50, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @mixin contains invalid type MixinRule\FooTrait.', + 50, + ], + [ + 'PHPDoc tag @mixin contains unknown class MixinRule\U.', + 59, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Generic type MixinRule\Consecteur in PHPDoc tag @mixin does not specify all template types of class MixinRule\Consecteur: T, U', + 76, + ], + [ + 'Class MixinRule\Foo referenced with incorrect case: MixinRule\foo.', + 84, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php b/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php index 785bdfcfe7..58efb39e07 100644 --- a/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php +++ b/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/new-static.php'], [ - [ - $error, - 10, - $tipText, - ], - [ - $error, - 25, - $tipText, - ], - ]); - } - + public function testRule(): void + { + $error = 'Unsafe usage of new static().'; + $tipText = 'See: https://phpstan.org/blog/solving-phpstan-error-unsafe-usage-of-new-static'; + $this->analyse([__DIR__ . '/data/new-static.php'], [ + [ + $error, + 10, + $tipText, + ], + [ + $error, + 25, + $tipText, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/NonClassAttributeClassRuleTest.php b/tests/PHPStan/Rules/Classes/NonClassAttributeClassRuleTest.php index b882278baa..4b376033dd 100644 --- a/tests/PHPStan/Rules/Classes/NonClassAttributeClassRuleTest.php +++ b/tests/PHPStan/Rules/Classes/NonClassAttributeClassRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - $this->analyse([__DIR__ . '/data/non-class-attribute-class.php'], [ - [ - 'Interface cannot be an Attribute class.', - 5, - ], - /* [ reported by a separate rule - 'Trait cannot be an Attribute class.', - 11, - ], */ - [ - 'Abstract class NonClassAttributeClass\Lorem cannot be an Attribute class.', - 23, - ], - [ - 'Attribute class NonClassAttributeClass\Ipsum constructor must be public.', - 29, - ], - ]); - } - + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/non-class-attribute-class.php'], [ + [ + 'Interface cannot be an Attribute class.', + 5, + ], + /* [ reported by a separate rule + 'Trait cannot be an Attribute class.', + 11, + ], */ + [ + 'Abstract class NonClassAttributeClass\Lorem cannot be an Attribute class.', + 23, + ], + [ + 'Attribute class NonClassAttributeClass\Ipsum constructor must be public.', + 29, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/TraitAttributeClassRuleTest.php b/tests/PHPStan/Rules/Classes/TraitAttributeClassRuleTest.php index fc08dcfe74..185d6eaed6 100644 --- a/tests/PHPStan/Rules/Classes/TraitAttributeClassRuleTest.php +++ b/tests/PHPStan/Rules/Classes/TraitAttributeClassRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - $this->analyse([__DIR__ . '/data/non-class-attribute-class.php'], [ - [ - 'Trait cannot be an Attribute class.', - 11, - ], - ]); - } - + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/non-class-attribute-class.php'], [ + [ + 'Trait cannot be an Attribute class.', + 11, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php index 233a39d859..888ba84880 100644 --- a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php +++ b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider() - )); - } - - public function testUnusedConstructorParameters(): void - { - $this->analyse([__DIR__ . '/data/unused-constructor-parameters.php'], [ - [ - 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $unusedParameter.', - 11, - ], - [ - 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $anotherUnusedParameter.', - 11, - ], - ]); - } - - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0'); - } - - $this->analyse([__DIR__ . '/data/unused-constructor-parameters-promoted-properties.php'], []); - } - - public function testBug1917(): void - { - $this->analyse([__DIR__ . '/data/bug-1917.php'], []); - } - + protected function getRule(): \PHPStan\Rules\Rule + { + return new UnusedConstructorParametersRule(new UnusedFunctionParametersCheck( + $this->createReflectionProvider() + )); + } + + public function testUnusedConstructorParameters(): void + { + $this->analyse([__DIR__ . '/data/unused-constructor-parameters.php'], [ + [ + 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $unusedParameter.', + 11, + ], + [ + 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $anotherUnusedParameter.', + 11, + ], + ]); + } + + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/unused-constructor-parameters-promoted-properties.php'], []); + } + + public function testBug1917(): void + { + $this->analyse([__DIR__ . '/data/bug-1917.php'], []); + } } diff --git a/tests/PHPStan/Rules/Classes/data/bug-1711.php b/tests/PHPStan/Rules/Classes/data/bug-1711.php index f41785b8a2..f6e2f34c0b 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-1711.php +++ b/tests/PHPStan/Rules/Classes/data/bug-1711.php @@ -2,37 +2,43 @@ namespace Bug1711; -class WrongBehavior { - private $x; - - public function __construct(?float $x) { - $this->x = $x; - } - - public function setX(?float $x) { - $this->x = $x; - } +class WrongBehavior +{ + private $x; + + public function __construct(?float $x) + { + $this->x = $x; + } + + public function setX(?float $x) + { + $this->x = $x; + } } -class CorrectBehavior { - private $x; +class CorrectBehavior +{ + private $x; - public function __construct(float $x) { - $this->x = $x; - } + public function __construct(float $x) + { + $this->x = $x; + } - public function setX(float $x) { - $this->x = $x; - } + public function setX(float $x) + { + $this->x = $x; + } } function (): void { - $items = [0.5, 1]; - - foreach ($items as $item) { - $wrong = new WrongBehavior($item); - $wrong->setX($item); - $correct = new CorrectBehavior($item); - $correct->setX($item); - } + $items = [0.5, 1]; + + foreach ($items as $item) { + $wrong = new WrongBehavior($item); + $wrong->setX($item); + $correct = new CorrectBehavior($item); + $correct->setX($item); + } }; diff --git a/tests/PHPStan/Rules/Classes/data/bug-1917.php b/tests/PHPStan/Rules/Classes/data/bug-1917.php index d9789f728c..20ebdacda1 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-1917.php +++ b/tests/PHPStan/Rules/Classes/data/bug-1917.php @@ -2,21 +2,24 @@ namespace Bug1917; -class A { - private $a; - private $b; +class A +{ + private $a; + private $b; - public function __construct($a, $b) - { - $this->a = $a; - $this->b = $b; + public function __construct($a, $b) + { + $this->a = $a; + $this->b = $b; - var_dump([$this->a, $this->b]); - } + var_dump([$this->a, $this->b]); + } } -class B extends A { - function __construct($a, $b) { - parent::__construct(...func_get_args()); - } +class B extends A +{ + public function __construct($a, $b) + { + parent::__construct(...func_get_args()); + } } diff --git a/tests/PHPStan/Rules/Classes/data/bug-3096.php b/tests/PHPStan/Rules/Classes/data/bug-3096.php index 42c332b5da..eb531320c0 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-3096.php +++ b/tests/PHPStan/Rules/Classes/data/bug-3096.php @@ -1,14 +1,16 @@ - $class - */ - public static function sayHello(\DateTimeInterface $object, string $class): void - { - assert($object instanceof $class); - } + /** + * @param class-string<\DateTimeInterface> $class + */ + public static function sayHello(\DateTimeInterface $object, string $class): void + { + assert($object instanceof $class); + } } diff --git a/tests/PHPStan/Rules/Classes/data/bug-3425.php b/tests/PHPStan/Rules/Classes/data/bug-3425.php index 534b5c715f..6c69785a4b 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-3425.php +++ b/tests/PHPStan/Rules/Classes/data/bug-3425.php @@ -1,5 +1,9 @@ - new \ArrayObject([ - 'properties' => [ - 'id' => [ - 'readOnly' => true, - 'type' => 'integer', - ], - 'description' => [ - 'type' => 'string', - ], - ], - ]), - 'Dummy-list' => new \ArrayObject([ - 'properties' => [ - 'id' => [ - 'readOnly' => true, - 'type' => 'integer', - ], - 'description' => [ - 'type' => 'string', - ], - ], - ]), - 'Dummy-list_details' => new \ArrayObject([ - 'properties' => [ - 'id' => [ - 'readOnly' => true, - 'type' => 'integer', - ], - 'description' => [ - 'type' => 'string', - ], - 'relatedDummy' => new \ArrayObject([ - '$ref' => '#/definitions/RelatedDummy-list_details', - ]), - ], - ]), - 'Dummy:OutputDto' => new \ArrayObject([ - 'type' => 'object', - 'properties' => [ - 'baz' => new \ArrayObject([ - 'readOnly' => true, - 'type' => 'string', - ]), - 'bat' => new \ArrayObject([ - 'type' => 'integer', - ]), - ], - ]), - 'Dummy:InputDto' => new \ArrayObject([ - 'type' => 'object', - 'properties' => [ - 'foo' => new \ArrayObject([ - 'type' => 'string', - ]), - 'bar' => new \ArrayObject([ - 'type' => 'integer', - ]), - ], - ]), - 'RelatedDummy-list_details' => new \ArrayObject([ - 'type' => 'object', - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - ]), - ], - ]), + 'Dummy' => new \ArrayObject([ + 'properties' => [ + 'id' => [ + 'readOnly' => true, + 'type' => 'integer', + ], + 'description' => [ + 'type' => 'string', + ], + ], + ]), + 'Dummy-list' => new \ArrayObject([ + 'properties' => [ + 'id' => [ + 'readOnly' => true, + 'type' => 'integer', + ], + 'description' => [ + 'type' => 'string', + ], + ], + ]), + 'Dummy-list_details' => new \ArrayObject([ + 'properties' => [ + 'id' => [ + 'readOnly' => true, + 'type' => 'integer', + ], + 'description' => [ + 'type' => 'string', + ], + 'relatedDummy' => new \ArrayObject([ + '$ref' => '#/definitions/RelatedDummy-list_details', + ]), + ], + ]), + 'Dummy:OutputDto' => new \ArrayObject([ + 'type' => 'object', + 'properties' => [ + 'baz' => new \ArrayObject([ + 'readOnly' => true, + 'type' => 'string', + ]), + 'bat' => new \ArrayObject([ + 'type' => 'integer', + ]), + ], + ]), + 'Dummy:InputDto' => new \ArrayObject([ + 'type' => 'object', + 'properties' => [ + 'foo' => new \ArrayObject([ + 'type' => 'string', + ]), + 'bar' => new \ArrayObject([ + 'type' => 'integer', + ]), + ], + ]), + 'RelatedDummy-list_details' => new \ArrayObject([ + 'type' => 'object', + 'properties' => [ + 'name' => new \ArrayObject([ + 'type' => 'string', + ]), + ], + ]), ]); diff --git a/tests/PHPStan/Rules/Classes/data/bug-4471.php b/tests/PHPStan/Rules/Classes/data/bug-4471.php index 46818e2ad7..95d97efa0a 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-4471.php +++ b/tests/PHPStan/Rules/Classes/data/bug-4471.php @@ -4,25 +4,23 @@ abstract class Foo { - } interface Bar { - } function (Foo $foo, Bar $bar, Baz $baz): void { - new $foo; - new $bar; - new $foo(1); - new $baz; + new $foo(); + new $bar(); + new $foo(1); + new $baz(); }; function (): void { - $foo = Foo::class; - new $foo; + $foo = Foo::class; + new $foo(); - $bar = Bar::class; - new $bar; + $bar = Bar::class; + new $bar(); }; diff --git a/tests/PHPStan/Rules/Classes/data/class-attributes.php b/tests/PHPStan/Rules/Classes/data/class-attributes.php index bba67367b7..035c1a1a08 100644 --- a/tests/PHPStan/Rules/Classes/data/class-attributes.php +++ b/tests/PHPStan/Rules/Classes/data/class-attributes.php @@ -4,114 +4,93 @@ class Foo { - } #[\Attribute] class Bar { - } #[\Attribute(\Attribute::TARGET_PROPERTY)] class Baz { - } #[Nonexistent] class Consecteur { - } #[Foo] class Lorem { - } #[baR] class Sit { - } #[Bar] class Ipsum { - } #[Baz] class Dolor { - } #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)] class Repeatable { - } #[Repeatable, Repeatable, Bar] #[Bar] class Amet { - } #[self(1)] class Blabla { - } #[\Attribute] abstract class AbstractAttribute { - } #[AbstractAttribute] class Blablabla { - } #[Bar(1)] class Bleble { - } #[\Attribute] class NonPublicConstructor { - - protected function __construct() - { - - } - + protected function __construct() + { + } } #[NonPublicConstructor] class Blebleble { - } #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)] class AttributeWithConstructor { - - public function __construct(int $i, string $s) - { - - } - + public function __construct(int $i, string $s) + { + } } #[AttributeWithConstructor(1, 'foo')] @@ -120,5 +99,4 @@ public function __construct(int $i, string $s) #[AttributeWithConstructor(i: 1, s: 'foo', r: 'bar')] class Blebleh { - } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-attribute.php b/tests/PHPStan/Rules/Classes/data/class-constant-attribute.php index 032fe761ba..e380999338 100644 --- a/tests/PHPStan/Rules/Classes/data/class-constant-attribute.php +++ b/tests/PHPStan/Rules/Classes/data/class-constant-attribute.php @@ -5,26 +5,22 @@ #[MyAttr(self::FOO), MyAttr(self::BAR)] class Foo { - - #[MyAttr(self::FOO), MyAttr(self::BAR)] - private const FOO = 1; - - #[MyAttr(self::FOO), MyAttr(self::BAR)] - private $fooProp; - - #[MyAttr(self::FOO), MyAttr(self::BAR)] - public function doFoo( - #[MyAttr(self::FOO), MyAttr(self::BAR)] - $test - ): void - { - - } - + #[MyAttr(self::FOO), MyAttr(self::BAR)] + private const FOO = 1; + + #[MyAttr(self::FOO), MyAttr(self::BAR)] + private $fooProp; + + #[MyAttr(self::FOO), MyAttr(self::BAR)] + public function doFoo( + #[MyAttr(self::FOO), + MyAttr(self::BAR)] + $test + ): void { + } } #[MyAttr(Foo::FOO), MyAttr(Foo::BAR)] function doFoo(): void { - } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-attributes.php b/tests/PHPStan/Rules/Classes/data/class-constant-attributes.php index 0da95d39e2..31b694862e 100644 --- a/tests/PHPStan/Rules/Classes/data/class-constant-attributes.php +++ b/tests/PHPStan/Rules/Classes/data/class-constant-attributes.php @@ -5,41 +5,32 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - #[Foo] - private const FOO = 1; - + #[Foo] + private const FOO = 1; } class Ipsum { - - #[Bar] - private const FOO = 1; - + #[Bar] + private const FOO = 1; } class Dolor { - - #[Baz] - private const FOO = 1; - + #[Baz] + private const FOO = 1; } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-defined.php b/tests/PHPStan/Rules/Classes/data/class-constant-defined.php index 2cc674c25d..4f98149a33 100644 --- a/tests/PHPStan/Rules/Classes/data/class-constant-defined.php +++ b/tests/PHPStan/Rules/Classes/data/class-constant-defined.php @@ -4,15 +4,13 @@ class Foo { + public const LOREM = 1; + public const IPSUM = 2; - const LOREM = 1; - const IPSUM = 2; - - public function fooMethod() - { - self::class; - self::LOREM; - self::IPSUM; - } - + public function fooMethod() + { + self::class; + self::LOREM; + self::IPSUM; + } } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-on-expr.php b/tests/PHPStan/Rules/Classes/data/class-constant-on-expr.php index 7e4d7b8677..286fbf2491 100644 --- a/tests/PHPStan/Rules/Classes/data/class-constant-on-expr.php +++ b/tests/PHPStan/Rules/Classes/data/class-constant-on-expr.php @@ -1,21 +1,20 @@ -= 8.0 += 8.0 namespace ClassConstantOnExpr; class Foo { - - public function doFoo( - \stdClass $std, - string $string, - ?\stdClass $stdOrNull, - ?string $stringOrNull - ): void - { - echo $std::class; - echo $string::class; - echo $stdOrNull::class; - echo $stringOrNull::class; - } - + public function doFoo( + \stdClass $std, + string $string, + ?\stdClass $stdOrNull, + ?string $stringOrNull + ): void { + echo $std::class; + echo $string::class; + echo $stdOrNull::class; + echo $stringOrNull::class; + } } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-visibility.php b/tests/PHPStan/Rules/Classes/data/class-constant-visibility.php index 6bee5dfb20..d4d5f3e39a 100644 --- a/tests/PHPStan/Rules/Classes/data/class-constant-visibility.php +++ b/tests/PHPStan/Rules/Classes/data/class-constant-visibility.php @@ -1,123 +1,113 @@ -union::FOO; - $this->union::BAR; + public function doIpsum(WithFooConstant $foo) + { + if ($foo instanceof WithFooAndBarConstant) { + $foo::FOO; + $foo::BAR; + $foo::BAZ; + } - $this->unknown::FOO; + $this->union::FOO; + $this->union::BAR; - /** @var string|int $stringOrInt */ - $stringOrInt = doFoo(); - $stringOrInt::FOO; - } + $this->unknown::FOO; + /** @var string|int $stringOrInt */ + $stringOrInt = doFoo(); + $stringOrInt::FOO; + } } function () { - FOO::PRIVATE_FOO; + FOO::PRIVATE_FOO; }; diff --git a/tests/PHPStan/Rules/Classes/data/class-exists.php b/tests/PHPStan/Rules/Classes/data/class-exists.php index 9ee33623bd..8a670d56c8 100644 --- a/tests/PHPStan/Rules/Classes/data/class-exists.php +++ b/tests/PHPStan/Rules/Classes/data/class-exists.php @@ -1,36 +1,36 @@ = 8.0 += 8.0 namespace DuplicatedPromotedProperty; class Foo { + private $foo; - private $foo; - - public function __construct( - private $foo, - private $bar, - private $bar - ) - { - - } - + public function __construct( + private $foo, + private $bar, + private $bar + ) { + } } diff --git a/tests/PHPStan/Rules/Classes/data/extends-error.php b/tests/PHPStan/Rules/Classes/data/extends-error.php index 7c54211b78..1a8e089751 100644 --- a/tests/PHPStan/Rules/Classes/data/extends-error.php +++ b/tests/PHPStan/Rules/Classes/data/extends-error.php @@ -4,39 +4,31 @@ class Foo extends Bar { - } interface BazInterface { - } class Lorem extends BazInterface { - } trait DolorTrait { - } class Ipsum extends DolorTrait { - } -new class extends DolorTrait { - +new class() extends DolorTrait { }; final class FinalFoo { - } class Sit extends FinalFoo { - } diff --git a/tests/PHPStan/Rules/Classes/data/extends-implements.php b/tests/PHPStan/Rules/Classes/data/extends-implements.php index 64d23563d1..c9d83f58a3 100644 --- a/tests/PHPStan/Rules/Classes/data/extends-implements.php +++ b/tests/PHPStan/Rules/Classes/data/extends-implements.php @@ -4,32 +4,26 @@ class Foo { - } class Bar extends Foo implements FooInterface { - } class Baz extends FOO implements FOOInterface { - } interface FooInterface { - } interface BarInterface extends FooInterface { - } interface BazInterface extends FOOInterface { - } /** @@ -37,10 +31,8 @@ interface BazInterface extends FOOInterface */ class FinalWithAnnotation { - } class ExtendsFinalWithAnnotation extends FinalWithAnnotation { - } diff --git a/tests/PHPStan/Rules/Classes/data/implements-error.php b/tests/PHPStan/Rules/Classes/data/implements-error.php index 84ce6ffe06..38e40a64b1 100644 --- a/tests/PHPStan/Rules/Classes/data/implements-error.php +++ b/tests/PHPStan/Rules/Classes/data/implements-error.php @@ -4,24 +4,19 @@ class Foo implements Bar { - } class Lorem implements Foo { - } trait DolorTrait { - } class Ipsum implements DolorTrait { - } -new class implements DolorTrait { - +new class() implements DolorTrait { }; diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-not-phpdoc.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-not-phpdoc.php index 0e956ae9a1..dd30bd2410 100644 --- a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-not-phpdoc.php +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-not-phpdoc.php @@ -4,43 +4,32 @@ class Foo { - - public function doFoo( - \stdClass $std - ) - { - if ($std instanceof \stdClass) { - - } - if ($std instanceof \Exception) { - - } - } - - /** - * @param \DateTimeImmutable $date - */ - public function doBar( - \DateTimeInterface $date - ) - { - if ($date instanceof \DateTimeInterface) { - - } - if ($date instanceof SomeFinalClass) { - - } - if ($date instanceof \DateTimeImmutable) { - - } - if ($date instanceof \DateTime) { - - } - } - + public function doFoo( + \stdClass $std + ) { + if ($std instanceof \stdClass) { + } + if ($std instanceof \Exception) { + } + } + + /** + * @param \DateTimeImmutable $date + */ + public function doBar( + \DateTimeInterface $date + ) { + if ($date instanceof \DateTimeInterface) { + } + if ($date instanceof SomeFinalClass) { + } + if ($date instanceof \DateTimeImmutable) { + } + if ($date instanceof \DateTime) { + } + } } final class SomeFinalClass { - } diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof.php index 106781de88..635ba2dc79 100644 --- a/tests/PHPStan/Rules/Classes/data/impossible-instanceof.php +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof.php @@ -4,282 +4,220 @@ interface Foo { - } interface Bar { - } interface BarChild extends Bar { - } class Lorem { - } class Ipsum extends Lorem { - } class Dolor { - } class FooImpl implements Foo { - } class Test { + public function doTest( + Foo $foo, + Bar $bar, + Lorem $lorem, + Ipsum $ipsum, + Dolor $dolor, + FooImpl $fooImpl, + BarChild $barChild + ) { + if ($foo instanceof Bar) { + } + if ($bar instanceof Foo) { + } + if ($lorem instanceof Lorem) { + } + if ($lorem instanceof Ipsum) { + } + if ($ipsum instanceof Lorem) { + } + if ($ipsum instanceof Ipsum) { + } + if ($dolor instanceof Lorem) { + } + if ($fooImpl instanceof Foo) { + } + if ($barChild instanceof Bar) { + } + + /** @var Collection|mixed[] $collection */ + $collection = doFoo(); + if ($collection instanceof Foo) { + } + + /** @var object $object */ + $object = doFoo(); + if ($object instanceof Foo) { + } + + $str = 'str'; + if ($str instanceof Foo) { + } + + if ($str instanceof $str) { + } + + if ($foo instanceof $str) { + } + + $self = new self(); + if ($self instanceof self) { + } + } - public function doTest( - Foo $foo, - Bar $bar, - Lorem $lorem, - Ipsum $ipsum, - Dolor $dolor, - FooImpl $fooImpl, - BarChild $barChild - ) - { - if ($foo instanceof Bar) { - - } - if ($bar instanceof Foo) { - - } - if ($lorem instanceof Lorem) { - - } - if ($lorem instanceof Ipsum) { - - } - if ($ipsum instanceof Lorem) { - - } - if ($ipsum instanceof Ipsum) { - - } - if ($dolor instanceof Lorem) { - - } - if ($fooImpl instanceof Foo) { - - } - if ($barChild instanceof Bar) { - - } - - /** @var Collection|mixed[] $collection */ - $collection = doFoo(); - if ($collection instanceof Foo) { - - } - - /** @var object $object */ - $object = doFoo(); - if ($object instanceof Foo) { - - } - - $str = 'str'; - if ($str instanceof Foo) { - - } - - if ($str instanceof $str) { - - } - - if ($foo instanceof $str) { - - } - - $self = new self(); - if ($self instanceof self) { - - } - } - - public function foreachWithTypeChange() - { - $foo = null; - foreach ([] as $val) { - if ($foo instanceof self) { - - } - if ($foo instanceof Lorem) { - - } - - $foo = new self(); - if ($foo instanceof self) { - - } - } - } - - public function whileWithTypeChange() - { - $foo = null; - while (fetch()) { - if ($foo instanceof self) { - - } - if ($foo instanceof Lorem) { - - } - - $foo = new self(); - if ($foo instanceof self) { - - } - } - } - - public function forWithTypeChange() - { - $foo = null; - for (;;) { - if ($foo instanceof self) { - - } - if ($foo instanceof Lorem) { - - } - - $foo = new self(); - if ($foo instanceof self) { + public function foreachWithTypeChange() + { + $foo = null; + foreach ([] as $val) { + if ($foo instanceof self) { + } + if ($foo instanceof Lorem) { + } + + $foo = new self(); + if ($foo instanceof self) { + } + } + } - } - } - } + public function whileWithTypeChange() + { + $foo = null; + while (fetch()) { + if ($foo instanceof self) { + } + if ($foo instanceof Lorem) { + } + + $foo = new self(); + if ($foo instanceof self) { + } + } + } + public function forWithTypeChange() + { + $foo = null; + for (;;) { + if ($foo instanceof self) { + } + if ($foo instanceof Lorem) { + } + + $foo = new self(); + if ($foo instanceof self) { + } + } + } } interface Collection extends \IteratorAggregate { - } final class FinalClassWithInvoke { - - public function __invoke() - { - - } - + public function __invoke() + { + } } final class FinalClassWithoutInvoke { - } class ClassWithInvoke { + public function __invoke() + { + } - public function __invoke() - { - - } - - public function doFoo(callable $callable, Foo $foo) - { - if ($callable instanceof self) { - - } - if ($callable instanceof FinalClassWithInvoke) { - - } - if ($callable instanceof FinalClassWithoutInvoke) { - - } - if ($callable instanceof Foo) { - - } - if ($callable instanceof Lorem) { - - } - } - + public function doFoo(callable $callable, Foo $foo) + { + if ($callable instanceof self) { + } + if ($callable instanceof FinalClassWithInvoke) { + } + if ($callable instanceof FinalClassWithoutInvoke) { + } + if ($callable instanceof Foo) { + } + if ($callable instanceof Lorem) { + } + } } class EliminateCompoundTypes { - - /** - * @param Lorem|Dolor $union - * @param Foo&Bar $intersection - */ - public function doFoo($union, $intersection) - { - if ($union instanceof Lorem || $union instanceof Dolor) { - - } elseif ($union instanceof Lorem) { - - } - - if ($intersection instanceof Foo && $intersection instanceof Bar) { - - } elseif ($intersection instanceof Foo) { - - } - - if ($intersection instanceof Foo) { - - } elseif ($intersection instanceof Bar) { - - } - } - + /** + * @param Lorem|Dolor $union + * @param Foo&Bar $intersection + */ + public function doFoo($union, $intersection) + { + if ($union instanceof Lorem || $union instanceof Dolor) { + } elseif ($union instanceof Lorem) { + } + + if ($intersection instanceof Foo && $intersection instanceof Bar) { + } elseif ($intersection instanceof Foo) { + } + + if ($intersection instanceof Foo) { + } elseif ($intersection instanceof Bar) { + } + } } class InstanceOfString { - - /** - * @param Foo|Bar|null $fooBarNull - */ - public function doFoo($fooBarNull) - { - $string = 'Foo'; - if (rand(0, 1) === 1) { - $string = 'Bar'; - } - if ($fooBarNull instanceof $string) { - return; - } - } - + /** + * @param Foo|Bar|null $fooBarNull + */ + public function doFoo($fooBarNull) + { + $string = 'Foo'; + if (rand(0, 1) === 1) { + $string = 'Bar'; + } + if ($fooBarNull instanceof $string) { + return; + } + } } trait TraitWithInstanceOfThis { - - public function doFoo() - { - if ($this instanceof Foo) { - - } - } - + public function doFoo() + { + if ($this instanceof Foo) { + } + } } class ClassUsingTrait implements Foo { - - use TraitWithInstanceOfThis; - + use TraitWithInstanceOfThis; } function (\Iterator $arg) { @@ -290,154 +228,165 @@ function (\Iterator $arg) { class ObjectSubtracted { + /** + * @param object $object + */ + public function doBar($object) + { + if ($object instanceof \Exception) { + return; + } + + if ($object instanceof \Exception) { + } + + if ($object instanceof \InvalidArgumentException) { + } + } - /** - * @param object $object - */ - public function doBar($object) - { - if ($object instanceof \Exception) { - return; - } - - if ($object instanceof \Exception) { - - } - - if ($object instanceof \InvalidArgumentException) { - - } - } - - public function doBaz(Bar $bar) - { - if ($bar instanceof BarChild) { - return; - } - - if ($bar instanceof BarChild) { - - } - - if ($bar instanceof BarGrandChild) { + public function doBaz(Bar $bar) + { + if ($bar instanceof BarChild) { + return; + } - } - } + if ($bar instanceof BarChild) { + } + if ($bar instanceof BarGrandChild) { + } + } } class BarGrandChild implements BarChild { - } class InvalidTypeTest { - /** - * @template ObjectT of InvalidTypeTest - * @template MixedT - * - * @param mixed $subject - * @param int $int - * @param object $objectWithoutClass - * @param InvalidTypeTest $object - * @param int|InvalidTypeTest $intOrObject - * @param string $string - * @param mixed $mixed - * @param mixed $mixed - * @param ObjectT $objectT - * @param MixedT $mixedT - */ - public function doTest($int, $objectWithoutClass, $object, $intOrObject, $string, $mixed, $objectT, $mixedT) - { - if ($mixed instanceof $int) { - } - - if ($mixed instanceof $objectWithoutClass) { - } - - if ($mixed instanceof $object) { - } - - if ($mixed instanceof $intOrObject) { - } - - if ($mixed instanceof $string) { - } - - if ($mixed instanceof $mixed) { - } - - if ($mixed instanceof $objectT) { - } - - if ($mixed instanceof $mixedT) { - } - } + /** + * @template ObjectT of InvalidTypeTest + * @template MixedT + * + * @param mixed $subject + * @param int $int + * @param object $objectWithoutClass + * @param InvalidTypeTest $object + * @param int|InvalidTypeTest $intOrObject + * @param string $string + * @param mixed $mixed + * @param mixed $mixed + * @param ObjectT $objectT + * @param MixedT $mixedT + */ + public function doTest($int, $objectWithoutClass, $object, $intOrObject, $string, $mixed, $objectT, $mixedT) + { + if ($mixed instanceof $int) { + } + + if ($mixed instanceof $objectWithoutClass) { + } + + if ($mixed instanceof $object) { + } + + if ($mixed instanceof $intOrObject) { + } + + if ($mixed instanceof $string) { + } + + if ($mixed instanceof $mixed) { + } + + if ($mixed instanceof $objectT) { + } + + if ($mixed instanceof $mixedT) { + } + } } class CheckInstanceofInIterableForeach { - - /** - * @param iterable $items - */ - public function test(iterable $items): void - { - foreach ($items as $item) { - if (!$item instanceof Foo) { - throw new \Exception('Unsupported'); - } - } - } - + /** + * @param iterable $items + */ + public function test(iterable $items): void + { + foreach ($items as $item) { + if (!$item instanceof Foo) { + throw new \Exception('Unsupported'); + } + } + } } class CheckInstanceofWithTemplates { - /** - * @template T of \Exception - * @param T $e - */ - public function test(\Throwable $t, $e): void { - if ($t instanceof $e) return; - if ($e instanceof $t) return; - } + /** + * @template T of \Exception + * @param T $e + */ + public function test(\Throwable $t, $e): void + { + if ($t instanceof $e) { + return; + } + if ($e instanceof $t) { + return; + } + } } class CheckGenericClassString { - /** - * @param \DateTimeInterface $a - * @param class-string<\DateTimeInterface> $b - * @param class-string<\DateTimeInterface> $c - */ - function test($a, $b, $c): void - { - if ($a instanceof $b) return; - if ($b instanceof $a) return; - if ($b instanceof $c) return; - } + /** + * @param \DateTimeInterface $a + * @param class-string<\DateTimeInterface> $b + * @param class-string<\DateTimeInterface> $c + */ + public function test($a, $b, $c): void + { + if ($a instanceof $b) { + return; + } + if ($b instanceof $a) { + return; + } + if ($b instanceof $c) { + return; + } + } } class CheckGenericClassStringWithConstantString { - /** - * @param class-string<\DateTimeInterface> $a - * @param \DateTimeInterface $b - */ - function test($a, $b): void - { - $t = \DateTimeInterface::class; - if ($a instanceof $t) return; - if ($b instanceof $t) return; - } + /** + * @param class-string<\DateTimeInterface> $a + * @param \DateTimeInterface $b + */ + public function test($a, $b): void + { + $t = \DateTimeInterface::class; + if ($a instanceof $t) { + return; + } + if ($b instanceof $t) { + return; + } + } } class CheckInstanceOfLsp { - function test(\DateTimeInterface $a, \DateTimeInterface $b): void { - if ($a instanceof $b) return; - if ($b instanceof $a) return; - } + public function test(\DateTimeInterface $a, \DateTimeInterface $b): void + { + if ($a instanceof $b) { + return; + } + if ($b instanceof $a) { + return; + } + } } diff --git a/tests/PHPStan/Rules/Classes/data/instanceof-class-exists.php b/tests/PHPStan/Rules/Classes/data/instanceof-class-exists.php index bbfa8d3bef..8aeefc026d 100644 --- a/tests/PHPStan/Rules/Classes/data/instanceof-class-exists.php +++ b/tests/PHPStan/Rules/Classes/data/instanceof-class-exists.php @@ -4,12 +4,10 @@ class Foo { - - public function doFoo(): void - { - /** @var object $object */ - $object = doFoo(); - class_exists(Bar::class) ? $object instanceof Bar : false; - } - + public function doFoo(): void + { + /** @var object $object */ + $object = doFoo(); + class_exists(Bar::class) ? $object instanceof Bar : false; + } } diff --git a/tests/PHPStan/Rules/Classes/data/instanceof-defined.php b/tests/PHPStan/Rules/Classes/data/instanceof-defined.php index 1b26b39c71..e43dc865b7 100644 --- a/tests/PHPStan/Rules/Classes/data/instanceof-defined.php +++ b/tests/PHPStan/Rules/Classes/data/instanceof-defined.php @@ -4,12 +4,9 @@ class Foo { - - public function foobar() - { - if ($this instanceof self) { - - } - } - + public function foobar() + { + if ($this instanceof self) { + } + } } diff --git a/tests/PHPStan/Rules/Classes/data/instanceof.php b/tests/PHPStan/Rules/Classes/data/instanceof.php index ac00ee7c7b..6348369046 100644 --- a/tests/PHPStan/Rules/Classes/data/instanceof.php +++ b/tests/PHPStan/Rules/Classes/data/instanceof.php @@ -3,17 +3,10 @@ namespace InstanceOfNamespace; if ($foo instanceof Foo) { - } elseif ($foo instanceof Bar) { - } elseif ($foo instanceof self) { - } elseif ($foo instanceof $bar) { - } elseif ($foo instanceof FOO) { - } elseif ($foo instanceof parent) { - -} elseif ($foo instanceof SELF) { - +} elseif ($foo instanceof self) { } diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-class-exists.php b/tests/PHPStan/Rules/Classes/data/instantiation-class-exists.php index 89687b9b6f..f0988194e8 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation-class-exists.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation-class-exists.php @@ -4,12 +4,10 @@ class Foo { - - public function doFoo(): void - { - if (class_exists(Bar::class)) { - $bar = new Bar(); - } - } - + public function doFoo(): void + { + if (class_exists(Bar::class)) { + $bar = new Bar(); + } + } } diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-classes.php b/tests/PHPStan/Rules/Classes/data/instantiation-classes.php index 990f8582fb..b50cff23cd 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation-classes.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation-classes.php @@ -4,38 +4,30 @@ class FooInstantiation { - } class BarInstantiation { - - public function __construct($bar) - { - - } - + public function __construct($bar) + { + } } abstract class LoremInstantiation { - } interface IpsumInstantiation { - } class ClassWithVariadicConstructor { - - public function __construct() - { - $argsCount = func_num_args(); - for ($i = 0; $i < $argsCount; $i++) { - $arg = func_get_arg($i); - } - } - + public function __construct() + { + $argsCount = func_num_args(); + for ($i = 0; $i < $argsCount; $i++) { + $arg = func_get_arg($i); + } + } } diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-named-arguments.php b/tests/PHPStan/Rules/Classes/data/instantiation-named-arguments.php index 56a620a02e..8dd8e1375f 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation-named-arguments.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation-named-arguments.php @@ -1,19 +1,18 @@ -= 8.0 += 8.0 namespace InstantiationNamedArguments; class Foo { + public function __construct(int $i, int $j) + { + } - public function __construct(int $i, int $j) - { - - } - - public function doFoo() - { - $s = new self(i: 1); - $r = new self(i: 1, j: 2, z: 3); - } - + public function doFoo() + { + $s = new self(i: 1); + $r = new self(i: 1, j: 2, z: 3); + } } diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php b/tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php index c8e8ed29f0..f57d07285a 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php @@ -1,34 +1,35 @@ -= 8.0 += 8.0 namespace InstantiationPromotedProperties; class Foo { - - public function __construct( - private array $foo, - /** @var array */private array $bar - ) { } - + public function __construct( + private array $foo, + /** @var array */ + private array $bar + ) { + } } class Bar { - - /** - * @param array $bar - */ - public function __construct( - private array $foo, - private array $bar - ) { } - + /** + * @param array $bar + */ + public function __construct( + private array $foo, + private array $bar + ) { + } } function () { - new Foo([], ['foo']); - new Foo([], [1]); + new Foo([], ['foo']); + new Foo([], [1]); - new Bar([], ['foo']); - new Bar([], [1]); + new Bar([], ['foo']); + new Bar([], [1]); }; diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-soap.php b/tests/PHPStan/Rules/Classes/data/instantiation-soap.php index 2025fd82ea..9643a34530 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation-soap.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation-soap.php @@ -3,6 +3,6 @@ namespace TestInstantiation; function () { - throw new \SoapFault('Server', 123); - throw new \SoapFault('Server', 'Some error message'); + throw new \SoapFault('Server', 123); + throw new \SoapFault('Server', 'Some error message'); }; diff --git a/tests/PHPStan/Rules/Classes/data/instantiation.php b/tests/PHPStan/Rules/Classes/data/instantiation.php index 58c5b2cf60..0fcc1265cf 100644 --- a/tests/PHPStan/Rules/Classes/data/instantiation.php +++ b/tests/PHPStan/Rules/Classes/data/instantiation.php @@ -1,276 +1,238 @@ - self::class, - 'key2' => 'UndefinedClass3', - ]; - - new $classes[$key](); - } + $classes = [ + 'key1' => self::class, + 'key2' => 'UndefinedClass3', + ]; + new $classes[$key](); + } } final class FinalClass { - - public function doFoo() - { - new static(); - new static(1); - } - + public function doFoo() + { + new static(); + new static(1); + } } class ClassWithFinalConstructor { + final public function __construct(int $i) + { + } - final public function __construct(int $i) - { - - } - - public function doFoo() - { - new static(1); - new static(); - } - + public function doFoo() + { + new static(1); + new static(); + } } interface InterfaceWithConstructor { - - public function __construct(int $i); - + public function __construct(int $i); } class ConstructorComingFromAnInterface implements InterfaceWithConstructor { + public function __construct(int $i) + { + } - public function __construct(int $i) - { - - } - - public function doFoo() - { - new static(1); - new static(); - } - + public function doFoo() + { + new static(1); + new static(); + } } abstract class AbstractClassWithFinalConstructor { + final protected function __construct() + { + } - protected final function __construct() - { - - } - - public function getInstance() - { - new static(); - new static(1); - } + public function getInstance() + { + new static(); + new static(1); + } } abstract class AbstractConstructor { + abstract public function __construct(string $s); - abstract public function __construct(string $s); - - public function doFoo() - { - new static('foo'); - new static(); - } - + public function doFoo() + { + new static('foo'); + new static(); + } } class ClassExtendingAbstractConstructor extends AbstractConstructor { + public function __construct(string $s) + { + } - public function __construct(string $s) - { - - } - - public function doBar() - { - new static('foo'); - new static(); - } - + public function doBar() + { + new static('foo'); + new static(); + } } diff --git a/tests/PHPStan/Rules/Classes/data/interface-extends-error.php b/tests/PHPStan/Rules/Classes/data/interface-extends-error.php index 8a4251a03a..0fad2d4f92 100644 --- a/tests/PHPStan/Rules/Classes/data/interface-extends-error.php +++ b/tests/PHPStan/Rules/Classes/data/interface-extends-error.php @@ -4,25 +4,20 @@ interface Foo extends Bar { - } class BazClass { - } interface Lorem extends BazClass { - } trait DolorTrait { - } interface Ipsum extends DolorTrait { - } diff --git a/tests/PHPStan/Rules/Classes/data/invalid-promoted-properties.php b/tests/PHPStan/Rules/Classes/data/invalid-promoted-properties.php index b28c29a53e..443244c399 100644 --- a/tests/PHPStan/Rules/Classes/data/invalid-promoted-properties.php +++ b/tests/PHPStan/Rules/Classes/data/invalid-promoted-properties.php @@ -4,44 +4,37 @@ class Foo { + public function __construct(public $i) + { + } - public function __construct(public $i) {} - - public function doFoo(private $j): void - { - - } - + public function doFoo(private $j): void + { + } } function (public $i): void { - }; $f = fn (public $i) => 'foo'; function foo(public $i): void { - } class Bar { - - abstract public function __construct(public $i); - + abstract public function __construct(public $i); } interface Baz { - - function __construct(public $i); - + public function __construct(public $i); } class Lorem { - - public function __construct(public ...$i) {} - + public function __construct(public ...$i) + { + } } diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 316a52c323..fcdb8d8d8a 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -2,7 +2,9 @@ namespace LocalTypeAliases; -class ExistingClassAlias {} +class ExistingClassAlias +{ +} /** * @phpstan-type ExportedTypeAlias \Countable&\Traversable diff --git a/tests/PHPStan/Rules/Classes/data/mixin.php b/tests/PHPStan/Rules/Classes/data/mixin.php index 63fa7cb7e4..8939b694fe 100644 --- a/tests/PHPStan/Rules/Classes/data/mixin.php +++ b/tests/PHPStan/Rules/Classes/data/mixin.php @@ -7,7 +7,6 @@ */ class Foo { - } /** @@ -15,7 +14,6 @@ class Foo */ class Bar { - } /** @@ -23,7 +21,6 @@ class Bar */ class Baz { - } /** @@ -33,12 +30,10 @@ class Baz */ class Lorem { - } trait FooTrait { - } /** @@ -58,7 +53,6 @@ class Ipsum */ class Dolor { - } /** @@ -67,7 +61,6 @@ class Dolor */ class Consecteur { - } /** @@ -75,7 +68,6 @@ class Consecteur */ class Sit { - } /** @@ -83,5 +75,4 @@ class Sit */ class Amet { - } diff --git a/tests/PHPStan/Rules/Classes/data/new-static.php b/tests/PHPStan/Rules/Classes/data/new-static.php index 6601e1cbb6..956e445135 100644 --- a/tests/PHPStan/Rules/Classes/data/new-static.php +++ b/tests/PHPStan/Rules/Classes/data/new-static.php @@ -4,114 +4,91 @@ class NoConstructor { - - public function doFoo() - { - $foo = new static(); - } - + public function doFoo() + { + $foo = new static(); + } } class NonFinalConstructor { - - public function __construct() - { - - } - - public function doFoo() - { - $foo = new static(); - } - + public function __construct() + { + } + + public function doFoo() + { + $foo = new static(); + } } final class NoConstructorInFinalClass { - - public function doFoo() - { - $foo = new static(); - } - + public function doFoo() + { + $foo = new static(); + } } final class NonFinalConstructorInFinalClass { - - public function __construct() - { - - } - - public function doFoo() - { - $foo = new static(); - } - + public function __construct() + { + } + + public function doFoo() + { + $foo = new static(); + } } class FinalConstructorInNonFinalClass { - - final public function __construct() - { - - } - - public function doFoo() - { - $foo = new static(); - } - + final public function __construct() + { + } + + public function doFoo() + { + $foo = new static(); + } } interface InterfaceWithConstructor { - - public function __construct(int $i); - + public function __construct(int $i); } class ConstructorComingFromAnInterface implements InterfaceWithConstructor { - - public function __construct(int $i) - { - - } - - public function doFoo() - { - $foo = new static(1); - } - + public function __construct(int $i) + { + } + + public function doFoo() + { + $foo = new static(1); + } } abstract class AbstractConstructor { + abstract public function __construct(string $s); - abstract public function __construct(string $s); - - public function doFoo() - { - new static('foo'); - } - + public function doFoo() + { + new static('foo'); + } } class ClassExtendingAbstractConstructor extends AbstractConstructor { - - public function __construct(string $s) - { - - } - - public function doBar() - { - new static('foo'); - } - + public function __construct(string $s) + { + } + + public function doBar() + { + new static('foo'); + } } diff --git a/tests/PHPStan/Rules/Classes/data/non-class-attribute-class.php b/tests/PHPStan/Rules/Classes/data/non-class-attribute-class.php index 8306553e32..5e99d3a5c4 100644 --- a/tests/PHPStan/Rules/Classes/data/non-class-attribute-class.php +++ b/tests/PHPStan/Rules/Classes/data/non-class-attribute-class.php @@ -5,34 +5,27 @@ #[\Attribute] interface Foo { - } #[\Attribute] trait Bar { - } #[\Attribute] class Baz { - } #[\Attribute] abstract class Lorem { - } #[\Attribute] class Ipsum { - - private function __construct() - { - - } - + private function __construct() + { + } } diff --git a/tests/PHPStan/Rules/Classes/data/php80-constructor.php b/tests/PHPStan/Rules/Classes/data/php80-constructor.php index bb45a7a17f..a76a431a96 100644 --- a/tests/PHPStan/Rules/Classes/data/php80-constructor.php +++ b/tests/PHPStan/Rules/Classes/data/php80-constructor.php @@ -2,20 +2,17 @@ class OldStyleConstructorOnPhp8 { + public function OldStyleConstructorOnPhp8(int $i) + { + } - public function OldStyleConstructorOnPhp8(int $i) - { - - } - - public static function create(): self - { - return new self(1); - } - + public static function create(): self + { + return new self(1); + } } function () { - new OldStyleConstructorOnPhp8(); - new OldStyleConstructorOnPhp8(1); + new OldStyleConstructorOnPhp8(); + new OldStyleConstructorOnPhp8(1); }; diff --git a/tests/PHPStan/Rules/Classes/data/trait-use-error.php b/tests/PHPStan/Rules/Classes/data/trait-use-error.php index 895dd41754..28d920c918 100644 --- a/tests/PHPStan/Rules/Classes/data/trait-use-error.php +++ b/tests/PHPStan/Rules/Classes/data/trait-use-error.php @@ -4,26 +4,22 @@ class Foo { - - use FooTrait; - + use FooTrait; } trait BarTrait { - - use Foo, FooTrait; - + use Foo; + use FooTrait; } interface Baz { + use BarTrait; - use BarTrait; - -} + } -new class { - use FooTrait; - use Baz; +new class() { + use FooTrait; + use Baz; }; diff --git a/tests/PHPStan/Rules/Classes/data/trait-use.php b/tests/PHPStan/Rules/Classes/data/trait-use.php index 1b077b85ed..79ec2f26f3 100644 --- a/tests/PHPStan/Rules/Classes/data/trait-use.php +++ b/tests/PHPStan/Rules/Classes/data/trait-use.php @@ -4,19 +4,14 @@ trait FooTrait { - } class Foo { - - use FOOTrait; - + use FOOTrait; } class Bar { - - use FooTrait; - + use FooTrait; } diff --git a/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-promoted-properties.php b/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-promoted-properties.php index 7823cbec22..2c77d6a750 100644 --- a/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-promoted-properties.php +++ b/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-promoted-properties.php @@ -1,18 +1,17 @@ -= 8.0 += 8.0 namespace UnusedConstructorParametersPromotedProperties; class Foo { + private int $y; - private int $y; - - public function __construct( - public int $x, - int $y - ) - { - $this->y = $y; - } - + public function __construct( + public int $x, + int $y + ) { + $this->y = $y; + } } diff --git a/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters.php b/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters.php index f033d2be5f..3a02f6f5f0 100644 --- a/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters.php +++ b/tests/PHPStan/Rules/Classes/data/unused-constructor-parameters.php @@ -4,40 +4,35 @@ class Foo { - - private $foo; - private $bar; - - public function __construct( - $usedParameter, - $usedParameterInStringOne, - $usedParameterInStringTwo, - $usedParameterInStringThree, - $usedParameterInCondition, - $usedParameterInClosureUse, - $usedParameterInCompact, - $unusedParameter, - $anotherUnusedParameter - ) - { - $this->foo = $usedParameter; - var_dump("Hello $usedParameterInStringOne"); - var_dump("Hello {$usedParameterInStringTwo}"); - var_dump("Hello ${usedParameterInStringThree}"); - if (doFoo()) { - $this->foo = $usedParameterInCondition; - } - function ($anotherUnusedParameter) use ($usedParameterInClosureUse) { - echo $anotherUnusedParameter; // different scope - }; - $this->bar = compact('usedParameterInCompact'); - } - + private $foo; + private $bar; + + public function __construct( + $usedParameter, + $usedParameterInStringOne, + $usedParameterInStringTwo, + $usedParameterInStringThree, + $usedParameterInCondition, + $usedParameterInClosureUse, + $usedParameterInCompact, + $unusedParameter, + $anotherUnusedParameter + ) { + $this->foo = $usedParameter; + var_dump("Hello $usedParameterInStringOne"); + var_dump("Hello {$usedParameterInStringTwo}"); + var_dump("Hello ${usedParameterInStringThree}"); + if (doFoo()) { + $this->foo = $usedParameterInCondition; + } + function ($anotherUnusedParameter) use ($usedParameterInClosureUse) { + echo $anotherUnusedParameter; // different scope + }; + $this->bar = compact('usedParameterInCompact'); + } } interface Bar { - - public function __construct($interfaceParameter); - + public function __construct($interfaceParameter); } diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index 26c45e4313..cff9e48f0d 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain, - true - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new BooleanAndConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain, + true + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-and.php'], [ - [ - 'Left side of && is always true.', - 15, - ], - [ - 'Right side of && is always true.', - 19, - ], - [ - 'Left side of && is always false.', - 24, - ], - [ - 'Right side of && is always false.', - 27, - ], - [ - 'Right side of && is always false.', - 30, - ], - [ - 'Right side of && is always true.', - 33, - ], - [ - 'Right side of && is always true.', - 36, - ], - [ - 'Right side of && is always true.', - 39, - ], - [ - 'Result of && is always false.', - 50, - ], - [ - 'Result of && is always true.', - 54, - $tipText, - ], - [ - 'Result of && is always false.', - 60, - ], - [ - 'Result of && is always true.', - 64, - //$tipText, - ], - [ - 'Result of && is always false.', - 66, - //$tipText, - ], - [ - 'Result of && is always false.', - 125, - ], - [ - 'Left side of && is always false.', - 139, - ], - [ - 'Right side of && is always false.', - 141, - ], - [ - 'Left side of && is always true.', - 145, - ], - [ - 'Right side of && is always true.', - 147, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/boolean-and-not-phpdoc.php'], [ - [ - 'Left side of && is always true.', - 24, - ], - [ - 'Right side of && is always true.', - 30, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/boolean-and.php'], [ + [ + 'Left side of && is always true.', + 15, + ], + [ + 'Right side of && is always true.', + 19, + ], + [ + 'Left side of && is always false.', + 24, + ], + [ + 'Right side of && is always false.', + 27, + ], + [ + 'Right side of && is always false.', + 30, + ], + [ + 'Right side of && is always true.', + 33, + ], + [ + 'Right side of && is always true.', + 36, + ], + [ + 'Right side of && is always true.', + 39, + ], + [ + 'Result of && is always false.', + 50, + ], + [ + 'Result of && is always true.', + 54, + $tipText, + ], + [ + 'Result of && is always false.', + 60, + ], + [ + 'Result of && is always true.', + 64, + //$tipText, + ], + [ + 'Result of && is always false.', + 66, + //$tipText, + ], + [ + 'Result of && is always false.', + 125, + ], + [ + 'Left side of && is always false.', + 139, + ], + [ + 'Right side of && is always false.', + 141, + ], + [ + 'Left side of && is always true.', + 145, + ], + [ + 'Right side of && is always true.', + 147, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-and-not-phpdoc.php'], [ - [ - 'Result of && is always false.', - 14, - $tipText, - ], - [ - 'Left side of && is always true.', - 24, - ], - [ - 'Left side of && is always true.', - 27, - $tipText, - ], - [ - 'Right side of && is always true.', - 30, - ], - [ - 'Right side of && is always true.', - 33, - $tipText, - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/boolean-and-not-phpdoc.php'], [ + [ + 'Left side of && is always true.', + 24, + ], + [ + 'Right side of && is always true.', + 30, + ], + ]); + } - public function dataTreatPhpDocTypesAsCertainRegression(): array - { - return [ - [ - true, - ], - [ - false, - ], - ]; - } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/boolean-and-not-phpdoc.php'], [ + [ + 'Result of && is always false.', + 14, + $tipText, + ], + [ + 'Left side of && is always true.', + 24, + ], + [ + 'Left side of && is always true.', + 27, + $tipText, + ], + [ + 'Right side of && is always true.', + 30, + ], + [ + 'Right side of && is always true.', + 33, + $tipText, + ], + ]); + } - /** - * @dataProvider dataTreatPhpDocTypesAsCertainRegression - * @param bool $treatPhpDocTypesAsCertain - */ - public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void - { - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->analyse([__DIR__ . '/data/boolean-and-treat-phpdoc-types-regression.php'], []); - } + public function dataTreatPhpDocTypesAsCertainRegression(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } - public function testBugComposerDependentVariables(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-composer-dependent-variables.php'], []); - } + /** + * @dataProvider dataTreatPhpDocTypesAsCertainRegression + * @param bool $treatPhpDocTypesAsCertain + */ + public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->analyse([__DIR__ . '/data/boolean-and-treat-phpdoc-types-regression.php'], []); + } - public function testBug2231(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-2231.php'], [ - [ - 'Result of && is always false.', - 21, - ], - ]); - } + public function testBugComposerDependentVariables(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-composer-dependent-variables.php'], []); + } + public function testBug2231(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-2231.php'], [ + [ + 'Result of && is always false.', + 21, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php index bfa0081cd6..f3f43719b6 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new BooleanNotConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/boolean-not.php'], [ - [ - 'Negated boolean expression is always false.', - 13, - ], - [ - 'Negated boolean expression is always true.', - 18, - ], - [ - 'Negated boolean expression is always false.', - 33, - ], - [ - 'Negated boolean expression is always false.', - 40, - ], - [ - 'Negated boolean expression is always true.', - 46, - ], - [ - 'Negated boolean expression is always false.', - 50, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/boolean-not-not-phpdoc.php'], [ - [ - 'Negated boolean expression is always false.', - 16, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/boolean-not.php'], [ + [ + 'Negated boolean expression is always false.', + 13, + ], + [ + 'Negated boolean expression is always true.', + 18, + ], + [ + 'Negated boolean expression is always false.', + 33, + ], + [ + 'Negated boolean expression is always false.', + 40, + ], + [ + 'Negated boolean expression is always true.', + 46, + ], + [ + 'Negated boolean expression is always false.', + 50, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/boolean-not-not-phpdoc.php'], [ - [ - 'Negated boolean expression is always false.', - 16, - ], - [ - 'Negated boolean expression is always false.', - 20, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/boolean-not-not-phpdoc.php'], [ + [ + 'Negated boolean expression is always false.', + 16, + ], + ]); + } - public function dataTreatPhpDocTypesAsCertainRegression(): array - { - return [ - [ - true, - ], - [ - false, - ], - ]; - } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/boolean-not-not-phpdoc.php'], [ + [ + 'Negated boolean expression is always false.', + 16, + ], + [ + 'Negated boolean expression is always false.', + 20, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } - /** - * @dataProvider dataTreatPhpDocTypesAsCertainRegression - * @param bool $treatPhpDocTypesAsCertain - */ - public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void - { - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->analyse([__DIR__ . '/../DeadCode/data/bug-without-issue-1.php'], []); - } + public function dataTreatPhpDocTypesAsCertainRegression(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } + /** + * @dataProvider dataTreatPhpDocTypesAsCertainRegression + * @param bool $treatPhpDocTypesAsCertain + */ + public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->analyse([__DIR__ . '/../DeadCode/data/bug-without-issue-1.php'], []); + } } diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index f02dc93cbb..5ba6f4d79a 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain, - true - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new BooleanOrConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain, + true + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-or.php'], [ - [ - 'Left side of || is always true.', - 15, - ], - [ - 'Right side of || is always true.', - 19, - ], - [ - 'Left side of || is always false.', - 24, - ], - [ - 'Right side of || is always false.', - 27, - ], - [ - 'Right side of || is always true.', - 30, - ], - [ - 'Right side of || is always false.', - 33, - ], - [ - 'Right side of || is always false.', - 36, - ], - [ - 'Right side of || is always false.', - 39, - ], - [ - 'Result of || is always true.', - 50, - $tipText, - ], - [ - 'Result of || is always true.', - 54, - $tipText, - ], - [ - 'Result of || is always true.', - 61, - ], - [ - 'Result of || is always true.', - 65, - ], - [ - 'Left side of || is always false.', - 77, - ], - [ - 'Right side of || is always false.', - 79, - ], - [ - 'Left side of || is always true.', - 83, - ], - [ - 'Right side of || is always true.', - 85, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/boolean-or-not-phpdoc.php'], [ - [ - 'Left side of || is always true.', - 24, - ], - [ - 'Right side of || is always true.', - 30, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/boolean-or.php'], [ + [ + 'Left side of || is always true.', + 15, + ], + [ + 'Right side of || is always true.', + 19, + ], + [ + 'Left side of || is always false.', + 24, + ], + [ + 'Right side of || is always false.', + 27, + ], + [ + 'Right side of || is always true.', + 30, + ], + [ + 'Right side of || is always false.', + 33, + ], + [ + 'Right side of || is always false.', + 36, + ], + [ + 'Right side of || is always false.', + 39, + ], + [ + 'Result of || is always true.', + 50, + $tipText, + ], + [ + 'Result of || is always true.', + 54, + $tipText, + ], + [ + 'Result of || is always true.', + 61, + ], + [ + 'Result of || is always true.', + 65, + ], + [ + 'Left side of || is always false.', + 77, + ], + [ + 'Right side of || is always false.', + 79, + ], + [ + 'Left side of || is always true.', + 83, + ], + [ + 'Right side of || is always true.', + 85, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-or-not-phpdoc.php'], [ - [ - 'Result of || is always true.', - 14, - $tipText, - ], - [ - 'Left side of || is always true.', - 24, - ], - [ - 'Left side of || is always true.', - 27, - $tipText, - ], - [ - 'Right side of || is always true.', - 30, - ], - [ - 'Right side of || is always true.', - 33, - $tipText, - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/boolean-or-not-phpdoc.php'], [ + [ + 'Left side of || is always true.', + 24, + ], + [ + 'Right side of || is always true.', + 30, + ], + ]); + } - public function dataTreatPhpDocTypesAsCertainRegression(): array - { - return [ - [ - true, - ], - [ - false, - ], - ]; - } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/boolean-or-not-phpdoc.php'], [ + [ + 'Result of || is always true.', + 14, + $tipText, + ], + [ + 'Left side of || is always true.', + 24, + ], + [ + 'Left side of || is always true.', + 27, + $tipText, + ], + [ + 'Right side of || is always true.', + 30, + ], + [ + 'Right side of || is always true.', + 33, + $tipText, + ], + ]); + } - /** - * @dataProvider dataTreatPhpDocTypesAsCertainRegression - * @param bool $treatPhpDocTypesAsCertain - */ - public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void - { - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->analyse([__DIR__ . '/data/boolean-or-treat-phpdoc-types-regression.php'], []); - } + public function dataTreatPhpDocTypesAsCertainRegression(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } + /** + * @dataProvider dataTreatPhpDocTypesAsCertainRegression + * @param bool $treatPhpDocTypesAsCertain + */ + public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAsCertain): void + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->analyse([__DIR__ . '/data/boolean-or-treat-phpdoc-types-regression.php'], []); + } } diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index 31cb731e65..10673e1f3d 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new ElseIfConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/elseif-condition.php'], [ - [ - 'Elseif condition is always true.', - 18, - ], - ]); - } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [ - [ - 'Elseif condition is always true.', - 18, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/elseif-condition.php'], [ + [ + 'Elseif condition is always true.', + 18, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [ - [ - 'Elseif condition is always true.', - 18, - ], - [ - 'Elseif condition is always true.', - 24, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [ + [ + 'Elseif condition is always true.', + 18, + ], + ]); + } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [ + [ + 'Elseif condition is always true.', + 18, + ], + [ + 'Elseif condition is always true.', + 24, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 4ad516f3ce..411dc08ec6 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } - - /** - * @return DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return [ - new class implements DynamicFunctionReturnTypeExtension { - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'always_true'; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new IfConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - return new ConstantBooleanType(true); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - }, - ]; - } + /** + * @return DynamicFunctionReturnTypeExtension[] + */ + public function getDynamicFunctionReturnTypeExtensions(): array + { + return [ + new class() implements DynamicFunctionReturnTypeExtension { + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'always_true'; + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - require_once __DIR__ . '/data/function-definition.php'; - $this->analyse([__DIR__ . '/data/if-condition.php'], [ - [ - 'If condition is always true.', - 40, - ], - [ - 'If condition is always false.', - 45, - ], - [ - 'If condition is always true.', - 96, - ], - [ - 'If condition is always true.', - 110, - ], - [ - 'If condition is always true.', - 113, - ], - [ - 'If condition is always true.', - 127, - ], - [ - 'If condition is always true.', - 287, - ], - [ - 'If condition is always false.', - 291, - ], - ]); - } + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + return new ConstantBooleanType(true); + } + }, + ]; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/if-condition-not-phpdoc.php'], [ - [ - 'If condition is always true.', - 16, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + require_once __DIR__ . '/data/function-definition.php'; + $this->analyse([__DIR__ . '/data/if-condition.php'], [ + [ + 'If condition is always true.', + 40, + ], + [ + 'If condition is always false.', + 45, + ], + [ + 'If condition is always true.', + 96, + ], + [ + 'If condition is always true.', + 110, + ], + [ + 'If condition is always true.', + 113, + ], + [ + 'If condition is always true.', + 127, + ], + [ + 'If condition is always true.', + 287, + ], + [ + 'If condition is always false.', + 291, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/if-condition-not-phpdoc.php'], [ - [ - 'If condition is always true.', - 16, - ], - [ - 'If condition is always true.', - 20, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/if-condition-not-phpdoc.php'], [ + [ + 'If condition is always true.', + 16, + ], + ]); + } - public function testBug4043(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4043.php'], [ - [ - 'If condition is always false.', - 43, - ], - [ - 'If condition is always true.', - 50, - ], - ]); - } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/if-condition-not-phpdoc.php'], [ + [ + 'If condition is always true.', + 16, + ], + [ + 'If condition is always true.', + 20, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + public function testBug4043(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4043.php'], [ + [ + 'If condition is always false.', + 43, + ], + [ + 'If condition is always true.', + 50, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 5a471e0a19..58dec52a2a 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [\stdClass::class], - $this->treatPhpDocTypesAsCertain - ), - $this->checkAlwaysTrueCheckTypeFunctionCall, - $this->treatPhpDocTypesAsCertain - ); - } + /** @var bool */ + private $treatPhpDocTypesAsCertain; - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new ImpossibleCheckTypeFunctionCallRule( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [\stdClass::class], + $this->treatPhpDocTypesAsCertain + ), + $this->checkAlwaysTrueCheckTypeFunctionCall, + $this->treatPhpDocTypesAsCertain + ); + } - public function testImpossibleCheckTypeFunctionCall(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse( - [__DIR__ . '/data/check-type-function-call.php'], - [ - [ - 'Call to function is_int() with int will always evaluate to true.', - 25, - ], - [ - 'Call to function is_int() with string will always evaluate to false.', - 31, - ], - [ - 'Call to function is_callable() with array will always evaluate to false.', - 44, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 48, - ], - [ - 'Call to function is_callable() with \'date\' will always evaluate to true.', - 84, - ], - [ - 'Call to function is_callable() with \'nonexistentFunction\' will always evaluate to false.', - 87, - ], - [ - 'Call to function is_numeric() with \'123\' will always evaluate to true.', - 102, - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 105, - ], - [ - 'Call to function is_numeric() with 123|float will always evaluate to true.', - 118, - ], - [ - 'Call to function is_string() with string will always evaluate to true.', - 140, - ], - [ - 'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'doFoo\' will always evaluate to true.', - 179, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doFoo\' will always evaluate to true.', - 191, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', - 194, - ], - [ - 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'fooProperty\' will always evaluate to true.', - 209, - ], - [ - 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'barProperty\' will always evaluate to false.', - 212, - ], - [ - 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', - 235, - ], - [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', - 244, - ], - [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', - 248, - ], - [ - 'Call to function in_array() with arguments \'foo\', array(\'foo\') and true will always evaluate to true.', - 252, - ], - [ - 'Call to function in_array() with arguments \'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', - 256, - ], - [ - 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', - 320, - ], - [ - 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', - 336, - ], - [ - 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', - 343, - ], - [ - 'Call to function array_key_exists() with \'a\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to true.', - 360, - ], - [ - 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', - 366, - ], - [ - 'Call to function is_string() with mixed will always evaluate to false.', - 560, - ], - [ - 'Call to function is_callable() with mixed will always evaluate to false.', - 571, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExists\' and \'testWithStringFirst…\' will always evaluate to true.', - 585, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', - 594, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', - 597, - ], - [ - 'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.', - 609, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'method\' will always evaluate to true.', - 624, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'someAnother\' will always evaluate to true.', - 627, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', - 630, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', - 633, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', - 636, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 639, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', - 642, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', - 645, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 648, - ], - [ - 'Call to function is_string() with string will always evaluate to true.', - 677, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function assert() with true will always evaluate to true.', - 692, - ], - [ - 'Call to function is_numeric() with \'123\' will always evaluate to true.', - 692, - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 693, - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 693, - ], - [ - 'Call to function assert() with true will always evaluate to true.', - 700, - ], - [ - 'Call to function is_numeric() with 123|float will always evaluate to true.', - 700, - ], - [ - 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', - 782, - ], - [ - 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', - 786, - ], - ] - ); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = false; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse( - [__DIR__ . '/data/check-type-function-call.php'], - [ - [ - 'Call to function is_int() with string will always evaluate to false.', - 31, - ], - [ - 'Call to function is_callable() with array will always evaluate to false.', - 44, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 48, - ], - [ - 'Call to function is_callable() with \'nonexistentFunction\' will always evaluate to false.', - 87, - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 105, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', - 194, - ], - [ - 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'barProperty\' will always evaluate to false.', - 212, - ], - [ - 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', - 235, - ], - [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', - 244, - ], - [ - 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', - 320, - ], - [ - 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', - 336, - ], - [ - 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', - 343, - ], - [ - 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', - 366, - ], - [ - 'Call to function is_string() with mixed will always evaluate to false.', - 560, - ], - [ - 'Call to function is_callable() with mixed will always evaluate to false.', - 571, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', - 594, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', - 597, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', - 630, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 639, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 648, - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 693, - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 693, - ], - ] - ); - } + public function testImpossibleCheckTypeFunctionCall(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse( + [__DIR__ . '/data/check-type-function-call.php'], + [ + [ + 'Call to function is_int() with int will always evaluate to true.', + 25, + ], + [ + 'Call to function is_int() with string will always evaluate to false.', + 31, + ], + [ + 'Call to function is_callable() with array will always evaluate to false.', + 44, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to function assert() with false will always evaluate to false.', + 48, + ], + [ + 'Call to function is_callable() with \'date\' will always evaluate to true.', + 84, + ], + [ + 'Call to function is_callable() with \'nonexistentFunction\' will always evaluate to false.', + 87, + ], + [ + 'Call to function is_numeric() with \'123\' will always evaluate to true.', + 102, + ], + [ + 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', + 105, + ], + [ + 'Call to function is_numeric() with 123|float will always evaluate to true.', + 118, + ], + [ + 'Call to function is_string() with string will always evaluate to true.', + 140, + ], + [ + 'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'doFoo\' will always evaluate to true.', + 179, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doFoo\' will always evaluate to true.', + 191, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', + 194, + ], + [ + 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'fooProperty\' will always evaluate to true.', + 209, + ], + [ + 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'barProperty\' will always evaluate to false.', + 212, + ], + [ + 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', + 235, + ], + [ + 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', + 244, + ], + [ + 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', + 248, + ], + [ + 'Call to function in_array() with arguments \'foo\', array(\'foo\') and true will always evaluate to true.', + 252, + ], + [ + 'Call to function in_array() with arguments \'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', + 256, + ], + [ + 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', + 320, + ], + [ + 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', + 336, + ], + [ + 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', + 343, + ], + [ + 'Call to function array_key_exists() with \'a\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to true.', + 360, + ], + [ + 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', + 366, + ], + [ + 'Call to function is_string() with mixed will always evaluate to false.', + 560, + ], + [ + 'Call to function is_callable() with mixed will always evaluate to false.', + 571, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExists\' and \'testWithStringFirst…\' will always evaluate to true.', + 585, + ], + [ + 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', + 594, + ], + [ + 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', + 597, + ], + [ + 'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.', + 609, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'method\' will always evaluate to true.', + 624, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'someAnother\' will always evaluate to true.', + 627, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', + 630, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', + 633, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', + 636, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', + 639, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', + 642, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', + 645, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', + 648, + ], + [ + 'Call to function is_string() with string will always evaluate to true.', + 677, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to function assert() with true will always evaluate to true.', + 692, + ], + [ + 'Call to function is_numeric() with \'123\' will always evaluate to true.', + 692, + ], + [ + 'Call to function assert() with false will always evaluate to false.', + 693, + ], + [ + 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', + 693, + ], + [ + 'Call to function assert() with true will always evaluate to true.', + 700, + ], + [ + 'Call to function is_numeric() with 123|float will always evaluate to true.', + 700, + ], + [ + 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', + 782, + ], + [ + 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', + 786, + ], + ] + ); + } - public function testDoNotReportTypesFromPhpDocs(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ - [ - 'Call to function is_int() with int will always evaluate to true.', - 16, - ], - ]); - } + public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = false; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse( + [__DIR__ . '/data/check-type-function-call.php'], + [ + [ + 'Call to function is_int() with string will always evaluate to false.', + 31, + ], + [ + 'Call to function is_callable() with array will always evaluate to false.', + 44, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to function assert() with false will always evaluate to false.', + 48, + ], + [ + 'Call to function is_callable() with \'nonexistentFunction\' will always evaluate to false.', + 87, + ], + [ + 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', + 105, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', + 194, + ], + [ + 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'barProperty\' will always evaluate to false.', + 212, + ], + [ + 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', + 235, + ], + [ + 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', + 244, + ], + [ + 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', + 320, + ], + [ + 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', + 336, + ], + [ + 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', + 343, + ], + [ + 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', + 366, + ], + [ + 'Call to function is_string() with mixed will always evaluate to false.', + 560, + ], + [ + 'Call to function is_callable() with mixed will always evaluate to false.', + 571, + ], + [ + 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', + 594, + ], + [ + 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', + 597, + ], + [ + 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', + 630, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', + 639, + ], + [ + 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', + 648, + ], + [ + 'Call to function assert() with false will always evaluate to false.', + 693, + ], + [ + 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', + 693, + ], + ] + ); + } - public function testReportTypesFromPhpDocs(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ - [ - 'Call to function is_int() with int will always evaluate to true.', - 16, - ], - [ - 'Call to function is_int() with int will always evaluate to true.', - 19, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportTypesFromPhpDocs(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ + [ + 'Call to function is_int() with int will always evaluate to true.', + 16, + ], + ]); + } - public function testBug2550(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-2550.php'], []); - } + public function testReportTypesFromPhpDocs(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ + [ + 'Call to function is_int() with int will always evaluate to true.', + 16, + ], + [ + 'Call to function is_int() with int will always evaluate to true.', + 19, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } - public function testBug3994(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-3994.php'], []); - } + public function testBug2550(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-2550.php'], []); + } - public function testBug1613(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-1613.php'], []); - } + public function testBug3994(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-3994.php'], []); + } - public function testBug2714(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-2714.php'], []); - } + public function testBug1613(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-1613.php'], []); + } - public function testBug4657(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/bug-4657.php'], []); - } + public function testBug2714(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-2714.php'], []); + } + public function testBug4657(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-4657.php'], []); + } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 633a48b728..7f95e2483c 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - true, - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } - - /** - * @return MethodTypeSpecifyingExtension[] - */ - protected function getMethodTypeSpecifyingExtensions(): array - { - return [ - new AssertionClassMethodTypeSpecifyingExtension(null), - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { - - /** @var TypeSpecifier */ - private $typeSpecifier; - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - - public function getClass(): string - { - return \PHPStan\Tests\AssertionClass::class; - } - - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'assertNotInt' - && count($node->args) > 0; - } - - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BooleanNot( - new \PhpParser\Node\Expr\FuncCall( - new \PhpParser\Node\Name('is_int'), - [ - $node->args[0], - ] - ) - ), - TypeSpecifierContext::createTruthy() - ); - } + public function getRule(): \PHPStan\Rules\Rule + { + return new ImpossibleCheckTypeMethodCallRule( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + true, + $this->treatPhpDocTypesAsCertain + ); + } - }, - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - /** @var TypeSpecifier */ - private $typeSpecifier; + /** + * @return MethodTypeSpecifyingExtension[] + */ + protected function getMethodTypeSpecifyingExtensions(): array + { + return [ + new AssertionClassMethodTypeSpecifyingExtension(null), + new class() implements + MethodTypeSpecifyingExtension, + TypeSpecifierAwareExtension { + /** @var TypeSpecifier */ + private $typeSpecifier; - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function getClass(): string - { - return \ImpossibleMethodCall\Foo::class; - } + public function getClass(): string + { + return \PHPStan\Tests\AssertionClass::class; + } - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'isSame' - && count($node->args) >= 2; - } + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + return $methodReflection->getName() === 'assertNotInt' + && count($node->args) > 0; + } - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BinaryOp\Identical( - $node->args[0]->value, - $node->args[1]->value - ), - TypeSpecifierContext::createTruthy() - ); - } + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BooleanNot( + new \PhpParser\Node\Expr\FuncCall( + new \PhpParser\Node\Name('is_int'), + [ + $node->args[0], + ] + ) + ), + TypeSpecifierContext::createTruthy() + ); + } + }, + new class() implements + MethodTypeSpecifyingExtension, + TypeSpecifierAwareExtension { + /** @var TypeSpecifier */ + private $typeSpecifier; - }, - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - /** @var TypeSpecifier */ - private $typeSpecifier; + public function getClass(): string + { + return \ImpossibleMethodCall\Foo::class; + } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + return $methodReflection->getName() === 'isSame' + && count($node->args) >= 2; + } - public function getClass(): string - { - return \ImpossibleMethodCall\Foo::class; - } + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BinaryOp\Identical( + $node->args[0]->value, + $node->args[1]->value + ), + TypeSpecifierContext::createTruthy() + ); + } + }, + new class() implements + MethodTypeSpecifyingExtension, + TypeSpecifierAwareExtension { + /** @var TypeSpecifier */ + private $typeSpecifier; - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'isNotSame' - && count($node->args) >= 2; - } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BinaryOp\NotIdentical( - $node->args[0]->value, - $node->args[1]->value - ), - TypeSpecifierContext::createTruthy() - ); - } + public function getClass(): string + { + return \ImpossibleMethodCall\Foo::class; + } - }, - ]; - } + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + return $methodReflection->getName() === 'isNotSame' + && count($node->args) >= 2; + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/impossible-method-call.php'], [ - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 14, - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with int will always evaluate to false.', - 15, - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with int will always evaluate to false.', - 30, - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with string will always evaluate to true.', - 36, - ], - [ - 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.', - 60, - ], - [ - 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 2 will always evaluate to false.', - 63, - ], - [ - 'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and 1 will always evaluate to false.', - 66, - ], - [ - 'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and 2 will always evaluate to true.', - 69, - ], - [ - 'Call to method ImpossibleMethodCall\Foo::isSame() with stdClass and stdClass will always evaluate to true.', - 78, - ], - ]); - } + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BinaryOp\NotIdentical( + $node->args[0]->value, + $node->args[1]->value + ), + TypeSpecifierContext::createTruthy() + ); + } + }, + ]; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/impossible-method-call-not-phpdoc.php'], [ - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 17, - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 19, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-method-call.php'], [ + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 14, + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with int will always evaluate to false.', + 15, + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with int will always evaluate to false.', + 30, + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with string will always evaluate to true.', + 36, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.', + 60, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 2 will always evaluate to false.', + 63, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and 1 will always evaluate to false.', + 66, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isNotSame() with 1 and 2 will always evaluate to true.', + 69, + ], + [ + 'Call to method ImpossibleMethodCall\Foo::isSame() with stdClass and stdClass will always evaluate to true.', + 78, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/impossible-method-call-not-phpdoc.php'], [ - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 17, - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 18, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', - 19, - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/impossible-method-call-not-phpdoc.php'], [ + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 17, + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 19, + ], + ]); + } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-method-call-not-phpdoc.php'], [ + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 17, + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 18, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', + 19, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 64079703c4..985d540120 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - true, - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + public function getRule(): \PHPStan\Rules\Rule + { + return new ImpossibleCheckTypeStaticMethodCallRule( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + true, + $this->treatPhpDocTypesAsCertain + ); + } - /** - * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] - */ - protected function getStaticMethodTypeSpecifyingExtensions(): array - { - return [ - new AssertionClassStaticMethodTypeSpecifyingExtension(null), - new AssertStaticMethodTypeSpecifyingExtension(), - ]; - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/impossible-static-method-call.php'], [ - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 13, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with string will always evaluate to false.', - 14, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 31, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with string will always evaluate to false.', - 32, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with 1 and 2 will always evaluate to true.', - 33, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with arguments 1, 2 and 3 will always evaluate to true.', - 34, - ], - ]); - } + /** + * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] + */ + protected function getStaticMethodTypeSpecifyingExtensions(): array + { + return [ + new AssertionClassStaticMethodTypeSpecifyingExtension(null), + new AssertStaticMethodTypeSpecifyingExtension(), + ]; + } - public function testDoNotReportPhpDocs(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/impossible-static-method-call-not-phpdoc.php'], [ - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 16, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 18, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-static-method-call.php'], [ + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 13, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with string will always evaluate to false.', + 14, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 31, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with string will always evaluate to false.', + 32, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with 1 and 2 will always evaluate to true.', + 33, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with arguments 1, 2 and 3 will always evaluate to true.', + 34, + ], + ]); + } - public function testReportPhpDocs(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/impossible-static-method-call-not-phpdoc.php'], [ - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 16, - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 17, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', - 18, - ], - ]); - } + public function testDoNotReportPhpDocs(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/impossible-static-method-call-not-phpdoc.php'], [ + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 16, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 18, + ], + ]); + } + public function testReportPhpDocs(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-static-method-call-not-phpdoc.php'], [ + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 16, + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 17, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to static method PHPStan\Tests\AssertionClass::assertInt() with int will always evaluate to true.', + 18, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 0c0c7109fd..d9cc201098 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/match-expr.php'], [ - [ - 'Match arm comparison between 1|2|3 and \'foo\' is always false.', - 14, - ], - [ - 'Match arm comparison between 1|2|3 and 0 is always false.', - 19, - ], - [ - 'Match arm comparison between 3 and 3 is always true.', - 28, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 29, - ], - [ - 'Match arm comparison between 3 and 3 is always true.', - 35, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 36, - ], - [ - 'Match arm comparison between 1 and 1 is always true.', - 40, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 41, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 42, - ], - [ - 'Match arm comparison between 1 and 1 is always true.', - 46, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 47, - ], - [ - 'Match expression does not handle remaining value: 3', - 50, - ], - [ - 'Match expression does not handle remaining values: 1|2|3', - 55, - ], - [ - 'Match arm comparison between 1|2 and 3 is always false.', - 65, - ], - [ - 'Match arm comparison between 1 and 1 is always true.', - 70, - ], - [ - 'Match arm comparison between true and false is always false.', - 86, - ], - [ - 'Match arm comparison between true and false is always false.', - 92, - ], - [ - 'Match expression does not handle remaining value: true', - 90, - ], - ]); - } + public function testRule(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/match-expr.php'], [ + [ + 'Match arm comparison between 1|2|3 and \'foo\' is always false.', + 14, + ], + [ + 'Match arm comparison between 1|2|3 and 0 is always false.', + 19, + ], + [ + 'Match arm comparison between 3 and 3 is always true.', + 28, + ], + [ + 'Match arm is unreachable because previous comparison is always true.', + 29, + ], + [ + 'Match arm comparison between 3 and 3 is always true.', + 35, + ], + [ + 'Match arm is unreachable because previous comparison is always true.', + 36, + ], + [ + 'Match arm comparison between 1 and 1 is always true.', + 40, + ], + [ + 'Match arm is unreachable because previous comparison is always true.', + 41, + ], + [ + 'Match arm is unreachable because previous comparison is always true.', + 42, + ], + [ + 'Match arm comparison between 1 and 1 is always true.', + 46, + ], + [ + 'Match arm is unreachable because previous comparison is always true.', + 47, + ], + [ + 'Match expression does not handle remaining value: 3', + 50, + ], + [ + 'Match expression does not handle remaining values: 1|2|3', + 55, + ], + [ + 'Match arm comparison between 1|2 and 3 is always false.', + 65, + ], + [ + 'Match arm comparison between 1 and 1 is always true.', + 70, + ], + [ + 'Match arm comparison between true and false is always false.', + 86, + ], + [ + 'Match arm comparison between true and false is always false.', + 92, + ], + [ + 'Match expression does not handle remaining value: true', + 90, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php index aa828378d7..3f866aace7 100644 --- a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/number-comparison-operators.php'], [ - [ - 'Comparison operation "<=" between int<6, max> and 2 is always false.', - 7, - ], - [ - 'Comparison operation ">" between int<2, 4> and 8 is always false.', - 13, - ], - [ - 'Comparison operation "<" between int and 5 is always true.', - 21, - ], - ]); - } - - public function testBug2648(): void - { - $this->analyse([__DIR__ . '/data/bug-2648-rule.php'], []); - } - - public function testBug2648Namespace(): void - { - $this->analyse([__DIR__ . '/data/bug-2648-namespace-rule.php'], []); - } - + protected function getRule(): Rule + { + return new NumberComparisonOperatorsConstantConditionRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/number-comparison-operators.php'], [ + [ + 'Comparison operation "<=" between int<6, max> and 2 is always false.', + 7, + ], + [ + 'Comparison operation ">" between int<2, 4> and 8 is always false.', + 13, + ], + [ + 'Comparison operation "<" between int and 5 is always true.', + 21, + ], + ]); + } + + public function testBug2648(): void + { + $this->analyse([__DIR__ . '/data/bug-2648-rule.php'], []); + } + + public function testBug2648Namespace(): void + { + $this->analyse([__DIR__ . '/data/bug-2648-namespace-rule.php'], []); + } } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 2b571f01e8..23e14cbaf4 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1,4 +1,6 @@ -checkAlwaysTrueStrictComparison); - } - - public function testStrictComparison(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse( - [__DIR__ . '/data/strict-comparison.php'], - [ - [ - 'Strict comparison using === between 1 and 1 will always evaluate to true.', - 10, - ], - [ - 'Strict comparison using === between 1 and \'1\' will always evaluate to false.', - 11, - ], - [ - 'Strict comparison using !== between 1 and \'1\' will always evaluate to true.', - 12, - ], - [ - 'Strict comparison using === between 1 and null will always evaluate to false.', - 14, - ], - [ - 'Strict comparison using === between StrictComparison\Bar and 1 will always evaluate to false.', - 15, - ], - [ - 'Strict comparison using === between 1 and array|bool|StrictComparison\Collection will always evaluate to false.', - 19, - ], - [ - 'Strict comparison using === between true and false will always evaluate to false.', - 30, - ], - [ - 'Strict comparison using === between false and true will always evaluate to false.', - 31, - ], - [ - 'Strict comparison using === between 1.0 and 1 will always evaluate to false.', - 46, - ], - [ - 'Strict comparison using === between 1 and 1.0 will always evaluate to false.', - 47, - ], - [ - 'Strict comparison using === between string and null will always evaluate to false.', - 69, - ], - [ - 'Strict comparison using !== between string and null will always evaluate to true.', - 76, - ], - [ - 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', - 88, - ], - [ - 'Strict comparison using === between 1|2|3 and null will always evaluate to false.', - 98, - ], - [ - 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', - 130, - ], - [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', - 140, - ], - [ - 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', - 154, - ], - [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', - 164, - ], - [ - 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', - 212, - ], - [ - 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', - 255, - ], - [ - 'Strict comparison using !== between stdClass and null will always evaluate to true.', - 271, - ], - [ - 'Strict comparison using === between 1 and 2 will always evaluate to false.', - 284, - ], - [ - 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', - 292, - ], - [ - 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', - 300, - ], - [ - 'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.', - 320, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 335, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 343, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 360, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 368, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 386, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 394, - ], - [ - 'Strict comparison using !== between null and null will always evaluate to false.', - 408, - ], - [ - 'Strict comparison using === between 0 and 0 will always evaluate to true.', - 426, - ], - [ - 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic - 438, - ], - [ - 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', - 464, - ], - [ - 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', - 466, - ], - [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', - 624, - ], - [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', - 635, - ], - [ - 'Strict comparison using === between \'foofoofoofoofoofoof…\' and \'foofoofoofoofoofoof…\' will always evaluate to true.', - 654, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 685, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 695, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 705, - ], - [ - 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', - 808, - ], - [ - 'Strict comparison using !== between mixed and 1 will always evaluate to true.', - 812, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 846, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 849, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 857, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 876, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 879, - ], - [ - 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', - 887, - ], - [ - 'Strict comparison using === between 1000 and 1000 will always evaluate to true.', - 910, - ], - ] - ); - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new StrictComparisonOfDifferentTypesRule($this->checkAlwaysTrueStrictComparison); + } - public function testStrictComparisonWithoutAlwaysTrue(): void - { - $this->checkAlwaysTrueStrictComparison = false; - $this->analyse( - [__DIR__ . '/data/strict-comparison.php'], - [ - [ - 'Strict comparison using === between 1 and \'1\' will always evaluate to false.', - 11, - ], - [ - 'Strict comparison using === between 1 and null will always evaluate to false.', - 14, - ], - [ - 'Strict comparison using === between StrictComparison\Bar and 1 will always evaluate to false.', - 15, - ], - [ - 'Strict comparison using === between 1 and array|bool|StrictComparison\Collection will always evaluate to false.', - 19, - ], - [ - 'Strict comparison using === between true and false will always evaluate to false.', - 30, - ], - [ - 'Strict comparison using === between false and true will always evaluate to false.', - 31, - ], - [ - 'Strict comparison using === between 1.0 and 1 will always evaluate to false.', - 46, - ], - [ - 'Strict comparison using === between 1 and 1.0 will always evaluate to false.', - 47, - ], - [ - 'Strict comparison using === between string and null will always evaluate to false.', - 69, - ], - [ - 'Strict comparison using === between 1|2|3 and null will always evaluate to false.', - 98, - ], - [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', - 140, - ], - [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', - 164, - ], - [ - 'Strict comparison using === between 1 and 2 will always evaluate to false.', - 284, - ], - [ - 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', - 292, - ], - [ - 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', - 300, - ], - [ - 'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.', - 320, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 335, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 343, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 360, - ], - [ - 'Strict comparison using === between int and \'string\' will always evaluate to false.', - 368, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 386, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 394, - ], - [ - 'Strict comparison using !== between null and null will always evaluate to false.', - 408, - ], - [ - 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic - 438, - ], - [ - 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', - 464, - ], - [ - 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', - 466, - ], - [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', - 624, - ], - [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', - 635, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 685, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 695, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 705, - ], - [ - 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', - 808, - ], - ] - ); - } + public function testStrictComparison(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse( + [__DIR__ . '/data/strict-comparison.php'], + [ + [ + 'Strict comparison using === between 1 and 1 will always evaluate to true.', + 10, + ], + [ + 'Strict comparison using === between 1 and \'1\' will always evaluate to false.', + 11, + ], + [ + 'Strict comparison using !== between 1 and \'1\' will always evaluate to true.', + 12, + ], + [ + 'Strict comparison using === between 1 and null will always evaluate to false.', + 14, + ], + [ + 'Strict comparison using === between StrictComparison\Bar and 1 will always evaluate to false.', + 15, + ], + [ + 'Strict comparison using === between 1 and array|bool|StrictComparison\Collection will always evaluate to false.', + 19, + ], + [ + 'Strict comparison using === between true and false will always evaluate to false.', + 30, + ], + [ + 'Strict comparison using === between false and true will always evaluate to false.', + 31, + ], + [ + 'Strict comparison using === between 1.0 and 1 will always evaluate to false.', + 46, + ], + [ + 'Strict comparison using === between 1 and 1.0 will always evaluate to false.', + 47, + ], + [ + 'Strict comparison using === between string and null will always evaluate to false.', + 69, + ], + [ + 'Strict comparison using !== between string and null will always evaluate to true.', + 76, + ], + [ + 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', + 88, + ], + [ + 'Strict comparison using === between 1|2|3 and null will always evaluate to false.', + 98, + ], + [ + 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', + 130, + ], + [ + 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 140, + ], + [ + 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', + 154, + ], + [ + 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 164, + ], + [ + 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', + 212, + ], + [ + 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', + 255, + ], + [ + 'Strict comparison using !== between stdClass and null will always evaluate to true.', + 271, + ], + [ + 'Strict comparison using === between 1 and 2 will always evaluate to false.', + 284, + ], + [ + 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', + 292, + ], + [ + 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', + 300, + ], + [ + 'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.', + 320, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 335, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 343, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 360, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 368, + ], + [ + 'Strict comparison using === between float and \'string\' will always evaluate to false.', + 386, + ], + [ + 'Strict comparison using === between float and \'string\' will always evaluate to false.', + 394, + ], + [ + 'Strict comparison using !== between null and null will always evaluate to false.', + 408, + ], + [ + 'Strict comparison using === between 0 and 0 will always evaluate to true.', + 426, + ], + [ + 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic + 438, + ], + [ + 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', + 464, + ], + [ + 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', + 466, + ], + [ + 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 624, + ], + [ + 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 635, + ], + [ + 'Strict comparison using === between \'foofoofoofoofoofoof…\' and \'foofoofoofoofoofoof…\' will always evaluate to true.', + 654, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 685, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 695, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 705, + ], + [ + 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', + 808, + ], + [ + 'Strict comparison using !== between mixed and 1 will always evaluate to true.', + 812, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 846, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 849, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 857, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 876, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 879, + ], + [ + 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', + 887, + ], + [ + 'Strict comparison using === between 1000 and 1000 will always evaluate to true.', + 910, + ], + ] + ); + } - public function testStrictComparisonPhp71(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/strict-comparison-71.php'], [ - [ - 'Strict comparison using === between null and null will always evaluate to true.', - 20, - ], - ]); - } + public function testStrictComparisonWithoutAlwaysTrue(): void + { + $this->checkAlwaysTrueStrictComparison = false; + $this->analyse( + [__DIR__ . '/data/strict-comparison.php'], + [ + [ + 'Strict comparison using === between 1 and \'1\' will always evaluate to false.', + 11, + ], + [ + 'Strict comparison using === between 1 and null will always evaluate to false.', + 14, + ], + [ + 'Strict comparison using === between StrictComparison\Bar and 1 will always evaluate to false.', + 15, + ], + [ + 'Strict comparison using === between 1 and array|bool|StrictComparison\Collection will always evaluate to false.', + 19, + ], + [ + 'Strict comparison using === between true and false will always evaluate to false.', + 30, + ], + [ + 'Strict comparison using === between false and true will always evaluate to false.', + 31, + ], + [ + 'Strict comparison using === between 1.0 and 1 will always evaluate to false.', + 46, + ], + [ + 'Strict comparison using === between 1 and 1.0 will always evaluate to false.', + 47, + ], + [ + 'Strict comparison using === between string and null will always evaluate to false.', + 69, + ], + [ + 'Strict comparison using === between 1|2|3 and null will always evaluate to false.', + 98, + ], + [ + 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 140, + ], + [ + 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 164, + ], + [ + 'Strict comparison using === between 1 and 2 will always evaluate to false.', + 284, + ], + [ + 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', + 292, + ], + [ + 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', + 300, + ], + [ + 'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.', + 320, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 335, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 343, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 360, + ], + [ + 'Strict comparison using === between int and \'string\' will always evaluate to false.', + 368, + ], + [ + 'Strict comparison using === between float and \'string\' will always evaluate to false.', + 386, + ], + [ + 'Strict comparison using === between float and \'string\' will always evaluate to false.', + 394, + ], + [ + 'Strict comparison using !== between null and null will always evaluate to false.', + 408, + ], + [ + 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic + 438, + ], + [ + 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', + 464, + ], + [ + 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', + 466, + ], + [ + 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 624, + ], + [ + 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 635, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 685, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 695, + ], + [ + 'Strict comparison using === between string|null and 1 will always evaluate to false.', + 705, + ], + [ + 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', + 808, + ], + ] + ); + } - public function testStrictComparisonPropertyNativeTypesPhp74(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/strict-comparison-property-native-types.php'], [ - [ - 'Strict comparison using === between string and null will always evaluate to false.', - 14, - ], - [ - 'Strict comparison using !== between string and null will always evaluate to true.', - 25, - ], - [ - 'Strict comparison using === between null and string will always evaluate to false.', - 36, - ], - [ - 'Strict comparison using !== between null and string will always evaluate to true.', - 47, - ], - ]); - } + public function testStrictComparisonPhp71(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/strict-comparison-71.php'], [ + [ + 'Strict comparison using === between null and null will always evaluate to true.', + 20, + ], + ]); + } - public function testBug2835(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-2835.php'], []); - } + public function testStrictComparisonPropertyNativeTypesPhp74(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/strict-comparison-property-native-types.php'], [ + [ + 'Strict comparison using === between string and null will always evaluate to false.', + 14, + ], + [ + 'Strict comparison using !== between string and null will always evaluate to true.', + 25, + ], + [ + 'Strict comparison using === between null and string will always evaluate to false.', + 36, + ], + [ + 'Strict comparison using !== between null and string will always evaluate to true.', + 47, + ], + ]); + } - public function testBug1860(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-1860.php'], [ - [ - 'Strict comparison using === between string and null will always evaluate to false.', - 15, - ], - [ - 'Strict comparison using !== between string and null will always evaluate to true.', - 19, - ], - ]); - } + public function testBug2835(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-2835.php'], []); + } - public function testBug3544(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-3544.php'], []); - } + public function testBug1860(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-1860.php'], [ + [ + 'Strict comparison using === between string and null will always evaluate to false.', + 15, + ], + [ + 'Strict comparison using !== between string and null will always evaluate to true.', + 19, + ], + ]); + } - public function testBug2675(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-2675.php'], []); - } + public function testBug3544(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-3544.php'], []); + } - public function testBug2220(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-2220.php'], []); - } + public function testBug2675(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-2675.php'], []); + } - public function testBug1707(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-1707.php'], []); - } + public function testBug2220(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-2220.php'], []); + } - public function testBug3357(): void - { - $this->checkAlwaysTrueStrictComparison = true; - $this->analyse([__DIR__ . '/data/bug-3357.php'], []); - } + public function testBug1707(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-1707.php'], []); + } + public function testBug3357(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-3357.php'], []); + } } diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index d68bf08601..2bf06dc711 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new TernaryOperatorConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/ternary.php'], [ - [ - 'Ternary operator condition is always true.', - 11, - ], - [ - 'Ternary operator condition is always false.', - 15, - ], - [ - 'Ternary operator condition is always false.', - 66, - ], - [ - 'Ternary operator condition is always false.', - 67, - ], - [ - 'Ternary operator condition is always true.', - 70, - ], - [ - 'Ternary operator condition is always true.', - 71, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/ternary-not-phpdoc.php'], [ - [ - 'Ternary operator condition is always true.', - 16, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/ternary.php'], [ + [ + 'Ternary operator condition is always true.', + 11, + ], + [ + 'Ternary operator condition is always false.', + 15, + ], + [ + 'Ternary operator condition is always false.', + 66, + ], + [ + 'Ternary operator condition is always false.', + 67, + ], + [ + 'Ternary operator condition is always true.', + 70, + ], + [ + 'Ternary operator condition is always true.', + 71, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/ternary-not-phpdoc.php'], [ - [ - 'Ternary operator condition is always true.', - 16, - ], - [ - 'Ternary operator condition is always true.', - 17, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/ternary-not-phpdoc.php'], [ + [ + 'Ternary operator condition is always true.', + 16, + ], + ]); + } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/ternary-not-phpdoc.php'], [ + [ + 'Ternary operator condition is always true.', + 16, + ], + [ + 'Ternary operator condition is always true.', + 17, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php index 66034184c4..29e40e839e 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): Rule + { + return new UnreachableIfBranchesRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable-if-branches.php'], [ - [ - 'Else branch is unreachable because previous condition is always true.', - 15, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 25, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 27, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 39, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 41, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ - [ - 'Elseif branch is unreachable because previous condition is always true.', - 18, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 28, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 38, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/unreachable-if-branches.php'], [ + [ + 'Else branch is unreachable because previous condition is always true.', + 15, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 25, + ], + [ + 'Else branch is unreachable because previous condition is always true.', + 27, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 39, + ], + [ + 'Else branch is unreachable because previous condition is always true.', + 41, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ - [ - 'Elseif branch is unreachable because previous condition is always true.', - 18, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 28, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 38, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 44, - $tipText, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 54, - //$tipText, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 64, - //$tipText, - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ + [ + 'Elseif branch is unreachable because previous condition is always true.', + 18, + ], + [ + 'Else branch is unreachable because previous condition is always true.', + 28, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 38, + ], + ]); + } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ + [ + 'Elseif branch is unreachable because previous condition is always true.', + 18, + ], + [ + 'Else branch is unreachable because previous condition is always true.', + 28, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 38, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 44, + $tipText, + ], + [ + 'Else branch is unreachable because previous condition is always true.', + 54, + //$tipText, + ], + [ + 'Elseif branch is unreachable because previous condition is always true.', + 64, + //$tipText, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php index 93c7d52bcc..2c50f32b46 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ), - $this->treatPhpDocTypesAsCertain - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } + protected function getRule(): Rule + { + return new UnreachableTernaryElseBranchRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 6, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 9, - ], - ]); - } + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 16, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 17, - ], - ]); - } + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch.php'], [ + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 6, + ], + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 9, + ], + ]); + } - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 16, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 17, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 19, - $tipText, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 20, - $tipText, - ], - ]); - } + public function testDoNotReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 16, + ], + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 17, + ], + ]); + } + public function testReportPhpDoc(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 16, + ], + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 17, + ], + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 19, + $tipText, + ], + [ + 'Else branch is unreachable because ternary operator condition is always true.', + 20, + $tipText, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/UsageOfVoidMatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/UsageOfVoidMatchExpressionRuleTest.php index bf873a965c..e38c90e955 100644 --- a/tests/PHPStan/Rules/Comparison/UsageOfVoidMatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UsageOfVoidMatchExpressionRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/void-match.php'], [ - [ - 'Result of match expression (void) is used.', - 21, - ], - ]); - } + public function testRule(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/void-match.php'], [ + [ + 'Result of match expression (void) is used.', + 21, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-and-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/boolean-and-not-phpdoc.php index 678e3a1e9c..b556021478 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-and-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-and-not-phpdoc.php @@ -4,36 +4,28 @@ class BooleanAnd { - - /** - * @param string|\DateTimeInterface $time - * @return string - */ - function doFoo($time): void - { - if (!\is_string($time) && (!$time instanceof \DateTimeInterface)) { - - } - } - - /** - * @param object $object - */ - public function doBar(self $self, $object): void - { - if ($self && rand(0, 1)) { - - } - if ($object && rand(0, 1)) { - - } - if (rand(0, 1) && $self) { - - } - if (rand(0, 1) && $object) { - - } - } - - + /** + * @param string|\DateTimeInterface $time + * @return string + */ + public function doFoo($time): void + { + if (!\is_string($time) && (!$time instanceof \DateTimeInterface)) { + } + } + + /** + * @param object $object + */ + public function doBar(self $self, $object): void + { + if ($self && rand(0, 1)) { + } + if ($object && rand(0, 1)) { + } + if (rand(0, 1) && $self) { + } + if (rand(0, 1) && $object) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-and-treat-phpdoc-types-regression.php b/tests/PHPStan/Rules/Comparison/data/boolean-and-treat-phpdoc-types-regression.php index a71261da51..0383879951 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-and-treat-phpdoc-types-regression.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-and-treat-phpdoc-types-regression.php @@ -4,30 +4,27 @@ class Foo { - - public function isDebugMode(): bool - { - return true; - } - - public function something(): void - { - - } - - public function doFoo(): void - { - $isDev = $this->isDebugMode(); - $used = false; - - while (true) { - if ($isDev && $used) { - return; - } - - $used = true; - $this->something(); - } - } - + public function isDebugMode(): bool + { + return true; + } + + public function something(): void + { + } + + public function doFoo(): void + { + $isDev = $this->isDebugMode(); + $used = false; + + while (true) { + if ($isDev && $used) { + return; + } + + $used = true; + $this->something(); + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-and.php b/tests/PHPStan/Rules/Comparison/data/boolean-and.php index f76f429f02..817cf9344f 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-and.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-and.php @@ -4,163 +4,140 @@ class BooleanAnd { - - public function doFoo(int $i, bool $j, \stdClass $std, ?\stdClass $nullableStd) - { - if ($i && $j) { - - } - - $one = 1; - if ($one && $i) { - - } - - if ($i && $std) { - - } - - $zero = 0; - if ($zero && $i) { - - } - if ($i && $zero) { - - } - if ($one === 0 && $one) { - - } - if ($one === 1 && $one) { - - } - if ($nullableStd && $nullableStd) { - - } - if ($nullableStd !== null && $nullableStd) { - - } - } - - /** - * @param Foo|Bar $union - * @param Lorem&Ipsum $intersection - */ - public function checkUnionAndIntersection($union, $intersection) - { - if ($union instanceof Foo && $union instanceof Bar) { - - } - - if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { - - } - - if ($union instanceof Foo || $union instanceof Bar) { - - } elseif ($union instanceof Foo && doFoo()) { - - } - - if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { - - } elseif ($intersection instanceof Lorem && doFoo()) { - - } - } - + public function doFoo(int $i, bool $j, \stdClass $std, ?\stdClass $nullableStd) + { + if ($i && $j) { + } + + $one = 1; + if ($one && $i) { + } + + if ($i && $std) { + } + + $zero = 0; + if ($zero && $i) { + } + if ($i && $zero) { + } + if ($one === 0 && $one) { + } + if ($one === 1 && $one) { + } + if ($nullableStd && $nullableStd) { + } + if ($nullableStd !== null && $nullableStd) { + } + } + + /** + * @param Foo|Bar $union + * @param Lorem&Ipsum $intersection + */ + public function checkUnionAndIntersection($union, $intersection) + { + if ($union instanceof Foo && $union instanceof Bar) { + } + + if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { + } + + if ($union instanceof Foo || $union instanceof Bar) { + } elseif ($union instanceof Foo && doFoo()) { + } + + if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { + } elseif ($intersection instanceof Lorem && doFoo()) { + } + } } class NonNullablePropertiesShouldNotReportError { + /** @var self */ + private $foo; - /** @var self */ - private $foo; - - /** @var self */ - private $bar; - - public function doFoo() - { - if ($this->foo !== null && $this->bar !== null) { - - } - } + /** @var self */ + private $bar; + public function doFoo() + { + if ($this->foo !== null && $this->bar !== null) { + } + } } class StringInIsset { - - public function doFoo(string $s, string $t) - { - if (isset($s[1]) && isset($t[1])) { - - } - } - + public function doFoo(string $s, string $t) + { + if (isset($s[1]) && isset($t[1])) { + } + } } class IssetBug { - - public function doFoo(string $alias, array $options = []) - { - list($name, $p) = explode('.', $alias); - if (isset($options['c']) && !\strpos($options['c'], '\\')) { - // ... - } - - if (!isset($options['c']) && \strpos($p, 'X') === 0) { - // ? - } - } - + public function doFoo(string $alias, array $options = []) + { + list($name, $p) = explode('.', $alias); + if (isset($options['c']) && !\strpos($options['c'], '\\')) { + // ... + } + + if (!isset($options['c']) && \strpos($p, 'X') === 0) { + // ? + } + } } class IntegerRangeType { - - public function doFoo(int $i, float $f) - { - if ($i < 3 && $i > 5) { // can never happen - } - - if ($f > 0 && $f < 1) { - } - } - + public function doFoo(int $i, float $f) + { + if ($i < 3 && $i > 5) { // can never happen + } + + if ($f > 0 && $f < 1) { + } + } } class AndInIfCondition { - public function andInIfCondition($mixed, int $i): void - { - if (!$mixed) { - if ($mixed && $i) { - } - if ($i && $mixed) { - } - } - if ($mixed) { - if ($mixed && $i) { - } - if ($i && $mixed) { - } - } - } + public function andInIfCondition($mixed, int $i): void + { + if (!$mixed) { + if ($mixed && $i) { + } + if ($i && $mixed) { + } + } + if ($mixed) { + if ($mixed && $i) { + } + if ($i && $mixed) { + } + } + } } -function getMaybeArray() : ?array { - if (rand(0, 1)) { return [1, 2, 3]; } - return null; +function getMaybeArray(): ?array +{ + if (rand(0, 1)) { + return [1, 2, 3]; + } + return null; } -function bug1924() { - $arr = [ - 'a' => getMaybeArray(), - 'b' => getMaybeArray(), - ]; +function bug1924() +{ + $arr = [ + 'a' => getMaybeArray(), + 'b' => getMaybeArray(), + ]; - if (isset($arr['a']) && isset($arr['b'])) { - } + if (isset($arr['a']) && isset($arr['b'])) { + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-not-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/boolean-not-not-phpdoc.php index 832eaffad9..18c499c42d 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-not-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-not-not-phpdoc.php @@ -4,22 +4,17 @@ class BooleanNot { + /** + * @param object $object + */ + public function doFoo( + self $self, + $object + ) { + if (!$self) { + } - /** - * @param object $object - */ - public function doFoo( - self $self, - $object - ) - { - if (!$self) { - - } - - if (!$object) { - - } - } - + if (!$object) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-not.php b/tests/PHPStan/Rules/Comparison/data/boolean-not.php index d5b9b781e1..119f573c23 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-not.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-not.php @@ -4,51 +4,45 @@ class BooleanNot { - - public function doFoo(int $i, \stdClass $std) - { - if (!$i) { - - } - if (!$std) { - - } - - $zero = 0; - if (!$zero) { - - } - } - - /** - * @param int $i - * @param \stdClass $std - * @param Foo|Bar $union - * @param Lorem&Ipsum $intersection - */ - public function elseifs(int $i, \stdClass $std, $union, $intersection) - { - if ($i) { - - } elseif (!$std) { - - } - } - - public function ternary(\stdClass $std) - { - !$std ? 'foo' : 'bar'; - } - - public function nestedIfConditions($mixed): void - { - if (!$mixed) { - if (!$mixed) { - } - } - if ($mixed) { - if (!$mixed) { - } - } - } + public function doFoo(int $i, \stdClass $std) + { + if (!$i) { + } + if (!$std) { + } + + $zero = 0; + if (!$zero) { + } + } + + /** + * @param int $i + * @param \stdClass $std + * @param Foo|Bar $union + * @param Lorem&Ipsum $intersection + */ + public function elseifs(int $i, \stdClass $std, $union, $intersection) + { + if ($i) { + } elseif (!$std) { + } + } + + public function ternary(\stdClass $std) + { + !$std ? 'foo' : 'bar'; + } + + public function nestedIfConditions($mixed): void + { + if (!$mixed) { + if (!$mixed) { + } + } + if ($mixed) { + if (!$mixed) { + } + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-or-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/boolean-or-not-phpdoc.php index 92848ef69b..5c0737dfac 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-or-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-or-not-phpdoc.php @@ -4,36 +4,28 @@ class BooleanOr { - - /** - * @param string|\DateTimeInterface $time - * @return string - */ - function doFoo($time): void - { - if (!(\is_string($time) || ($time instanceof \DateTimeInterface))) { - - } - } - - /** - * @param object $object - */ - public function doBar(self $self, $object): void - { - if ($self || rand(0, 1)) { - - } - if ($object || rand(0, 1)) { - - } - if (rand(0, 1) || $self) { - - } - if (rand(0, 1) || $object) { - - } - } - - + /** + * @param string|\DateTimeInterface $time + * @return string + */ + public function doFoo($time): void + { + if (!(\is_string($time) || ($time instanceof \DateTimeInterface))) { + } + } + + /** + * @param object $object + */ + public function doBar(self $self, $object): void + { + if ($self || rand(0, 1)) { + } + if ($object || rand(0, 1)) { + } + if (rand(0, 1) || $self) { + } + if (rand(0, 1) || $object) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-or-treat-phpdoc-types-regression.php b/tests/PHPStan/Rules/Comparison/data/boolean-or-treat-phpdoc-types-regression.php index 49bf5497e3..6e1b1ebbb1 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-or-treat-phpdoc-types-regression.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-or-treat-phpdoc-types-regression.php @@ -4,61 +4,60 @@ class Foo { - - /** @var int */ - protected $index = 0; - - /** @var string[][] */ - protected $data = [ - 0 => ['type' => 'id', 'value' => 'foo'], - 1 => ['type' => 'special', 'value' => '.'], - 2 => ['type' => 'id', 'value' => 'bar'], - 3 => ['type' => 'special', 'value' => ';'], - ]; - - protected function next(): void - { - $this->index = $this->index + 1; - } - - protected function check(string $type, ?string $value = null): bool { - return ($this->type() === $type) && (($value === null) || ($this->value() === $value)); - } - - protected function type(): string - { - return $this->data[$this->index]['type']; - } - - protected function value(): string - { - return $this->data[$this->index]['value']; - } - - public function separatedName(): string - { - $name = ''; - $previousType = null; - $separator = '.'; - - $currentValue = $this->value(); - - while ((($this->check('special', $separator)) || ($this->check('id'))) && - (($previousType === null) || ($this->type() !== $previousType)) && - (($previousType !== null) || ($currentValue !== $separator)) - ) { - $name .= $currentValue; - $previousType = $this->type(); - - $this->next(); - $currentValue = $this->value(); - } - - if (($previousType === null) || ($previousType !== 'id')) { - throw new \RuntimeException(); - } - - return $name; - } - + /** @var int */ + protected $index = 0; + + /** @var string[][] */ + protected $data = [ + 0 => ['type' => 'id', 'value' => 'foo'], + 1 => ['type' => 'special', 'value' => '.'], + 2 => ['type' => 'id', 'value' => 'bar'], + 3 => ['type' => 'special', 'value' => ';'], + ]; + + protected function next(): void + { + $this->index = $this->index + 1; + } + + protected function check(string $type, ?string $value = null): bool + { + return ($this->type() === $type) && (($value === null) || ($this->value() === $value)); + } + + protected function type(): string + { + return $this->data[$this->index]['type']; + } + + protected function value(): string + { + return $this->data[$this->index]['value']; + } + + public function separatedName(): string + { + $name = ''; + $previousType = null; + $separator = '.'; + + $currentValue = $this->value(); + + while ((($this->check('special', $separator)) || ($this->check('id'))) && + (($previousType === null) || ($this->type() !== $previousType)) && + (($previousType !== null) || ($currentValue !== $separator)) + ) { + $name .= $currentValue; + $previousType = $this->type(); + + $this->next(); + $currentValue = $this->value(); + } + + if (($previousType === null) || ($previousType !== 'id')) { + throw new \RuntimeException(); + } + + return $name; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/boolean-or.php b/tests/PHPStan/Rules/Comparison/data/boolean-or.php index 066a334314..db91247a6c 100644 --- a/tests/PHPStan/Rules/Comparison/data/boolean-or.php +++ b/tests/PHPStan/Rules/Comparison/data/boolean-or.php @@ -4,86 +4,71 @@ class BooleanOr { - - public function doFoo(int $i, bool $j, \stdClass $std, ?\stdClass $nullableStd) - { - if ($i || $j) { - - } - - $one = 1; - if ($one || $i) { - - } - - if ($i || $std) { - - } - - $zero = 0; - if ($zero || $i) { - - } - if ($i || $zero) { - - } - if ($one === 0 || $one) { - - } - if ($one === 1 || $one) { - - } - if ($nullableStd || $nullableStd) { - - } - if ($nullableStd !== null || $nullableStd) { - - } - } - - /** - * @param Foo|Bar $union - * @param Lorem&Ipsum $intersection - */ - public function checkUnionAndIntersection($union, $intersection) - { - if ($union instanceof Foo || $union instanceof Bar) { - - } - - if ($intersection instanceof Lorem || $intersection instanceof Ipsum) { - - } - } - - public function directorySeparator() - { - if (DIRECTORY_SEPARATOR === '/' || DIRECTORY_SEPARATOR === '\\') { - - } - - if ('/' === DIRECTORY_SEPARATOR || '\\' === DIRECTORY_SEPARATOR) { - - } - } - + public function doFoo(int $i, bool $j, \stdClass $std, ?\stdClass $nullableStd) + { + if ($i || $j) { + } + + $one = 1; + if ($one || $i) { + } + + if ($i || $std) { + } + + $zero = 0; + if ($zero || $i) { + } + if ($i || $zero) { + } + if ($one === 0 || $one) { + } + if ($one === 1 || $one) { + } + if ($nullableStd || $nullableStd) { + } + if ($nullableStd !== null || $nullableStd) { + } + } + + /** + * @param Foo|Bar $union + * @param Lorem&Ipsum $intersection + */ + public function checkUnionAndIntersection($union, $intersection) + { + if ($union instanceof Foo || $union instanceof Bar) { + } + + if ($intersection instanceof Lorem || $intersection instanceof Ipsum) { + } + } + + public function directorySeparator() + { + if (DIRECTORY_SEPARATOR === '/' || DIRECTORY_SEPARATOR === '\\') { + } + + if ('/' === DIRECTORY_SEPARATOR || '\\' === DIRECTORY_SEPARATOR) { + } + } } class OrInIfCondition { - public function orInIfCondition($mixed, int $i): void - { - if (!$mixed) { - if ($mixed || $i) { - } - if ($i || $mixed) { - } - } - if ($mixed) { - if ($mixed || $i) { - } - if ($i || $mixed) { - } - } - } + public function orInIfCondition($mixed, int $i): void + { + if (!$mixed) { + if ($mixed || $i) { + } + if ($i || $mixed) { + } + } + if ($mixed) { + if ($mixed || $i) { + } + if ($i || $mixed) { + } + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-1613.php b/tests/PHPStan/Rules/Comparison/data/bug-1613.php index 563f7d93de..8ae21e55fa 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-1613.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-1613.php @@ -4,11 +4,11 @@ class TestClass { - public function test(string $index) - { - $array = [ - "123" => "test" - ]; - return array_key_exists($index, $array); - } + public function test(string $index) + { + $array = [ + "123" => "test" + ]; + return array_key_exists($index, $array); + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-1707.php b/tests/PHPStan/Rules/Comparison/data/bug-1707.php index bc7a527f01..d40a660c7c 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-1707.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-1707.php @@ -4,20 +4,19 @@ class Test { - public function foo(): void - { - $values = ['a' => 1, 'b' => 2]; - $keys = ['a', 'b', 'c', 'd']; + public function foo(): void + { + $values = ['a' => 1, 'b' => 2]; + $keys = ['a', 'b', 'c', 'd']; - foreach ($keys as $key) { - if(array_key_exists($key, $values)){ - unset($values[$key]); - } + foreach ($keys as $key) { + if (array_key_exists($key, $values)) { + unset($values[$key]); + } - if(0 === \count($values)) { - break; - } - } - - } + if (0 === \count($values)) { + break; + } + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-1860.php b/tests/PHPStan/Rules/Comparison/data/bug-1860.php index beb2714bbf..15c7ca1094 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-1860.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-1860.php @@ -4,21 +4,18 @@ class Foo { + public function doFoo(): string + { + } - public function doFoo(): string - { - - } - - public function doBar(): void - { - if ($this->doFoo() === null) { - echo 'foo'; - } - - if ($this->doFoo() !== null) { - echo 'bar'; - } - } + public function doBar(): void + { + if ($this->doFoo() === null) { + echo 'foo'; + } + if ($this->doFoo() !== null) { + echo 'bar'; + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2220.php b/tests/PHPStan/Rules/Comparison/data/bug-2220.php index 1db5e4fbcc..6023194c61 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2220.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2220.php @@ -4,23 +4,28 @@ class HelloWorld { - /** - * @var string - */ - private $privateModule; + /** + * @var string + */ + private $privateModule; - public function sayHello(): void - { - $resource = $this->getResource(); + public function sayHello(): void + { + $resource = $this->getResource(); - if ($resource === "{$this->privateModule}:abcdef") { - $this->abc(); - } elseif ($resource === "{$this->privateModule}:xyz") { - $this->abc(); - } - } + if ($resource === "{$this->privateModule}:abcdef") { + $this->abc(); + } elseif ($resource === "{$this->privateModule}:xyz") { + $this->abc(); + } + } - private function abc(): void {} + private function abc(): void + { + } - private function getResource(): string { return 'string'; } + private function getResource(): string + { + return 'string'; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2550.php b/tests/PHPStan/Rules/Comparison/data/bug-2550.php index a01b8834fc..875a336dcf 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2550.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2550.php @@ -6,16 +6,14 @@ class Foo { + public function doFoo() + { + $apples = [1, 'a']; - public function doFoo() - { - $apples = [1, 'a']; - - foreach($apples as $apple) { - if (is_numeric($apple)) { - assertType('1', $apple); - } - } - } - + foreach ($apples as $apple) { + if (is_numeric($apple)) { + assertType('1', $apple); + } + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2648-namespace-rule.php b/tests/PHPStan/Rules/Comparison/data/bug-2648-namespace-rule.php index c969d7e9c2..b5cbe40c19 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2648-namespace-rule.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2648-namespace-rule.php @@ -6,11 +6,11 @@ $foo = $_GET['bar']; if (count($foo) > 0) { - foreach ($foo as $key => $value) { - unset($foo[$key]); - } + foreach ($foo as $key => $value) { + unset($foo[$key]); + } - if(count($foo) > 0) { - // $foo is actually empty now - } + if (count($foo) > 0) { + // $foo is actually empty now + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2648-rule.php b/tests/PHPStan/Rules/Comparison/data/bug-2648-rule.php index ebd83be1be..f97ea7a82d 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2648-rule.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2648-rule.php @@ -4,11 +4,11 @@ $foo = $_GET['bar']; if (count($foo) > 0) { - foreach ($foo as $key => $value) { - unset($foo[$key]); - } + foreach ($foo as $key => $value) { + unset($foo[$key]); + } - if(count($foo) > 0) { - // $foo is actually empty now - } + if (count($foo) > 0) { + // $foo is actually empty now + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2675.php b/tests/PHPStan/Rules/Comparison/data/bug-2675.php index ec07f08eeb..f0eefabf97 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2675.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2675.php @@ -1,21 +1,25 @@ - str_repeat('a', rand(1, 10)), - ]; - } + // creation of unpredictable values at 'type' key + for ($i = 0; $i < 3; $i++) { + $list[] = [ + 'type' => str_repeat('a', rand(1, 10)), + ]; + } - $list[] = [ - 'type' => 'x', - ]; + $list[] = [ + 'type' => 'x', + ]; - foreach ($list as $item) { - if (in_array($item['type'], ['aaa', 'aaaa'], TRUE)) { - echo 'OK'; - } - } + foreach ($list as $item) { + if (in_array($item['type'], ['aaa', 'aaaa'], true)) { + echo 'OK'; + } + } }; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2835.php b/tests/PHPStan/Rules/Comparison/data/bug-2835.php index 14cfa87d80..58bd24ce4f 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-2835.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-2835.php @@ -4,26 +4,25 @@ class Foo { - - /** - * @param array $tokens - * @return bool - */ - public function doFoo(array $tokens): bool { - $i = 0; - while (isset($tokens[$i])) { - if ($tokens[$i]['code'] !== 1) { - $i++; - continue; - } - $i++; - if ($tokens[$i]['code'] !== 2) { - $i++; - continue; - } - return true; - } - return false; - } - + /** + * @param array $tokens + * @return bool + */ + public function doFoo(array $tokens): bool + { + $i = 0; + while (isset($tokens[$i])) { + if ($tokens[$i]['code'] !== 1) { + $i++; + continue; + } + $i++; + if ($tokens[$i]['code'] !== 2) { + $i++; + continue; + } + return true; + } + return false; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3357.php b/tests/PHPStan/Rules/Comparison/data/bug-3357.php index 19ba5a05fb..05c06e0685 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-3357.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-3357.php @@ -3,19 +3,19 @@ namespace Bug3357; function (): void { - /** - * @var array{foo?: string} - */ - $foo = []; + /** + * @var array{foo?: string} + */ + $foo = []; - assert($foo === []); + assert($foo === []); }; function (): void { - /** - * @var array{foo: string, bar?: string} - */ - $foo = ['foo' => '']; + /** + * @var array{foo: string, bar?: string} + */ + $foo = ['foo' => '']; - assert($foo === ['foo' => '']); + assert($foo === ['foo' => '']); }; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3544.php b/tests/PHPStan/Rules/Comparison/data/bug-3544.php index 3b3184b494..46985bca28 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-3544.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-3544.php @@ -1,22 +1,24 @@ - $input - */ - public function foo(array $input): void - { - if( ! array_key_exists( 'foo', $input)) { - throw new \LogicException(); - } + /** + * @param array $input + */ + public function foo(array $input): void + { + if (! array_key_exists('foo', $input)) { + throw new \LogicException(); + } - unset($input['foo']); + unset($input['foo']); - if($input === []) { - echo 'hello'; - } - } + if ($input === []) { + echo 'hello'; + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3994.php b/tests/PHPStan/Rules/Comparison/data/bug-3994.php index 1ead0bb9f3..a2953c4034 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-3994.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-3994.php @@ -4,11 +4,9 @@ class HelloWorld { - - public function split(string $str): void - { - $parts = explode(".", $str); - assert(count($parts) === 4); - } - + public function split(string $str): void + { + $parts = explode(".", $str); + assert(count($parts) === 4); + } } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4043.php b/tests/PHPStan/Rules/Comparison/data/bug-4043.php index 96253b26f7..d2b399be65 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-4043.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-4043.php @@ -4,50 +4,46 @@ function number(int $diff, string $s): string { - } function (): void { - $uppedDate = new \DateTime('now -1s'); - $diff = $uppedDate->diff(new \DateTime()); - assert($diff !== false); - - $mdiff = (int)$diff->format('%m'); - $ddiff = (int)$diff->format('%d'); - $hdiff = (int)$diff->format('%H'); - $idiff = (int)$diff->format('%i'); - - $m = $mdiff ? number($mdiff, 'месяц|месяца|месяцев') : ''; - $d = $ddiff ? number($ddiff, 'день|дня|дней') : ''; - $h = $hdiff ? number($hdiff, 'час|часа|часов') : ''; - $i = $idiff ? number($idiff, 'минута|минуты|минут') : ''; - - $content = array_filter([$m, $d, $h, $i]); - if ($content) { - echo 1; - } + $uppedDate = new \DateTime('now -1s'); + $diff = $uppedDate->diff(new \DateTime()); + assert($diff !== false); + + $mdiff = (int)$diff->format('%m'); + $ddiff = (int)$diff->format('%d'); + $hdiff = (int)$diff->format('%H'); + $idiff = (int)$diff->format('%i'); + + $m = $mdiff ? number($mdiff, 'месяц|месяца|месяцев') : ''; + $d = $ddiff ? number($ddiff, 'день|дня|дней') : ''; + $h = $hdiff ? number($hdiff, 'час|часа|часов') : ''; + $i = $idiff ? number($idiff, 'минута|минуты|минут') : ''; + + $content = array_filter([$m, $d, $h, $i]); + if ($content) { + echo 1; + } }; function (): void { - $a = []; - if (rand(0, 1)) { - $a[] = 1; - } - if ($a) { - - } + $a = []; + if (rand(0, 1)) { + $a[] = 1; + } + if ($a) { + } }; function (): void { - $a = []; - if ($a) { - - } + $a = []; + if ($a) { + } }; function (): void { - $a = [1]; - if ($a) { - - } + $a = [1]; + if ($a) { + } }; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4657.php b/tests/PHPStan/Rules/Comparison/data/bug-4657.php index f01f87be15..111954a1a9 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-4657.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-4657.php @@ -5,11 +5,11 @@ use DateTime; function (): void { - $value = null; - $callback = function () use (&$value) : void { - $value = new DateTime(); - }; - $callback(); + $value = null; + $callback = function () use (&$value): void { + $value = new DateTime(); + }; + $callback(); - assert(!is_null($value)); + assert(!is_null($value)); }; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-composer-dependent-variables.php b/tests/PHPStan/Rules/Comparison/data/bug-composer-dependent-variables.php index e289c029b5..b15a7486ec 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-composer-dependent-variables.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-composer-dependent-variables.php @@ -4,19 +4,19 @@ function removeSubNode($mainNode, $name) { - /** @var mixed $decoded */ - $decoded = doFoo(); + /** @var mixed $decoded */ + $decoded = doFoo(); - if (empty($decoded[$mainNode])) { - return true; - } + if (empty($decoded[$mainNode])) { + return true; + } - $subName = null; - if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) { - list($name, $subName) = explode('.', $name, 2); - } + $subName = null; + if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) { + list($name, $subName) = explode('.', $name, 2); + } - if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { - return true; - } + if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { + return true; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/check-type-function-call-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/check-type-function-call-not-phpdoc.php index 3321e8929b..265d387770 100644 --- a/tests/PHPStan/Rules/Comparison/data/check-type-function-call-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/check-type-function-call-not-phpdoc.php @@ -4,21 +4,16 @@ class Foo { - - /** - * @param int $phpDocInteger - */ - public function doFoo( - int $realInteger, - $phpDocInteger - ) - { - if (is_int($realInteger)) { - - } - if (is_int($phpDocInteger)) { - - } - } - + /** + * @param int $phpDocInteger + */ + public function doFoo( + int $realInteger, + $phpDocInteger + ) { + if (is_int($realInteger)) { + } + if (is_int($phpDocInteger)) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php index cf6bcad137..72f2be88d0 100644 --- a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php +++ b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php @@ -4,844 +4,736 @@ class Foo { - - /** - * @param int $integer - * @param int|string $integerOrString - * @param string $string - * @param callable $callable - * @param array $array - * @param array $arrayOfInt - */ - public function doFoo( - int $integer, - $integerOrString, - string $string, - callable $callable, - array $array, - array $arrayOfInt - ) - { - if (is_int($integer)) { // always true - - } - if (is_int($integerOrString)) { // fine - - } - if (is_int($string)) { // always false - - } - $className = 'Foo'; - if (is_a($className, \Throwable::class, true)) { // should be fine - - } - if (is_array($callable)) { - - } - if (is_callable($array)) { - - } - if (is_callable($arrayOfInt)) { - - } - - assert($integer instanceof \stdClass); - } - + /** + * @param int $integer + * @param int|string $integerOrString + * @param string $string + * @param callable $callable + * @param array $array + * @param array $arrayOfInt + */ + public function doFoo( + int $integer, + $integerOrString, + string $string, + callable $callable, + array $array, + array $arrayOfInt + ) { + if (is_int($integer)) { // always true + } + if (is_int($integerOrString)) { // fine + } + if (is_int($string)) { // always false + } + $className = 'Foo'; + if (is_a($className, \Throwable::class, true)) { // should be fine + } + if (is_array($callable)) { + } + if (is_callable($array)) { + } + if (is_callable($arrayOfInt)) { + } + + assert($integer instanceof \stdClass); + } } class TypeCheckInSwitch { - - public function doFoo($value) - { - switch (true) { - case is_int($value): - case is_float($value): - break; - } - } - + public function doFoo($value) + { + switch (true) { + case is_int($value): + case is_float($value): + break; + } + } } class StringIsNotAlwaysCallable { - - public function doFoo(string $s) - { - if (is_callable($s)) { - $s(); - } - } - + public function doFoo(string $s) + { + if (is_callable($s)) { + $s(); + } + } } class CheckIsCallable { - - public function test() - { - if (is_callable('date')) { - - } - if (is_callable('nonexistentFunction')) { - - } - } - + public function test() + { + if (is_callable('date')) { + } + if (is_callable('nonexistentFunction')) { + } + } } class IsNumeric { - - public function test(string $str, float $float) - { - if (is_numeric($str)) { - - } - if (is_numeric('123')) { - - } - if (is_numeric('blabla')) { - - } - - $isNumeric = $float; - $maybeNumeric = $float; - if (doFoo()) { - $isNumeric = 123; - $maybeNumeric = 123; - } else { - $maybeNumeric = $str; - } - - if (is_numeric($isNumeric)) { - - } - if ($maybeNumeric) { - - } - } - + public function test(string $str, float $float) + { + if (is_numeric($str)) { + } + if (is_numeric('123')) { + } + if (is_numeric('blabla')) { + } + + $isNumeric = $float; + $maybeNumeric = $float; + if (doFoo()) { + $isNumeric = 123; + $maybeNumeric = 123; + } else { + $maybeNumeric = $str; + } + + if (is_numeric($isNumeric)) { + } + if ($maybeNumeric) { + } + } } class CheckDefaultArrayKeys { - - /** - * @param string[] $array - */ - public function doFoo(array $array) - { - foreach ($array as $key => $val) { - if (is_int($key)) { - return; - } - if (is_string($key)) { - return; - } - } - } - + /** + * @param string[] $array + */ + public function doFoo(array $array) + { + foreach ($array as $key => $val) { + if (is_int($key)) { + return; + } + if (is_string($key)) { + return; + } + } + } } class IsSubclassOfTest { - - public function doFoo( - string $string, - ?string $nullableString - ) - { - is_subclass_of($string, $nullableString); - is_subclass_of($nullableString, $string); - is_subclass_of($nullableString, 'Foo'); - } - + public function doFoo( + string $string, + ?string $nullableString + ) { + is_subclass_of($string, $nullableString); + is_subclass_of($nullableString, $string); + is_subclass_of($nullableString, 'Foo'); + } } class DefinedConstant { - - public function doFoo() - { - if (defined('DEFINITELY_DOES_NOT_EXIST')) { - - } - if (!defined('ANOTHER_DEFINITELY_DOES_NOT_EXIST')) { - - } - - $foo = new Foo(); - if (method_exists($foo, 'test')) { - - } - if (method_exists($foo, 'doFoo')) { - - } - } - + public function doFoo() + { + if (defined('DEFINITELY_DOES_NOT_EXIST')) { + } + if (!defined('ANOTHER_DEFINITELY_DOES_NOT_EXIST')) { + } + + $foo = new Foo(); + if (method_exists($foo, 'test')) { + } + if (method_exists($foo, 'doFoo')) { + } + } } final class FinalClassWithMethodExists { - - public function doFoo() - { - if (method_exists($this, 'doFoo')) { - - } - if (method_exists($this, 'doBar')) { - - } - } - + public function doFoo() + { + if (method_exists($this, 'doFoo')) { + } + if (method_exists($this, 'doBar')) { + } + } } final class FinalClassWithPropertyExists { - - /** @var int */ - private $fooProperty; - - public function doFoo() - { - if (property_exists($this, 'fooProperty')) { - - } - if (property_exists($this, 'barProperty')) { - - } - } - + /** @var int */ + private $fooProperty; + + public function doFoo() + { + if (property_exists($this, 'fooProperty')) { + } + if (property_exists($this, 'barProperty')) { + } + } } class InArray { - - public function doFoo( - string $s, - int $i, - $mixed - ) - { - if (in_array('foo', $mixed, true)) { - - } - - if (in_array($s, ['foo' ,'bar'], true)) { - - } - if (in_array($i, ['foo', 'bar'], true)) { - - } - - $fooOrBar = 'foo'; - if (rand(0, 1) === 0) { - $fooOrBar = 'bar'; - } - - if (in_array($fooOrBar, ['baz', 'lorem'], true)) { - - } - - if (in_array($fooOrBar, ['foo', 'bar'], true)) { - - } - - if (in_array('foo', ['foo'], true)) { - - } - - if (in_array('foo', ['foo', 'bar'], true)) { - - } - - $arr = ['foo', 'bar']; - if (rand(0, 1) === 0) { - $arr = false; - } - - if (in_array('foo', $arr, true)) { - - } - } - - /** - * @param string $s - * @param string[] $strings - */ - public function doBar( - string $s, - array $strings - ) - { - if (in_array($s, $strings, true)) { - - } - } - - /** - * @param string $s - * @param array $mixedArray - * @param (string|float)[] $stringsOrFloats - */ - public function doBaz( - string $s, - array $mixedArray, - array $stringsOrFloats - ) - { - if (in_array($s, $mixedArray, true)) { - - } - if (in_array('s', $mixedArray, true)) { - - } - if (in_array($s, $stringsOrFloats, true)) { - - } - if (in_array('s', $stringsOrFloats, true)) { - - } - } - - public function checkByCondition(int $x) - { - $data = []; - if ($x === 0) { - $data[] = 'foo'; - } - - if (in_array('foo', $data, true)) { - - } - - if (in_array('bar', $data, true)) { - - } - } - - public function checkByConditionWithNonEmpty(int $x) - { - $data = ['bar']; - if ($x === 0) { - $data[] = 'foo'; - } - - if (in_array('foo', $data, true)) { - - } - - if (in_array('baz', $data, true)) { - - } - } - - public function checkWithEmpty() - { - if (in_array('foo', [], true)) { - - } - } - + public function doFoo( + string $s, + int $i, + $mixed + ) { + if (in_array('foo', $mixed, true)) { + } + + if (in_array($s, ['foo' ,'bar'], true)) { + } + if (in_array($i, ['foo', 'bar'], true)) { + } + + $fooOrBar = 'foo'; + if (rand(0, 1) === 0) { + $fooOrBar = 'bar'; + } + + if (in_array($fooOrBar, ['baz', 'lorem'], true)) { + } + + if (in_array($fooOrBar, ['foo', 'bar'], true)) { + } + + if (in_array('foo', ['foo'], true)) { + } + + if (in_array('foo', ['foo', 'bar'], true)) { + } + + $arr = ['foo', 'bar']; + if (rand(0, 1) === 0) { + $arr = false; + } + + if (in_array('foo', $arr, true)) { + } + } + + /** + * @param string $s + * @param string[] $strings + */ + public function doBar( + string $s, + array $strings + ) { + if (in_array($s, $strings, true)) { + } + } + + /** + * @param string $s + * @param array $mixedArray + * @param (string|float)[] $stringsOrFloats + */ + public function doBaz( + string $s, + array $mixedArray, + array $stringsOrFloats + ) { + if (in_array($s, $mixedArray, true)) { + } + if (in_array('s', $mixedArray, true)) { + } + if (in_array($s, $stringsOrFloats, true)) { + } + if (in_array('s', $stringsOrFloats, true)) { + } + } + + public function checkByCondition(int $x) + { + $data = []; + if ($x === 0) { + $data[] = 'foo'; + } + + if (in_array('foo', $data, true)) { + } + + if (in_array('bar', $data, true)) { + } + } + + public function checkByConditionWithNonEmpty(int $x) + { + $data = ['bar']; + if ($x === 0) { + $data[] = 'foo'; + } + + if (in_array('foo', $data, true)) { + } + + if (in_array('baz', $data, true)) { + } + } + + public function checkWithEmpty() + { + if (in_array('foo', [], true)) { + } + } } class ArrayKeyExists { - - public function doFoo(string $s) - { - $a = ['a' => 1]; - if (rand(0, 1) === 1) { - $a['b'] = 2; - } - - if (array_key_exists('a', $a)) { - - } - if (array_key_exists('b', $a)) { - - } - if (array_key_exists('c', $a)) { - - } - if (array_key_exists($s, $a)) { - - } - - /** @var array $stringKeys */ - $stringKeys = doFoo(); - if (array_key_exists($s, $stringKeys)) { - - } - - $b = ['a' => 1, 'b' => 2, 'c' => 3]; - if (array_key_exists($s, $b)) { - - } - - $appleModels = [ - 'iPhone1,1' => 'iPhone', - 'iPhone1,2' => 'iPhone 3G', - 'iPhone2,1' => 'iPhone 3GS', - 'iPhone3,1' => 'iPhone 4', - 'iPhone3,2' => 'iPhone 4', - 'iPhone3,3' => 'iPhone 4', - 'iPhone4,1' => 'iPhone 4S', - 'iPhone5,1' => 'iPhone 5', - 'iPhone5,2' => 'iPhone 5', - 'iPhone5,3' => 'iPhone 5C', - 'iPhone5,4' => 'iPhone 5C', - 'iPhone6,1' => 'iPhone 5S', - 'iPhone6,2' => 'iPhone 5S', - 'iPhone7,1' => 'iPhone 6 Plus', - 'iPhone7,2' => 'iPhone 6', - 'iPhone8,1' => 'iPhone 6S', - 'iPhone8,2' => 'iPhone 6S Plus', - 'iPhone8,4' => 'iPhone SE', - 'iPhone9,1' => 'iPhone 7', - 'iPhone9,2' => 'iPhone 7 Plus', - 'iPhone9,3' => 'iPhone 7', - 'iPhone9,4' => 'iPhone 7 Plus', - 'iPhone10,1' => 'iPhone 8', - 'iPhone10,2' => 'iPhone 8 Plus', - 'iPhone10,3' => 'iPhone X', - 'iPhone10,4' => 'iPhone 8', - 'iPhone10,5' => 'iPhone 8 Plus', - 'iPhone10,6' => 'iPhone X', - 'iPad1,1' => 'iPad', - 'iPad2,1' => 'iPad 2', - 'iPad2,2' => 'iPad 2', - 'iPad2,3' => 'iPad 2', - 'iPad2,4' => 'iPad 2', - 'iPad2,5' => 'iPad Mini', - 'iPad2,6' => 'iPad Mini', - 'iPad2,7' => 'iPad Mini', - 'iPad3,1' => 'iPad 3', - 'iPad3,2' => 'iPad 3', - 'iPad3,3' => 'iPad 3', - 'iPad3,4' => 'iPad 4', - 'iPad3,5' => 'iPad 4', - 'iPad3,6' => 'iPad 4', - 'iPad4,1' => 'iPad Air', - 'iPad4,2' => 'iPad Air', - 'iPad4,3' => 'iPad Air', - 'iPad4,4' => 'iPad Mini 2', - 'iPad4,5' => 'iPad Mini 2', - 'iPad4,6' => 'iPad Mini 2', - 'iPad4,7' => 'iPad Mini 3', - 'iPad4,8' => 'iPad Mini 3', - 'iPad4,9' => 'iPad Mini 3', - 'iPad5,1' => 'iPad Mini 4', - 'iPad5,2' => 'iPad Mini 4', - 'iPad5,3' => 'iPad Air 2', - 'iPad5,4' => 'iPad Air 2', - 'iPad6,3' => 'iPad Pro (9.7 inch)', - 'iPad6,4' => 'iPad Pro (9.7 inch)', - 'iPad6,7' => 'iPad Pro (12.9 inch)', - 'iPad6,8' => 'iPad Pro (12.9 inch)', - 'iPod1,1' => 'iPod Touch (1nd Gen)', - 'iPod2,1' => 'iPod Touch (2nd Gen)', - 'iPod3,1' => 'iPod Touch (3rd Gen)', - 'iPod4,1' => 'iPod Touch (4th Gen)', - 'iPod5,1' => 'iPod Touch (5th Gen)', - 'iPod7,1' => 'iPod Touch (6th Gen)', - ]; - if (array_key_exists($s, $appleModels)) { - - } - } - + public function doFoo(string $s) + { + $a = ['a' => 1]; + if (rand(0, 1) === 1) { + $a['b'] = 2; + } + + if (array_key_exists('a', $a)) { + } + if (array_key_exists('b', $a)) { + } + if (array_key_exists('c', $a)) { + } + if (array_key_exists($s, $a)) { + } + + /** @var array $stringKeys */ + $stringKeys = doFoo(); + if (array_key_exists($s, $stringKeys)) { + } + + $b = ['a' => 1, 'b' => 2, 'c' => 3]; + if (array_key_exists($s, $b)) { + } + + $appleModels = [ + 'iPhone1,1' => 'iPhone', + 'iPhone1,2' => 'iPhone 3G', + 'iPhone2,1' => 'iPhone 3GS', + 'iPhone3,1' => 'iPhone 4', + 'iPhone3,2' => 'iPhone 4', + 'iPhone3,3' => 'iPhone 4', + 'iPhone4,1' => 'iPhone 4S', + 'iPhone5,1' => 'iPhone 5', + 'iPhone5,2' => 'iPhone 5', + 'iPhone5,3' => 'iPhone 5C', + 'iPhone5,4' => 'iPhone 5C', + 'iPhone6,1' => 'iPhone 5S', + 'iPhone6,2' => 'iPhone 5S', + 'iPhone7,1' => 'iPhone 6 Plus', + 'iPhone7,2' => 'iPhone 6', + 'iPhone8,1' => 'iPhone 6S', + 'iPhone8,2' => 'iPhone 6S Plus', + 'iPhone8,4' => 'iPhone SE', + 'iPhone9,1' => 'iPhone 7', + 'iPhone9,2' => 'iPhone 7 Plus', + 'iPhone9,3' => 'iPhone 7', + 'iPhone9,4' => 'iPhone 7 Plus', + 'iPhone10,1' => 'iPhone 8', + 'iPhone10,2' => 'iPhone 8 Plus', + 'iPhone10,3' => 'iPhone X', + 'iPhone10,4' => 'iPhone 8', + 'iPhone10,5' => 'iPhone 8 Plus', + 'iPhone10,6' => 'iPhone X', + 'iPad1,1' => 'iPad', + 'iPad2,1' => 'iPad 2', + 'iPad2,2' => 'iPad 2', + 'iPad2,3' => 'iPad 2', + 'iPad2,4' => 'iPad 2', + 'iPad2,5' => 'iPad Mini', + 'iPad2,6' => 'iPad Mini', + 'iPad2,7' => 'iPad Mini', + 'iPad3,1' => 'iPad 3', + 'iPad3,2' => 'iPad 3', + 'iPad3,3' => 'iPad 3', + 'iPad3,4' => 'iPad 4', + 'iPad3,5' => 'iPad 4', + 'iPad3,6' => 'iPad 4', + 'iPad4,1' => 'iPad Air', + 'iPad4,2' => 'iPad Air', + 'iPad4,3' => 'iPad Air', + 'iPad4,4' => 'iPad Mini 2', + 'iPad4,5' => 'iPad Mini 2', + 'iPad4,6' => 'iPad Mini 2', + 'iPad4,7' => 'iPad Mini 3', + 'iPad4,8' => 'iPad Mini 3', + 'iPad4,9' => 'iPad Mini 3', + 'iPad5,1' => 'iPad Mini 4', + 'iPad5,2' => 'iPad Mini 4', + 'iPad5,3' => 'iPad Air 2', + 'iPad5,4' => 'iPad Air 2', + 'iPad6,3' => 'iPad Pro (9.7 inch)', + 'iPad6,4' => 'iPad Pro (9.7 inch)', + 'iPad6,7' => 'iPad Pro (12.9 inch)', + 'iPad6,8' => 'iPad Pro (12.9 inch)', + 'iPod1,1' => 'iPod Touch (1nd Gen)', + 'iPod2,1' => 'iPod Touch (2nd Gen)', + 'iPod3,1' => 'iPod Touch (3rd Gen)', + 'iPod4,1' => 'iPod Touch (4th Gen)', + 'iPod5,1' => 'iPod Touch (5th Gen)', + 'iPod7,1' => 'iPod Touch (6th Gen)', + ]; + if (array_key_exists($s, $appleModels)) { + } + } } class PropertyExistsUniversalCrate { - - private $foo; - - /** - * @param \stdClass $std - * @param \stdClass|self $stdOrSelf - */ - public function doFoo( - \stdClass $std, - $stdOrSelf - ) - { - if (property_exists($std, 'foo')) { - - } - /*if (property_exists($stdOrSelf, 'foo')) { // To solve this, we'd need FoundPropertyReflection::isNative() to return TrinaryLogic - - }*/ - if (property_exists($stdOrSelf, 'bar')) { - - } - } - + private $foo; + + /** + * @param \stdClass $std + * @param \stdClass|self $stdOrSelf + */ + public function doFoo( + \stdClass $std, + $stdOrSelf + ) { + if (property_exists($std, 'foo')) { + } + /*if (property_exists($stdOrSelf, 'foo')) { // To solve this, we'd need FoundPropertyReflection::isNative() to return TrinaryLogic + + }*/ + if (property_exists($stdOrSelf, 'bar')) { + } + } } class ObjectCallable { - - /** - * @param object $object - * @return int - */ - public function isStatic($object): int - { - return is_callable([$object, 'yo']) ? 1 : 2; - } - - /** - * @param mixed $object - */ - public function isStatic2($object): int - { - return is_callable([$object, 'yo']) ? 1 : 2; - } - - - /** - * @param mixed $object - */ - public function isStatic3($object): int - { - if(is_object($object)) { - return is_callable([$object, 'yo']) ? 1 : 2; - } - - return 0; - } - + /** + * @param object $object + * @return int + */ + public function isStatic($object): int + { + return is_callable([$object, 'yo']) ? 1 : 2; + } + + /** + * @param mixed $object + */ + public function isStatic2($object): int + { + return is_callable([$object, 'yo']) ? 1 : 2; + } + + + /** + * @param mixed $object + */ + public function isStatic3($object): int + { + if (is_object($object)) { + return is_callable([$object, 'yo']) ? 1 : 2; + } + + return 0; + } } class ArrayKeyExistsRepeated { - - public function doFoo(array $data) - { - if (array_key_exists('dealers_dealers_id', $data)) { - $has = true; - } - - if (!array_key_exists('dealers_dealers_id', $data)) { - $has = false; - } - } - + public function doFoo(array $data) + { + if (array_key_exists('dealers_dealers_id', $data)) { + $has = true; + } + + if (!array_key_exists('dealers_dealers_id', $data)) { + $has = false; + } + } } class ArrayKeyExistsWithConstantArray { - - public function test() - { - $array = []; - for ($i = 0; $i < 2; $i++) { - if (!array_key_exists('x', $array)) { - $array['x'] = 1; - } - } - } - + public function test() + { + $array = []; + for ($i = 0; $i < 2; $i++) { + if (!array_key_exists('x', $array)) { + $array['x'] = 1; + } + } + } } class CheckIsStringOnSubtractedMixed { - - public function doFoo($mixed) - { - if (is_string($mixed)) { - return; - } - - if (is_string($mixed)) { - return; - } - } - - public function doBar($mixed) - { - if (is_callable($mixed)) { - return; - } - - if (is_callable($mixed)) { - - } - } - + public function doFoo($mixed) + { + if (is_string($mixed)) { + return; + } + + if (is_string($mixed)) { + return; + } + } + + public function doBar($mixed) + { + if (is_callable($mixed)) { + return; + } + + if (is_callable($mixed)) { + } + } } class MethodExists { - public function testWithStringFirstArgument(): void - { - /** @var string $string */ - $string = doFoo(); + public function testWithStringFirstArgument(): void + { + /** @var string $string */ + $string = doFoo(); - if (method_exists(MethodExists::class, 'testWithStringFirstArgument')) { - } + if (method_exists(MethodExists::class, 'testWithStringFirstArgument')) { + } - if (method_exists(MethodExists::class, 'undefinedMethod')) { - } + if (method_exists(MethodExists::class, 'undefinedMethod')) { + } - if (method_exists(MethodExists::class, $string)) { - } + if (method_exists(MethodExists::class, $string)) { + } - if (method_exists('UndefinedClass', $string)) { - } + if (method_exists('UndefinedClass', $string)) { + } - if (method_exists('UndefinedClass', 'test')) { - } + if (method_exists('UndefinedClass', 'test')) { + } - if (method_exists($string, 'test')) { - } - } + if (method_exists($string, 'test')) { + } + } - public function testWithNewObjectInFirstArgument(): void - { - /** @var string $string */ - $string = doFoo(); + public function testWithNewObjectInFirstArgument(): void + { + /** @var string $string */ + $string = doFoo(); - if (method_exists((new MethodExists()), 'testWithNewObjectInFirstArgument')) { - } + if (method_exists((new MethodExists()), 'testWithNewObjectInFirstArgument')) { + } - if (method_exists((new MethodExists()), 'undefinedMethod')) { - } + if (method_exists((new MethodExists()), 'undefinedMethod')) { + } - if (method_exists((new MethodExists()), $string)) { - } - } + if (method_exists((new MethodExists()), $string)) { + } + } } trait MethodExistsTrait { - public function test() - { - if (method_exists($this, 'method')) { - } + public function test() + { + if (method_exists($this, 'method')) { + } - if (method_exists($this, 'someAnother')) { - } + if (method_exists($this, 'someAnother')) { + } - if (method_exists($this, 'unknown')) { - } + if (method_exists($this, 'unknown')) { + } - if (method_exists(get_called_class(), 'method')) { - } + if (method_exists(get_called_class(), 'method')) { + } - if (method_exists(get_called_class(), 'someAnother')) { - } + if (method_exists(get_called_class(), 'someAnother')) { + } - if (method_exists(get_called_class(), 'unknown')) { - } + if (method_exists(get_called_class(), 'unknown')) { + } - if (method_exists(static::class, 'method')) { - } + if (method_exists(static::class, 'method')) { + } - if (method_exists(static::class, 'someAnother')) { - } + if (method_exists(static::class, 'someAnother')) { + } - if (method_exists(static::class, 'unknown')) { - } - } + if (method_exists(static::class, 'unknown')) { + } + } - public function method() - { - } + public function method() + { + } } final class MethodExistsWithTrait { - use MethodExistsTrait; + use MethodExistsTrait; - public function someAnother() - { - $this->test(); - } + public function someAnother() + { + $this->test(); + } } class CheckIsStringInElseIf { - - /** - * @param Foo|string $a - */ - public function doFoo($a): bool - { - if ($a instanceof Foo) { - return true; - } elseif (!is_string($a)) { - throw new \Exception('Not Bar or string'); - } - - return false; - } - + /** + * @param Foo|string $a + */ + public function doFoo($a): bool + { + if ($a instanceof Foo) { + return true; + } elseif (!is_string($a)) { + throw new \Exception('Not Bar or string'); + } + + return false; + } } class AssertIsNumeric { - - public function doFoo(string $str, float $float) - { - assert(is_numeric($str)); - assert(is_numeric('123')); - assert(is_numeric('blabla')); - - $isNumeric = $float; - if (doFoo()) { - $isNumeric = 123; - } - - assert(is_numeric($isNumeric)); - } - - /** - * @param int|string $item - */ - public function doBar($item): void - { - if (!is_numeric($item)) { - throw new \Exception; - } - - echo $item; - } - - /** - * @param string|float|int $value - */ - public function doBaz($value): void - { - if (is_numeric($value)) { - - } - } - - public function doLorem(string $rating): ?int - { - $ratingMapping = [ - 'UR' => 0, - 'NR' => 0, - 'G' => 0, - 'PG' => 8, - 'PG-13' => 13, - 'R' => 15, - 'NC-17' => 17, - ]; - - if (array_key_exists($rating, $ratingMapping)) { - $rating = $ratingMapping[$rating]; - } - - if (is_numeric($rating)) { - - } - } - - /** - * @param mixed[] $data - */ - function doIpsum(array $data): void - { - foreach ($data as $index => $element) { - if (is_numeric($index)) { - echo "numeric key\n"; - } - } - } - + public function doFoo(string $str, float $float) + { + assert(is_numeric($str)); + assert(is_numeric('123')); + assert(is_numeric('blabla')); + + $isNumeric = $float; + if (doFoo()) { + $isNumeric = 123; + } + + assert(is_numeric($isNumeric)); + } + + /** + * @param int|string $item + */ + public function doBar($item): void + { + if (!is_numeric($item)) { + throw new \Exception(); + } + + echo $item; + } + + /** + * @param string|float|int $value + */ + public function doBaz($value): void + { + if (is_numeric($value)) { + } + } + + public function doLorem(string $rating): ?int + { + $ratingMapping = [ + 'UR' => 0, + 'NR' => 0, + 'G' => 0, + 'PG' => 8, + 'PG-13' => 13, + 'R' => 15, + 'NC-17' => 17, + ]; + + if (array_key_exists($rating, $ratingMapping)) { + $rating = $ratingMapping[$rating]; + } + + if (is_numeric($rating)) { + } + } + + /** + * @param mixed[] $data + */ + public function doIpsum(array $data): void + { + foreach ($data as $index => $element) { + if (is_numeric($index)) { + echo "numeric key\n"; + } + } + } } class Bug2221 { + public $foo; - public $foo; - - public function doFoo(): void - { - if (property_exists(Bug2221::class, 'foo')) { + public function doFoo(): void + { + if (property_exists(Bug2221::class, 'foo')) { + } - } + assert(property_exists(Bug2221::class, 'foo')); - assert(property_exists(Bug2221::class, 'foo')); + if (property_exists(Bug2221::class, 'bar')) { + } - if (property_exists(Bug2221::class, 'bar')) { + assert(property_exists(Bug2221::class, 'bar')); + } - } + public function doBar(self $self): void + { + if (property_exists($self, 'foo')) { + } - assert(property_exists(Bug2221::class, 'bar')); - } + assert(property_exists($self, 'foo')); - public function doBar(self $self): void - { - if (property_exists($self, 'foo')) { + if (property_exists($self, 'bar')) { + } - } + assert(property_exists($self, 'bar')); + } - assert(property_exists($self, 'foo')); + public function doBaz(\stdClass $std): void + { + if (property_exists($std, 'foo')) { + } - if (property_exists($self, 'bar')) { + assert(property_exists($std, 'foo')); + } - } - - assert(property_exists($self, 'bar')); - } - - public function doBaz(\stdClass $std): void - { - if (property_exists($std, 'foo')) { - - } - - assert(property_exists($std, 'foo')); - } - - public function doLorem(\SimpleXMLElement $xml): void - { - if (property_exists($xml, 'foo')) { - - } - - assert(property_exists($xml, 'foo')); - } + public function doLorem(\SimpleXMLElement $xml): void + { + if (property_exists($xml, 'foo')) { + } + assert(property_exists($xml, 'foo')); + } } class InArray2 { - - /** - * @param array $ints - */ - public function doFoo($ints): void - { - if ($ints === []) { - return; - } - - if (in_array(0, $ints, true)) { - - } - } - - /** - * @param \stdClass $std - * @param array $stdClassesOrNull - */ - public function doBar($std, $stdClassesOrNull): void - { - if ($stdClassesOrNull === []) { - return; - } - - if (in_array($std, $stdClassesOrNull, true)) { - - } - } - + /** + * @param array $ints + */ + public function doFoo($ints): void + { + if ($ints === []) { + return; + } + + if (in_array(0, $ints, true)) { + } + } + + /** + * @param \stdClass $std + * @param array $stdClassesOrNull + */ + public function doBar($std, $stdClassesOrNull): void + { + if ($stdClassesOrNull === []) { + return; + } + + if (in_array($std, $stdClassesOrNull, true)) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/elseif-condition-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/elseif-condition-not-phpdoc.php index a9623f8b45..09bc3f0033 100644 --- a/tests/PHPStan/Rules/Comparison/data/elseif-condition-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/elseif-condition-not-phpdoc.php @@ -4,26 +4,19 @@ class ElseIfCondition { - - /** - * @param object $object - */ - public function doFoo( - self $self, - $object - ): void - { - if (rand(0, 1)) { - - } elseif ($self) { - - } - - if (rand(0, 1)) { - - } elseif ($object) { - - } - } - + /** + * @param object $object + */ + public function doFoo( + self $self, + $object + ): void { + if (rand(0, 1)) { + } elseif ($self) { + } + + if (rand(0, 1)) { + } elseif ($object) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/elseif-condition.php b/tests/PHPStan/Rules/Comparison/data/elseif-condition.php index 7e69f7ec52..1498293799 100644 --- a/tests/PHPStan/Rules/Comparison/data/elseif-condition.php +++ b/tests/PHPStan/Rules/Comparison/data/elseif-condition.php @@ -4,38 +4,28 @@ class ElseIfCondition { - - /** - * @param int $i - * @param \stdClass $std - * @param Foo|Bar $union - * @param Lorem&Ipsum $intersection - */ - public function doFoo(int $i, \stdClass $std, $union, $intersection) - { - if ($i) { - - } elseif ($std) { - - } - - if ($i) { - - } elseif (!$std) { - - } - - if ($union instanceof Foo || $union instanceof Bar) { - - } elseif ($union instanceof Foo && true) { - - } - - if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { - - } elseif ($intersection instanceof Lorem && true) { - - } - } - + /** + * @param int $i + * @param \stdClass $std + * @param Foo|Bar $union + * @param Lorem&Ipsum $intersection + */ + public function doFoo(int $i, \stdClass $std, $union, $intersection) + { + if ($i) { + } elseif ($std) { + } + + if ($i) { + } elseif (!$std) { + } + + if ($union instanceof Foo || $union instanceof Bar) { + } elseif ($union instanceof Foo && true) { + } + + if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { + } elseif ($intersection instanceof Lorem && true) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/function-definition.php b/tests/PHPStan/Rules/Comparison/data/function-definition.php index c4ef947394..e07b6ffa88 100644 --- a/tests/PHPStan/Rules/Comparison/data/function-definition.php +++ b/tests/PHPStan/Rules/Comparison/data/function-definition.php @@ -2,5 +2,5 @@ function always_true() { - return true; + return true; }; diff --git a/tests/PHPStan/Rules/Comparison/data/if-condition-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/if-condition-not-phpdoc.php index 7a44241617..b7e3d2bad6 100644 --- a/tests/PHPStan/Rules/Comparison/data/if-condition-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/if-condition-not-phpdoc.php @@ -4,22 +4,17 @@ class IfCondition { + /** + * @param object $object + */ + public function doFoo( + self $self, + $object + ): void { + if ($self) { + } - /** - * @param object $object - */ - public function doFoo( - self $self, - $object - ): void - { - if ($self) { - - } - - if ($object) { - - } - } - + if ($object) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/if-condition.php b/tests/PHPStan/Rules/Comparison/data/if-condition.php index 54887dc4de..cb977b43b6 100644 --- a/tests/PHPStan/Rules/Comparison/data/if-condition.php +++ b/tests/PHPStan/Rules/Comparison/data/if-condition.php @@ -4,292 +4,253 @@ class Foo { - } class Bar { - } interface Lorem { - } interface Ipsum { - } class IfCondition { - - /** - * @param int $i - * @param \stdClass $std - * @param Foo|Bar $union - * @param Lorem&Ipsum $intersection - */ - public function doFoo(int $i, \stdClass $std, $union, $intersection) - { - if ($i) { - - } - - if ($std) { - - } - - $zero = 0; - if ($zero) { - - } - - if ($union instanceof Foo || $union instanceof Bar) { - - } - - if ($union instanceof Foo && $union instanceof Bar) { - - } - - if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { - - } - - if ($intersection instanceof Lorem || $intersection instanceof Ipsum) { - - } - } - - public function conditionalArray() - { - $arr = []; - - if (doFoo()) { - $arr += ['abc']; - } - - if ($arr) { - - } - } - - public function skipDifferentRule() - { - if (!false) { - - } - if (!true) { - - } - } - - public function skipTypeSpecifyingFunctions( - object $object - ) - { - if (is_object($object)) { - - } - if (always_true()) { - - } - } - + /** + * @param int $i + * @param \stdClass $std + * @param Foo|Bar $union + * @param Lorem&Ipsum $intersection + */ + public function doFoo(int $i, \stdClass $std, $union, $intersection) + { + if ($i) { + } + + if ($std) { + } + + $zero = 0; + if ($zero) { + } + + if ($union instanceof Foo || $union instanceof Bar) { + } + + if ($union instanceof Foo && $union instanceof Bar) { + } + + if ($intersection instanceof Lorem && $intersection instanceof Ipsum) { + } + + if ($intersection instanceof Lorem || $intersection instanceof Ipsum) { + } + } + + public function conditionalArray() + { + $arr = []; + + if (doFoo()) { + $arr += ['abc']; + } + + if ($arr) { + } + } + + public function skipDifferentRule() + { + if (!false) { + } + if (!true) { + } + } + + public function skipTypeSpecifyingFunctions( + object $object + ) { + if (is_object($object)) { + } + if (always_true()) { + } + } } final class FinalClass { - - const FOO = true; - - public function doFoo() - { - if (self::FOO) { - - } - if (static::FOO) { - - } - } - + public const FOO = true; + + public function doFoo() + { + if (self::FOO) { + } + if (static::FOO) { + } + } } class NotFinalClass { - - const FOO = true; - - public function doFoo() - { - if (self::FOO) { - - } - if (static::FOO) { - - } - } - + public const FOO = true; + + public function doFoo() + { + if (self::FOO) { + } + if (static::FOO) { + } + } } class IgnoredBreakBranch { - - public function doFoo() - { - $hasBar = false; - foreach (['a','b'] as $key) { - if (rand(0,100) > 50) { - if (rand(0,101) > 50) { - $hasBar = true; - break; - } - return 'foo'; - } - } - - if ($hasBar) { - return 'bar'; - } - return 'default'; - } - - public function doBar() - { - $a = false; - - foreach ([1, 2, 3] as $_) { - if (rand(0, 1)) { - break; - } - $a = true; - } - - if ($a) {} - } - - public function doBaz(array $arr) - { - $a = false; - - foreach ($arr as $_) { - if (rand(0, 1)) { - break; - } - $a = true; - } - - if ($a) {} - } - + public function doFoo() + { + $hasBar = false; + foreach (['a','b'] as $key) { + if (rand(0, 100) > 50) { + if (rand(0, 101) > 50) { + $hasBar = true; + break; + } + return 'foo'; + } + } + + if ($hasBar) { + return 'bar'; + } + return 'default'; + } + + public function doBar() + { + $a = false; + + foreach ([1, 2, 3] as $_) { + if (rand(0, 1)) { + break; + } + $a = true; + } + + if ($a) { + } + } + + public function doBaz(array $arr) + { + $a = false; + + foreach ($arr as $_) { + if (rand(0, 1)) { + break; + } + $a = true; + } + + if ($a) { + } + } } class ClosureWithReturn { - - public function doFoo(self $foo) - { - $f = function ($cond) use (&$var) { - if ($cond) { - $var = true; - return; - } - $var = false; - }; - - $foo->doFoo($foo); - - if ($var) { - - } - } - + public function doFoo(self $foo) + { + $f = function ($cond) use (&$var) { + if ($cond) { + $var = true; + return; + } + $var = false; + }; + + $foo->doFoo($foo); + + if ($var) { + } + } } class ForeachWithContinue { - - public function doFoo() - { - $tokens = token_get_all(' 1]; - unset($dict[$s]); - if ($dict) { - - } - } - + public function foo(string $s): void + { + $dict = ['a' => 1]; + unset($dict[$s]); + if ($dict) { + } + } } class VarAnnotationAboveIf { - - public function doFoo() - { - $bool = true; - - /** @var bool $bool */ - if ($bool) { - - } - } - + public function doFoo() + { + $bool = true; + + /** @var bool $bool */ + if ($bool) { + } + } } class NestedIfConditions { - public function nestedIfConditions($mixed): void - { - if ($mixed) { - if ($mixed) { - } - } - if (!$mixed) { - if ($mixed) { - } - } - } + public function nestedIfConditions($mixed): void + { + if ($mixed) { + if ($mixed) { + } + } + if (!$mixed) { + if ($mixed) { + } + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/impossible-method-call-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/impossible-method-call-not-phpdoc.php index fa8ccce923..2162673ecd 100644 --- a/tests/PHPStan/Rules/Comparison/data/impossible-method-call-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/impossible-method-call-not-phpdoc.php @@ -4,19 +4,16 @@ class Foo { - - /** - * @param string $phpDocString - */ - public function doFoo( - string $realString, - $phpDocString - ) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertString($realString); - $assertion->assertString($phpDocString); - $assertion->assertString($phpDocString); - } - + /** + * @param string $phpDocString + */ + public function doFoo( + string $realString, + $phpDocString + ) { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertString($realString); + $assertion->assertString($phpDocString); + $assertion->assertString($phpDocString); + } } diff --git a/tests/PHPStan/Rules/Comparison/data/impossible-method-call.php b/tests/PHPStan/Rules/Comparison/data/impossible-method-call.php index c213bfb8d5..bfc0283607 100644 --- a/tests/PHPStan/Rules/Comparison/data/impossible-method-call.php +++ b/tests/PHPStan/Rules/Comparison/data/impossible-method-call.php @@ -4,99 +4,84 @@ class Foo { - - public function doFoo( - string $foo, - int $bar - ) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertString($foo); - $assertion->assertString($bar); - } - - /** - * @param string|int $foo - */ - public function doBar($foo) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertString($foo); - } - - public function doBaz(int $foo) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertNotInt($foo); - } - - public function doLorem(string $foo) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertNotInt($foo); - } - - /** - * @param string|int $foo - */ - public function doIpsum($foo) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion->assertNotInt($foo); - } - - public function isSame($expected, $actual): bool - { - return $expected === $actual; - } - - public function isNotSame($expected, $actual): bool - { - return $expected !== $actual; - } - - public function doDolor(\stdClass $std1, \stdClass $std2) - { - if ($this->isSame(1, 1)) { - - } - if ($this->isSame(1, 2)) { - - } - if ($this->isNotSame(1, 1)) { - - } - if ($this->isNotSame(1, 2)) { - - } - if ($this->isSame(new \stdClass(), new \stdClass())) { - - } - if ($this->isNotSame(new \stdClass(), new \stdClass())) { - - } - if ($this->isSame($std1, $std1)) { - - } - if ($this->isNotSame($std1, $std1)) { - - } - if ($this->isSame($std1, $std2)) { - - } - if ($this->isNotSame($std1, $std2)) { - - } - if ($this->isSame($this->nullableInt(), 1)) { - if ($this->isSame($this->nullableInt(), null)) { - - } - } - } - - public function nullableInt(): ?int - { - - } - + public function doFoo( + string $foo, + int $bar + ) { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertString($foo); + $assertion->assertString($bar); + } + + /** + * @param string|int $foo + */ + public function doBar($foo) + { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertString($foo); + } + + public function doBaz(int $foo) + { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertNotInt($foo); + } + + public function doLorem(string $foo) + { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertNotInt($foo); + } + + /** + * @param string|int $foo + */ + public function doIpsum($foo) + { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion->assertNotInt($foo); + } + + public function isSame($expected, $actual): bool + { + return $expected === $actual; + } + + public function isNotSame($expected, $actual): bool + { + return $expected !== $actual; + } + + public function doDolor(\stdClass $std1, \stdClass $std2) + { + if ($this->isSame(1, 1)) { + } + if ($this->isSame(1, 2)) { + } + if ($this->isNotSame(1, 1)) { + } + if ($this->isNotSame(1, 2)) { + } + if ($this->isSame(new \stdClass(), new \stdClass())) { + } + if ($this->isNotSame(new \stdClass(), new \stdClass())) { + } + if ($this->isSame($std1, $std1)) { + } + if ($this->isNotSame($std1, $std1)) { + } + if ($this->isSame($std1, $std2)) { + } + if ($this->isNotSame($std1, $std2)) { + } + if ($this->isSame($this->nullableInt(), 1)) { + if ($this->isSame($this->nullableInt(), null)) { + } + } + } + + public function nullableInt(): ?int + { + } } diff --git a/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call-not-phpdoc.php index 0ca620caf9..9da7deab97 100644 --- a/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call-not-phpdoc.php @@ -4,18 +4,15 @@ class Foo { - - /** - * @param int $phpDocInt - */ - public function doFoo( - int $realInt, - $phpDocInt - ) - { - \PHPStan\Tests\AssertionClass::assertInt($realInt); - \PHPStan\Tests\AssertionClass::assertInt($phpDocInt); - \PHPStan\Tests\AssertionClass::assertInt($phpDocInt); - } - + /** + * @param int $phpDocInt + */ + public function doFoo( + int $realInt, + $phpDocInt + ) { + \PHPStan\Tests\AssertionClass::assertInt($realInt); + \PHPStan\Tests\AssertionClass::assertInt($phpDocInt); + \PHPStan\Tests\AssertionClass::assertInt($phpDocInt); + } } diff --git a/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call.php b/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call.php index 01a0125a69..d660cbdbe3 100644 --- a/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call.php +++ b/tests/PHPStan/Rules/Comparison/data/impossible-static-method-call.php @@ -4,52 +4,47 @@ class Foo { - - public function doFoo( - int $foo, - string $bar - ) - { - \PHPStan\Tests\AssertionClass::assertInt($foo); - \PHPStan\Tests\AssertionClass::assertInt($bar); - } - - /** - * @param string|int $bar - */ - public function doBar($bar) - { - \PHPStan\Tests\AssertionClass::assertInt($bar); - } - - public function doBaz( - int $foo, - string $bar - ) - { - $assertion = new \PHPStan\Tests\AssertionClass(); - $assertion::assertInt($foo); - $assertion::assertInt($bar); - $assertion::assertInt(1, 2); - $assertion::assertInt(1, 2, 3); - } - - public function doPhpunit() - { - \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); - \PHPUnit\Framework\Assert::assertSame(302, $this->nullableInt()); - \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); - } - - public function doPhpunitNot() - { - \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); - \PHPUnit\Framework\Assert::assertNotSame(302, $this->nullableInt()); - } - - public function nullableInt(): ?int - { - - } - + public function doFoo( + int $foo, + string $bar + ) { + \PHPStan\Tests\AssertionClass::assertInt($foo); + \PHPStan\Tests\AssertionClass::assertInt($bar); + } + + /** + * @param string|int $bar + */ + public function doBar($bar) + { + \PHPStan\Tests\AssertionClass::assertInt($bar); + } + + public function doBaz( + int $foo, + string $bar + ) { + $assertion = new \PHPStan\Tests\AssertionClass(); + $assertion::assertInt($foo); + $assertion::assertInt($bar); + $assertion::assertInt(1, 2); + $assertion::assertInt(1, 2, 3); + } + + public function doPhpunit() + { + \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); + \PHPUnit\Framework\Assert::assertSame(302, $this->nullableInt()); + \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); + } + + public function doPhpunitNot() + { + \PHPUnit\Framework\Assert::assertSame(200, $this->nullableInt()); + \PHPUnit\Framework\Assert::assertNotSame(302, $this->nullableInt()); + } + + public function nullableInt(): ?int + { + } } diff --git a/tests/PHPStan/Rules/Comparison/data/match-expr.php b/tests/PHPStan/Rules/Comparison/data/match-expr.php index c56cfb8e34..8d1d823b03 100644 --- a/tests/PHPStan/Rules/Comparison/data/match-expr.php +++ b/tests/PHPStan/Rules/Comparison/data/match-expr.php @@ -1,140 +1,139 @@ -= 8.0 += 8.0 namespace MatchExprRule; class Foo { - - /** - * @param 1|2|3 $i - */ - public function doFoo(int $i): void - { - match ($i) { - 'foo' => null, // always false - default => null, - }; - - match ($i) { - 0 => null, - 1 => null, - 2 => null, - 3 => null, // always true, but do not report (it's the last one) - }; - - match ($i) { - 1 => null, - 2 => null, - 3 => null, // always true - report with strict-rules - 4 => null, // unreachable - }; - - match ($i) { - 1 => null, - 2 => null, - 3 => null, // always true - report with strict-rules - default => null, // unreachable - }; - - match (1) { - 1 => null, // always true - report with strict-rules - 2 => null, // unreachable - 3 => null, // unreachable - }; - - match (1) { - 1 => null, // always true - report with strict-rules - default => null, // unreachable - }; - - match ($i) { - 1, 2 => null, - // unhandled - }; - - match ($i) { - // unhandled - }; - - match ($i) { - 1, 2 => null, - default => null, // OK - }; - - match ($i) { - 3, 3 => null, // second 3 is always false - default => null, - }; - - match (1) { - 1 => 1, // always true - report with strict-rules - }; - - match ($i) { - default => 1, - }; - - match ($i) { - default => 1, - 1 => 2, - }; - } - - public function doBar(\Exception $e): void - { - match (true) { - $e instanceof \InvalidArgumentException, $e instanceof \InvalidArgumentException => true, - default => null, - }; - - match (true) { - $e instanceof \InvalidArgumentException => true, - $e instanceof \InvalidArgumentException => true, - }; - } - - /** - * @param \stdClass&\Exception $obj - */ - public function doBaz($obj): void - { - match ($obj) { - - }; - } - - public function doFooConstants(int $i): void - { - - } - + /** + * @param 1|2|3 $i + */ + public function doFoo(int $i): void + { + match ($i) { + 'foo' => null, // always false + default => null, + }; + + match ($i) { + 0 => null, + 1 => null, + 2 => null, + 3 => null, // always true, but do not report (it's the last one) + }; + + match ($i) { + 1 => null, + 2 => null, + 3 => null, // always true - report with strict-rules + 4 => null, // unreachable + }; + + match ($i) { + 1 => null, + 2 => null, + 3 => null, // always true - report with strict-rules + default => null, // unreachable + }; + + match (1) { + 1 => null, // always true - report with strict-rules + 2 => null, // unreachable + 3 => null, // unreachable + }; + + match (1) { + 1 => null, // always true - report with strict-rules + default => null, // unreachable + }; + + match ($i) { + 1, 2 => null, + // unhandled + }; + + match ($i) { + // unhandled + }; + + match ($i) { + 1, 2 => null, + default => null, // OK + }; + + match ($i) { + 3, 3 => null, // second 3 is always false + // no break + default => null, + }; + + match (1) { + 1 => 1, // always true - report with strict-rules + }; + + match ($i) { + default => 1, + }; + + match ($i) { + // no break + default => 1, + 1 => 2, + }; + } + + public function doBar(\Exception $e): void + { + match (true) { + $e instanceof \InvalidArgumentException, $e instanceof \InvalidArgumentException => true, + default => null, + }; + + match (true) { + $e instanceof \InvalidArgumentException => true, + $e instanceof \InvalidArgumentException => true, + }; + } + + /** + * @param \stdClass&\Exception $obj + */ + public function doBaz($obj): void + { + match ($obj) { + }; + } + + public function doFooConstants(int $i): void + { + } } class BarConstants { - - const TEST1 = 1; - const TEST2 = 2; - - /** - * @param BarConstants::TEST1|BarConstants::TEST2 $i - */ - public function doFoo(int $i): void { - match ($i) { - BarConstants::TEST1 => 'foo', - BarConstants::TEST2 => 'bar', - }; - } - - /** - * @param BarConstants::TEST* $i - */ - public function doBar(int $i): void { - match ($i) { - BarConstants::TEST1 => 'foo', - BarConstants::TEST2 => 'bar', - }; - } - - + public const TEST1 = 1; + public const TEST2 = 2; + + /** + * @param BarConstants::TEST1|BarConstants::TEST2 $i + */ + public function doFoo(int $i): void + { + match ($i) { + BarConstants::TEST1 => 'foo', + BarConstants::TEST2 => 'bar', + }; + } + + /** + * @param BarConstants::TEST* $i + */ + public function doBar(int $i): void + { + match ($i) { + BarConstants::TEST1 => 'foo', + BarConstants::TEST2 => 'bar', + }; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php b/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php index eb7d0d5c77..7a67c90511 100644 --- a/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php +++ b/tests/PHPStan/Rules/Comparison/data/number-comparison-operators.php @@ -3,23 +3,20 @@ namespace NumberComparisonOperators; function (int $i, int $j): void { - if ($i > 5) { - if ($i <= 2) { + if ($i > 5) { + if ($i <= 2) { + } + } - } - } - - if ($j >= 2 && $j < 5) { - if ($j > 8) { - - } - } + if ($j >= 2 && $j < 5) { + if ($j > 8) { + } + } }; function (int $i): void { - if ($i < 2) { - if ($i < 5) { - - } - } + if ($i < 2) { + if ($i < 5) { + } + } }; diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison-71.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison-71.php index 3693818f8d..6041bdda0b 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison-71.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison-71.php @@ -4,34 +4,32 @@ class Foo { + public function returnsNullableString(): ?bool + { + return false; + } - public function returnsNullableString(): ?bool - { - return false; - } + public function doCheckNullableString(): int + { + $result = $this->returnsNullableString(); + if ($result === true) { + return 1; + } elseif ($result === false) { + return 2; + } elseif ($result === null) { + return 3; + } + return 4; + } - public function doCheckNullableString(): int - { - $result = $this->returnsNullableString(); - if ($result === true) { - return 1; - } else if ($result === false) { - return 2; - } else if ($result === null) { - return 3; - } - return 4; - } - - public function doCheckNullableAndAddString(?int $memoryLimit): void - { - if ($memoryLimit === null) { - $memoryLimit = 'abc'; - } - - if ($memoryLimit === 'abc') { - // doSomething - } - } + public function doCheckNullableAndAddString(?int $memoryLimit): void + { + if ($memoryLimit === null) { + $memoryLimit = 'abc'; + } + if ($memoryLimit === 'abc') { + // doSomething + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison-property-native-types.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison-property-native-types.php index e973c1c1da..65cb496d37 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison-property-native-types.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison-property-native-types.php @@ -1,56 +1,48 @@ -= 7.4 += 7.4 namespace StrictComparisonPropertyNativeTypes; class Foo { + private string $string; - private string $string; - - private ?string $nullableString; - - public function doFoo(): void - { - if ($this->string === null) { - - } - - if ($this->nullableString === null) { - - } - } - - public function doBar(): void - { - if ($this->string !== null) { - - } - - if ($this->nullableString !== null) { - - } - } - - public function doBaz(): void - { - if (null === $this->string) { + private ?string $nullableString; - } + public function doFoo(): void + { + if ($this->string === null) { + } - if (null === $this->nullableString) { + if ($this->nullableString === null) { + } + } - } - } + public function doBar(): void + { + if ($this->string !== null) { + } - public function doLorem(): void - { - if (null !== $this->string) { + if ($this->nullableString !== null) { + } + } - } + public function doBaz(): void + { + if (null === $this->string) { + } - if (null !== $this->nullableString) { + if (null === $this->nullableString) { + } + } - } - } + public function doLorem(): void + { + if (null !== $this->string) { + } + if (null !== $this->nullableString) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 368aab3305..228919dce9 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -4,948 +4,830 @@ class Foo { - - public function doFoo() - { - 1 === 1; - 1 === '1'; // wrong - 1 !== '1'; // wrong - doFoo() === doBar(); - 1 === null; - (new Bar()) === 1; // wrong - - /** @var Foo[]|Collection|bool $unionIterableType */ - $unionIterableType = doFoo(); - 1 === $unionIterableType; - false === $unionIterableType; - $unionIterableType === [new Foo()]; - $unionIterableType === new Collection(); - - /** @var bool $boolean */ - $boolean = doFoo(); - true === $boolean; - false === $boolean; - $boolean === true; - $boolean === false; - true === false; - false === true; - - $foo = new self(); - $this === $foo; - - $trueOrFalseInSwitch = false; - switch ('foo') { - case 'foo': - $trueOrFalseInSwitch = true; - break; - } - if ($trueOrFalseInSwitch === true) { - - } - - 1.0 === 1; - 1 === 1.0; - - /** @var string|mixed $stringOrMixed */ - $stringOrMixed = doFoo(); - $stringOrMixed === 'foo'; - } - - public function doBar(string $a = null, string $b = null): string - { - if ($a === null && $b === null) { - return 'no value'; - } - - if ($a !== null && $b !== null) { - return $a . $b; - } - - return ''; - } - - public function acceptsString(string $a) - { - if ($a === null) { - - } - } - - public function anotherAcceptsString(string $a) - { - if ($a !== null) { - - } - } - - public function foreachWithTypeChange() - { - $foo = null; - foreach ([] as $val) { - if ($foo !== null) { - - } - if ($foo !== 1) { - - } - - if (something()) { - $foo = new self(); - } - } - - foreach ([1, 2, 3] as $val) { - if ($val === null) { - - } - $val = null; - } - } - - /** - * @param int[]|true $a - */ - public function unionOfIntegersAndTrue($a) - { - if ($a !== true) { - $a = []; - } - - if ($a !== true) { - $a[] = 1; - } - - if ($a !== true && count($a) > 0) { - $a = reset($a); - } - } - - public function whileWithTypeChange() - { - $foo = null; - while (fetch()) { - if ($foo !== null) { - - } - if ($foo !== 1) { - - } - - if (something()) { - $foo = new self(); - } - } - - while ($val = $this->returnArray()) { - if ($val === null) { - - } - $val = null; - } - } - - public function forWithTypeChange() - { - $foo = null; - for (;;) { - if ($foo !== null) { - - } - if ($foo !== 1) { - - } - - if (something()) { - $foo = new self(); - } - } - - for (; $val = $this->returnArray();) { - if ($val === null) { - - } - $val = null; - } - } - - private function returnArray(): array - { - - } - + public function doFoo() + { + 1 === 1; + 1 === '1'; // wrong + 1 !== '1'; // wrong + doFoo() === doBar(); + 1 === null; + (new Bar()) === 1; // wrong + + /** @var Foo[]|Collection|bool $unionIterableType */ + $unionIterableType = doFoo(); + 1 === $unionIterableType; + false === $unionIterableType; + $unionIterableType === [new Foo()]; + $unionIterableType === new Collection(); + + /** @var bool $boolean */ + $boolean = doFoo(); + true === $boolean; + false === $boolean; + $boolean === true; + $boolean === false; + true === false; + false === true; + + $foo = new self(); + $this === $foo; + + $trueOrFalseInSwitch = false; + switch ('foo') { + case 'foo': + $trueOrFalseInSwitch = true; + break; + } + if ($trueOrFalseInSwitch === true) { + } + + 1.0 === 1; + 1 === 1.0; + + /** @var string|mixed $stringOrMixed */ + $stringOrMixed = doFoo(); + $stringOrMixed === 'foo'; + } + + public function doBar(string $a = null, string $b = null): string + { + if ($a === null && $b === null) { + return 'no value'; + } + + if ($a !== null && $b !== null) { + return $a . $b; + } + + return ''; + } + + public function acceptsString(string $a) + { + if ($a === null) { + } + } + + public function anotherAcceptsString(string $a) + { + if ($a !== null) { + } + } + + public function foreachWithTypeChange() + { + $foo = null; + foreach ([] as $val) { + if ($foo !== null) { + } + if ($foo !== 1) { + } + + if (something()) { + $foo = new self(); + } + } + + foreach ([1, 2, 3] as $val) { + if ($val === null) { + } + $val = null; + } + } + + /** + * @param int[]|true $a + */ + public function unionOfIntegersAndTrue($a) + { + if ($a !== true) { + $a = []; + } + + if ($a !== true) { + $a[] = 1; + } + + if ($a !== true && count($a) > 0) { + $a = reset($a); + } + } + + public function whileWithTypeChange() + { + $foo = null; + while (fetch()) { + if ($foo !== null) { + } + if ($foo !== 1) { + } + + if (something()) { + $foo = new self(); + } + } + + while ($val = $this->returnArray()) { + if ($val === null) { + } + $val = null; + } + } + + public function forWithTypeChange() + { + $foo = null; + for (;;) { + if ($foo !== null) { + } + if ($foo !== 1) { + } + + if (something()) { + $foo = new self(); + } + } + + for (; $val = $this->returnArray();) { + if ($val === null) { + } + $val = null; + } + } + + private function returnArray(): array + { + } } class Node { - - /** @var self|null */ - private $next; - - /** @var int */ - private $id; - - public function iterate(): void - { - for ($node = $this; $node !== null; $node = $node->next) { - // ... - } - } - - public function checkCycle() - { - if ($this->next !== null) { - $iter = $this->next; - while ($iter !== null) { - if ($iter->id === $this->id) { - throw new \Exception('Cycle detected.'); - } - - $iter = $iter->next; - } - } - } - - public function checkAnotherCycle() - { - if ($this->next !== null) { - $iter = $this->next; - while ($iter !== false) { - if ($iter->id === $this->id) { - throw new \Exception('Cycle detected.'); - } - - $iter = $iter->next; - } - } - } - - public function finallyNullability() - { - $result = null; - try { - if (doFoo()) { - throw new \Exception(); - } - $result = '1'; - } finally { - if ($result !== null) { - - } - } - } - - public function checkForCycle() - { - if ($this->next !== null) { - $iter = $this->next; - for (;$iter !== null;) { - if ($iter->id === $this->id) { - throw new \Exception('Cycle detected.'); - } - - $iter = $iter->next; - } - } - } - - public function checkAnotherForCycle() - { - if ($this->next !== null) { - $iter = $this->next; - for (;$iter !== false;) { - if ($iter->id === $this->id) { - throw new \Exception('Cycle detected.'); - } - - $iter = $iter->next; - } - } - } - - public function looseNullCheck(?\stdClass $foo) - { - if ($foo == null) { - return; - } - - if ($foo !== null) { - - } - } + /** @var self|null */ + private $next; + + /** @var int */ + private $id; + + public function iterate(): void + { + for ($node = $this; $node !== null; $node = $node->next) { + // ... + } + } + + public function checkCycle() + { + if ($this->next !== null) { + $iter = $this->next; + while ($iter !== null) { + if ($iter->id === $this->id) { + throw new \Exception('Cycle detected.'); + } + + $iter = $iter->next; + } + } + } + + public function checkAnotherCycle() + { + if ($this->next !== null) { + $iter = $this->next; + while ($iter !== false) { + if ($iter->id === $this->id) { + throw new \Exception('Cycle detected.'); + } + + $iter = $iter->next; + } + } + } + + public function finallyNullability() + { + $result = null; + try { + if (doFoo()) { + throw new \Exception(); + } + $result = '1'; + } finally { + if ($result !== null) { + } + } + } + + public function checkForCycle() + { + if ($this->next !== null) { + $iter = $this->next; + for (;$iter !== null;) { + if ($iter->id === $this->id) { + throw new \Exception('Cycle detected.'); + } + + $iter = $iter->next; + } + } + } + + public function checkAnotherForCycle() + { + if ($this->next !== null) { + $iter = $this->next; + for (;$iter !== false;) { + if ($iter->id === $this->id) { + throw new \Exception('Cycle detected.'); + } + + $iter = $iter->next; + } + } + } + + public function looseNullCheck(?\stdClass $foo) + { + if ($foo == null) { + return; + } + + if ($foo !== null) { + } + } } class ConstantValuesComparison { - - function testInt() - { - $a = 1; - $b = 2; - $a === $b; - } - - - function testArray() - { - $a = ['X' => 1]; - $b = ['X' => 2]; - $a === $b; - } - - - function testArrayTricky() - { - $a = ['X' => 1, 'Y' => 2]; - $b = ['X' => 2, 'Y' => 1]; - $a === $b; - } - - - function testArrayTrickyAlternative() - { - $a = ['X' => 1, 'Y' => 2]; - $b = ['Y' => 2, 'X' => 1]; - $a === $b; - } - + public function testInt() + { + $a = 1; + $b = 2; + $a === $b; + } + + + public function testArray() + { + $a = ['X' => 1]; + $b = ['X' => 2]; + $a === $b; + } + + + public function testArrayTricky() + { + $a = ['X' => 1, 'Y' => 2]; + $b = ['X' => 2, 'Y' => 1]; + $a === $b; + } + + + public function testArrayTrickyAlternative() + { + $a = ['X' => 1, 'Y' => 2]; + $b = ['Y' => 2, 'X' => 1]; + $a === $b; + } } class PredefinedConstants { - - public function doFoo() - { - DIRECTORY_SEPARATOR === '/'; - DIRECTORY_SEPARATOR === '\\'; - DIRECTORY_SEPARATOR === '//'; - } - + public function doFoo() + { + DIRECTORY_SEPARATOR === '/'; + DIRECTORY_SEPARATOR === '\\'; + DIRECTORY_SEPARATOR === '//'; + } } class ConstantTypeInWhile { - - public function doFoo() - { - $i = 0; - while ($i++) { - if ($i === 1000000) { - - } - if ($i === 'string') { - - } - } - - if ($i === 1000000) { - - } - if ($i === 'string') { - - } - } - + public function doFoo() + { + $i = 0; + while ($i++) { + if ($i === 1000000) { + } + if ($i === 'string') { + } + } + + if ($i === 1000000) { + } + if ($i === 'string') { + } + } } class ConstantTypeInDoWhile { - - public function doFoo() - { - $i = 0; - do { - if ($i === 1000000) { - - } - if ($i === 'string') { - - } - } while ($i++); - - if ($i === 1000000) { - - } - if ($i === 'string') { - - } - } - + public function doFoo() + { + $i = 0; + do { + if ($i === 1000000) { + } + if ($i === 'string') { + } + } while ($i++); + + if ($i === 1000000) { + } + if ($i === 'string') { + } + } } class ConstantAssignOperatorInWhile { - - public function doFoo(bool $bool) - { - $i = 10.0; - while ($bool) { - $i /= 5; - if ($i === 1000000.0) { - - } - if ($i === 'string') { - - } - } - - if ($i === 1000000.0) { - - } - if ($i === 'string') { - - } - } - + public function doFoo(bool $bool) + { + $i = 10.0; + while ($bool) { + $i /= 5; + if ($i === 1000000.0) { + } + if ($i === 'string') { + } + } + + if ($i === 1000000.0) { + } + if ($i === 'string') { + } + } } class NullArrayKey { - - public function doFoo() - { - $array = []; - $array['key'] = null; - if ($array['key'] !== null) { - - } - } - + public function doFoo() + { + $array = []; + $array['key'] = null; + if ($array['key'] !== null) { + } + } } class OverwriteSpecifiedVariable { - - public function doFoo() - { - /** @var int[] $array */ - $array = doFoo(); - if ($array['key'] === 1) { - $array = [ - 'key' => 0, - ]; - $array['key'] === 0; - } - } - + public function doFoo() + { + /** @var int[] $array */ + $array = doFoo(); + if ($array['key'] === 1) { + $array = [ + 'key' => 0, + ]; + $array['key'] === 0; + } + } } class StrictComparisonOfSpecifiedFunctionCall { - - public function doFoo() - { - if (is_int($this->nullableInt())) { - if ($this->nullableInt() === null) { - - } - } - } - - public function nullableInt(): ?int - { - - } - + public function doFoo() + { + if (is_int($this->nullableInt())) { + if ($this->nullableInt() === null) { + } + } + } + + public function nullableInt(): ?int + { + } } class CheckDefaultArrayKeys { - - /** - * @param string[] $array - */ - public function doFoo(array $array) - { - foreach ($array as $key => $val) { - if ($key === 1) { - - } elseif ($key === 'str') { - - } elseif ($key === 1.0) { - - } elseif ($key === new \stdClass()) { - - } - } - } - + /** + * @param string[] $array + */ + public function doFoo(array $array) + { + foreach ($array as $key => $val) { + if ($key === 1) { + } elseif ($key === 'str') { + } elseif ($key === 1.0) { + } elseif ($key === new \stdClass()) { + } + } + } } class DoNotReportPropertyFetchAndNullComparison { - - /** @var self */ - private $foo; - - /** @var self */ - private static $bar; - - public function doFoo() - { - if ($this->foo === null) { - - } - if ($this->foo !== null) { - - } - if (null === $this->foo) { - - } - if (null !== $this->foo) { - - } - } - - public function doBar() - { - if (self::$bar === null) { - - } - if (self::$bar !== null) { - - } - if (null === self::$bar) { - - } - if (null !== self::$bar) { - - } - } - + /** @var self */ + private $foo; + + /** @var self */ + private static $bar; + + public function doFoo() + { + if ($this->foo === null) { + } + if ($this->foo !== null) { + } + if (null === $this->foo) { + } + if (null !== $this->foo) { + } + } + + public function doBar() + { + if (self::$bar === null) { + } + if (self::$bar !== null) { + } + if (null === self::$bar) { + } + if (null !== self::$bar) { + } + } } class ComparingAgainstEmptyArray { - - /** - * @param string[] $strings - * @param mixed[] $mixeds - */ - public function doFoo( - array $strings, - array $mixeds - ) - { - if ($strings === []) { - - } - if ($mixeds === []) { - - } - } - - /** - * @param string[] $strings - * @param mixed[] $mixeds - */ - public function doBar( - array $strings, - array $mixeds - ) - { - if ([] === $strings) { - - } - if ([] === $mixeds) { - - } - } - + /** + * @param string[] $strings + * @param mixed[] $mixeds + */ + public function doFoo( + array $strings, + array $mixeds + ) { + if ($strings === []) { + } + if ($mixeds === []) { + } + } + + /** + * @param string[] $strings + * @param mixed[] $mixeds + */ + public function doBar( + array $strings, + array $mixeds + ) { + if ([] === $strings) { + } + if ([] === $mixeds) { + } + } } class StaticVar { + public function doFoo() + { + static $i = null; - public function doFoo() - { - static $i = null; - - if ($i === 5) { - - } - - $i = rand(0, 1); - } + if ($i === 5) { + } + $i = rand(0, 1); + } } class DuplicateConditionNeverError { - - public function sort(int $a, int $b) - { - $c = 0; - - if ($c === $a && $c === $b) { - return +1; - } - - if ($c === $a) { - return -1; - } - - if ($c === $b) { - return +1; - } - } - + public function sort(int $a, int $b) + { + $c = 0; + + if ($c === $a && $c === $b) { + return +1; + } + + if ($c === $a) { + return -1; + } + + if ($c === $b) { + return +1; + } + } } class CoalesceWithConstantArray { + /** @var string[] */ + private const B = [ + 'foo' => 'bar', + ]; - /** @var string[] */ - private const B = [ - 'foo' => 'bar', - ]; - - public function doFoo(string $x): string - { - $class = self::B[$x] ?? null; + public function doFoo(string $x): string + { + $class = self::B[$x] ?? null; - if ($class === null) { - throw new \Exception(); - } - - return $class; - } + if ($class === null) { + throw new \Exception(); + } + return $class; + } } class NonIdempotentOperationInForeach { - - public function doFoo(array $array) - { - $i = 10; - foreach ($array as $foo) { - if (rand(0, 1) === 100) { - $i *= 10; - if ($i === 'foo') { - } - } - } - - $nullableVal = null; - foreach ($array as $foo) { - if ($nullableVal === null) { - $nullableVal = 1; - } else { - $nullableVal *= 10; - if ($nullableVal === 'foo') { - - } - } - } - } - + public function doFoo(array $array) + { + $i = 10; + foreach ($array as $foo) { + if (rand(0, 1) === 100) { + $i *= 10; + if ($i === 'foo') { + } + } + } + + $nullableVal = null; + foreach ($array as $foo) { + if ($nullableVal === null) { + $nullableVal = 1; + } else { + $nullableVal *= 10; + if ($nullableVal === 'foo') { + } + } + } + } } class ArrayWithLongStrings { - - public function doFoo() - { - $array = ['foofoofoofoofoofoofoo','foofoofoofoofoofoofob']; - - foreach ($array as $value) { - if ('foofoofoofoofoofoofoo' === $value) { - echo 'nope'; - } elseif ('foofoofoofoofoofoofob' === $value) { - echo 'nop nope'; - } - } - } - + public function doFoo() + { + $array = ['foofoofoofoofoofoofoo','foofoofoofoofoofoofob']; + + foreach ($array as $value) { + if ('foofoofoofoofoofoofoo' === $value) { + echo 'nope'; + } elseif ('foofoofoofoofoofoofob' === $value) { + echo 'nop nope'; + } + } + } } class ArrayObjectToArrayCount { - - public function doFoo() - { - $rules = (array) new \ArrayObject([1, 2, 3]); - if (count($rules) === 1) { - - } - } - + public function doFoo() + { + $rules = (array) new \ArrayObject([1, 2, 3]); + if (count($rules) === 1) { + } + } } class WrongNullabilityInPhpDoc { - - /** - * @param string $str - */ - public function doFoo(?string $str) - { - $str === 'str'; - $str === null; - $str === 1; - } - - /** - * @param string $str - */ - public function doBar(?string $str = null) - { - $str === 'str'; - $str === null; - $str === 1; - } - - /** - * @param string $str - */ - public function doBaz(?string $str = '') - { - $str === 'str'; - $str === null; - $str === 1; - } - + /** + * @param string $str + */ + public function doFoo(?string $str) + { + $str === 'str'; + $str === null; + $str === 1; + } + + /** + * @param string $str + */ + public function doBar(?string $str = null) + { + $str === 'str'; + $str === null; + $str === 1; + } + + /** + * @param string $str + */ + public function doBaz(?string $str = '') + { + $str === 'str'; + $str === null; + $str === 1; + } } class ComplexSwitch { - - public function testing(array $types): void { - $test = null; - foreach ($types as $t) { - switch ($t) { - case 'foo': - $test = 'fff'; - break; - - case 'bar': - $test = 'bbb'; - break; - } - - if ($test !== null) { - echo "Found"; - break; - } - } - - if ($test === null) { - echo "Is null"; - } - } - + public function testing(array $types): void + { + $test = null; + foreach ($types as $t) { + switch ($t) { + case 'foo': + $test = 'fff'; + break; + + case 'bar': + $test = 'bbb'; + break; + } + + if ($test !== null) { + echo "Found"; + break; + } + } + + if ($test === null) { + echo "Is null"; + } + } } class IgnoredBreakBranchInForeach { - - public function doFoo(string $xvalue, array $allVowels) - { - $lastLetter = null; - foreach ($allVowels as $yvalue) { - if (strcmp($xvalue, $yvalue) == 0 ) { - $lastLetter = $xvalue; - break; - } else { - continue; - } - } - if ($lastLetter !== null) { - } - } - + public function doFoo(string $xvalue, array $allVowels) + { + $lastLetter = null; + foreach ($allVowels as $yvalue) { + if (strcmp($xvalue, $yvalue) == 0) { + $lastLetter = $xvalue; + break; + } else { + continue; + } + } + if ($lastLetter !== null) { + } + } } class DecrementInForeachWithBreak { - - public function doFoo() - { - $max = 0 === rand(0, 1) ? 2 : 3; - foreach ([1, 2, 3, 4, 5] as $number) { - if (0 === $max) { - break; - } - - echo $number; - $max--; - } - } - + public function doFoo() + { + $max = 0 === rand(0, 1) ? 2 : 3; + foreach ([1, 2, 3, 4, 5] as $number) { + if (0 === $max) { + break; + } + + echo $number; + $max--; + } + } } class RewrittenArray { - - public function doFoo(array $args) - { - if (isset($args[0]) === true) { - if (is_array($args[0]) === true) { - $args = $args[0]; - } - - if (isset($args[0]) === false) { - echo 'foo'; - } - } - } - + public function doFoo(array $args) + { + if (isset($args[0]) === true) { + if (is_array($args[0]) === true) { + $args = $args[0]; + } + + if (isset($args[0]) === false) { + echo 'foo'; + } + } + } } class SubtractedMixed { - - public function doFoo($mixed) - { - if ($mixed === 1) { - return; - } - - if (is_string($mixed)) { - return; - } - - if ($mixed === 'foo') { - return; - } - - if ($mixed !== 1) { - return; - } - } - - public function doBar($mixed) - { - if ($mixed === null) { - - } elseif ($this->foo === 'bbb') { - - } - - $mixed === null; - } - + public function doFoo($mixed) + { + if ($mixed === 1) { + return; + } + + if (is_string($mixed)) { + return; + } + + if ($mixed === 'foo') { + return; + } + + if ($mixed !== 1) { + return; + } + } + + public function doBar($mixed) + { + if ($mixed === null) { + } elseif ($this->foo === 'bbb') { + } + + $mixed === null; + } } class InvalidatingProperties { - - /** @var self */ - private $self; - - /** @var self */ - private $selfa; - - /** @var string */ - private $prop; - - public function test() - { - assert($this->self->prop === 'foo'); - assert($this->selfa->prop === 'foo'); - if ($this->self->prop === 'foo') { - - } - if ($this->selfa->prop === 'foo') { - - } - - $this->self = new self(); - if ($this->self->prop === 'foo') { - - } - if ($this->selfa->prop === 'foo') { - - } - } - + /** @var self */ + private $self; + + /** @var self */ + private $selfa; + + /** @var string */ + private $prop; + + public function test() + { + assert($this->self->prop === 'foo'); + assert($this->selfa->prop === 'foo'); + if ($this->self->prop === 'foo') { + } + if ($this->selfa->prop === 'foo') { + } + + $this->self = new self(); + if ($this->self->prop === 'foo') { + } + if ($this->selfa->prop === 'foo') { + } + } } class InvalidatingVariables { - - /** @var string */ - private $prop; - - public function test() - { - $self = new self(); - $selfa = new self(); - assert($self->prop === 'foo'); - assert($selfa->prop === 'foo'); - if ($self->prop === 'foo') { - - } - if ($selfa->prop === 'foo') { - - } - - $self = new self(); - if ($self->prop === 'foo') { - - } - if ($selfa->prop === 'foo') { - - } - } - + /** @var string */ + private $prop; + + public function test() + { + $self = new self(); + $selfa = new self(); + assert($self->prop === 'foo'); + assert($selfa->prop === 'foo'); + if ($self->prop === 'foo') { + } + if ($selfa->prop === 'foo') { + } + + $self = new self(); + if ($self->prop === 'foo') { + } + if ($selfa->prop === 'foo') { + } + } } class InvalidationAfterCallingSideEffects { - - public function doFoo(\DateTime $dt) - { - assert($dt->getTimestamp() === 1000); - $dt->modify('+1 hours'); - if ($dt->getTimestamp() === 1000) { - - } - } - - public function doBar(\DateTimeImmutable $dti) - { - assert($dti->getTimestamp() === 1000); - $dti->modify('+1 hours'); - if ($dti->getTimestamp() === 1000) { - - } - } - + public function doFoo(\DateTime $dt) + { + assert($dt->getTimestamp() === 1000); + $dt->modify('+1 hours'); + if ($dt->getTimestamp() === 1000) { + } + } + + public function doBar(\DateTimeImmutable $dti) + { + assert($dti->getTimestamp() === 1000); + $dti->modify('+1 hours'); + if ($dti->getTimestamp() === 1000) { + } + } } class InvalidateAfterCallingSideEffectsUserlandMethod { - - /** @var int|null */ - private $foo; - - public function doFoo() - { - $this->foo = 123; - $this->returnsVoid(); - if ($this->foo === 456) { - - } - } - - private function returnsVoid(): void - { - $this->foo = null; - } - + /** @var int|null */ + private $foo; + + public function doFoo() + { + $this->foo = 123; + $this->returnsVoid(); + if ($this->foo === 456) { + } + } + + private function returnsVoid(): void + { + $this->foo = null; + } } -abstract class RandomAbstractClass {} +abstract class RandomAbstractClass +{ +} class AvoidRegressionBecauseThisWasReturningAnErrorInPhpstan11_5 { - private const MAPPING = [\stdClass::class, RandomAbstractClass::class]; - - public function test(int $key) - { - if (is_subclass_of(self::MAPPING[$key], RandomAbstractClass::class) === false) { - return; - } - } + private const MAPPING = [\stdClass::class, RandomAbstractClass::class]; + + public function test(int $key) + { + if (is_subclass_of(self::MAPPING[$key], RandomAbstractClass::class) === false) { + return; + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/ternary-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/ternary-not-phpdoc.php index db99e46e51..ffb60b0a35 100644 --- a/tests/PHPStan/Rules/Comparison/data/ternary-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/ternary-not-phpdoc.php @@ -4,17 +4,14 @@ class Ternary { - - /** - * @param object $object - */ - public function doFoo( - self $self, - $object - ): void - { - $self ? 'foo' : 'bar'; - $object ? 'foo' : 'bar'; - } - + /** + * @param object $object + */ + public function doFoo( + self $self, + $object + ): void { + $self ? 'foo' : 'bar'; + $object ? 'foo' : 'bar'; + } } diff --git a/tests/PHPStan/Rules/Comparison/data/ternary.php b/tests/PHPStan/Rules/Comparison/data/ternary.php index e2c3048ec4..9e18ab9f76 100644 --- a/tests/PHPStan/Rules/Comparison/data/ternary.php +++ b/tests/PHPStan/Rules/Comparison/data/ternary.php @@ -4,72 +4,70 @@ class Ternary { + public function doFoo(int $i, \stdClass $std) + { + $i ? 'foo' : 'bar'; + $std ? 'foo' : 'bar'; + !$std ? 'foo' : 'bar'; - public function doFoo(int $i, \stdClass $std) - { - $i ? 'foo' : 'bar'; - $std ? 'foo' : 'bar'; - !$std ? 'foo' : 'bar'; + $zero = 0; + $zero ? 'foo' : 'bar'; + } - $zero = 0; - $zero ? 'foo' : 'bar'; - } + public function doBar(array $a) + { + $a ? 1 : 2; + $a ? 3 : 4; + } - public function doBar(array $a) - { - $a ? 1 : 2; - $a ? 3 : 4; - } + public function doBaz(array $a) + { + if (!$a) { + } - public function doBaz(array $a) - { - if (!$a) { - } + print $a ? 'aa' : 'bb'; + } - print $a ? 'aa' : 'bb'; - } + public function doLorem(array $a) + { + if (!$a || $a['a']) { + } elseif ($a['b']) { + } - public function doLorem(array $a) - { - if (!$a || $a['a']) { - } elseif ($a['b']) { - } + print $a ? 'aa' : 'bb'; + } - print $a ? 'aa' : 'bb'; - } + public function doIpsum(array $keys, string $value): void + { + if ($value) { + $params = []; - public function doIpsum(array $keys, string $value): void - { - if ($value) { - $params = []; + foreach ($keys as $k) { + $operator = $params ? ' OR ' : ''; + $likeParam = true; - foreach ($keys as $k) { - $operator = $params ? ' OR ' : ''; - $likeParam = true; + switch ($k) { + case 'abc': + $likeParam = false; + break; + } - switch ($k) { - case 'abc': - $likeParam = false; - break; - } - - if ($likeParam) { - $params[] = 'like...'; - } - } - } - } - - public function ternaryInIfCondition($mixed): void - { - if (!$mixed) { - $a = $mixed ? 'foo' : 'bar'; - $b = $mixed ?: 'bar'; - } - if ($mixed) { - $a = $mixed ? 'foo' : 'bar'; - $b = $mixed ?: 'bar'; - } - } + if ($likeParam) { + $params[] = 'like...'; + } + } + } + } + public function ternaryInIfCondition($mixed): void + { + if (!$mixed) { + $a = $mixed ? 'foo' : 'bar'; + $b = $mixed ?: 'bar'; + } + if ($mixed) { + $a = $mixed ? 'foo' : 'bar'; + $b = $mixed ?: 'bar'; + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches-not-phpdoc.php b/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches-not-phpdoc.php index 14e6efabf0..6bfa0949f2 100644 --- a/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches-not-phpdoc.php +++ b/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches-not-phpdoc.php @@ -4,66 +4,43 @@ class Foo { - - /** - * @param self $phpDocSelf - */ - public function doFoo( - self $self, - $phpDocSelf - ) - { - if ($self instanceof self) { - - } elseif (rand(0, 1)) { - - } - - if (rand(0, 1)) { - - } elseif (rand(0, 1)) { - - } elseif ($self instanceof self) { - - } else { - - } - - if (rand(0, 1)) { - - } elseif (rand(0, 1)) { - - } elseif ($self instanceof self) { - - } elseif (rand(0, 1)) { - - } - - if ($phpDocSelf instanceof self) { - - } elseif (rand(0, 1)) { - - } - - if (rand(0, 1)) { - - } elseif (rand(0, 1)) { - - } elseif ($phpDocSelf instanceof self) { - - } else { - - } - - if (rand(0, 1)) { - - } elseif (rand(0, 1)) { - - } elseif ($phpDocSelf instanceof self) { - - } elseif (rand(0, 1)) { - - } - } - + /** + * @param self $phpDocSelf + */ + public function doFoo( + self $self, + $phpDocSelf + ) { + if ($self instanceof self) { + } elseif (rand(0, 1)) { + } + + if (rand(0, 1)) { + } elseif (rand(0, 1)) { + } elseif ($self instanceof self) { + } else { + } + + if (rand(0, 1)) { + } elseif (rand(0, 1)) { + } elseif ($self instanceof self) { + } elseif (rand(0, 1)) { + } + + if ($phpDocSelf instanceof self) { + } elseif (rand(0, 1)) { + } + + if (rand(0, 1)) { + } elseif (rand(0, 1)) { + } elseif ($phpDocSelf instanceof self) { + } else { + } + + if (rand(0, 1)) { + } elseif (rand(0, 1)) { + } elseif ($phpDocSelf instanceof self) { + } elseif (rand(0, 1)) { + } + } } diff --git a/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches.php b/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches.php index cebb31f750..c5edc707d1 100644 --- a/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches.php +++ b/tests/PHPStan/Rules/Comparison/data/unreachable-if-branches.php @@ -1,45 +1,27 @@ = 8.0 += 8.0 namespace VoidMatch; class Foo { + public function doFoo(): void + { + } - public function doFoo(): void - { - - } - - public function doBar(int $i): void - { - match ($i) { - 1 => $this->doFoo(), - 2 => $this->doFoo(), - default => $this->doFoo(), - }; - - $a = match ($i) { - 1 => $this->doFoo(), - 2 => $this->doFoo(), - default => $this->doFoo(), - }; - } + public function doBar(int $i): void + { + match ($i) { + 1 => $this->doFoo(), + 2 => $this->doFoo(), + default => $this->doFoo(), + }; + $a = match ($i) { + 1 => $this->doFoo(), + 2 => $this->doFoo(), + // no break + default => $this->doFoo(), + }; + } } diff --git a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php index cef571f595..88f29006c4 100644 --- a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php +++ b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/constants.php'], [ - [ - 'Constant NONEXISTENT_CONSTANT not found.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Constant DEFINED_CONSTANT not found.', - 13, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - /*[ - 'Constant DEFINED_CONSTANT_IF not found.', - 21, - ],*/ - ]); - } - - public function testCompilerHaltOffsetConstantFalseDetection(): void - { - $this->analyse([__DIR__ . '/data/compiler-halt-offset-const-defined.php'], []); - } + public function testConstants(): void + { + define('FOO_CONSTANT', 'foo'); + define('Constants\\BAR_CONSTANT', 'bar'); + define('OtherConstants\\BAZ_CONSTANT', 'baz'); + $this->analyse([__DIR__ . '/data/constants.php'], [ + [ + 'Constant NONEXISTENT_CONSTANT not found.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant DEFINED_CONSTANT not found.', + 13, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + /*[ + 'Constant DEFINED_CONSTANT_IF not found.', + 21, + ],*/ + ]); + } - public function testCompilerHaltOffsetConstantIsUndefinedDetection(): void - { - $this->analyse([__DIR__ . '/data/compiler-halt-offset-const-not-defined.php'], [ - [ - 'Constant __COMPILER_HALT_OFFSET__ not found.', - 3, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + public function testCompilerHaltOffsetConstantFalseDetection(): void + { + $this->analyse([__DIR__ . '/data/compiler-halt-offset-const-defined.php'], []); + } - ], - ]); - } + public function testCompilerHaltOffsetConstantIsUndefinedDetection(): void + { + $this->analyse([__DIR__ . '/data/compiler-halt-offset-const-not-defined.php'], [ + [ + 'Constant __COMPILER_HALT_OFFSET__ not found.', + 3, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - public function testConstEquals(): void - { - $this->analyse([__DIR__ . '/data/const-equals.php'], []); - } + ], + ]); + } - public function testConstEqualsNoNamespace(): void - { - $this->analyse([__DIR__ . '/data/const-equals-no-namespace.php'], []); - } + public function testConstEquals(): void + { + $this->analyse([__DIR__ . '/data/const-equals.php'], []); + } + public function testConstEqualsNoNamespace(): void + { + $this->analyse([__DIR__ . '/data/const-equals-no-namespace.php'], []); + } } diff --git a/tests/PHPStan/Rules/Constants/DirectAlwaysUsedClassConstantsExtensionProvider.php b/tests/PHPStan/Rules/Constants/DirectAlwaysUsedClassConstantsExtensionProvider.php index 924992ae73..1b5c557ba0 100644 --- a/tests/PHPStan/Rules/Constants/DirectAlwaysUsedClassConstantsExtensionProvider.php +++ b/tests/PHPStan/Rules/Constants/DirectAlwaysUsedClassConstantsExtensionProvider.php @@ -1,27 +1,27 @@ -extensions = $extensions; - } - - /** - * @return AlwaysUsedClassConstantsExtension[] - */ - public function getExtensions(): array - { - return $this->extensions; - } + /** + * @param AlwaysUsedClassConstantsExtension[] $extensions + */ + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + /** + * @return AlwaysUsedClassConstantsExtension[] + */ + public function getExtensions(): array + { + return $this->extensions; + } } diff --git a/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-defined.php b/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-defined.php index a116c619bb..8989be68ab 100644 --- a/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-defined.php +++ b/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-defined.php @@ -1,12 +1,12 @@ - $name, - ]); + return strtr($template, [ + '%name%' => $name, + ]); } echo greet('Bob'); diff --git a/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-not-defined.php b/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-not-defined.php index 37d95c6b5c..055a8ebb7f 100644 --- a/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-not-defined.php +++ b/tests/PHPStan/Rules/Constants/data/compiler-halt-offset-const-not-defined.php @@ -1,3 +1,5 @@ - CONSOLE_PATH . ' cron:update-popular-today --no-debug', - 'schedule' => '0 */6 * * *', - 'output' => 'logs/update-popular-today.log', + 'command' => CONSOLE_PATH . ' cron:update-popular-today --no-debug', + 'schedule' => '0 */6 * * *', + 'output' => 'logs/update-popular-today.log', ]; define('ANOTHER_PATH', 'test'); @@ -12,6 +12,6 @@ echo ANOTHER_PATH; function () { - echo CONSOLE_PATH; - echo ANOTHER_PATH; + echo CONSOLE_PATH; + echo ANOTHER_PATH; }; diff --git a/tests/PHPStan/Rules/Constants/data/const-equals.php b/tests/PHPStan/Rules/Constants/data/const-equals.php index 6f7af2a83d..a12d72225f 100644 --- a/tests/PHPStan/Rules/Constants/data/const-equals.php +++ b/tests/PHPStan/Rules/Constants/data/const-equals.php @@ -4,9 +4,9 @@ const CONSOLE_PATH = __DIR__ . '/../../bin/console'; [ - 'command' => CONSOLE_PATH . ' cron:update-popular-today --no-debug', - 'schedule' => '0 */6 * * *', - 'output' => 'logs/update-popular-today.log', + 'command' => CONSOLE_PATH . ' cron:update-popular-today --no-debug', + 'schedule' => '0 */6 * * *', + 'output' => 'logs/update-popular-today.log', ]; define('ConstEquals\\ANOTHER_PATH', 'test'); @@ -18,7 +18,7 @@ echo \DiffNamespace\ANOTHER_PATH; function () { - echo CONSOLE_PATH; - echo ANOTHER_PATH; - echo \DiffNamespace\ANOTHER_PATH; + echo CONSOLE_PATH; + echo ANOTHER_PATH; + echo \DiffNamespace\ANOTHER_PATH; }; diff --git a/tests/PHPStan/Rules/Constants/data/constants.php b/tests/PHPStan/Rules/Constants/data/constants.php index c7e9d5439c..2c163fe897 100644 --- a/tests/PHPStan/Rules/Constants/data/constants.php +++ b/tests/PHPStan/Rules/Constants/data/constants.php @@ -10,19 +10,19 @@ echo NONEXISTENT_CONSTANT; function () { - echo DEFINED_CONSTANT; - define('DEFINED_CONSTANT', true); - echo DEFINED_CONSTANT; + echo DEFINED_CONSTANT; + define('DEFINED_CONSTANT', true); + echo DEFINED_CONSTANT; - if (defined('DEFINED_CONSTANT_IF')) { - echo DEFINED_CONSTANT_IF; - } + if (defined('DEFINED_CONSTANT_IF')) { + echo DEFINED_CONSTANT_IF; + } - echo DEFINED_CONSTANT_IF; + echo DEFINED_CONSTANT_IF; - if (!defined("OMIT_INDIC_FIX_1") || OMIT_INDIC_FIX_1 != 1) { - // ... - } + if (!defined("OMIT_INDIC_FIX_1") || OMIT_INDIC_FIX_1 != 1) { + // ... + } }; const CONSTANT_IN_CONST_ASSIGN = 1; diff --git a/tests/PHPStan/Rules/DateTimeInstantiationRuleTest.php b/tests/PHPStan/Rules/DateTimeInstantiationRuleTest.php index 6bd57a85c0..3cca054d45 100644 --- a/tests/PHPStan/Rules/DateTimeInstantiationRuleTest.php +++ b/tests/PHPStan/Rules/DateTimeInstantiationRuleTest.php @@ -1,4 +1,6 @@ -analyse( - [__DIR__ . '/data/datetime-instantiation.php'], - [ - [ - 'Instantiating DateTime with 2020.11.17 produces an error: Double time specification', - 3, - ], - /*[ - 'Instantiating DateTimeImmutable with asdfasdf produces a warning: Double timezone specification', - 5, - ],*/ - [ - 'Instantiating DateTimeImmutable with asdfasdf produces an error: The timezone could not be found in the database', - 5, - ], - [ - 'Instantiating DateTimeImmutable with 2020.11.17 produces an error: Double time specification', - 10, - ], - [ - 'Instantiating DateTimeImmutable with 2020.11.18 produces an error: Double time specification', - 17, - ], - /*[ - 'Instantiating DateTime with 2020-04-31 produces a warning: The parsed date was invalid', - 20, - ],*/ - ] - ); - } - + public function test(): void + { + $this->analyse( + [__DIR__ . '/data/datetime-instantiation.php'], + [ + [ + 'Instantiating DateTime with 2020.11.17 produces an error: Double time specification', + 3, + ], + /*[ + 'Instantiating DateTimeImmutable with asdfasdf produces a warning: Double timezone specification', + 5, + ],*/ + [ + 'Instantiating DateTimeImmutable with asdfasdf produces an error: The timezone could not be found in the database', + 5, + ], + [ + 'Instantiating DateTimeImmutable with 2020.11.17 produces an error: Double time specification', + 10, + ], + [ + 'Instantiating DateTimeImmutable with 2020.11.18 produces an error: Double time specification', + 17, + ], + /*[ + 'Instantiating DateTime with 2020-04-31 produces a warning: The parsed date was invalid', + 20, + ],*/ + ] + ); + } } diff --git a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php index 23a83440e5..de40f40f22 100644 --- a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/noop.php'], [ - [ - 'Expression "$arr" on a separate line does not do anything.', - 9, - ], - [ - 'Expression "$arr[\'test\']" on a separate line does not do anything.', - 10, - ], - [ - 'Expression "$foo::$test" on a separate line does not do anything.', - 11, - ], - [ - 'Expression "$foo->test" on a separate line does not do anything.', - 12, - ], - [ - 'Expression "\'foo\'" on a separate line does not do anything.', - 14, - ], - [ - 'Expression "1" on a separate line does not do anything.', - 15, - ], - [ - 'Expression "@\'foo\'" on a separate line does not do anything.', - 17, - ], - [ - 'Expression "+1" on a separate line does not do anything.', - 18, - ], - [ - 'Expression "-1" on a separate line does not do anything.', - 19, - ], - [ - 'Expression "isset($test)" on a separate line does not do anything.', - 25, - ], - [ - 'Expression "empty($test)" on a separate line does not do anything.', - 26, - ], - [ - 'Expression "true" on a separate line does not do anything.', - 27, - ], - [ - 'Expression "\DeadCodeNoop\Foo::TEST" on a separate line does not do anything.', - 28, - ], - [ - 'Expression "(string) 1" on a separate line does not do anything.', - 30, - ], - ]); - } - - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/noop.php'], [ + [ + 'Expression "$arr" on a separate line does not do anything.', + 9, + ], + [ + 'Expression "$arr[\'test\']" on a separate line does not do anything.', + 10, + ], + [ + 'Expression "$foo::$test" on a separate line does not do anything.', + 11, + ], + [ + 'Expression "$foo->test" on a separate line does not do anything.', + 12, + ], + [ + 'Expression "\'foo\'" on a separate line does not do anything.', + 14, + ], + [ + 'Expression "1" on a separate line does not do anything.', + 15, + ], + [ + 'Expression "@\'foo\'" on a separate line does not do anything.', + 17, + ], + [ + 'Expression "+1" on a separate line does not do anything.', + 18, + ], + [ + 'Expression "-1" on a separate line does not do anything.', + 19, + ], + [ + 'Expression "isset($test)" on a separate line does not do anything.', + 25, + ], + [ + 'Expression "empty($test)" on a separate line does not do anything.', + 26, + ], + [ + 'Expression "true" on a separate line does not do anything.', + 27, + ], + [ + 'Expression "\DeadCodeNoop\Foo::TEST" on a separate line does not do anything.', + 28, + ], + [ + 'Expression "(string) 1" on a separate line does not do anything.', + 30, + ], + ]); + } - $this->analyse([__DIR__ . '/data/nullsafe-property-fetch-noop.php'], [ - [ - 'Expression "$ref?->name" on a separate line does not do anything.', - 10, - ], - ]); - } + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/nullsafe-property-fetch-noop.php'], [ + [ + 'Expression "$ref?->name" on a separate line does not do anything.', + 10, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 16692000ca..fdfa11c3f8 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -1,4 +1,6 @@ -treatPhpDocTypesAsCertain; - } - - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable.php'], [ - [ - 'Unreachable statement - code above always terminates.', - 12, - ], - [ - 'Unreachable statement - code above always terminates.', - 19, - ], - [ - 'Unreachable statement - code above always terminates.', - 30, - ], - [ - 'Unreachable statement - code above always terminates.', - 71, - ], - ]); - } - - public function testRuleTopLevel(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable-top-level.php'], [ - [ - 'Unreachable statement - code above always terminates.', - 5, - ], - ]); - } - - public function dataBugWithoutGitHubIssue1(): array - { - return [ - [ - true, - ], - [ - false, - ], - ]; - } - - /** - * @dataProvider dataBugWithoutGitHubIssue1 - * @param bool $treatPhpDocTypesAsCertain - */ - public function testBugWithoutGitHubIssue1(bool $treatPhpDocTypesAsCertain): void - { - $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->analyse([__DIR__ . '/data/bug-without-issue-1.php'], []); - } - - public function testBug4070(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4070.php'], []); - } - - public function testBug4070Two(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4070_2.php'], []); - } - - public function testBug4076(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4076.php'], []); - } - - public function testBug4535(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4535.php'], []); - } - - public function testBug4346(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-4346.php'], []); - } - - public function testBug2913(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-2913.php'], []); - } - + /** @var bool */ + private $treatPhpDocTypesAsCertain; + + protected function getRule(): Rule + { + return new UnreachableStatementRule(); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } + + public function testRule(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/unreachable.php'], [ + [ + 'Unreachable statement - code above always terminates.', + 12, + ], + [ + 'Unreachable statement - code above always terminates.', + 19, + ], + [ + 'Unreachable statement - code above always terminates.', + 30, + ], + [ + 'Unreachable statement - code above always terminates.', + 71, + ], + ]); + } + + public function testRuleTopLevel(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/unreachable-top-level.php'], [ + [ + 'Unreachable statement - code above always terminates.', + 5, + ], + ]); + } + + public function dataBugWithoutGitHubIssue1(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } + + /** + * @dataProvider dataBugWithoutGitHubIssue1 + * @param bool $treatPhpDocTypesAsCertain + */ + public function testBugWithoutGitHubIssue1(bool $treatPhpDocTypesAsCertain): void + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->analyse([__DIR__ . '/data/bug-without-issue-1.php'], []); + } + + public function testBug4070(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4070.php'], []); + } + + public function testBug4070Two(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4070_2.php'], []); + } + + public function testBug4076(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4076.php'], []); + } + + public function testBug4535(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4535.php'], []); + } + + public function testBug4346(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4346.php'], []); + } + + public function testBug2913(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-2913.php'], []); + } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php index a6e12e7cab..59e288b6b2 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php @@ -1,4 +1,6 @@ -getDeclaringClass()->getName() === TestExtension::class - && $constant->getName() === 'USED'; - } - - }, - ]) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/unused-private-constant.php'], [ - [ - 'Constant UnusedPrivateConstant\Foo::BAR_CONST is unused.', - 10, - ], - [ - 'Constant UnusedPrivateConstant\TestExtension::UNUSED is unused.', - 23, - ], - ]); - } - + protected function getRule(): Rule + { + return new UnusedPrivateConstantRule( + new DirectAlwaysUsedClassConstantsExtensionProvider([ + new class() implements AlwaysUsedClassConstantsExtension { + public function isAlwaysUsed(ConstantReflection $constant): bool + { + return $constant->getDeclaringClass()->getName() === TestExtension::class + && $constant->getName() === 'USED'; + } + }, + ]) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/unused-private-constant.php'], [ + [ + 'Constant UnusedPrivateConstant\Foo::BAR_CONST is unused.', + 10, + ], + [ + 'Constant UnusedPrivateConstant\TestExtension::UNUSED is unused.', + 23, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index b06a4c013d..00ed72a094 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/unused-private-method.php'], [ - [ - 'Method UnusedPrivateMethod\Foo::doFoo() is unused.', - 8, - ], - [ - 'Method UnusedPrivateMethod\Foo::doBar() is unused.', - 13, - ], - [ - 'Static method UnusedPrivateMethod\Foo::unusedStaticMethod() is unused.', - 44, - ], - [ - 'Method UnusedPrivateMethod\Bar::doBaz() is unused.', - 59, - ], - [ - 'Method UnusedPrivateMethod\Lorem::doBaz() is unused.', - 97, - ], - ]); - } - - public function testBug3630(): void - { - $this->analyse([__DIR__ . '/data/bug-3630.php'], []); - } - - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/nullsafe-unused-private-method.php'], []); - } - + protected function getRule(): Rule + { + return new UnusedPrivateMethodRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/unused-private-method.php'], [ + [ + 'Method UnusedPrivateMethod\Foo::doFoo() is unused.', + 8, + ], + [ + 'Method UnusedPrivateMethod\Foo::doBar() is unused.', + 13, + ], + [ + 'Static method UnusedPrivateMethod\Foo::unusedStaticMethod() is unused.', + 44, + ], + [ + 'Method UnusedPrivateMethod\Bar::doBaz() is unused.', + 59, + ], + [ + 'Method UnusedPrivateMethod\Lorem::doBaz() is unused.', + 97, + ], + ]); + } + + public function testBug3630(): void + { + $this->analyse([__DIR__ . '/data/bug-3630.php'], []); + } + + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/nullsafe-unused-private-method.php'], []); + } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 0df99db626..2d8d12d3c5 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -1,4 +1,6 @@ -getDeclaringClass()->getName() === 'UnusedPrivateProperty\\TestExtension' - && in_array($propertyName, [ - 'read', - 'used', - ], true); - } - - public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool - { - return $property->getDeclaringClass()->getName() === 'UnusedPrivateProperty\\TestExtension' - && in_array($propertyName, [ - 'written', - 'used', - ], true); - } - - public function isInitialized(PropertyReflection $property, string $propertyName): bool - { - return false; - } - - }, - ]), - $this->alwaysWrittenTags, - $this->alwaysReadTags, - true - ); - } - - public function testRule(): void - { - if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 7.4 or static reflection.'); - } - - $this->alwaysWrittenTags = []; - $this->alwaysReadTags = []; - - $this->analyse([__DIR__ . '/data/unused-private-property.php'], [ - [ - 'Property UnusedPrivateProperty\Foo::$bar is never read, only written.', - 10, - ], - [ - 'Property UnusedPrivateProperty\Foo::$baz is unused.', - 12, - ], - [ - 'Property UnusedPrivateProperty\Foo::$lorem is never written, only read.', - 14, - ], - [ - 'Property UnusedPrivateProperty\Bar::$baz is never written, only read.', - 57, - ], - [ - 'Static property UnusedPrivateProperty\Baz::$bar is never read, only written.', - 86, - ], - [ - 'Static property UnusedPrivateProperty\Baz::$baz is unused.', - 88, - ], - [ - 'Static property UnusedPrivateProperty\Baz::$lorem is never written, only read.', - 90, - ], - [ - 'Property UnusedPrivateProperty\Lorem::$baz is never read, only written.', - 117, - ], - [ - 'Property class@anonymous/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php:152::$bar is unused.', - 153, - ], - [ - 'Property UnusedPrivateProperty\DolorWithAnonymous::$foo is unused.', - 148, - ], - ]); - $this->analyse([__DIR__ . '/data/TestExtension.php'], [ - [ - 'Property UnusedPrivateProperty\TestExtension::$unused is unused.', - 8, - ], - [ - 'Property UnusedPrivateProperty\TestExtension::$read is never written, only read.', - 10, - ], - [ - 'Property UnusedPrivateProperty\TestExtension::$written is never read, only written.', - 12, - ], - ]); - } - - public function testAlwaysUsedTags(): void - { - $this->alwaysWrittenTags = ['@ORM\Column']; - $this->alwaysReadTags = ['@get']; - $this->analyse([__DIR__ . '/data/private-property-with-tags.php'], [ - [ - 'Property PrivatePropertyWithTags\Foo::$title is never read, only written.', - 13, - ], - [ - 'Property PrivatePropertyWithTags\Foo::$text is never written, only read.', - 18, - ], - ]); - } - - public function testTrait(): void - { - $this->alwaysWrittenTags = []; - $this->alwaysReadTags = []; - $this->analyse([__DIR__ . '/data/private-property-trait.php'], []); - } - - public function testBug3636(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->alwaysWrittenTags = []; - $this->alwaysReadTags = []; - $this->analyse([__DIR__ . '/data/bug-3636.php'], [ - [ - 'Property Bug3636\Bar::$date is never written, only read.', - 22, - ], - ]); - } - - public function testPromotedProperties(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->alwaysWrittenTags = []; - $this->alwaysReadTags = ['@get']; - $this->analyse([__DIR__ . '/data/unused-private-promoted-property.php'], [ - [ - 'Property UnusedPrivatePromotedProperty\Foo::$lorem is never read, only written.', - 12, - ], - ]); - } - - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->alwaysWrittenTags = []; - $this->alwaysReadTags = []; - $this->analyse([__DIR__ . '/data/nullsafe-unused-private-property.php'], []); - } - + /** @var string[] */ + private $alwaysWrittenTags; + + /** @var string[] */ + private $alwaysReadTags; + + protected function getRule(): Rule + { + return new UnusedPrivatePropertyRule( + new DirectReadWritePropertiesExtensionProvider([ + new class() implements ReadWritePropertiesExtension { + public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool + { + return $property->getDeclaringClass()->getName() === 'UnusedPrivateProperty\\TestExtension' + && in_array($propertyName, [ + 'read', + 'used', + ], true); + } + + public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool + { + return $property->getDeclaringClass()->getName() === 'UnusedPrivateProperty\\TestExtension' + && in_array($propertyName, [ + 'written', + 'used', + ], true); + } + + public function isInitialized(PropertyReflection $property, string $propertyName): bool + { + return false; + } + }, + ]), + $this->alwaysWrittenTags, + $this->alwaysReadTags, + true + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4 or static reflection.'); + } + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + + $this->analyse([__DIR__ . '/data/unused-private-property.php'], [ + [ + 'Property UnusedPrivateProperty\Foo::$bar is never read, only written.', + 10, + ], + [ + 'Property UnusedPrivateProperty\Foo::$baz is unused.', + 12, + ], + [ + 'Property UnusedPrivateProperty\Foo::$lorem is never written, only read.', + 14, + ], + [ + 'Property UnusedPrivateProperty\Bar::$baz is never written, only read.', + 57, + ], + [ + 'Static property UnusedPrivateProperty\Baz::$bar is never read, only written.', + 86, + ], + [ + 'Static property UnusedPrivateProperty\Baz::$baz is unused.', + 88, + ], + [ + 'Static property UnusedPrivateProperty\Baz::$lorem is never written, only read.', + 90, + ], + [ + 'Property UnusedPrivateProperty\Lorem::$baz is never read, only written.', + 117, + ], + [ + 'Property class@anonymous/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php:152::$bar is unused.', + 153, + ], + [ + 'Property UnusedPrivateProperty\DolorWithAnonymous::$foo is unused.', + 148, + ], + ]); + $this->analyse([__DIR__ . '/data/TestExtension.php'], [ + [ + 'Property UnusedPrivateProperty\TestExtension::$unused is unused.', + 8, + ], + [ + 'Property UnusedPrivateProperty\TestExtension::$read is never written, only read.', + 10, + ], + [ + 'Property UnusedPrivateProperty\TestExtension::$written is never read, only written.', + 12, + ], + ]); + } + + public function testAlwaysUsedTags(): void + { + $this->alwaysWrittenTags = ['@ORM\Column']; + $this->alwaysReadTags = ['@get']; + $this->analyse([__DIR__ . '/data/private-property-with-tags.php'], [ + [ + 'Property PrivatePropertyWithTags\Foo::$title is never read, only written.', + 13, + ], + [ + 'Property PrivatePropertyWithTags\Foo::$text is never written, only read.', + 18, + ], + ]); + } + + public function testTrait(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/private-property-trait.php'], []); + } + + public function testBug3636(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-3636.php'], [ + [ + 'Property Bug3636\Bar::$date is never written, only read.', + 22, + ], + ]); + } + + public function testPromotedProperties(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = ['@get']; + $this->analyse([__DIR__ . '/data/unused-private-promoted-property.php'], [ + [ + 'Property UnusedPrivatePromotedProperty\Foo::$lorem is never read, only written.', + 12, + ], + ]); + } + + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/nullsafe-unused-private-property.php'], []); + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/TestExtension.php b/tests/PHPStan/Rules/DeadCode/data/TestExtension.php index 5c2f9c8306..05ed320163 100644 --- a/tests/PHPStan/Rules/DeadCode/data/TestExtension.php +++ b/tests/PHPStan/Rules/DeadCode/data/TestExtension.php @@ -4,13 +4,11 @@ class TestExtension { + private $unused; - private $unused; + private $read; - private $read; - - private $written; - - private $used; + private $written; + private $used; } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-2913.php b/tests/PHPStan/Rules/DeadCode/data/bug-2913.php index de3055094f..3965f66428 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-2913.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-2913.php @@ -3,17 +3,17 @@ namespace Bug2913; function (): int { - do { - $tier = mt_rand(1, 6); // generate random tier - switch ($tier) { - case 6: - // tier 6 too high for weapons, try generating - // another random tier value. - break; - default: - break 2; - } - } while (true); + do { + $tier = mt_rand(1, 6); // generate random tier + switch ($tier) { + case 6: + // tier 6 too high for weapons, try generating + // another random tier value. + break; + default: + break 2; + } + } while (true); - return $tier; + return $tier; }; diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-3630.php b/tests/PHPStan/Rules/DeadCode/data/bug-3630.php index 602dbe49a7..f529add5af 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-3630.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-3630.php @@ -4,7 +4,7 @@ class HelloWorld { - private function __clone() - { - } + private function __clone() + { + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-3636.php b/tests/PHPStan/Rules/DeadCode/data/bug-3636.php index dd40b218ed..6f4bf5ea95 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-3636.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-3636.php @@ -1,55 +1,49 @@ -= 7.4 += 7.4 namespace Bug3636; class Foo { + /** @var \DateTimeImmutable */ + private $date; - /** @var \DateTimeImmutable */ - private $date; - - public function getDate(): \DateTimeImmutable - { - return $this->date ??= new \DateTimeImmutable(); - } - + public function getDate(): \DateTimeImmutable + { + return $this->date ??= new \DateTimeImmutable(); + } } class Bar { + /** @var \DateTimeImmutable */ + private $date; - /** @var \DateTimeImmutable */ - private $date; - - public function getDate(): ?\DateTimeImmutable - { - return $this->date ?? null; - } - + public function getDate(): ?\DateTimeImmutable + { + return $this->date ?? null; + } } class Baz { + /** @var string */ + private $date; - /** @var string */ - private $date; - - public function getDate(): string - { - return $this->date ?? ($this->date = random_bytes(16)); - } - + public function getDate(): string + { + return $this->date ?? ($this->date = random_bytes(16)); + } } class Lorem { + /** @var string */ + private static $date; - /** @var string */ - private static $date; - - public function getDate(): string - { - return self::$date ?? (self::$date = random_bytes(16)); - } - + public function getDate(): string + { + return self::$date ?? (self::$date = random_bytes(16)); + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4070.php b/tests/PHPStan/Rules/DeadCode/data/bug-4070.php index dd625dc152..e547894c72 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-4070.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4070.php @@ -5,11 +5,11 @@ array_shift($argv); while ($argv) { - $arg = array_shift($argv); - if ($arg === 'foo') { - continue; - } - die(); + $arg = array_shift($argv); + if ($arg === 'foo') { + continue; + } + die(); } echo "finished\n"; diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4070_2.php b/tests/PHPStan/Rules/DeadCode/data/bug-4070_2.php index 1867daf4a4..509fa801cc 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-4070_2.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4070_2.php @@ -3,15 +3,15 @@ namespace Bug4070Two; function () { - array_shift($argv); + array_shift($argv); - while ($argv) { - $arg = array_shift($argv); - if ($arg === 'foo') { - continue; - } - die(); - } + while ($argv) { + $arg = array_shift($argv); + if ($arg === 'foo') { + continue; + } + die(); + } - echo "finished\n"; + echo "finished\n"; }; diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4076.php b/tests/PHPStan/Rules/DeadCode/data/bug-4076.php index be5b929832..4cebd0211c 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-4076.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4076.php @@ -4,22 +4,21 @@ class Foo { + public function test(int $x, int $y): int + { + switch ($x) { + case 0: + return 0; + case 1: + if ($y == 2) { + // continue after the switch + break; + } + // no break + default: + return 99; + } - function test(int $x, int $y): int - { - switch($x) { - case 0: - return 0; - case 1: - if ($y == 2) { - // continue after the switch - break; - } - default: - return 99; - } - - return -1; - } - + return -1; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4346.php b/tests/PHPStan/Rules/DeadCode/data/bug-4346.php index 1bfeab950e..c4fe8b1baa 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-4346.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4346.php @@ -3,10 +3,10 @@ namespace Bug4346; function (): void { - while (true) { - while (true) { - break 2; - } - } - echo 2; + while (true) { + while (true) { + break 2; + } + } + echo 2; }; diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4535.php b/tests/PHPStan/Rules/DeadCode/data/bug-4535.php index c0feb30378..5028d99ae7 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-4535.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4535.php @@ -1,16 +1,18 @@ -getLastScheduleRequest(); - - $p = []; - foreach($thread->getParticipants() as $user) - $p[$user->getId()] = $user; - - $reminderMsg = null; - $reachedSR = !$threadSR; - foreach($thread->getMessages() as $msg) - { - $msgSR = $msg->getScheduleRequest(); - if(!$reachedSR && ($threadSR && $msgSR != $threadSR)) - continue; - - $reachedSR = true; - - if(!$reminderMsg) - $reminderMsg = $msg; - - unset($p[$msg->getFromUser()->getId()]); - - if(!$p) - return false; - } - - if(!$reminderMsg) - throw new \UnexpectedValueException('Expected a reminderMsg but got null for thread'); - - return true; - } + public function checkAndSendThreadNotRepliedNotification(MessageThread $thread): bool + { + $threadSR = $thread->getLastScheduleRequest(); + + $p = []; + foreach ($thread->getParticipants() as $user) { + $p[$user->getId()] = $user; + } + + $reminderMsg = null; + $reachedSR = !$threadSR; + foreach ($thread->getMessages() as $msg) { + $msgSR = $msg->getScheduleRequest(); + if (!$reachedSR && ($threadSR && $msgSR != $threadSR)) { + continue; + } + + $reachedSR = true; + + if (!$reminderMsg) { + $reminderMsg = $msg; + } + + unset($p[$msg->getFromUser()->getId()]); + + if (!$p) { + return false; + } + } + + if (!$reminderMsg) { + throw new \UnexpectedValueException('Expected a reminderMsg but got null for thread'); + } + + return true; + } } class Foo { - /** @var int */ - protected $index = 0; - - /** @var string[][] */ - protected $data = [ - 0 => ['type' => 'id', 'value' => 'foo'], - 1 => ['type' => 'special', 'value' => '.'], - 2 => ['type' => 'id', 'value' => 'bar'], - 3 => ['type' => 'special', 'value' => ';'], - ]; - - protected function next(): void - { - $this->index = $this->index + 1; - } - - protected function check(string $type, ?string $value = null): bool { - return ($this->type() === $type) && (($value === null) || ($this->value() === $value)); - } - - protected function type(): string - { - return $this->data[$this->index]['type']; - } - - protected function value(): string - { - return $this->data[$this->index]['value']; - } - - public function separatedName(): string - { - $name = ''; - $previousType = null; - $separator = '.'; - - $currentValue = $this->value(); - - while ((($this->check('special', $separator)) || ($this->check('id'))) && - (($previousType === null) || ($this->type() !== $previousType)) && - (($previousType !== null) || ($currentValue !== $separator)) - ) { - $name .= $currentValue; - $previousType = $this->type(); - - $this->next(); - $currentValue = $this->value(); - } - - if (($previousType === null) || ($previousType !== 'id')) { - throw new \RuntimeException(); - } - - return $name; - } + /** @var int */ + protected $index = 0; + + /** @var string[][] */ + protected $data = [ + 0 => ['type' => 'id', 'value' => 'foo'], + 1 => ['type' => 'special', 'value' => '.'], + 2 => ['type' => 'id', 'value' => 'bar'], + 3 => ['type' => 'special', 'value' => ';'], + ]; + + protected function next(): void + { + $this->index = $this->index + 1; + } + + protected function check(string $type, ?string $value = null): bool + { + return ($this->type() === $type) && (($value === null) || ($this->value() === $value)); + } + + protected function type(): string + { + return $this->data[$this->index]['type']; + } + + protected function value(): string + { + return $this->data[$this->index]['value']; + } + + public function separatedName(): string + { + $name = ''; + $previousType = null; + $separator = '.'; + + $currentValue = $this->value(); + + while ((($this->check('special', $separator)) || ($this->check('id'))) && + (($previousType === null) || ($this->type() !== $previousType)) && + (($previousType !== null) || ($currentValue !== $separator)) + ) { + $name .= $currentValue; + $previousType = $this->type(); + + $this->next(); + $currentValue = $this->value(); + } + + if (($previousType === null) || ($previousType !== 'id')) { + throw new \RuntimeException(); + } + + return $name; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/noop.php b/tests/PHPStan/Rules/DeadCode/data/noop.php index c025831720..4b93686bcc 100644 --- a/tests/PHPStan/Rules/DeadCode/data/noop.php +++ b/tests/PHPStan/Rules/DeadCode/data/noop.php @@ -3,29 +3,29 @@ namespace DeadCodeNoop; function (stdClass $foo) { - $foo->foo(); + $foo->foo(); - $arr = []; - $arr; - $arr['test']; - $foo::$test; - $foo->test; + $arr = []; + $arr; + $arr['test']; + $foo::$test; + $foo->test; - 'foo'; - 1; + 'foo'; + 1; - @'foo'; - +1; - -1; + @'foo'; + +1; + -1; - +$foo->foo(); - -$foo->foo(); - @$foo->foo(); + +$foo->foo(); + -$foo->foo(); + @$foo->foo(); - isset($test); - empty($test); - true; - Foo::TEST; + isset($test); + empty($test); + true; + Foo::TEST; - (string) 1; + (string) 1; }; diff --git a/tests/PHPStan/Rules/DeadCode/data/nullsafe-property-fetch-noop.php b/tests/PHPStan/Rules/DeadCode/data/nullsafe-property-fetch-noop.php index 4b32c173d8..11386a75a3 100644 --- a/tests/PHPStan/Rules/DeadCode/data/nullsafe-property-fetch-noop.php +++ b/tests/PHPStan/Rules/DeadCode/data/nullsafe-property-fetch-noop.php @@ -1,13 +1,13 @@ -= 8.0 += 8.0 namespace NullsafePropertyFetchNoop; class Foo { - - public function doFoo(?\ReflectionClass $ref): void - { - $ref?->name; - } - + public function doFoo(?\ReflectionClass $ref): void + { + $ref?->name; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-method.php b/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-method.php index 59db9903be..cfe08e4621 100644 --- a/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-method.php +++ b/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-method.php @@ -1,18 +1,17 @@ -= 8.0 += 8.0 namespace NullsafeUnusedPrivateMethod; class Foo { + public function doFoo(?self $self): void + { + $self?->doBar(); + } - public function doFoo(?self $self): void - { - $self?->doBar(); - } - - private function doBar(): void - { - - } - + private function doBar(): void + { + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-property.php b/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-property.php index a4e68bd586..8767130569 100644 --- a/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-property.php +++ b/tests/PHPStan/Rules/DeadCode/data/nullsafe-unused-private-property.php @@ -1,15 +1,15 @@ -= 8.0 += 8.0 namespace NullsafeUnusedPrivateProperty; class Foo { + private string $bar = 'foo'; - private string $bar = 'foo'; - - public function doFoo(?self $self): void - { - echo $self?->bar; - } - + public function doFoo(?self $self): void + { + echo $self?->bar; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/private-property-trait.php b/tests/PHPStan/Rules/DeadCode/data/private-property-trait.php index 753b43f778..877581cdc1 100644 --- a/tests/PHPStan/Rules/DeadCode/data/private-property-trait.php +++ b/tests/PHPStan/Rules/DeadCode/data/private-property-trait.php @@ -4,38 +4,34 @@ trait FooTrait { + private $prop1; - private $prop1; + private $prop2; - private $prop2; + public function doFoo() + { + echo $this->prop1; + } - public function doFoo() - { - echo $this->prop1; - } - - public function setFoo($prop1) - { - $this->prop1 = $prop1; - } - - public function getProp3() - { - return $this->prop3; - } + public function setFoo($prop1) + { + $this->prop1 = $prop1; + } + public function getProp3() + { + return $this->prop3; + } } class ClassUsingTrait { + use FooTrait; - use FooTrait; - - private $prop3; - - public function __construct(string $prop3) - { - $this->prop3 = $prop3; - } + private $prop3; + public function __construct(string $prop3) + { + $this->prop3 = $prop3; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/private-property-with-tags.php b/tests/PHPStan/Rules/DeadCode/data/private-property-with-tags.php index 91c5beeb1f..0910503f4d 100644 --- a/tests/PHPStan/Rules/DeadCode/data/private-property-with-tags.php +++ b/tests/PHPStan/Rules/DeadCode/data/private-property-with-tags.php @@ -6,21 +6,19 @@ class Foo { + /** + * @ORM\Column(type="big_integer", options={"unsigned": true}) + */ + private $title; - /** - * @ORM\Column(type="big_integer", options={"unsigned": true}) - */ - private $title; - - /** - * @get - */ - private $text; - - /** - * @ORM\Column(type="big_integer", options={"unsigned": true}) - * @get - */ - private $author; + /** + * @get + */ + private $text; + /** + * @ORM\Column(type="big_integer", options={"unsigned": true}) + * @get + */ + private $author; } diff --git a/tests/PHPStan/Rules/DeadCode/data/unreachable.php b/tests/PHPStan/Rules/DeadCode/data/unreachable.php index 69b6c3c8bc..4c2ec3421b 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unreachable.php +++ b/tests/PHPStan/Rules/DeadCode/data/unreachable.php @@ -4,113 +4,107 @@ class Foo { - - public function doFoo() - { - if (doFoo()) { - return; - return; - } - } - - public function doBar(string $foo) - { - return; - echo $foo; - } - - public function doBaz($foo) - { - if ($foo) { - return; - } else{ - return; - } - - echo $foo; - } - - public function doLorem() - { - return; - // this is why... - } - - /** - * @param \stdClass[] $all - */ - public function doIpsum(array $all) - { - foreach ($all as $a) { - - } - - if (isset($a)) { - throw new \Exception(); - } - - var_dump($a); - } - - public function whileIssue(\DateTime $dt) - { - while ($dt->getTimestamp() === 1000) { - - } - - echo $dt->getTimestamp(); - } - - public function otherWhileIssue(\DateTime $dt) - { - assert($dt->getTimestamp() === 1000); - while ($dt->getTimestamp() === 1000) { - - } - - echo $dt->getTimestamp(); - } - - public function anotherWhileIssue(\DateTime $dt) - { - while ($dt->getTimestamp() === 1000) { - $dt->modify('+1 day'); - } - - echo $dt->getTimestamp(); - } - - public function yetOtherWhileIssue(\DateTime $dt) - { - assert($dt->getTimestamp() === 1000); - while ($dt->getTimestamp() === 1000) { - $dt->modify('+1 day'); - } - - echo $dt->getTimestamp(); - } - - public function yetAnotherWhileIssue(\DateTime $dt) - { - while ($this->somethingAboutDateTime($dt) === false) { - - } - - echo $dt->getTimestamp(); - } - - public function yetYetAnotherWhileIssue(\DateTime $dt) - { - while ($this->somethingAboutDateTime($dt) === false) { - $dt->modify('+1 day'); - } - - echo $dt->getTimestamp(); - } - - private function somethingAboutDateTime(\DateTime $dt): bool - { - return rand(0, 1) ? true : false; - } - + public function doFoo() + { + if (doFoo()) { + return; + return; + } + } + + public function doBar(string $foo) + { + return; + echo $foo; + } + + public function doBaz($foo) + { + if ($foo) { + return; + } else { + return; + } + + echo $foo; + } + + public function doLorem() + { + return; + // this is why... + } + + /** + * @param \stdClass[] $all + */ + public function doIpsum(array $all) + { + foreach ($all as $a) { + } + + if (isset($a)) { + throw new \Exception(); + } + + var_dump($a); + } + + public function whileIssue(\DateTime $dt) + { + while ($dt->getTimestamp() === 1000) { + } + + echo $dt->getTimestamp(); + } + + public function otherWhileIssue(\DateTime $dt) + { + assert($dt->getTimestamp() === 1000); + while ($dt->getTimestamp() === 1000) { + } + + echo $dt->getTimestamp(); + } + + public function anotherWhileIssue(\DateTime $dt) + { + while ($dt->getTimestamp() === 1000) { + $dt->modify('+1 day'); + } + + echo $dt->getTimestamp(); + } + + public function yetOtherWhileIssue(\DateTime $dt) + { + assert($dt->getTimestamp() === 1000); + while ($dt->getTimestamp() === 1000) { + $dt->modify('+1 day'); + } + + echo $dt->getTimestamp(); + } + + public function yetAnotherWhileIssue(\DateTime $dt) + { + while ($this->somethingAboutDateTime($dt) === false) { + } + + echo $dt->getTimestamp(); + } + + public function yetYetAnotherWhileIssue(\DateTime $dt) + { + while ($this->somethingAboutDateTime($dt) === false) { + $dt->modify('+1 day'); + } + + echo $dt->getTimestamp(); + } + + private function somethingAboutDateTime(\DateTime $dt): bool + { + return rand(0, 1) ? true : false; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php index ca5e53fba9..aedb72d87e 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php @@ -4,21 +4,19 @@ class Foo { + private const FOO_CONST = 1; - private const FOO_CONST = 1; - - private const BAR_CONST = 2; - - public function doFoo() - { - echo self::FOO_CONST; - } + private const BAR_CONST = 2; + public function doFoo() + { + echo self::FOO_CONST; + } } class TestExtension { - private const USED = 1; + private const USED = 1; - private const UNUSED = 2; + private const UNUSED = 2; } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php index be6f11bdd0..029c69efa2 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php @@ -4,153 +4,127 @@ class Foo { - - private function doFoo() - { - $this->doFoo(); - } - - private function doBar() - { - $this->doBaz(); - } - - private function doBaz() - { - self::calledStatically(); - } - - private function calledStatically() - { - - } - - private function __construct() - { - $this->staticMethod(); - self::anotherStaticMethod(); - } - - private static function staticMethod() - { - - } - - private static function anotherStaticMethod() - { - - } - - private static function unusedStaticMethod() - { - - } - + private function doFoo() + { + $this->doFoo(); + } + + private function doBar() + { + $this->doBaz(); + } + + private function doBaz() + { + self::calledStatically(); + } + + private function calledStatically() + { + } + + private function __construct() + { + $this->staticMethod(); + self::anotherStaticMethod(); + } + + private static function staticMethod() + { + } + + private static function anotherStaticMethod() + { + } + + private static function unusedStaticMethod() + { + } } class Bar { - - private function doFoo() - { - - } - - private function doBaz() - { - $cb = [$this, 'doBaz']; - $cb(); - } - - public function doBar() - { - $cb = [$this, 'doFoo']; - $cb(); - } - + private function doFoo() + { + } + + private function doBaz() + { + $cb = [$this, 'doBaz']; + $cb(); + } + + public function doBar() + { + $cb = [$this, 'doFoo']; + $cb(); + } } class Baz { - - private function doFoo() - { - - } - - public function doBar(string $name) - { - $cb = [$this, $name]; - $cb(); - } - + private function doFoo() + { + } + + public function doBar(string $name) + { + $cb = [$this, $name]; + $cb(); + } } class Lorem { - - private function doFoo() - { - - } - - private function doBaz() - { - - } - - public function doBar() - { - $m = 'doFoo'; - $this->{$m}(); - } - + private function doFoo() + { + } + + private function doBaz() + { + } + + public function doBar() + { + $m = 'doFoo'; + $this->{$m}(); + } } class Ipsum { - - private function doFoo() - { - - } - - public function doBar(string $s) - { - $this->{$s}(); - } - + private function doFoo() + { + } + + public function doBar(string $s) + { + $this->{$s}(); + } } trait FooTrait { - - private function doFoo() - { - - } - - private function doBar() - { - - } - - public function doBaz() - { - $this->doFoo(); - $this->doLorem(); - } - + private function doFoo() + { + } + + private function doBar() + { + } + + public function doBaz() + { + $this->doFoo(); + $this->doLorem(); + } } class UsingFooTrait { + use FooTrait; - use FooTrait; - - private function doLorem() - { - - } - + private function doLorem() + { + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-promoted-property.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-promoted-property.php index 81c6e3934c..cf2c1969a2 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-promoted-property.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-promoted-property.php @@ -1,21 +1,23 @@ -= 8.0 += 8.0 namespace UnusedPrivatePromotedProperty; class Foo { + public function __construct( + public $foo, + protected $bar, + private $baz, + private $lorem, + /** @get */ + private $ipsum + ) { + } - public function __construct( - public $foo, - protected $bar, - private $baz, - private $lorem, - /** @get */ private $ipsum - ) { } - - public function getBaz() - { - return $this->baz; - } - + public function getBaz() + { + return $this->baz; + } } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php index 3511bee8a9..c63e89ebb0 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php @@ -1,157 +1,147 @@ -= 7.4 += 7.4 namespace UnusedPrivateProperty; class Foo { + private $foo; - private $foo; - - private $bar; // write-only + private $bar; // write-only - private $baz; // unused + private $baz; // unused - private $lorem; // read-only + private $lorem; // read-only - private $ipsum; + private $ipsum; - private $dolor = 0; + private $dolor = 0; - public function __construct() - { - $this->foo = 1; - $this->bar = 2; - $this->ipsum['foo']['bar'] = 3; - $this->dolor++; - } + public function __construct() + { + $this->foo = 1; + $this->bar = 2; + $this->ipsum['foo']['bar'] = 3; + $this->dolor++; + } - public function getFoo() - { - return $this->foo; - } + public function getFoo() + { + return $this->foo; + } - public function getLorem() - { - return $this->lorem; - } + public function getLorem() + { + return $this->lorem; + } - public function getIpsum() - { - return $this->ipsum; - } - - public function getDolor(): int - { - return $this->dolor; - } + public function getIpsum() + { + return $this->ipsum; + } + public function getDolor(): int + { + return $this->dolor; + } } class Bar { + private int $foo; - private int $foo; - - private int $bar; // do not report read-only, it's uninitialized + private int $bar; // do not report read-only, it's uninitialized - private $baz; // report read-only + private $baz; // report read-only - public function __construct() - { - $this->foo = 1; - } + public function __construct() + { + $this->foo = 1; + } - public function getFoo(): int - { - return $this->foo; - } + public function getFoo(): int + { + return $this->foo; + } - public function getBar(): int - { - return $this->bar; - } - - public function getBaz(): int - { - return $this->baz; - } + public function getBar(): int + { + return $this->bar; + } + public function getBaz(): int + { + return $this->baz; + } } class Baz { + private static $foo; - private static $foo; - - private static $bar; // write-only - - private static $baz; // unused + private static $bar; // write-only - private static $lorem; // read-only + private static $baz; // unused - public static function doFoo() - { - self::$foo = 1; - self::$bar = 2; - } + private static $lorem; // read-only - public static function getFoo() - { - return self::$foo; - } + public static function doFoo() + { + self::$foo = 1; + self::$bar = 2; + } - public static function getLorem() - { - return self::$lorem; - } + public static function getFoo() + { + return self::$foo; + } + public static function getLorem() + { + return self::$lorem; + } } class Lorem { + private $foo = 'foo'; - private $foo = 'foo'; + private $bar = 'bar'; - private $bar = 'bar'; + private $baz = 'baz'; - private $baz = 'baz'; - - public function doFoo() - { - $nameProperties = [ - 'foo', - 'bar', - ]; - - foreach ($nameProperties as $nameProperty) { - echo "Hello, {$this->$nameProperty}"; - } - } + public function doFoo() + { + $nameProperties = [ + 'foo', + 'bar', + ]; + foreach ($nameProperties as $nameProperty) { + echo "Hello, {$this->$nameProperty}"; + } + } } class Ipsum { + private $foo = 'foo'; - private $foo = 'foo'; - - public function doBar(string $s) - { - echo $this->{$s}; - } - + public function doBar(string $s) + { + echo $this->{$s}; + } } class DolorWithAnonymous { - - private $foo; - - public function doFoo() - { - new class () { - private $bar; - }; - } - + private $foo; + + public function doFoo() + { + new class() { + private $bar; + }; + } } diff --git a/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php index f172d91e98..4f46a3f6e9 100644 --- a/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider()); - } - - public function testRuleInPhpStanNamespace(): void - { - $this->analyse([__DIR__ . '/data/dump-type.php'], [ - [ - 'Dumped type: array&nonEmpty', - 10, - ], - [ - 'Missing argument for PHPStan\dumpType() function call.', - 11, - ], - ]); - } - - public function testRuleInDifferentNamespace(): void - { - $this->analyse([__DIR__ . '/data/dump-type-ns.php'], [ - [ - 'Dumped type: array&nonEmpty', - 10, - ], - ]); - } - - public function testRuleInUse(): void - { - $this->analyse([__DIR__ . '/data/dump-type-use.php'], [ - [ - 'Dumped type: array&nonEmpty', - 12, - ], - [ - 'Dumped type: array&nonEmpty', - 13, - ], - ]); - } - + protected function getRule(): Rule + { + return new DumpTypeRule($this->createReflectionProvider()); + } + + public function testRuleInPhpStanNamespace(): void + { + $this->analyse([__DIR__ . '/data/dump-type.php'], [ + [ + 'Dumped type: array&nonEmpty', + 10, + ], + [ + 'Missing argument for PHPStan\dumpType() function call.', + 11, + ], + ]); + } + + public function testRuleInDifferentNamespace(): void + { + $this->analyse([__DIR__ . '/data/dump-type-ns.php'], [ + [ + 'Dumped type: array&nonEmpty', + 10, + ], + ]); + } + + public function testRuleInUse(): void + { + $this->analyse([__DIR__ . '/data/dump-type-use.php'], [ + [ + 'Dumped type: array&nonEmpty', + 12, + ], + [ + 'Dumped type: array&nonEmpty', + 13, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Debug/FileAssertRuleTest.php b/tests/PHPStan/Rules/Debug/FileAssertRuleTest.php index bf1ea7711f..0bde5c9670 100644 --- a/tests/PHPStan/Rules/Debug/FileAssertRuleTest.php +++ b/tests/PHPStan/Rules/Debug/FileAssertRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider()); + } - protected function getRule(): Rule - { - return new FileAssertRule($this->createReflectionProvider()); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/file-asserts.php'], [ - [ - 'Expected type array, actual: array', - 19, - ], - [ - 'Expected native type false, actual: bool', - 36, - ], - [ - 'Expected native type true, actual: bool', - 37, - ], - [ - 'Expected variable certainty Yes, actual: No', - 45, - ], - [ - 'Expected variable certainty Maybe, actual: No', - 46, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/file-asserts.php'], [ + [ + 'Expected type array, actual: array', + 19, + ], + [ + 'Expected native type false, actual: bool', + 36, + ], + [ + 'Expected native type true, actual: bool', + 37, + ], + [ + 'Expected variable certainty Yes, actual: No', + 45, + ], + [ + 'Expected variable certainty Maybe, actual: No', + 46, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Debug/data/dump-type-ns.php b/tests/PHPStan/Rules/Debug/data/dump-type-ns.php index b031002326..5cd2b3e645 100644 --- a/tests/PHPStan/Rules/Debug/data/dump-type-ns.php +++ b/tests/PHPStan/Rules/Debug/data/dump-type-ns.php @@ -3,10 +3,10 @@ namespace App\Foo; function (array $a) { - if ($a === []) { - return; - } + if ($a === []) { + return; + } - \PHPStan\dumpType($a); - dumpType($a); + \PHPStan\dumpType($a); + dumpType($a); }; diff --git a/tests/PHPStan/Rules/Debug/data/dump-type-use.php b/tests/PHPStan/Rules/Debug/data/dump-type-use.php index c170cb976e..a5b8031e2b 100644 --- a/tests/PHPStan/Rules/Debug/data/dump-type-use.php +++ b/tests/PHPStan/Rules/Debug/data/dump-type-use.php @@ -5,10 +5,10 @@ use function PHPStan\dumpType; function (array $a) { - if ($a === []) { - return; - } + if ($a === []) { + return; + } - \PHPStan\dumpType($a); - dumpType($a); + \PHPStan\dumpType($a); + dumpType($a); }; diff --git a/tests/PHPStan/Rules/Debug/data/dump-type.php b/tests/PHPStan/Rules/Debug/data/dump-type.php index 52f5deb1c6..06928722f3 100644 --- a/tests/PHPStan/Rules/Debug/data/dump-type.php +++ b/tests/PHPStan/Rules/Debug/data/dump-type.php @@ -3,10 +3,10 @@ namespace PHPStan; function (array $a) { - if ($a === []) { - return; - } + if ($a === []) { + return; + } - dumpType($a); - dumpType(); + dumpType($a); + dumpType(); }; diff --git a/tests/PHPStan/Rules/Debug/data/file-asserts.php b/tests/PHPStan/Rules/Debug/data/file-asserts.php index 5f9fb9cc8d..836e72592e 100644 --- a/tests/PHPStan/Rules/Debug/data/file-asserts.php +++ b/tests/PHPStan/Rules/Debug/data/file-asserts.php @@ -9,41 +9,39 @@ class Foo { - - /** - * @param array $a - */ - public function doFoo(array $a): void - { - assertType('array', $a); - assertType('array', $a); - } - - /** - * @param non-empty-array $a - */ - public function doBar(array $a): void - { - assertType('array&nonEmpty', $a); - assertNativeType('array', $a); - - assertType('false', $a === []); - assertType('true', $a !== []); - - assertNativeType('bool', $a === []); - assertNativeType('bool', $a !== []); - - assertNativeType('false', $a === []); - assertNativeType('true', $a !== []); - } - - public function doBaz($a): void - { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - assertVariableCertainty(TrinaryLogic::createNo(), $b); - - assertVariableCertainty(TrinaryLogic::createYes(), $b); - assertVariableCertainty(TrinaryLogic::createMaybe(), $b); - } - + /** + * @param array $a + */ + public function doFoo(array $a): void + { + assertType('array', $a); + assertType('array', $a); + } + + /** + * @param non-empty-array $a + */ + public function doBar(array $a): void + { + assertType('array&nonEmpty', $a); + assertNativeType('array', $a); + + assertType('false', $a === []); + assertType('true', $a !== []); + + assertNativeType('bool', $a === []); + assertNativeType('bool', $a !== []); + + assertNativeType('false', $a === []); + assertNativeType('true', $a !== []); + } + + public function doBaz($a): void + { + assertVariableCertainty(TrinaryLogic::createYes(), $a); + assertVariableCertainty(TrinaryLogic::createNo(), $b); + + assertVariableCertainty(TrinaryLogic::createYes(), $b); + assertVariableCertainty(TrinaryLogic::createMaybe(), $b); + } } diff --git a/tests/PHPStan/Rules/DummyRule.php b/tests/PHPStan/Rules/DummyRule.php index 697c9a4ead..99061f6971 100644 --- a/tests/PHPStan/Rules/DummyRule.php +++ b/tests/PHPStan/Rules/DummyRule.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/unthrown-exception.php'], [ - [ - 'Dead catch - Throwable is never thrown in the try block.', - 12, - ], - [ - 'Dead catch - Exception is never thrown in the try block.', - 21, - ], - [ - 'Dead catch - Exception is never thrown in the try block.', - 38, - ], - [ - 'Dead catch - RuntimeException is never thrown in the try block.', - 49, - ], - [ - 'Dead catch - Throwable is never thrown in the try block.', - 71, - ], - [ - 'Dead catch - InvalidArgumentException is never thrown in the try block.', - 84, - ], - [ - 'Dead catch - DomainException is never thrown in the try block.', - 117, - ], - [ - 'Dead catch - Throwable is never thrown in the try block.', - 119, - ], - [ - 'Dead catch - Exception is never thrown in the try block.', - 171, - ], - [ - 'Dead catch - Exception is never thrown in the try block.', - 180, - ], - ]); - } - - public function testBug4806(): void - { - $this->analyse([__DIR__ . '/data/bug-4806.php'], [ - [ - 'Dead catch - ArgumentCountError is never thrown in the try block.', - 65, - ], - [ - 'Dead catch - Throwable is never thrown in the try block.', - 119, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/unthrown-exception.php'], [ + [ + 'Dead catch - Throwable is never thrown in the try block.', + 12, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 21, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 38, + ], + [ + 'Dead catch - RuntimeException is never thrown in the try block.', + 49, + ], + [ + 'Dead catch - Throwable is never thrown in the try block.', + 71, + ], + [ + 'Dead catch - InvalidArgumentException is never thrown in the try block.', + 84, + ], + [ + 'Dead catch - DomainException is never thrown in the try block.', + 117, + ], + [ + 'Dead catch - Throwable is never thrown in the try block.', + 119, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 171, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 180, + ], + ]); + } - public function testBug4805(): void - { - $this->analyse([__DIR__ . '/data/bug-4805.php'], [ - [ - 'Dead catch - OutOfBoundsException is never thrown in the try block.', - 44, - ], - [ - 'Dead catch - OutOfBoundsException is never thrown in the try block.', - 66, - ], - ]); - } + public function testBug4806(): void + { + $this->analyse([__DIR__ . '/data/bug-4806.php'], [ + [ + 'Dead catch - ArgumentCountError is never thrown in the try block.', + 65, + ], + [ + 'Dead catch - Throwable is never thrown in the try block.', + 119, + ], + ]); + } - public function testBug4863(): void - { - $this->analyse([__DIR__ . '/data/bug-4863.php'], []); - } + public function testBug4805(): void + { + $this->analyse([__DIR__ . '/data/bug-4805.php'], [ + [ + 'Dead catch - OutOfBoundsException is never thrown in the try block.', + 44, + ], + [ + 'Dead catch - OutOfBoundsException is never thrown in the try block.', + 66, + ], + ]); + } - public function testBug4814(): void - { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('Test requires PHP 7.3.'); - } + public function testBug4863(): void + { + $this->analyse([__DIR__ . '/data/bug-4863.php'], []); + } - $this->analyse([__DIR__ . '/data/bug-4814.php'], [ - [ - 'Dead catch - JsonException is never thrown in the try block.', - 16, - ], - ]); - } + public function testBug4814(): void + { + if (PHP_VERSION_ID < 70300) { + $this->markTestSkipped('Test requires PHP 7.3.'); + } + $this->analyse([__DIR__ . '/data/bug-4814.php'], [ + [ + 'Dead catch - JsonException is never thrown in the try block.', + 16, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php index 9875365e55..aafc673a08 100644 --- a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new CaughtExceptionExistenceRule( - $broker, - new ClassCaseSensitivityCheck($broker), - true - ); - } - - public function testCheckCaughtException(): void - { - $this->analyse([__DIR__ . '/data/catch.php'], [ - [ - 'Caught class TestCatch\FooCatch is not an exception.', - 17, - ], - [ - 'Caught class FooCatchException not found.', - 29, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - - ], - [ - 'Class TestCatch\MyCatchException referenced with incorrect case: TestCatch\MyCatchEXCEPTION.', - 41, - ], - ]); - } - - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/class-exists.php'], []); - } - - public function testBug3690(): void - { - $this->analyse([__DIR__ . '/data/bug-3690.php'], []); - } - + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new CaughtExceptionExistenceRule( + $broker, + new ClassCaseSensitivityCheck($broker), + true + ); + } + + public function testCheckCaughtException(): void + { + $this->analyse([__DIR__ . '/data/catch.php'], [ + [ + 'Caught class TestCatch\FooCatch is not an exception.', + 17, + ], + [ + 'Caught class FooCatchException not found.', + 29, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + + ], + [ + 'Class TestCatch\MyCatchException referenced with incorrect case: TestCatch\MyCatchEXCEPTION.', + 41, + ], + ]); + } + + public function testClassExists(): void + { + $this->analyse([__DIR__ . '/data/class-exists.php'], []); + } + + public function testBug3690(): void + { + $this->analyse([__DIR__ . '/data/bug-3690.php'], []); + } } diff --git a/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php b/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php index 828cab35ce..47cdf4eeb7 100644 --- a/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/dead-catch.php'], [ - [ - 'Dead catch - TypeError is already caught by Throwable above.', - 27, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/dead-catch.php'], [ + [ + 'Dead catch - TypeError is already caught by Throwable above.', + 27, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php b/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php index ce28a09ecc..0b4b7d70f3 100644 --- a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php +++ b/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php @@ -1,4 +1,6 @@ -createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses); - $this->assertSame($expectedResult, $resolver->isCheckedException($className)); - } - + /** + * @dataProvider dataIsCheckedException + * @param string[] $uncheckedExceptionRegexes + * @param string[] $uncheckedExceptionClasses + * @param string $className + * @param bool $expectedResult + */ + public function testIsCheckedException( + array $uncheckedExceptionRegexes, + array $uncheckedExceptionClasses, + string $className, + bool $expectedResult + ): void { + $resolver = new ExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses); + $this->assertSame($expectedResult, $resolver->isCheckedException($className)); + } } diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php index 09aa80196f..4807d2e8b8 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), + [], + [\PHPStan\ShouldNotHappenException::class] + )) + ); + } - protected function getRule(): Rule - { - return new MissingCheckedExceptionInFunctionThrowsRule( - new MissingCheckedExceptionInThrowsCheck(new ExceptionTypeResolver( - $this->createReflectionProvider(), - [], - [\PHPStan\ShouldNotHappenException::class] - )) - ); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/missing-exception-function-throws.php'; - $this->analyse([__DIR__ . '/data/missing-exception-function-throws.php'], [ - [ - 'Function MissingExceptionFunctionThrows\doBaz() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 20, - ], - [ - 'Function MissingExceptionFunctionThrows\doLorem() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 26, - ], - [ - 'Function MissingExceptionFunctionThrows\doLorem2() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 31, - ], - [ - 'Function MissingExceptionFunctionThrows\doBar2() throws checked exception LogicException but it\'s missing from the PHPDoc @throws tag.', - 51, - ], - [ - 'Function MissingExceptionFunctionThrows\doBar3() throws checked exception LogicException but it\'s missing from the PHPDoc @throws tag.', - 57, - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/missing-exception-function-throws.php'; + $this->analyse([__DIR__ . '/data/missing-exception-function-throws.php'], [ + [ + 'Function MissingExceptionFunctionThrows\doBaz() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 20, + ], + [ + 'Function MissingExceptionFunctionThrows\doLorem() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 26, + ], + [ + 'Function MissingExceptionFunctionThrows\doLorem2() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 31, + ], + [ + 'Function MissingExceptionFunctionThrows\doBar2() throws checked exception LogicException but it\'s missing from the PHPDoc @throws tag.', + 51, + ], + [ + 'Function MissingExceptionFunctionThrows\doBar3() throws checked exception LogicException but it\'s missing from the PHPDoc @throws tag.', + 57, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php index 9893b6c764..cc9ca35dd6 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), + [], + [\PHPStan\ShouldNotHappenException::class] + )) + ); + } - protected function getRule(): Rule - { - return new MissingCheckedExceptionInMethodThrowsRule( - new MissingCheckedExceptionInThrowsCheck(new ExceptionTypeResolver( - $this->createReflectionProvider(), - [], - [\PHPStan\ShouldNotHappenException::class] - )) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/missing-exception-method-throws.php'], [ - [ - 'Method MissingExceptionMethodThrows\Foo::doBaz() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 23, - ], - [ - 'Method MissingExceptionMethodThrows\Foo::doLorem() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 29, - ], - [ - 'Method MissingExceptionMethodThrows\Foo::doLorem2() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', - 34, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-exception-method-throws.php'], [ + [ + 'Method MissingExceptionMethodThrows\Foo::doBaz() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 23, + ], + [ + 'Method MissingExceptionMethodThrows\Foo::doLorem() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 29, + ], + [ + 'Method MissingExceptionMethodThrows\Foo::doLorem2() throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 34, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php index 268b220f34..c32c541b45 100644 --- a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/overwritten-exit-point.php'], [ - [ - 'This return is overwritten by a different one in the finally block below.', - 11, - ], - [ - 'This return is overwritten by a different one in the finally block below.', - 13, - ], - [ - 'The overwriting return is on this line.', - 15, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/overwritten-exit-point.php'], [ + [ + 'This return is overwritten by a different one in the finally block below.', + 11, + ], + [ + 'This return is overwritten by a different one in the finally block below.', + 13, + ], + [ + 'The overwriting return is on this line.', + 15, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/ThrowExpressionRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowExpressionRuleTest.php index 2134ca920c..5121a299ae 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowExpressionRuleTest.php @@ -1,4 +1,6 @@ -phpVersion); - } - - public function dataRule(): array - { - return [ - [ - 70400, - [ - [ - 'Throw expression is supported only on PHP 8.0 and later.', - 10, - ], - ], - ], - [ - 80000, - [], - ], - ]; - } - - /** - * @dataProvider dataRule - * @param int $phpVersion - * @param mixed[] $expectedErrors - */ - public function testRule(int $phpVersion, array $expectedErrors): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0'); - } - - $this->phpVersion = new PhpVersion($phpVersion); - $this->analyse([__DIR__ . '/data/throw-expr.php'], $expectedErrors); - } - + /** @var PhpVersion */ + private $phpVersion; + + protected function getRule(): Rule + { + return new ThrowExpressionRule($this->phpVersion); + } + + public function dataRule(): array + { + return [ + [ + 70400, + [ + [ + 'Throw expression is supported only on PHP 8.0 and later.', + 10, + ], + ], + ], + [ + 80000, + [], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param int $phpVersion + * @param mixed[] $expectedErrors + */ + public function testRule(int $phpVersion, array $expectedErrors): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->phpVersion = new PhpVersion($phpVersion); + $this->analyse([__DIR__ . '/data/throw-expr.php'], $expectedErrors); + } } diff --git a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php index 82871e6145..07f6e99f79 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/too-wide-throws-function.php'], [ - [ - 'Function TooWideThrowsFunction\doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 26, - ], - [ - 'Function TooWideThrowsFunction\doFoo7() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 48, - ], - [ - 'Function TooWideThrowsFunction\doFoo8() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 57, - ], - [ - 'Function TooWideThrowsFunction\doFoo9() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 63, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/too-wide-throws-function.php'], [ + [ + 'Function TooWideThrowsFunction\doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 26, + ], + [ + 'Function TooWideThrowsFunction\doFoo7() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 48, + ], + [ + 'Function TooWideThrowsFunction\doFoo8() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 57, + ], + [ + 'Function TooWideThrowsFunction\doFoo9() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 63, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php index eddbd31bf6..b6f4bd4923 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class), new TooWideThrowTypeCheck()); + } - protected function getRule(): Rule - { - return new TooWideMethodThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck()); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/too-wide-throws-method.php'], [ - [ - 'Method TooWideThrowsMethod\Foo::doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 29, - ], - [ - 'Method TooWideThrowsMethod\Foo::doFoo7() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 51, - ], - [ - 'Method TooWideThrowsMethod\Foo::doFoo8() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 60, - ], - [ - 'Method TooWideThrowsMethod\Foo::doFoo9() has DomainException in PHPDoc @throws tag but it\'s not thrown.', - 66, - ], - [ - 'Method TooWideThrowsMethod\ParentClass::doFoo() has LogicException in PHPDoc @throws tag but it\'s not thrown.', - 77, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/too-wide-throws-method.php'], [ + [ + 'Method TooWideThrowsMethod\Foo::doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 29, + ], + [ + 'Method TooWideThrowsMethod\Foo::doFoo7() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 51, + ], + [ + 'Method TooWideThrowsMethod\Foo::doFoo8() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 60, + ], + [ + 'Method TooWideThrowsMethod\Foo::doFoo9() has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 66, + ], + [ + 'Method TooWideThrowsMethod\ParentClass::doFoo() has LogicException in PHPDoc @throws tag but it\'s not thrown.', + 77, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-3690.php b/tests/PHPStan/Rules/Exceptions/data/bug-3690.php index b2ce8e6b96..f5b58b9beb 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-3690.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-3690.php @@ -1,18 +1,17 @@ - $foo */ - public static function doBar(string $foo, bool $flag): bool - { - try{ - $foo::canThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + /** @param class-string $foo */ + public static function doBar(string $foo, bool $flag): bool + { + try { + $foo::canThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } - /** @param class-string $foo */ - public static function doBar2(string $foo, bool $flag): bool - { - try{ - $foo::cannotThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + /** @param class-string $foo */ + public static function doBar2(string $foo, bool $flag): bool + { + try { + $foo::cannotThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } - /** @param class-string $foo */ - public static function doBaz(string $foo, bool $flag): bool - { - try{ - $foo::canThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + /** @param class-string $foo */ + public static function doBaz(string $foo, bool $flag): bool + { + try { + $foo::canThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } - /** @param class-string $foo */ - public static function doBaz2(string $foo, bool $flag): bool - { - try{ - $foo::cannotThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + /** @param class-string $foo */ + public static function doBaz2(string $foo, bool $flag): bool + { + try { + $foo::cannotThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } - public static function doLorem(string $foo, bool $flag): bool - { - try{ - $foo::canThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + public static function doLorem(string $foo, bool $flag): bool + { + try { + $foo::canThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } - public static function doLorem2(string $foo, bool $flag): bool - { - try{ - $foo::cannotThrow($flag); - return true; - } catch(\OutOfBoundsException $e){ - return false; - } - } + public static function doLorem2(string $foo, bool $flag): bool + { + try { + $foo::cannotThrow($flag); + return true; + } catch (\OutOfBoundsException $e) { + return false; + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-4806.php b/tests/PHPStan/Rules/Exceptions/data/bug-4806.php index 78d1ae5d38..c1d7b89118 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-4806.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-4806.php @@ -4,121 +4,109 @@ class NeverThrows { - /** - * @throws void - */ - final public function __construct() - { - } + /** + * @throws void + */ + final public function __construct() + { + } } class HasNoConstructor { - } class MayThrowArgumentCountError { - /** - * @throws \ArgumentCountError - */ - public function __construct() - { - throw new \ArgumentCountError(); - } + /** + * @throws \ArgumentCountError + */ + public function __construct() + { + throw new \ArgumentCountError(); + } } class ImplicitThrow { - - public function __construct() - { - - } - + public function __construct() + { + } } class Foo { - - /** - * @param class-string $class - */ - function createNotSpecified(string $class): object - { - try { - $object = new $class(); - } catch (\ArgumentCountError $error) { - - } - - return $object; - } - - /** - * @param class-string $class - */ - function createNeverThrows(string $class): object - { - try { - $object = new $class(); - } catch (\ArgumentCountError $throwable) { - - } - - return $object; - } - - /** - * @param class-string $class - */ - function createMayThrowArgumentCountError(string $class): object - { - try { - $object = new $class(); - } catch (\ArgumentCountError $error) { - - } - - return $object; - } - - /** - * @param class-string $class - */ - function createMayThrowArgumentCountErrorB(string $class): object - { - try { - $object = new $class(); - } catch (\Throwable $throwable) { - - } - - return $object; - } - - /** - * @param class-string $class - */ - function implicitThrow(string $class): void - { - try { - $object = new $class(); - } catch (\Throwable $throwable) { - - } - } - - /** - * @param class-string $class - */ - function hasNoConstructor(string $class): void - { - try { - $object = new $class(); - } catch (\Throwable $throwable) { - - } - } - + /** + * @param class-string $class + */ + public function createNotSpecified(string $class): object + { + try { + $object = new $class(); + } catch (\ArgumentCountError $error) { + } + + return $object; + } + + /** + * @param class-string $class + */ + public function createNeverThrows(string $class): object + { + try { + $object = new $class(); + } catch (\ArgumentCountError $throwable) { + } + + return $object; + } + + /** + * @param class-string $class + */ + public function createMayThrowArgumentCountError(string $class): object + { + try { + $object = new $class(); + } catch (\ArgumentCountError $error) { + } + + return $object; + } + + /** + * @param class-string $class + */ + public function createMayThrowArgumentCountErrorB(string $class): object + { + try { + $object = new $class(); + } catch (\Throwable $throwable) { + } + + return $object; + } + + /** + * @param class-string $class + */ + public function implicitThrow(string $class): void + { + try { + $object = new $class(); + } catch (\Throwable $throwable) { + } + } + + /** + * @param class-string $class + */ + public function hasNoConstructor(string $class): void + { + try { + $object = new $class(); + } catch (\Throwable $throwable) { + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-4814.php b/tests/PHPStan/Rules/Exceptions/data/bug-4814.php index 9959138f6e..f1008c2e90 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-4814.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-4814.php @@ -3,17 +3,15 @@ namespace Bug4814; function (string $s): void { - try { - json_decode($s, true, 512, JSON_THROW_ON_ERROR); - } catch (\JsonException $e) { - - } + try { + json_decode($s, true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + } }; function (string $s): void { - try { - json_decode($s); - } catch (\JsonException $e) { - - } + try { + json_decode($s); + } catch (\JsonException $e) { + } }; diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-4863.php b/tests/PHPStan/Rules/Exceptions/data/bug-4863.php index 724c8a1f03..c1cd51abb8 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-4863.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-4863.php @@ -4,14 +4,15 @@ class HelloWorld { - /** - * @return \Generator - */ - public function generate(): \Generator { - try { - yield 'test'; - } catch (\Exception $exception) { - echo $exception->getMessage(); - } - } + /** + * @return \Generator + */ + public function generate(): \Generator + { + try { + yield 'test'; + } catch (\Exception $exception) { + echo $exception->getMessage(); + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/catch.php b/tests/PHPStan/Rules/Exceptions/data/catch.php index 82720fb9da..3d7dbf435d 100644 --- a/tests/PHPStan/Rules/Exceptions/data/catch.php +++ b/tests/PHPStan/Rules/Exceptions/data/catch.php @@ -4,40 +4,28 @@ class FooCatch { - } class MyCatchException extends \Exception { - } try { - } catch (\TestCatch\FooCatch $e) { // not an exception - } try { - } catch (\TestCatch\MyCatchException $e) { - } try { - } catch (\FooCatchException $e) { // nonexistent exception class - } try { - } catch (\TypeError $e) { - } try { - } catch (\TestCatch\MyCatchEXCEPTION $e) { - } diff --git a/tests/PHPStan/Rules/Exceptions/data/class-exists.php b/tests/PHPStan/Rules/Exceptions/data/class-exists.php index d74c6abcf2..360c2ed0da 100644 --- a/tests/PHPStan/Rules/Exceptions/data/class-exists.php +++ b/tests/PHPStan/Rules/Exceptions/data/class-exists.php @@ -4,18 +4,14 @@ class Foo { + public function doFoo(): void + { + if (!class_exists(FooException::class)) { + return; + } - public function doFoo(): void - { - if (!class_exists(FooException::class)) { - return; - } - - try { - - } catch (FooException $e) { - - } - } - + try { + } catch (FooException $e) { + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/dead-catch.php b/tests/PHPStan/Rules/Exceptions/data/dead-catch.php index a2765c41d9..b8967b3379 100644 --- a/tests/PHPStan/Rules/Exceptions/data/dead-catch.php +++ b/tests/PHPStan/Rules/Exceptions/data/dead-catch.php @@ -4,29 +4,20 @@ class Foo { - - public function doFoo() - { - try { - - } catch (\Exception $e) { - - } catch (\TypeError $e) { - - } catch (\Throwable $t) { - - } - } - - public function doBar() - { - try { - - } catch (\Throwable $e) { - - } catch (\TypeError $e) { - - } - } - + public function doFoo() + { + try { + } catch (\Exception $e) { + } catch (\TypeError $e) { + } catch (\Throwable $t) { + } + } + + public function doBar() + { + try { + } catch (\Throwable $e) { + } catch (\TypeError $e) { + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/missing-exception-function-throws.php b/tests/PHPStan/Rules/Exceptions/data/missing-exception-function-throws.php index fa699f6bd7..0275b07e9e 100644 --- a/tests/PHPStan/Rules/Exceptions/data/missing-exception-function-throws.php +++ b/tests/PHPStan/Rules/Exceptions/data/missing-exception-function-throws.php @@ -5,54 +5,53 @@ /** @throws \InvalidArgumentException */ function doFoo(): void { - throw new \InvalidArgumentException(); // ok + throw new \InvalidArgumentException(); // ok } /** @throws \LogicException */ function doBar(): void { - throw new \InvalidArgumentException(); // ok + throw new \InvalidArgumentException(); // ok } /** @throws \RuntimeException */ function doBaz(): void { - throw new \InvalidArgumentException(); // error + throw new \InvalidArgumentException(); // error } /** @throws \RuntimeException */ function doLorem(): void { - throw new \InvalidArgumentException(); // error + throw new \InvalidArgumentException(); // error } function doLorem2(): void { - throw new \InvalidArgumentException(); // error + throw new \InvalidArgumentException(); // error } function doLorem3(): void { - try { - throw new \InvalidArgumentException(); // ok - } catch (\InvalidArgumentException $e) { - - } + try { + throw new \InvalidArgumentException(); // ok + } catch (\InvalidArgumentException $e) { + } } function doIpsum(): void { - throw new \PHPStan\ShouldNotHappenException(); // ok + throw new \PHPStan\ShouldNotHappenException(); // ok } /** @throws \InvalidArgumentException */ function doBar2(): void { - throw new \LogicException(); // error + throw new \LogicException(); // error } /** @throws void */ function doBar3(): void { - throw new \LogicException(); // error + throw new \LogicException(); // error } diff --git a/tests/PHPStan/Rules/Exceptions/data/missing-exception-method-throws.php b/tests/PHPStan/Rules/Exceptions/data/missing-exception-method-throws.php index 66c280bf79..ce7f41c34c 100644 --- a/tests/PHPStan/Rules/Exceptions/data/missing-exception-method-throws.php +++ b/tests/PHPStan/Rules/Exceptions/data/missing-exception-method-throws.php @@ -4,57 +4,54 @@ class Foo { - - /** @throws \InvalidArgumentException */ - public function doFoo(): void - { - throw new \InvalidArgumentException(); // ok - } - - /** @throws \LogicException */ - public function doBar(): void - { - throw new \InvalidArgumentException(); // ok - } - - /** @throws \RuntimeException */ - public function doBaz(): void - { - throw new \InvalidArgumentException(); // error - } - - /** @throws \RuntimeException */ - public function doLorem(): void - { - throw new \InvalidArgumentException(); // error - } - - public function doLorem2(): void - { - throw new \InvalidArgumentException(); // error - } - - public function doLorem3(): void - { - try { - throw new \InvalidArgumentException(); // ok - } catch (\InvalidArgumentException $e) { - - } - } - - public function doIpsum(): void - { - throw new \PHPStan\ShouldNotHappenException(); // ok - } - - public function doDolor(): void - { - try { - doFoo(); - } catch (\Throwable $e) { - throw $e; - } - } - + /** @throws \InvalidArgumentException */ + public function doFoo(): void + { + throw new \InvalidArgumentException(); // ok + } + + /** @throws \LogicException */ + public function doBar(): void + { + throw new \InvalidArgumentException(); // ok + } + + /** @throws \RuntimeException */ + public function doBaz(): void + { + throw new \InvalidArgumentException(); // error + } + + /** @throws \RuntimeException */ + public function doLorem(): void + { + throw new \InvalidArgumentException(); // error + } + + public function doLorem2(): void + { + throw new \InvalidArgumentException(); // error + } + + public function doLorem3(): void + { + try { + throw new \InvalidArgumentException(); // ok + } catch (\InvalidArgumentException $e) { + } + } + + public function doIpsum(): void + { + throw new \PHPStan\ShouldNotHappenException(); // ok + } + + public function doDolor(): void + { + try { + doFoo(); + } catch (\Throwable $e) { + throw $e; + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/overwritten-exit-point.php b/tests/PHPStan/Rules/Exceptions/data/overwritten-exit-point.php index 05b29bb86e..41ad10af3d 100644 --- a/tests/PHPStan/Rules/Exceptions/data/overwritten-exit-point.php +++ b/tests/PHPStan/Rules/Exceptions/data/overwritten-exit-point.php @@ -3,31 +3,29 @@ namespace OverwrittenExitPoint; function (): string { - try { - if (rand(0, 1)) { - throw new \Exception(); - } + try { + if (rand(0, 1)) { + throw new \Exception(); + } - return 'foo'; - } catch (\Exception $e) { - return 'bar'; - } finally { - return 'baz'; - } + return 'foo'; + } catch (\Exception $e) { + return 'bar'; + } finally { + return 'baz'; + } }; function (): string { - try { - - } finally { - return 'foo'; - } + try { + } finally { + return 'foo'; + } }; function (): string { - try { - return 'foo'; - } finally { - - } + try { + return 'foo'; + } finally { + } }; diff --git a/tests/PHPStan/Rules/Exceptions/data/throw-expr.php b/tests/PHPStan/Rules/Exceptions/data/throw-expr.php index 83548d383b..256870db11 100644 --- a/tests/PHPStan/Rules/Exceptions/data/throw-expr.php +++ b/tests/PHPStan/Rules/Exceptions/data/throw-expr.php @@ -1,15 +1,15 @@ -= 8.0 += 8.0 namespace ThrowExpr; class Bar { + public function doFoo(bool $b): void + { + $b ? true : throw new \Exception(); - public function doFoo(bool $b): void - { - $b ? true : throw new \Exception(); - - throw new \Exception(); - } - + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php index f4bae0e0ae..17af9d82c3 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php @@ -7,47 +7,47 @@ /** @throws \InvalidArgumentException */ function doFoo(): void // ok { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @throws \LogicException */ function doFoo2(): void // ok { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @throws \InvalidArgumentException */ function doFoo3(): void // ok { - throw new \LogicException(); + throw new \LogicException(); } /** @throws \InvalidArgumentException|\DomainException */ function doFoo4(): void // error - DomainException unused { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @throws void */ function doFoo5(): void // ok - picked up by different rule { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @throws \InvalidArgumentException|\DomainException */ function doFoo6(): void // ok { - if (rand(0, 1)) { - throw new \InvalidArgumentException(); - } + if (rand(0, 1)) { + throw new \InvalidArgumentException(); + } - throw new DomainException(); + throw new DomainException(); } /** @throws \DomainException */ function doFoo7(): void // error - DomainException unused { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @@ -56,11 +56,10 @@ function doFoo7(): void // error - DomainException unused */ function doFoo8(): void // error - DomainException unused { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(); } /** @throws \DomainException */ function doFoo9(): void // error - DomainException unused { - } diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php index 819d89c87b..f1ebc7ffed 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php @@ -6,144 +6,131 @@ class Foo { - - /** @throws \InvalidArgumentException */ - public function doFoo(): void // ok - { - throw new \InvalidArgumentException(); - } - - /** @throws \LogicException */ - public function doFoo2(): void // ok - { - throw new \InvalidArgumentException(); - } - - /** @throws \InvalidArgumentException */ - public function doFoo3(): void // ok - { - throw new \LogicException(); - } - - /** @throws \InvalidArgumentException|\DomainException */ - public function doFoo4(): void // error - DomainException unused - { - throw new \InvalidArgumentException(); - } - - /** @throws void */ - public function doFoo5(): void // ok - picked up by different rule - { - throw new \InvalidArgumentException(); - } - - /** @throws \InvalidArgumentException|\DomainException */ - public function doFoo6(): void // ok - { - if (rand(0, 1)) { - throw new \InvalidArgumentException(); - } - - throw new DomainException(); - } - - /** @throws \DomainException */ - public function doFoo7(): void // error - DomainException unused - { - throw new \InvalidArgumentException(); - } - - /** - * @throws \InvalidArgumentException - * @throws \DomainException - */ - public function doFoo8(): void // error - DomainException unused - { - throw new \InvalidArgumentException(); - } - - /** @throws \DomainException */ - public function doFoo9(): void // error - DomainException unused - { - - } - + /** @throws \InvalidArgumentException */ + public function doFoo(): void // ok + { + throw new \InvalidArgumentException(); + } + + /** @throws \LogicException */ + public function doFoo2(): void // ok + { + throw new \InvalidArgumentException(); + } + + /** @throws \InvalidArgumentException */ + public function doFoo3(): void // ok + { + throw new \LogicException(); + } + + /** @throws \InvalidArgumentException|\DomainException */ + public function doFoo4(): void // error - DomainException unused + { + throw new \InvalidArgumentException(); + } + + /** @throws void */ + public function doFoo5(): void // ok - picked up by different rule + { + throw new \InvalidArgumentException(); + } + + /** @throws \InvalidArgumentException|\DomainException */ + public function doFoo6(): void // ok + { + if (rand(0, 1)) { + throw new \InvalidArgumentException(); + } + + throw new DomainException(); + } + + /** @throws \DomainException */ + public function doFoo7(): void // error - DomainException unused + { + throw new \InvalidArgumentException(); + } + + /** + * @throws \InvalidArgumentException + * @throws \DomainException + */ + public function doFoo8(): void // error - DomainException unused + { + throw new \InvalidArgumentException(); + } + + /** @throws \DomainException */ + public function doFoo9(): void // error - DomainException unused + { + } } class ParentClass { - - /** @throws \LogicException */ - public function doFoo(): void - { - - } - + /** @throws \LogicException */ + public function doFoo(): void + { + } } class ChildClass extends ParentClass { - - public function doFoo(): void - { - - } - + public function doFoo(): void + { + } } class ThrowsReflectionException { - - /** @throws \ReflectionException */ - public function doFoo(string $s): void - { - new \ReflectionClass($s); - } - + /** @throws \ReflectionException */ + public function doFoo(string $s): void + { + new \ReflectionClass($s); + } } class SkipThrowable { - - /** - * @throws \InvalidArgumentException - * @throws \DomainException - */ - public function doFoo(\Throwable $t) - { - if (rand(0, 1)) { - throw new \InvalidArgumentException(); - } - - throw $t; - } - - /** - * @throws \InvalidArgumentException - */ - public function doBar(): void - { - try { - throw new \InvalidArgumentException(); - } catch (\Throwable $e) { - throw $e; - } - } - - /** - * @throws \InvalidArgumentException - */ - public function doBaz(): void - { - try { - if (rand(0, 1)) { - throw new \InvalidArgumentException(); - } - - doFoo(); - } catch (\Throwable $e) { - throw $e; - } - } - + /** + * @throws \InvalidArgumentException + * @throws \DomainException + */ + public function doFoo(\Throwable $t) + { + if (rand(0, 1)) { + throw new \InvalidArgumentException(); + } + + throw $t; + } + + /** + * @throws \InvalidArgumentException + */ + public function doBar(): void + { + try { + throw new \InvalidArgumentException(); + } catch (\Throwable $e) { + throw $e; + } + } + + /** + * @throws \InvalidArgumentException + */ + public function doBaz(): void + { + try { + if (rand(0, 1)) { + throw new \InvalidArgumentException(); + } + + doFoo(); + } catch (\Throwable $e) { + throw $e; + } + } } diff --git a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php index 7b3b4a1289..032e26922c 100644 --- a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php +++ b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php @@ -4,191 +4,165 @@ class Foo { - - public function doFoo(): void - { - try { - $foo = 1; - } catch (\Throwable $e) { - // pass - } - } - - public function doBar(): void - { - try { - $foo = 1; - } catch (\Exception $e) { - // pass - } - } - - /** @throws \InvalidArgumentException */ - public function throwIae(): void - { - - } - - public function doBaz(): void - { - try { - $this->throwIae(); - } catch (\InvalidArgumentException $e) { - - } catch (\Exception $e) { - // dead - } catch (\Throwable $e) { - // not dead - } - } - - public function doLorem(): void - { - try { - $this->throwIae(); - } catch (\RuntimeException $e) { - // dead - } catch (\Throwable $e) { - - } - } - - public function doIpsum(): void - { - try { - $this->throwIae(); - } catch (\Throwable $e) { - - } - } - - public function doDolor(): void - { - try { - throw new \InvalidArgumentException(); - } catch (\InvalidArgumentException $e) { - - } catch (\Throwable $e) { - - } - } - - public function doSit(): void - { - try { - try { - \ThrowPoints\Helpers\maybeThrows(); - } catch (\InvalidArgumentException $e) { - - } - } catch (\InvalidArgumentException $e) { - - } - } - - /** - * @throws \InvalidArgumentException - * @throws \DomainException - */ - public function doAmet() - { - - } - - public function doAmet1() - { - try { - $this->doAmet(); - } catch (\InvalidArgumentException $e) { - - } catch (\DomainException $e) { - - } catch (\Throwable $e) { - // not dead - } - } - - public function doAmet2() - { - try { - throw new \InvalidArgumentException(); - } catch (\InvalidArgumentException $e) { - - } catch (\DomainException $e) { - // dead - } catch (\Throwable $e) { - // dead - } - } - - public function doConsecteur() - { - try { - if (false) { - - } elseif ($this->doAmet()) { - - } - } catch (\InvalidArgumentException $e) { - - } - } - + public function doFoo(): void + { + try { + $foo = 1; + } catch (\Throwable $e) { + // pass + } + } + + public function doBar(): void + { + try { + $foo = 1; + } catch (\Exception $e) { + // pass + } + } + + /** @throws \InvalidArgumentException */ + public function throwIae(): void + { + } + + public function doBaz(): void + { + try { + $this->throwIae(); + } catch (\InvalidArgumentException $e) { + } catch (\Exception $e) { + // dead + } catch (\Throwable $e) { + // not dead + } + } + + public function doLorem(): void + { + try { + $this->throwIae(); + } catch (\RuntimeException $e) { + // dead + } catch (\Throwable $e) { + } + } + + public function doIpsum(): void + { + try { + $this->throwIae(); + } catch (\Throwable $e) { + } + } + + public function doDolor(): void + { + try { + throw new \InvalidArgumentException(); + } catch (\InvalidArgumentException $e) { + } catch (\Throwable $e) { + } + } + + public function doSit(): void + { + try { + try { + \ThrowPoints\Helpers\maybeThrows(); + } catch (\InvalidArgumentException $e) { + } + } catch (\InvalidArgumentException $e) { + } + } + + /** + * @throws \InvalidArgumentException + * @throws \DomainException + */ + public function doAmet() + { + } + + public function doAmet1() + { + try { + $this->doAmet(); + } catch (\InvalidArgumentException $e) { + } catch (\DomainException $e) { + } catch (\Throwable $e) { + // not dead + } + } + + public function doAmet2() + { + try { + throw new \InvalidArgumentException(); + } catch (\InvalidArgumentException $e) { + } catch (\DomainException $e) { + // dead + } catch (\Throwable $e) { + // dead + } + } + + public function doConsecteur() + { + try { + if (false) { + } elseif ($this->doAmet()) { + } + } catch (\InvalidArgumentException $e) { + } + } } class InlineThrows { - - public function doFoo() - { - try { - /** @throws \InvalidArgumentException */ - echo 1; - } catch (\InvalidArgumentException $e) { - - } - } - - public function doBar() - { - try { - /** @throws \InvalidArgumentException */ - $i = 1; - } catch (\InvalidArgumentException $e) { - - } - } - + public function doFoo() + { + try { + /** @throws \InvalidArgumentException */ + echo 1; + } catch (\InvalidArgumentException $e) { + } + } + + public function doBar() + { + try { + /** @throws \InvalidArgumentException */ + $i = 1; + } catch (\InvalidArgumentException $e) { + } + } } class TestDateTime { - - public function doFoo(): void - { - try { - new \DateTime(); - } catch (\Exception $e) { - - } - } - - public function doBar(): void - { - try { - new \DateTime('now'); - } catch (\Exception $e) { - - } - } - - public function doBaz(string $s): void - { - try { - new \DateTime($s); - } catch (\Exception $e) { - - } - } - + public function doFoo(): void + { + try { + new \DateTime(); + } catch (\Exception $e) { + } + } + + public function doBar(): void + { + try { + new \DateTime('now'); + } catch (\Exception $e) { + } + } + + public function doBaz(string $s): void + { + try { + new \DateTime($s); + } catch (\Exception $e) { + } + } } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index fb03a56fcf..118708f264 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ArrowFunctionAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new ArrowFunctionAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/arrow-function-attributes.php'], [ - [ - 'Attribute class ArrowFunctionAttributes\Foo does not have the function target.', - 28, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/arrow-function-attributes.php'], [ + [ + 'Attribute class ArrowFunctionAttributes\Foo does not have the function target.', + 28, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnNullsafeByRefRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnNullsafeByRefRuleTest.php index 22cca6e10e..4e902fd7d8 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnNullsafeByRefRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnNullsafeByRefRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/arrow-function-nullsafe-by-ref.php'], [ - [ - 'Nullsafe cannot be returned by reference.', - 6, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/arrow-function-nullsafe-by-ref.php'], [ + [ + 'Nullsafe cannot be returned by reference.', + 6, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php index a3a584919b..14812d341c 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), - true, - false, - true, - false - ))); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/arrow-functions-return-type.php'], [ - [ - 'Anonymous function should return string but returns int.', - 12, - ], - [ - 'Anonymous function should return int but returns string.', - 14, - ], - ]); - } - - public function testBug3261(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $this->analyse([__DIR__ . '/data/bug-3261.php'], []); - } - + protected function getRule(): Rule + { + return new ArrowFunctionReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper( + $this->createReflectionProvider(), + true, + false, + true, + false + ))); + } + + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/arrow-functions-return-type.php'], [ + [ + 'Anonymous function should return string but returns int.', + 12, + ], + [ + 'Anonymous function should return int but returns string.', + 14, + ], + ]); + } + + public function testBug3261(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/bug-3261.php'], []); + } } diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index ff2252c2f8..fd3faa50ee 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false); + return new CallCallablesRule( + new FunctionCallParametersCheck( + $ruleLevelHelper, + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + $ruleLevelHelper, + true + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); - return new CallCallablesRule( - new FunctionCallParametersCheck( - $ruleLevelHelper, - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - $ruleLevelHelper, - true - ); - } - - public function testRule(): void - { - if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped(); - } - $this->analyse([__DIR__ . '/data/callables.php'], [ - [ - 'Trying to invoke string but it might not be a callable.', - 17, - ], - [ - 'Callable \'date\' invoked with 0 parameters, 1-2 required.', - 21, - ], - [ - 'Trying to invoke \'nonexistent\' but it\'s not a callable.', - 25, - ], - [ - 'Parameter #1 $i of callable array($this(CallCallables\Foo), \'doBar\') expects int, string given.', - 33, - ], - [ - 'Callable array(\'CallCallables\\\\Foo\', \'doStaticBaz\') invoked with 1 parameter, 0 required.', - 39, - ], - [ - 'Callable \'CallCallables\\\\Foo:…\' invoked with 1 parameter, 0 required.', - 41, - ], - [ - 'Call to private method privateFooMethod() of class CallCallables\Foo.', - 52, - ], - [ - 'Closure invoked with 0 parameters, 1-2 required.', - 58, - ], - [ - 'Result of closure (void) is used.', - 59, - ], - [ - 'Closure invoked with 0 parameters, at least 1 required.', - 64, - ], - [ - 'Parameter #1 $i of closure expects int, string given.', - 70, - ], - [ - 'Parameter #1 $str of callable class@anonymous/tests/PHPStan/Rules/Functions/data/callables.php:75 expects string, int given.', - 81, - ], - [ - 'Trying to invoke \'\' but it\'s not a callable.', - 86, - ], - [ - 'Invoking callable on an unknown class CallCallables\Bar.', - 90, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Parameter #1 ...$foo of closure expects CallCallables\Foo, array given.', - 106, - ], - [ - 'Trying to invoke CallCallables\Baz but it might not be a callable.', - 113, - ], - [ - 'Trying to invoke array(object, \'bar\') but it might not be a callable.', - 131, - ], - [ - 'Closure invoked with 0 parameters, 3 required.', - 146, - ], - [ - 'Closure invoked with 1 parameter, 3 required.', - 147, - ], - [ - 'Closure invoked with 2 parameters, 3 required.', - 148, - ], - [ - 'Trying to invoke array(object, \'yo\') but it might not be a callable.', - 163, - ], - [ - 'Trying to invoke array(object, \'yo\') but it might not be a callable.', - 167, - ], - [ - 'Trying to invoke array(\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\') but it might not be a callable.', - 179, - ], - ]); - } - - public function testNamedArguments(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testRule(): void + { + if (PHP_VERSION_ID >= 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped(); + } + $this->analyse([__DIR__ . '/data/callables.php'], [ + [ + 'Trying to invoke string but it might not be a callable.', + 17, + ], + [ + 'Callable \'date\' invoked with 0 parameters, 1-2 required.', + 21, + ], + [ + 'Trying to invoke \'nonexistent\' but it\'s not a callable.', + 25, + ], + [ + 'Parameter #1 $i of callable array($this(CallCallables\Foo), \'doBar\') expects int, string given.', + 33, + ], + [ + 'Callable array(\'CallCallables\\\\Foo\', \'doStaticBaz\') invoked with 1 parameter, 0 required.', + 39, + ], + [ + 'Callable \'CallCallables\\\\Foo:…\' invoked with 1 parameter, 0 required.', + 41, + ], + [ + 'Call to private method privateFooMethod() of class CallCallables\Foo.', + 52, + ], + [ + 'Closure invoked with 0 parameters, 1-2 required.', + 58, + ], + [ + 'Result of closure (void) is used.', + 59, + ], + [ + 'Closure invoked with 0 parameters, at least 1 required.', + 64, + ], + [ + 'Parameter #1 $i of closure expects int, string given.', + 70, + ], + [ + 'Parameter #1 $str of callable class@anonymous/tests/PHPStan/Rules/Functions/data/callables.php:75 expects string, int given.', + 81, + ], + [ + 'Trying to invoke \'\' but it\'s not a callable.', + 86, + ], + [ + 'Invoking callable on an unknown class CallCallables\Bar.', + 90, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Parameter #1 ...$foo of closure expects CallCallables\Foo, array given.', + 106, + ], + [ + 'Trying to invoke CallCallables\Baz but it might not be a callable.', + 113, + ], + [ + 'Trying to invoke array(object, \'bar\') but it might not be a callable.', + 131, + ], + [ + 'Closure invoked with 0 parameters, 3 required.', + 146, + ], + [ + 'Closure invoked with 1 parameter, 3 required.', + 147, + ], + [ + 'Closure invoked with 2 parameters, 3 required.', + 148, + ], + [ + 'Trying to invoke array(object, \'yo\') but it might not be a callable.', + 163, + ], + [ + 'Trying to invoke array(object, \'yo\') but it might not be a callable.', + 167, + ], + [ + 'Trying to invoke array(\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\') but it might not be a callable.', + 179, + ], + ]); + } - $this->analyse([__DIR__ . '/data/callables-named-arguments.php'], [ - [ - 'Missing parameter $j (int) in call to closure.', - 14, - ], - [ - 'Unknown parameter $i in call to callable callable(int, int): void.', - 23, - ], - [ - 'Missing parameter $ (int) in call to callable callable(int, int): void.', - 23, - ], - [ - 'Missing parameter $j (int) in call to callable callable(int, int): void.', - 24, - ], - [ - 'Unknown parameter $z in call to callable callable(int, int): void.', - 25, - ], - ]); - } + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/callables-named-arguments.php'], [ + [ + 'Missing parameter $j (int) in call to closure.', + 14, + ], + [ + 'Unknown parameter $i in call to callable callable(int, int): void.', + 23, + ], + [ + 'Missing parameter $ (int) in call to callable callable(int, int): void.', + 23, + ], + [ + 'Missing parameter $j (int) in call to callable callable(int, int): void.', + 24, + ], + [ + 'Unknown parameter $z in call to callable callable(int, int): void.', + 25, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ef79626a21..2a7ddb3576 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new CallToFunctionParametersRule( - $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true) - ); - } - - public function testCallToFunctionWithoutParameters(): void - { - require_once __DIR__ . '/data/existing-function-definition.php'; - $this->analyse([__DIR__ . '/data/existing-function.php'], []); - } - - public function testCallToFunctionWithIncorrectParameters(): void - { - require_once __DIR__ . '/data/incorrect-call-to-function-definition.php'; - $this->analyse([__DIR__ . '/data/incorrect-call-to-function.php'], [ - [ - 'Function IncorrectCallToFunction\foo invoked with 1 parameter, 2 required.', - 5, - ], - [ - 'Function IncorrectCallToFunction\foo invoked with 3 parameters, 2 required.', - 7, - ], - [ - 'Parameter #1 $foo of function IncorrectCallToFunction\bar expects int, string given.', - 14, - ], - [ - 'Parameter #1 $callback of function set_error_handler expects (callable(int, string, string, int, array): bool)|null, Closure(mixed, mixed, mixed, mixed): void given.', - 16, - ], - ]); - } - - public function testcallToFunctionWithCorrectParameters(): void - { - $this->analyse([__DIR__ . '/data/call-functions.php'], []); - } - - public function testCallToFunctionWithOptionalParameters(): void - { - require_once __DIR__ . '/data/call-to-function-with-optional-parameters-definition.php'; - $this->analyse([__DIR__ . '/data/call-to-function-with-optional-parameters.php'], [ - [ - 'Function CallToFunctionWithOptionalParameters\foo invoked with 3 parameters, 1-2 required.', - 9, - ], - [ - 'Parameter #1 $object of function get_class expects object, null given.', - 12, - ], - [ - 'Parameter #1 $object of function get_class expects object, object|null given.', - 16, - ], - ]); - } - - public function testCallToFunctionWithDynamicParameters(): void - { - require_once __DIR__ . '/data/function-with-variadic-parameters-definition.php'; - $this->analyse([__DIR__ . '/data/function-with-variadic-parameters.php'], [ - [ - 'Function FunctionWithVariadicParameters\foo invoked with 0 parameters, at least 1 required.', - 6, - ], - [ - 'Parameter #3 ...$foo of function FunctionWithVariadicParameters\foo expects int, null given.', - 12, - ], - [ - 'Function FunctionWithVariadicParameters\bar invoked with 0 parameters, at least 1 required.', - 18, - ], - ]); - } - - public function testCallToFunctionWithNullableDynamicParameters(): void - { - require_once __DIR__ . '/data/function-with-nullable-variadic-parameters-definition.php'; - $this->analyse([__DIR__ . '/data/function-with-nullable-variadic-parameters.php'], [ - [ - 'Function FunctionWithNullableVariadicParameters\foo invoked with 0 parameters, at least 1 required.', - 6, - ], - ]); - } - - public function testCallToFunctionWithDynamicIterableParameters(): void - { - require_once __DIR__ . '/data/function-with-variadic-parameters-definition.php'; - $this->analyse([__DIR__ . '/data/function-with-variadic-parameters-7.1.php'], [ - [ - 'Parameter #2 ...$foo of function FunctionWithVariadicParameters\foo expects int, string given.', - 16, - ], - ]); - } - - public function testCallToArrayUnique(): void - { - $this->analyse([__DIR__ . '/data/call-to-array-unique.php'], [ - [ - 'Function array_unique invoked with 3 parameters, 1-2 required.', - 3, - ], - ]); - } - - public function testCallToArrayMapVariadic(): void - { - $this->analyse([__DIR__ . '/data/call-to-array-map-unique.php'], []); - } - - public function testCallToWeirdFunctions(): void - { - if (PHP_VERSION_ID >= 80000) { - $errors = [ - [ - 'Function implode invoked with 0 parameters, 1-2 required.', - 3, - ], - [ - 'Function implode invoked with 3 parameters, 1-2 required.', - 6, - ], - [ - 'Function strtok invoked with 0 parameters, 1-2 required.', - 8, - ], - [ - 'Function strtok invoked with 3 parameters, 1-2 required.', - 11, - ], - [ - 'Function fputcsv invoked with 1 parameter, 2-5 required.', - 12, - ], - [ - 'Function imagepng invoked with 0 parameters, 1-4 required.', - 16, - ], - [ - 'Function imagepng invoked with 5 parameters, 1-4 required.', - 19, - ], - [ - 'Function locale_get_display_language invoked with 3 parameters, 1-2 required.', - 30, - ], - [ - 'Function mysqli_fetch_all invoked with 0 parameters, 1-2 required.', - 32, - ], - [ - 'Function mysqli_fetch_all invoked with 3 parameters, 1-2 required.', - 35, - ], - [ - 'Function openssl_open invoked with 4 parameters, 5-6 required.', - 38, - ], - [ - 'Function openssl_open invoked with 7 parameters, 5-6 required.', - 39, - ], - [ - 'Function openssl_x509_parse invoked with 3 parameters, 1-2 required.', - 43, - ], - [ - 'Function openssl_pkcs12_export invoked with 6 parameters, 4-5 required.', - 49, - ], - [ - 'Parameter #1 $depth of function xdebug_call_class expects int, string given.', - 51, - ], - ]; - } else { - $errors = [ - [ - 'Function implode invoked with 0 parameters, 1-2 required.', - 3, - ], - [ - 'Function implode invoked with 3 parameters, 1-2 required.', - 6, - ], - [ - 'Function strtok invoked with 0 parameters, 1-2 required.', - 8, - ], - [ - 'Function strtok invoked with 3 parameters, 1-2 required.', - 11, - ], - [ - 'Function fputcsv invoked with 1 parameter, 2-5 required.', - 12, - ], - [ - 'Function imagepng invoked with 0 parameters, 1-4 required.', - 16, - ], - [ - 'Function imagepng invoked with 5 parameters, 1-4 required.', - 19, - ], - [ - 'Function locale_get_display_language invoked with 3 parameters, 1-2 required.', - 30, - ], - [ - 'Function mysqli_fetch_all invoked with 0 parameters, 1-2 required.', - 32, - ], - [ - 'Function mysqli_fetch_all invoked with 3 parameters, 1-2 required.', - 35, - ], - [ - 'Function openssl_open invoked with 7 parameters, 4-6 required.', - 39, - ], - [ - 'Function openssl_x509_parse invoked with 3 parameters, 1-2 required.', - 43, - ], - [ - 'Function openssl_pkcs12_export invoked with 6 parameters, 4-5 required.', - 49, - ], - [ - 'Parameter #1 $depth of function xdebug_call_class expects int, string given.', - 51, - ], - ]; - } - $this->analyse([__DIR__ . '/data/call-to-weird-functions.php'], $errors); - } - - /** - * @requires PHP 7.1.1 - */ - public function testUnpackOnAfter711(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70101) { - $this->markTestSkipped('This test requires PHP >= 7.1.1'); - } - $this->analyse([__DIR__ . '/data/unpack.php'], [ - [ - 'Function unpack invoked with 0 parameters, 2-3 required.', - 3, - ], - ]); - } - - public function testPassingNonVariableToParameterPassedByReference(): void - { - require_once __DIR__ . '/data/passed-by-reference.php'; - $this->analyse([__DIR__ . '/data/passed-by-reference.php'], [ - [ - 'Parameter #1 $foo of function PassedByReference\foo is passed by reference, so it expects variables only.', - 32, - ], - [ - 'Parameter #1 $foo of function PassedByReference\foo is passed by reference, so it expects variables only.', - 33, - ], - [ - 'Parameter #1 $array of function reset expects array, null given.', - 39, - ], - ]); - } - - public function testImplodeOnPhp74(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $errors = [ - [ - 'Parameter #1 $glue of function implode expects string, array given.', - 8, - ], - [ - 'Parameter #2 $pieces of function implode expects array, string given.', - 8, - ], - ]; - if (PHP_VERSION_ID < 70400) { - $errors = []; - } - - $this->analyse([__DIR__ . '/data/implode-74.php'], $errors); - } - - public function testImplodeOnLessThanPhp74(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test skipped on 7.4.'); - } - - $errors = []; - if (PHP_VERSION_ID >= 70400) { - $errors = [ - [ - 'Parameter #1 $glue of function implode expects string, array given.', - 8, - ], - [ - 'Parameter #2 $pieces of function implode expects array, string given.', - 8, - ], - ]; - } - - $this->analyse([__DIR__ . '/data/implode-74.php'], $errors); - } - - public function testVariableIsNotNullAfterSeriesOfConditions(): void - { - require_once __DIR__ . '/data/variable-is-not-null-after-conditions.php'; - $this->analyse([__DIR__ . '/data/variable-is-not-null-after-conditions.php'], []); - } - - public function testUnionIterableTypeShouldAcceptTypeFromOtherTypes(): void - { - require_once __DIR__ . '/data/union-iterable-type-issue.php'; - $this->analyse([__DIR__ . '/data/union-iterable-type-issue.php'], []); - } - - public function testCallToFunctionInForeachCondition(): void - { - require_once __DIR__ . '/data/foreach-condition.php'; - $this->analyse([__DIR__ . '/data/foreach-condition.php'], [ - [ - 'Parameter #1 $i of function CallToFunctionInForeachCondition\takesString expects string, int given.', - 20, - ], - ]); - } - - public function testCallToFunctionInDoWhileLoop(): void - { - require_once __DIR__ . '/data/do-while-loop.php'; - $this->analyse([__DIR__ . '/data/do-while-loop.php'], []); - } - - public function testRemoveArrayFromIterable(): void - { - require_once __DIR__ . '/data/remove-array-from-iterable.php'; - $this->analyse([__DIR__ . '/data/remove-array-from-iterable.php'], []); - } - - public function testUnpackOperator(): void - { - $this->analyse([__DIR__ . '/data/unpack-operator.php'], [ - [ - 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, array given.', - 18, - ], - [ - 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, array given.', - 19, - ], - [ - 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, UnpackOperator\Foo given.', - 22, - ], - [ - 'Parameter #2 ...$values of function printf expects bool|float|int|string|null, UnpackOperator\Foo given.', - 24, - ], - ]); - } - - public function testFputCsv(): void - { - $this->analyse([__DIR__ . '/data/fputcsv-fields-parameter.php'], [ - [ - 'Parameter #2 $fields of function fputcsv expects array, array given.', - 35, - ], - ]); - } - - - public function testPutCsvWithStringable(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test skipped on lower version than 8.0 (needs Stringable interface, added in PHP8)'); - } - - $this->analyse([__DIR__ . '/data/fputcsv-fields-parameter-php8.php'], [ - // No issues expected - ]); - } - - public function testFunctionWithNumericParameterThatCreatedByAddition(): void - { - $this->analyse([__DIR__ . '/data/function-with-int-parameter-that-created-by-addition.php'], [ - [ - 'Parameter #1 $num of function dechex expects int, float|int given.', - 40, - ], - ]); - } - - public function testWhileLoopLookForAssignsInBranchesVariableExistence(): void - { - $this->analyse([__DIR__ . '/data/while-loop-look-for-assigns.php'], []); - } - - public function testCallableOrClosureProblem(): void - { - require_once __DIR__ . '/data/callable-or-closure-problem.php'; - $this->analyse([__DIR__ . '/data/callable-or-closure-problem.php'], []); - } - - public function testGenericFunction(): void - { - require_once __DIR__ . '/data/call-generic-function.php'; - $this->analyse([__DIR__ . '/data/call-generic-function.php'], [ - [ - 'Unable to resolve the template type A in call to function CallGenericFunction\f', - 15, - 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', - ], - [ - 'Parameter #1 $a of function CallGenericFunction\g expects DateTime, DateTimeImmutable given.', - 26, - ], - [ - 'Unable to resolve the template type A in call to function CallGenericFunction\g', - 26, - 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', - ], - ]); - } - - public function testNamedArguments(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $errors = [ - [ - 'Missing parameter $j (int) in call to function FunctionNamedArguments\foo.', - 7, - ], - [ - 'Unknown parameter $z in call to function FunctionNamedArguments\foo.', - 8, - ], - [ - 'Unknown parameter $a in call to function array_merge.', - 14, - ], - ]; - if (PHP_VERSION_ID < 80000) { - $errors[] = [ - 'Missing parameter $arr1 (array) in call to function array_merge.', - 14, - ]; - } - - require_once __DIR__ . '/data/named-arguments-define.php'; - $this->analyse([__DIR__ . '/data/named-arguments.php'], $errors); - } - - public function testBug4514(): void - { - $this->analyse([__DIR__ . '/data/bug-4514.php'], []); - } - - public function testBug4530(): void - { - $this->analyse([__DIR__ . '/data/bug-4530.php'], []); - } - - public function testBug2268(): void - { - require_once __DIR__ . '/data/bug-2268.php'; - $this->analyse([__DIR__ . '/data/bug-2268.php'], []); - } - - public function testBug2434(): void - { - require_once __DIR__ . '/data/bug-2434.php'; - $this->analyse([__DIR__ . '/data/bug-2434.php'], []); - } - - public function testBug2846(): void - { - $this->analyse([__DIR__ . '/data/bug-2846.php'], []); - } - - public function testBug3608(): void - { - $this->analyse([__DIR__ . '/data/bug-3608.php'], []); - } - - public function testBug3920(): void - { - $this->analyse([__DIR__ . '/data/bug-3920.php'], []); - } - + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new CallToFunctionParametersRule( + $broker, + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true) + ); + } + + public function testCallToFunctionWithoutParameters(): void + { + require_once __DIR__ . '/data/existing-function-definition.php'; + $this->analyse([__DIR__ . '/data/existing-function.php'], []); + } + + public function testCallToFunctionWithIncorrectParameters(): void + { + require_once __DIR__ . '/data/incorrect-call-to-function-definition.php'; + $this->analyse([__DIR__ . '/data/incorrect-call-to-function.php'], [ + [ + 'Function IncorrectCallToFunction\foo invoked with 1 parameter, 2 required.', + 5, + ], + [ + 'Function IncorrectCallToFunction\foo invoked with 3 parameters, 2 required.', + 7, + ], + [ + 'Parameter #1 $foo of function IncorrectCallToFunction\bar expects int, string given.', + 14, + ], + [ + 'Parameter #1 $callback of function set_error_handler expects (callable(int, string, string, int, array): bool)|null, Closure(mixed, mixed, mixed, mixed): void given.', + 16, + ], + ]); + } + + public function testcallToFunctionWithCorrectParameters(): void + { + $this->analyse([__DIR__ . '/data/call-functions.php'], []); + } + + public function testCallToFunctionWithOptionalParameters(): void + { + require_once __DIR__ . '/data/call-to-function-with-optional-parameters-definition.php'; + $this->analyse([__DIR__ . '/data/call-to-function-with-optional-parameters.php'], [ + [ + 'Function CallToFunctionWithOptionalParameters\foo invoked with 3 parameters, 1-2 required.', + 9, + ], + [ + 'Parameter #1 $object of function get_class expects object, null given.', + 12, + ], + [ + 'Parameter #1 $object of function get_class expects object, object|null given.', + 16, + ], + ]); + } + + public function testCallToFunctionWithDynamicParameters(): void + { + require_once __DIR__ . '/data/function-with-variadic-parameters-definition.php'; + $this->analyse([__DIR__ . '/data/function-with-variadic-parameters.php'], [ + [ + 'Function FunctionWithVariadicParameters\foo invoked with 0 parameters, at least 1 required.', + 6, + ], + [ + 'Parameter #3 ...$foo of function FunctionWithVariadicParameters\foo expects int, null given.', + 12, + ], + [ + 'Function FunctionWithVariadicParameters\bar invoked with 0 parameters, at least 1 required.', + 18, + ], + ]); + } + + public function testCallToFunctionWithNullableDynamicParameters(): void + { + require_once __DIR__ . '/data/function-with-nullable-variadic-parameters-definition.php'; + $this->analyse([__DIR__ . '/data/function-with-nullable-variadic-parameters.php'], [ + [ + 'Function FunctionWithNullableVariadicParameters\foo invoked with 0 parameters, at least 1 required.', + 6, + ], + ]); + } + + public function testCallToFunctionWithDynamicIterableParameters(): void + { + require_once __DIR__ . '/data/function-with-variadic-parameters-definition.php'; + $this->analyse([__DIR__ . '/data/function-with-variadic-parameters-7.1.php'], [ + [ + 'Parameter #2 ...$foo of function FunctionWithVariadicParameters\foo expects int, string given.', + 16, + ], + ]); + } + + public function testCallToArrayUnique(): void + { + $this->analyse([__DIR__ . '/data/call-to-array-unique.php'], [ + [ + 'Function array_unique invoked with 3 parameters, 1-2 required.', + 3, + ], + ]); + } + + public function testCallToArrayMapVariadic(): void + { + $this->analyse([__DIR__ . '/data/call-to-array-map-unique.php'], []); + } + + public function testCallToWeirdFunctions(): void + { + if (PHP_VERSION_ID >= 80000) { + $errors = [ + [ + 'Function implode invoked with 0 parameters, 1-2 required.', + 3, + ], + [ + 'Function implode invoked with 3 parameters, 1-2 required.', + 6, + ], + [ + 'Function strtok invoked with 0 parameters, 1-2 required.', + 8, + ], + [ + 'Function strtok invoked with 3 parameters, 1-2 required.', + 11, + ], + [ + 'Function fputcsv invoked with 1 parameter, 2-5 required.', + 12, + ], + [ + 'Function imagepng invoked with 0 parameters, 1-4 required.', + 16, + ], + [ + 'Function imagepng invoked with 5 parameters, 1-4 required.', + 19, + ], + [ + 'Function locale_get_display_language invoked with 3 parameters, 1-2 required.', + 30, + ], + [ + 'Function mysqli_fetch_all invoked with 0 parameters, 1-2 required.', + 32, + ], + [ + 'Function mysqli_fetch_all invoked with 3 parameters, 1-2 required.', + 35, + ], + [ + 'Function openssl_open invoked with 4 parameters, 5-6 required.', + 38, + ], + [ + 'Function openssl_open invoked with 7 parameters, 5-6 required.', + 39, + ], + [ + 'Function openssl_x509_parse invoked with 3 parameters, 1-2 required.', + 43, + ], + [ + 'Function openssl_pkcs12_export invoked with 6 parameters, 4-5 required.', + 49, + ], + [ + 'Parameter #1 $depth of function xdebug_call_class expects int, string given.', + 51, + ], + ]; + } else { + $errors = [ + [ + 'Function implode invoked with 0 parameters, 1-2 required.', + 3, + ], + [ + 'Function implode invoked with 3 parameters, 1-2 required.', + 6, + ], + [ + 'Function strtok invoked with 0 parameters, 1-2 required.', + 8, + ], + [ + 'Function strtok invoked with 3 parameters, 1-2 required.', + 11, + ], + [ + 'Function fputcsv invoked with 1 parameter, 2-5 required.', + 12, + ], + [ + 'Function imagepng invoked with 0 parameters, 1-4 required.', + 16, + ], + [ + 'Function imagepng invoked with 5 parameters, 1-4 required.', + 19, + ], + [ + 'Function locale_get_display_language invoked with 3 parameters, 1-2 required.', + 30, + ], + [ + 'Function mysqli_fetch_all invoked with 0 parameters, 1-2 required.', + 32, + ], + [ + 'Function mysqli_fetch_all invoked with 3 parameters, 1-2 required.', + 35, + ], + [ + 'Function openssl_open invoked with 7 parameters, 4-6 required.', + 39, + ], + [ + 'Function openssl_x509_parse invoked with 3 parameters, 1-2 required.', + 43, + ], + [ + 'Function openssl_pkcs12_export invoked with 6 parameters, 4-5 required.', + 49, + ], + [ + 'Parameter #1 $depth of function xdebug_call_class expects int, string given.', + 51, + ], + ]; + } + $this->analyse([__DIR__ . '/data/call-to-weird-functions.php'], $errors); + } + + /** + * @requires PHP 7.1.1 + */ + public function testUnpackOnAfter711(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70101) { + $this->markTestSkipped('This test requires PHP >= 7.1.1'); + } + $this->analyse([__DIR__ . '/data/unpack.php'], [ + [ + 'Function unpack invoked with 0 parameters, 2-3 required.', + 3, + ], + ]); + } + + public function testPassingNonVariableToParameterPassedByReference(): void + { + require_once __DIR__ . '/data/passed-by-reference.php'; + $this->analyse([__DIR__ . '/data/passed-by-reference.php'], [ + [ + 'Parameter #1 $foo of function PassedByReference\foo is passed by reference, so it expects variables only.', + 32, + ], + [ + 'Parameter #1 $foo of function PassedByReference\foo is passed by reference, so it expects variables only.', + 33, + ], + [ + 'Parameter #1 $array of function reset expects array, null given.', + 39, + ], + ]); + } + + public function testImplodeOnPhp74(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $errors = [ + [ + 'Parameter #1 $glue of function implode expects string, array given.', + 8, + ], + [ + 'Parameter #2 $pieces of function implode expects array, string given.', + 8, + ], + ]; + if (PHP_VERSION_ID < 70400) { + $errors = []; + } + + $this->analyse([__DIR__ . '/data/implode-74.php'], $errors); + } + + public function testImplodeOnLessThanPhp74(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test skipped on 7.4.'); + } + + $errors = []; + if (PHP_VERSION_ID >= 70400) { + $errors = [ + [ + 'Parameter #1 $glue of function implode expects string, array given.', + 8, + ], + [ + 'Parameter #2 $pieces of function implode expects array, string given.', + 8, + ], + ]; + } + + $this->analyse([__DIR__ . '/data/implode-74.php'], $errors); + } + + public function testVariableIsNotNullAfterSeriesOfConditions(): void + { + require_once __DIR__ . '/data/variable-is-not-null-after-conditions.php'; + $this->analyse([__DIR__ . '/data/variable-is-not-null-after-conditions.php'], []); + } + + public function testUnionIterableTypeShouldAcceptTypeFromOtherTypes(): void + { + require_once __DIR__ . '/data/union-iterable-type-issue.php'; + $this->analyse([__DIR__ . '/data/union-iterable-type-issue.php'], []); + } + + public function testCallToFunctionInForeachCondition(): void + { + require_once __DIR__ . '/data/foreach-condition.php'; + $this->analyse([__DIR__ . '/data/foreach-condition.php'], [ + [ + 'Parameter #1 $i of function CallToFunctionInForeachCondition\takesString expects string, int given.', + 20, + ], + ]); + } + + public function testCallToFunctionInDoWhileLoop(): void + { + require_once __DIR__ . '/data/do-while-loop.php'; + $this->analyse([__DIR__ . '/data/do-while-loop.php'], []); + } + + public function testRemoveArrayFromIterable(): void + { + require_once __DIR__ . '/data/remove-array-from-iterable.php'; + $this->analyse([__DIR__ . '/data/remove-array-from-iterable.php'], []); + } + + public function testUnpackOperator(): void + { + $this->analyse([__DIR__ . '/data/unpack-operator.php'], [ + [ + 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, array given.', + 18, + ], + [ + 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, array given.', + 19, + ], + [ + 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, UnpackOperator\Foo given.', + 22, + ], + [ + 'Parameter #2 ...$values of function printf expects bool|float|int|string|null, UnpackOperator\Foo given.', + 24, + ], + ]); + } + + public function testFputCsv(): void + { + $this->analyse([__DIR__ . '/data/fputcsv-fields-parameter.php'], [ + [ + 'Parameter #2 $fields of function fputcsv expects array, array given.', + 35, + ], + ]); + } + + + public function testPutCsvWithStringable(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test skipped on lower version than 8.0 (needs Stringable interface, added in PHP8)'); + } + + $this->analyse([__DIR__ . '/data/fputcsv-fields-parameter-php8.php'], [ + // No issues expected + ]); + } + + public function testFunctionWithNumericParameterThatCreatedByAddition(): void + { + $this->analyse([__DIR__ . '/data/function-with-int-parameter-that-created-by-addition.php'], [ + [ + 'Parameter #1 $num of function dechex expects int, float|int given.', + 40, + ], + ]); + } + + public function testWhileLoopLookForAssignsInBranchesVariableExistence(): void + { + $this->analyse([__DIR__ . '/data/while-loop-look-for-assigns.php'], []); + } + + public function testCallableOrClosureProblem(): void + { + require_once __DIR__ . '/data/callable-or-closure-problem.php'; + $this->analyse([__DIR__ . '/data/callable-or-closure-problem.php'], []); + } + + public function testGenericFunction(): void + { + require_once __DIR__ . '/data/call-generic-function.php'; + $this->analyse([__DIR__ . '/data/call-generic-function.php'], [ + [ + 'Unable to resolve the template type A in call to function CallGenericFunction\f', + 15, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + [ + 'Parameter #1 $a of function CallGenericFunction\g expects DateTime, DateTimeImmutable given.', + 26, + ], + [ + 'Unable to resolve the template type A in call to function CallGenericFunction\g', + 26, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + ]); + } + + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $errors = [ + [ + 'Missing parameter $j (int) in call to function FunctionNamedArguments\foo.', + 7, + ], + [ + 'Unknown parameter $z in call to function FunctionNamedArguments\foo.', + 8, + ], + [ + 'Unknown parameter $a in call to function array_merge.', + 14, + ], + ]; + if (PHP_VERSION_ID < 80000) { + $errors[] = [ + 'Missing parameter $arr1 (array) in call to function array_merge.', + 14, + ]; + } + + require_once __DIR__ . '/data/named-arguments-define.php'; + $this->analyse([__DIR__ . '/data/named-arguments.php'], $errors); + } + + public function testBug4514(): void + { + $this->analyse([__DIR__ . '/data/bug-4514.php'], []); + } + + public function testBug4530(): void + { + $this->analyse([__DIR__ . '/data/bug-4530.php'], []); + } + + public function testBug2268(): void + { + require_once __DIR__ . '/data/bug-2268.php'; + $this->analyse([__DIR__ . '/data/bug-2268.php'], []); + } + + public function testBug2434(): void + { + require_once __DIR__ . '/data/bug-2434.php'; + $this->analyse([__DIR__ . '/data/bug-2434.php'], []); + } + + public function testBug2846(): void + { + $this->analyse([__DIR__ . '/data/bug-2846.php'], []); + } + + public function testBug3608(): void + { + $this->analyse([__DIR__ . '/data/bug-3608.php'], []); + } + + public function testBug3920(): void + { + $this->analyse([__DIR__ . '/data/bug-3920.php'], []); + } } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php index 26053f6cb5..18b17fa629 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider()); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/function-call-statement-no-side-effects.php'], [ - [ - 'Call to function sprintf() on a separate line has no effect.', - 11, - ], - ]); - } - - public function testPhpDoc(): void - { - require_once __DIR__ . '/data/function-call-statement-no-side-effects-phpdoc-definition.php'; - $this->analyse([__DIR__ . '/data/function-call-statement-no-side-effects-phpdoc.php'], [ - [ - 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure1() on a separate line has no effect.', - 8, - ], - [ - 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure2() on a separate line has no effect.', - 9, - ], - [ - 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure3() on a separate line has no effect.', - 10, - ], - [ - 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pureAndThrowsVoid() on a separate line has no effect.', - 11, - ], - ]); - } - - public function testBug4455(): void - { - require_once __DIR__ . '/data/bug-4455.php'; - $this->analyse([__DIR__ . '/data/bug-4455.php'], []); - } - + protected function getRule(): Rule + { + return new CallToFunctionStamentWithoutSideEffectsRule($this->createReflectionProvider()); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/function-call-statement-no-side-effects.php'], [ + [ + 'Call to function sprintf() on a separate line has no effect.', + 11, + ], + ]); + } + + public function testPhpDoc(): void + { + require_once __DIR__ . '/data/function-call-statement-no-side-effects-phpdoc-definition.php'; + $this->analyse([__DIR__ . '/data/function-call-statement-no-side-effects-phpdoc.php'], [ + [ + 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure1() on a separate line has no effect.', + 8, + ], + [ + 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure2() on a separate line has no effect.', + 9, + ], + [ + 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pure3() on a separate line has no effect.', + 10, + ], + [ + 'Call to function FunctionCallStatementNoSideEffectsPhpDoc\pureAndThrowsVoid() on a separate line has no effect.', + 11, + ], + ]); + } + + public function testBug4455(): void + { + require_once __DIR__ . '/data/bug-4455.php'; + $this->analyse([__DIR__ . '/data/bug-4455.php'], []); + } } diff --git a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php index 6b9e2cfe01..97626f7bfa 100644 --- a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new CallToNonExistentFunctionRule($this->createReflectionProvider(), true); - } - - public function testEmptyFile(): void - { - $this->analyse([__DIR__ . '/data/empty.php'], []); - } - - public function testCallToExistingFunction(): void - { - require_once __DIR__ . '/data/existing-function-definition.php'; - $this->analyse([__DIR__ . '/data/existing-function.php'], []); - } + public function testEmptyFile(): void + { + $this->analyse([__DIR__ . '/data/empty.php'], []); + } - public function testCallToNonexistentFunction(): void - { - $this->analyse([__DIR__ . '/data/nonexistent-function.php'], [ - [ - 'Function foobarNonExistentFunction not found.', - 5, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + public function testCallToExistingFunction(): void + { + require_once __DIR__ . '/data/existing-function-definition.php'; + $this->analyse([__DIR__ . '/data/existing-function.php'], []); + } - ], - ]); - } + public function testCallToNonexistentFunction(): void + { + $this->analyse([__DIR__ . '/data/nonexistent-function.php'], [ + [ + 'Function foobarNonExistentFunction not found.', + 5, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - public function testCallToNonexistentNestedFunction(): void - { - $this->analyse([__DIR__ . '/data/nonexistent-nested-function.php'], [ - [ - 'Function barNonExistentFunction not found.', - 5, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - ], - ]); - } + public function testCallToNonexistentNestedFunction(): void + { + $this->analyse([__DIR__ . '/data/nonexistent-nested-function.php'], [ + [ + 'Function barNonExistentFunction not found.', + 5, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - public function testCallToIncorrectCaseFunctionName(): void - { - require_once __DIR__ . '/data/incorrect-function-case-definition.php'; - $this->analyse([__DIR__ . '/data/incorrect-function-case.php'], [ - [ - 'Call to function IncorrectFunctionCase\fooBar() with incorrect case: foobar', - 5, - ], - [ - 'Call to function IncorrectFunctionCase\fooBar() with incorrect case: IncorrectFunctionCase\foobar', - 7, - ], - [ - 'Call to function htmlspecialchars() with incorrect case: htmlSpecialChars', - 10, - ], - ]); - } + ], + ]); + } - public function testMatchExprAnalysis(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testCallToIncorrectCaseFunctionName(): void + { + require_once __DIR__ . '/data/incorrect-function-case-definition.php'; + $this->analyse([__DIR__ . '/data/incorrect-function-case.php'], [ + [ + 'Call to function IncorrectFunctionCase\fooBar() with incorrect case: foobar', + 5, + ], + [ + 'Call to function IncorrectFunctionCase\fooBar() with incorrect case: IncorrectFunctionCase\foobar', + 7, + ], + [ + 'Call to function htmlspecialchars() with incorrect case: htmlSpecialChars', + 10, + ], + ]); + } - $this->analyse([__DIR__ . '/data/match-expr-analysis.php'], [ - [ - 'Function lorem not found.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Function ipsum not found.', - 11, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Function dolor not found.', - 11, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Function sit not found.', - 12, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testMatchExprAnalysis(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/match-expr-analysis.php'], [ + [ + 'Function lorem not found.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function ipsum not found.', + 11, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function dolor not found.', + 11, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function sit not found.', + 12, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index 6eedf97ab1..10f20f29c4 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ClosureAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new ClosureAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/closure-attributes.php'], [ - [ - 'Attribute class ClosureAttributes\Foo does not have the function target.', - 28, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/closure-attributes.php'], [ + [ + 'Attribute class ClosureAttributes\Foo does not have the function target.', + 28, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php index addb8aaf1a..fd1336fdfe 100644 --- a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false))); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false))); - } - - public function testClosureReturnTypeRule(): void - { - $this->analyse([__DIR__ . '/data/closureReturnTypes.php'], [ - [ - 'Anonymous function should return int but returns string.', - 21, - ], - [ - 'Anonymous function should return string but returns int.', - 28, - ], - [ - 'Anonymous function should return ClosureReturnTypes\Foo but returns ClosureReturnTypes\Bar.', - 35, - ], - [ - 'Anonymous function should return SomeOtherNamespace\Foo but returns ClosureReturnTypes\Foo.', - 39, - ], - [ - 'Anonymous function should return SomeOtherNamespace\Baz but returns ClosureReturnTypes\Foo.', - 46, - ], - [ - 'Anonymous function should return array()|null but empty return statement found.', - 88, - ], - [ - 'Anonymous function should return string but returns int.', - 105, - ], - [ - 'Anonymous function should return string but returns int.', - 115, - ], - [ - 'Anonymous function should return string but returns int.', - 118, - ], - ]); - } - - public function testClosureReturnTypeRulePhp70(): void - { - $this->analyse([__DIR__ . '/data/closureReturnTypes-7.0.php'], [ - [ - 'Anonymous function should return int but empty return statement found.', - 4, - ], - [ - 'Anonymous function should return string but empty return statement found.', - 8, - ], - ]); - } + public function testClosureReturnTypeRule(): void + { + $this->analyse([__DIR__ . '/data/closureReturnTypes.php'], [ + [ + 'Anonymous function should return int but returns string.', + 21, + ], + [ + 'Anonymous function should return string but returns int.', + 28, + ], + [ + 'Anonymous function should return ClosureReturnTypes\Foo but returns ClosureReturnTypes\Bar.', + 35, + ], + [ + 'Anonymous function should return SomeOtherNamespace\Foo but returns ClosureReturnTypes\Foo.', + 39, + ], + [ + 'Anonymous function should return SomeOtherNamespace\Baz but returns ClosureReturnTypes\Foo.', + 46, + ], + [ + 'Anonymous function should return array()|null but empty return statement found.', + 88, + ], + [ + 'Anonymous function should return string but returns int.', + 105, + ], + [ + 'Anonymous function should return string but returns int.', + 115, + ], + [ + 'Anonymous function should return string but returns int.', + 118, + ], + ]); + } - public function testClosureReturnTypePhp71Typehints(): void - { - $this->analyse([__DIR__ . '/data/closure-7.1ReturnTypes.php'], [ - [ - 'Anonymous function should return int|null but returns string.', - 9, - ], - [ - 'Anonymous function should return iterable but returns string.', - 22, - ], - ]); - } + public function testClosureReturnTypeRulePhp70(): void + { + $this->analyse([__DIR__ . '/data/closureReturnTypes-7.0.php'], [ + [ + 'Anonymous function should return int but empty return statement found.', + 4, + ], + [ + 'Anonymous function should return string but empty return statement found.', + 8, + ], + ]); + } - public function testBug3891(): void - { - $this->analyse([__DIR__ . '/data/bug-3891.php'], []); - } + public function testClosureReturnTypePhp71Typehints(): void + { + $this->analyse([__DIR__ . '/data/closure-7.1ReturnTypes.php'], [ + [ + 'Anonymous function should return int|null but returns string.', + 9, + ], + [ + 'Anonymous function should return iterable but returns string.', + 22, + ], + ]); + } + public function testBug3891(): void + { + $this->analyse([__DIR__ . '/data/bug-3891.php'], []); + } } diff --git a/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php index a84faa310a..bff21895e8 100644 --- a/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/closure-uses-this.php'], [ - [ - 'Anonymous function uses $this assigned to variable $that. Use $this directly in the function body.', - 16, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/closure-uses-this.php'], [ + [ + 'Anonymous function uses $this assigned to variable $that. Use $this directly in the function body.', + 16, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index f93f2c077b..e59d0874f0 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/arrow-function-typehints.php'], [ - [ - 'Parameter $bar of anonymous function has invalid typehint type ArrowFunctionExistingClassesInTypehints\Bar.', - 10, - ], - [ - 'Return typehint of anonymous function has invalid type ArrowFunctionExistingClassesInTypehints\Baz.', - 10, - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + } - public function dataNativeUnionTypes(): array - { - return [ - [ - 70400, - [ - [ - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', - 23, - ], - [ - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', - 24, - ], - ], - ], - [ - 80000, - [], - ], - ]; - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/arrow-function-typehints.php'], [ + [ + 'Parameter $bar of anonymous function has invalid typehint type ArrowFunctionExistingClassesInTypehints\Bar.', + 10, + ], + [ + 'Return typehint of anonymous function has invalid type ArrowFunctionExistingClassesInTypehints\Baz.', + 10, + ], + ]); + } - /** - * @dataProvider dataNativeUnionTypes - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testNativeUnionTypes(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function dataNativeUnionTypes(): array + { + return [ + [ + 70400, + [ + [ + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 23, + ], + [ + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 24, + ], + ], + ], + [ + 80000, + [], + ], + ]; + } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); - } + /** + * @dataProvider dataNativeUnionTypes + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testNativeUnionTypes(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function dataRequiredParameterAfterOptional(): array - { - return [ - [ - 70400, - [], - ], - [ - 80000, - [ - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 5, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 9, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 11, - ], - ], - ], - ]; - } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); + } - /** - * @dataProvider dataRequiredParameterAfterOptional - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + public function dataRequiredParameterAfterOptional(): array + { + return [ + [ + 70400, + [], + ], + [ + 80000, + [ + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 5, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 9, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 11, + ], + ], + ], + ]; + } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/required-parameter-after-optional-arrow.php'], $errors); - } + /** + * @dataProvider dataRequiredParameterAfterOptional + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/required-parameter-after-optional-arrow.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index d9364af66b..730263c944 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); - } - - public function testExistingClassInTypehint(): void - { - $this->analyse([__DIR__ . '/data/closure-typehints.php'], [ - [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\NonexistentClass.', - 10, - ], - [ - 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehints\BarFunctionTypehints.', - 15, - ], - [ - 'Class TestClosureFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestClosureFunctionTypehints\fOOfUnctionTypehints.', - 30, - ], - [ - 'Class TestClosureFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestClosureFunctionTypehints\FOOfUnctionTypehintS.', - 30, - ], - [ - 'Parameter $trait of anonymous function has invalid typehint type TestClosureFunctionTypehints\SomeTrait.', - 45, - ], - [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\SomeTrait.', - 50, - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + } - public function testValidTypehintPhp71(): void - { - $this->analyse([__DIR__ . '/data/closure-7.1-typehints.php'], [ - [ - 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehintsPhp71\NonexistentClass.', - 35, - ], - [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehintsPhp71\NonexistentClass.', - 35, - ], - ]); - } + public function testExistingClassInTypehint(): void + { + $this->analyse([__DIR__ . '/data/closure-typehints.php'], [ + [ + 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\NonexistentClass.', + 10, + ], + [ + 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehints\BarFunctionTypehints.', + 15, + ], + [ + 'Class TestClosureFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestClosureFunctionTypehints\fOOfUnctionTypehints.', + 30, + ], + [ + 'Class TestClosureFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestClosureFunctionTypehints\FOOfUnctionTypehintS.', + 30, + ], + [ + 'Parameter $trait of anonymous function has invalid typehint type TestClosureFunctionTypehints\SomeTrait.', + 45, + ], + [ + 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\SomeTrait.', + 50, + ], + ]); + } - /** - * @requires PHP 7.2 - */ - public function testValidTypehintPhp72(): void - { - $this->analyse([__DIR__ . '/data/closure-7.2-typehints.php'], []); - } + public function testValidTypehintPhp71(): void + { + $this->analyse([__DIR__ . '/data/closure-7.1-typehints.php'], [ + [ + 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehintsPhp71\NonexistentClass.', + 35, + ], + [ + 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehintsPhp71\NonexistentClass.', + 35, + ], + ]); + } - public function testVoidParameterTypehint(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection'); - } - $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ - [ - 'Parameter $param of anonymous function has invalid typehint type void.', - 5, - ], - ]); - } + /** + * @requires PHP 7.2 + */ + public function testValidTypehintPhp72(): void + { + $this->analyse([__DIR__ . '/data/closure-7.2-typehints.php'], []); + } - public function dataNativeUnionTypes(): array - { - return [ - [ - 70400, - [ - [ - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', - 15, - ], - [ - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', - 19, - ], - ], - ], - [ - 80000, - [], - ], - ]; - } + public function testVoidParameterTypehint(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection'); + } + $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ + [ + 'Parameter $param of anonymous function has invalid typehint type void.', + 5, + ], + ]); + } - /** - * @dataProvider dataNativeUnionTypes - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testNativeUnionTypes(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function dataNativeUnionTypes(): array + { + return [ + [ + 70400, + [ + [ + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 15, + ], + [ + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 19, + ], + ], + ], + [ + 80000, + [], + ], + ]; + } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); - } + /** + * @dataProvider dataNativeUnionTypes + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testNativeUnionTypes(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function dataRequiredParameterAfterOptional(): array - { - return [ - [ - 70400, - [], - ], - [ - 80000, - [ - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 5, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 13, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 17, - ], - ], - ], - ]; - } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); + } - /** - * @dataProvider dataRequiredParameterAfterOptional - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void - { - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/required-parameter-after-optional-closures.php'], $errors); - } + public function dataRequiredParameterAfterOptional(): array + { + return [ + [ + 70400, + [], + ], + [ + 80000, + [ + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 5, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 13, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 17, + ], + ], + ], + ]; + } + /** + * @dataProvider dataRequiredParameterAfterOptional + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void + { + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/required-parameter-after-optional-closures.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 9200981e8c..c66ece3ca9 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); - } - - public function testExistingClassInTypehint(): void - { - require_once __DIR__ . '/data/typehints.php'; - $this->analyse([__DIR__ . '/data/typehints.php'], [ - [ - 'Return typehint of function TestFunctionTypehints\foo() has invalid type TestFunctionTypehints\NonexistentClass.', - 15, - ], - [ - 'Parameter $bar of function TestFunctionTypehints\bar() has invalid typehint type TestFunctionTypehints\BarFunctionTypehints.', - 20, - ], - [ - 'Return typehint of function TestFunctionTypehints\returnParent() has invalid type TestFunctionTypehints\parent.', - 33, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\fOOFunctionTypehints.', - 38, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\fOOFunctionTypehintS.', - 38, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', - 47, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', - 47, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', - 56, - ], - [ - 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', - 56, - ], - [ - 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInNative() has invalid typehint type TestFunctionTypehints\SomeTrait.', - 61, - ], - [ - 'Return typehint of function TestFunctionTypehints\referencesTraitsInNative() has invalid type TestFunctionTypehints\SomeTrait.', - 61, - ], - [ - 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid typehint type TestFunctionTypehints\SomeTrait.', - 70, - ], - [ - 'Return typehint of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid type TestFunctionTypehints\SomeTrait.', - 70, - ], - [ - 'Parameter $string of function TestFunctionTypehints\genericClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', - 78, - ], - [ - 'Parameter $string of function TestFunctionTypehints\genericTemplateClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', - 87, - ], - [ - 'Template type T of function TestFunctionTypehints\templateTypeMissingInParameter() is not referenced in a parameter.', - 96, - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + } - public function testWithoutNamespace(): void - { - require_once __DIR__ . '/data/typehintsWithoutNamespace.php'; - $this->analyse([__DIR__ . '/data/typehintsWithoutNamespace.php'], [ - [ - 'Return typehint of function fooWithoutNamespace() has invalid type NonexistentClass.', - 13, - ], - [ - 'Parameter $bar of function barWithoutNamespace() has invalid typehint type BarFunctionTypehints.', - 18, - ], - [ - 'Return typehint of function returnParentWithoutNamespace() has invalid type parent.', - 31, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: fOOFunctionTypehints.', - 36, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: fOOFunctionTypehintS.', - 36, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', - 45, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', - 45, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', - 54, - ], - [ - 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', - 54, - ], - [ - 'Parameter $trait of function referencesTraitsInNativeWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', - 59, - ], - [ - 'Return typehint of function referencesTraitsInNativeWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', - 59, - ], - [ - 'Parameter $trait of function referencesTraitsInPhpDocWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', - 68, - ], - [ - 'Return typehint of function referencesTraitsInPhpDocWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', - 68, - ], - ]); - } + public function testExistingClassInTypehint(): void + { + require_once __DIR__ . '/data/typehints.php'; + $this->analyse([__DIR__ . '/data/typehints.php'], [ + [ + 'Return typehint of function TestFunctionTypehints\foo() has invalid type TestFunctionTypehints\NonexistentClass.', + 15, + ], + [ + 'Parameter $bar of function TestFunctionTypehints\bar() has invalid typehint type TestFunctionTypehints\BarFunctionTypehints.', + 20, + ], + [ + 'Return typehint of function TestFunctionTypehints\returnParent() has invalid type TestFunctionTypehints\parent.', + 33, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\fOOFunctionTypehints.', + 38, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\fOOFunctionTypehintS.', + 38, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', + 47, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', + 47, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', + 56, + ], + [ + 'Class TestFunctionTypehints\FooFunctionTypehints referenced with incorrect case: TestFunctionTypehints\FOOFunctionTypehints.', + 56, + ], + [ + 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInNative() has invalid typehint type TestFunctionTypehints\SomeTrait.', + 61, + ], + [ + 'Return typehint of function TestFunctionTypehints\referencesTraitsInNative() has invalid type TestFunctionTypehints\SomeTrait.', + 61, + ], + [ + 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid typehint type TestFunctionTypehints\SomeTrait.', + 70, + ], + [ + 'Return typehint of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid type TestFunctionTypehints\SomeTrait.', + 70, + ], + [ + 'Parameter $string of function TestFunctionTypehints\genericClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', + 78, + ], + [ + 'Parameter $string of function TestFunctionTypehints\genericTemplateClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', + 87, + ], + [ + 'Template type T of function TestFunctionTypehints\templateTypeMissingInParameter() is not referenced in a parameter.', + 96, + ], + ]); + } - public function testVoidParameterTypehint(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection'); - } - $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ - [ - 'Parameter $param of function VoidParameterTypehint\doFoo() has invalid typehint type void.', - 9, - ], - ]); - } + public function testWithoutNamespace(): void + { + require_once __DIR__ . '/data/typehintsWithoutNamespace.php'; + $this->analyse([__DIR__ . '/data/typehintsWithoutNamespace.php'], [ + [ + 'Return typehint of function fooWithoutNamespace() has invalid type NonexistentClass.', + 13, + ], + [ + 'Parameter $bar of function barWithoutNamespace() has invalid typehint type BarFunctionTypehints.', + 18, + ], + [ + 'Return typehint of function returnParentWithoutNamespace() has invalid type parent.', + 31, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: fOOFunctionTypehints.', + 36, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: fOOFunctionTypehintS.', + 36, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', + 45, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', + 45, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', + 54, + ], + [ + 'Class FooFunctionTypehints referenced with incorrect case: FOOFunctionTypehints.', + 54, + ], + [ + 'Parameter $trait of function referencesTraitsInNativeWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', + 59, + ], + [ + 'Return typehint of function referencesTraitsInNativeWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', + 59, + ], + [ + 'Parameter $trait of function referencesTraitsInPhpDocWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', + 68, + ], + [ + 'Return typehint of function referencesTraitsInPhpDocWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', + 68, + ], + ]); + } - public function dataNativeUnionTypes(): array - { - return [ - [ - 70400, - [ - [ - 'Function NativeUnionTypesSupport\foo() uses native union types but they\'re supported only on PHP 8.0 and later.', - 5, - ], - [ - 'Function NativeUnionTypesSupport\bar() uses native union types but they\'re supported only on PHP 8.0 and later.', - 10, - ], - ], - ], - [ - 80000, - [], - ], - ]; - } + public function testVoidParameterTypehint(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection'); + } + $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ + [ + 'Parameter $param of function VoidParameterTypehint\doFoo() has invalid typehint type void.', + 9, + ], + ]); + } - /** - * @dataProvider dataNativeUnionTypes - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testNativeUnionTypes(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function dataNativeUnionTypes(): array + { + return [ + [ + 70400, + [ + [ + 'Function NativeUnionTypesSupport\foo() uses native union types but they\'re supported only on PHP 8.0 and later.', + 5, + ], + [ + 'Function NativeUnionTypesSupport\bar() uses native union types but they\'re supported only on PHP 8.0 and later.', + 10, + ], + ], + ], + [ + 80000, + [], + ], + ]; + } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); - } + /** + * @dataProvider dataNativeUnionTypes + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testNativeUnionTypes(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function dataRequiredParameterAfterOptional(): array - { - return [ - [ - 70400, - [], - ], - [ - 80000, - [ - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 5, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 14, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 18, - ], - ], - ], - ]; - } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); + } - /** - * @dataProvider dataRequiredParameterAfterOptional - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void - { - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); - } + public function dataRequiredParameterAfterOptional(): array + { + return [ + [ + 70400, + [], + ], + [ + 80000, + [ + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 5, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 14, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 18, + ], + ], + ], + ]; + } + /** + * @dataProvider dataRequiredParameterAfterOptional + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void + { + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index ddca8aacbe..00a22137da 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new FunctionAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new FunctionAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/function-attributes.php'], [ - [ - 'Attribute class FunctionAttributes\Foo does not have the function target.', - 23, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/function-attributes.php'], [ + [ + 'Attribute class FunctionAttributes\Foo does not have the function target.', + 23, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/IncompatibleDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Functions/IncompatibleDefaultParameterTypeRuleTest.php index 6dd5a8e4be..f3aa4f614b 100644 --- a/tests/PHPStan/Rules/Functions/IncompatibleDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/IncompatibleDefaultParameterTypeRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/incompatible-default-parameter-type-functions.php'], [ - [ - 'Default value of the parameter #1 $string (false) of function IncompatibleDefaultParameter\takesString() is incompatible with type string.', - 15, - ], - ]); - } - - public function testBug3349(): void - { - require_once __DIR__ . '/data/define-bug-3349.php'; - $this->analyse([__DIR__ . '/data/bug-3349.php'], []); - } + public function testFunctions(): void + { + require_once __DIR__ . '/data/incompatible-default-parameter-type-functions.php'; + $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-functions.php'], [ + [ + 'Default value of the parameter #1 $string (false) of function IncompatibleDefaultParameter\takesString() is incompatible with type string.', + 15, + ], + ]); + } + public function testBug3349(): void + { + require_once __DIR__ . '/data/define-bug-3349.php'; + $this->analyse([__DIR__ . '/data/bug-3349.php'], []); + } } diff --git a/tests/PHPStan/Rules/Functions/InnerFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/InnerFunctionRuleTest.php index fe6fb38f23..91b464f71c 100644 --- a/tests/PHPStan/Rules/Functions/InnerFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/InnerFunctionRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/inner-functions.php'], [ - [ - 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.', - 7, - ], - [ - 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.', - 18, - ], - ]); - } - + public function testInnerFunction(): void + { + $this->analyse([__DIR__ . '/data/inner-functions.php'], [ + [ + 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.', + 7, + ], + [ + 'Inner named functions are not supported by PHPStan. Consider refactoring to an anonymous function, class method, or a top-level-defined function. See issue #165 (https://github.com/phpstan/phpstan/issues/165) for more details.', + 18, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index 93bc8df9bd..e5728fa392 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true)); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/missing-function-parameter-typehint.php'; - $this->analyse([__DIR__ . '/data/missing-function-parameter-typehint.php'], [ - [ - 'Function globalFunction() has parameter $b with no typehint specified.', - 9, - ], - [ - 'Function globalFunction() has parameter $c with no typehint specified.', - 9, - ], - [ - 'Function MissingFunctionParameterTypehint\namespacedFunction() has parameter $d with no typehint specified.', - 24, - ], - [ - 'Function MissingFunctionParameterTypehint\missingArrayTypehint() has parameter $a with no value type specified in iterable type array.', - 36, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\missingPhpDocIterableTypehint() has parameter $a with no value type specified in iterable type array.', - 44, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\unionTypeWithUnknownArrayValueTypehint() has parameter $a with no value type specified in iterable type array.', - 60, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\acceptsGenericInterface() has parameter $i with generic interface MissingFunctionParameterTypehint\GenericInterface but does not specify its types: T, U', - 111, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Function MissingFunctionParameterTypehint\acceptsGenericClass() has parameter $c with generic class MissingFunctionParameterTypehint\GenericClass but does not specify its types: A, B', - 130, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Function MissingFunctionParameterTypehint\missingIterableTypehint() has parameter $iterable with no value type specified in iterable type iterable.', - 135, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\missingIterableTypehintPhpDoc() has parameter $iterable with no value type specified in iterable type iterable.', - 143, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\missingTraversableTypehint() has parameter $traversable with no value type specified in iterable type Traversable.', - 148, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\missingTraversableTypehintPhpDoc() has parameter $traversable with no value type specified in iterable type Traversable.', - 156, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionParameterTypehint\missingCallableSignature() has parameter $cb with no signature specified for callable.', - 161, - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/missing-function-parameter-typehint.php'; + $this->analyse([__DIR__ . '/data/missing-function-parameter-typehint.php'], [ + [ + 'Function globalFunction() has parameter $b with no typehint specified.', + 9, + ], + [ + 'Function globalFunction() has parameter $c with no typehint specified.', + 9, + ], + [ + 'Function MissingFunctionParameterTypehint\namespacedFunction() has parameter $d with no typehint specified.', + 24, + ], + [ + 'Function MissingFunctionParameterTypehint\missingArrayTypehint() has parameter $a with no value type specified in iterable type array.', + 36, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\missingPhpDocIterableTypehint() has parameter $a with no value type specified in iterable type array.', + 44, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\unionTypeWithUnknownArrayValueTypehint() has parameter $a with no value type specified in iterable type array.', + 60, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\acceptsGenericInterface() has parameter $i with generic interface MissingFunctionParameterTypehint\GenericInterface but does not specify its types: T, U', + 111, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Function MissingFunctionParameterTypehint\acceptsGenericClass() has parameter $c with generic class MissingFunctionParameterTypehint\GenericClass but does not specify its types: A, B', + 130, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Function MissingFunctionParameterTypehint\missingIterableTypehint() has parameter $iterable with no value type specified in iterable type iterable.', + 135, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\missingIterableTypehintPhpDoc() has parameter $iterable with no value type specified in iterable type iterable.', + 143, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\missingTraversableTypehint() has parameter $traversable with no value type specified in iterable type Traversable.', + 148, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\missingTraversableTypehintPhpDoc() has parameter $traversable with no value type specified in iterable type Traversable.', + 156, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionParameterTypehint\missingCallableSignature() has parameter $cb with no signature specified for callable.', + 161, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php index 006902082e..7fd5c571d4 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true)); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/missing-function-return-typehint.php'; - $this->analyse([__DIR__ . '/data/missing-function-return-typehint.php'], [ - [ - 'Function globalFunction1() has no return typehint specified.', - 5, - ], - [ - 'Function MissingFunctionReturnTypehint\namespacedFunction1() has no return typehint specified.', - 30, - ], - [ - 'Function MissingFunctionReturnTypehint\unionTypeWithUnknownArrayValueTypehint() return type has no value type specified in iterable type array.', - 51, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Function MissingFunctionReturnTypehint\returnsGenericInterface() return type with generic interface MissingFunctionReturnTypehint\GenericInterface does not specify its types: T, U', - 70, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Function MissingFunctionReturnTypehint\returnsGenericClass() return type with generic class MissingFunctionReturnTypehint\GenericClass does not specify its types: A, B', - 89, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Function MissingFunctionReturnTypehint\genericGenericMissingTemplateArgs() return type with generic class MissingFunctionReturnTypehint\GenericClass does not specify its types: A, B', - 105, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Function MissingFunctionReturnTypehint\closureWithNoPrototype() return type has no signature specified for Closure.', - 113, - ], - [ - 'Function MissingFunctionReturnTypehint\callableWithNoPrototype() return type has no signature specified for callable.', - 127, - ], - [ - 'Function MissingFunctionReturnTypehint\callableNestedNoPrototype() return type has no signature specified for callable.', - 141, - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/missing-function-return-typehint.php'; + $this->analyse([__DIR__ . '/data/missing-function-return-typehint.php'], [ + [ + 'Function globalFunction1() has no return typehint specified.', + 5, + ], + [ + 'Function MissingFunctionReturnTypehint\namespacedFunction1() has no return typehint specified.', + 30, + ], + [ + 'Function MissingFunctionReturnTypehint\unionTypeWithUnknownArrayValueTypehint() return type has no value type specified in iterable type array.', + 51, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Function MissingFunctionReturnTypehint\returnsGenericInterface() return type with generic interface MissingFunctionReturnTypehint\GenericInterface does not specify its types: T, U', + 70, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Function MissingFunctionReturnTypehint\returnsGenericClass() return type with generic class MissingFunctionReturnTypehint\GenericClass does not specify its types: A, B', + 89, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Function MissingFunctionReturnTypehint\genericGenericMissingTemplateArgs() return type with generic class MissingFunctionReturnTypehint\GenericClass does not specify its types: A, B', + 105, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Function MissingFunctionReturnTypehint\closureWithNoPrototype() return type has no signature specified for Closure.', + 113, + ], + [ + 'Function MissingFunctionReturnTypehint\callableWithNoPrototype() return type has no signature specified for callable.', + 127, + ], + [ + 'Function MissingFunctionReturnTypehint\callableNestedNoPrototype() return type has no signature specified for callable.', + 141, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index cdebc6b530..8fda5ad390 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ParamAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new ParamAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/param-attributes.php'], [ - [ - 'Attribute class ParamAttributes\Foo does not have the parameter target.', - 27, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/param-attributes.php'], [ + [ + 'Attribute class ParamAttributes\Foo does not have the parameter target.', + 27, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php index b89c61e71c..734421f9ea 100644 --- a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/printf.php'], [ - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 6, - ], - [ - 'Call to sprintf contains 0 placeholders, 1 value given.', - 7, - ], - [ - 'Call to sprintf contains 1 placeholder, 2 values given.', - 8, - ], - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 9, - ], - [ - 'Call to sprintf contains 2 placeholders, 0 values given.', - 10, - ], - [ - 'Call to sprintf contains 4 placeholders, 0 values given.', - 11, - ], - [ - 'Call to sprintf contains 5 placeholders, 2 values given.', - 13, - ], - [ - 'Call to sprintf contains 1 placeholder, 2 values given.', - 15, - ], - [ - 'Call to sprintf contains 6 placeholders, 0 values given.', - 16, - ], - [ - 'Call to sprintf contains 2 placeholders, 0 values given.', - 17, - ], - [ - 'Call to sprintf contains 1 placeholder, 0 values given.', - 18, - ], - [ - 'Call to sscanf contains 2 placeholders, 1 value given.', - 21, - ], - [ - 'Call to fscanf contains 2 placeholders, 1 value given.', - 25, - ], - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 27, - ], - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 29, - ], - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 45, - ], - [ - 'Call to sprintf contains 2 placeholders, 1 value given.', - 52, - ], - [ - 'Call to sprintf contains 2 placeholders, 3 values given.', - 54, - ], - ]); - } - - public function testBug4717(): void - { - $errors = [ - [ - 'Call to sprintf contains 1 placeholder, 2 values given.', - 5, - ], - ]; - if (PHP_VERSION_ID >= 80000) { - $errors = []; - } - $this->analyse([__DIR__ . '/data/bug-4717.php'], $errors); - } + public function testFile(): void + { + $this->analyse([__DIR__ . '/data/printf.php'], [ + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 6, + ], + [ + 'Call to sprintf contains 0 placeholders, 1 value given.', + 7, + ], + [ + 'Call to sprintf contains 1 placeholder, 2 values given.', + 8, + ], + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 9, + ], + [ + 'Call to sprintf contains 2 placeholders, 0 values given.', + 10, + ], + [ + 'Call to sprintf contains 4 placeholders, 0 values given.', + 11, + ], + [ + 'Call to sprintf contains 5 placeholders, 2 values given.', + 13, + ], + [ + 'Call to sprintf contains 1 placeholder, 2 values given.', + 15, + ], + [ + 'Call to sprintf contains 6 placeholders, 0 values given.', + 16, + ], + [ + 'Call to sprintf contains 2 placeholders, 0 values given.', + 17, + ], + [ + 'Call to sprintf contains 1 placeholder, 0 values given.', + 18, + ], + [ + 'Call to sscanf contains 2 placeholders, 1 value given.', + 21, + ], + [ + 'Call to fscanf contains 2 placeholders, 1 value given.', + 25, + ], + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 27, + ], + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 29, + ], + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 45, + ], + [ + 'Call to sprintf contains 2 placeholders, 1 value given.', + 52, + ], + [ + 'Call to sprintf contains 2 placeholders, 3 values given.', + 54, + ], + ]); + } + public function testBug4717(): void + { + $errors = [ + [ + 'Call to sprintf contains 1 placeholder, 2 values given.', + 5, + ], + ]; + if (PHP_VERSION_ID >= 80000) { + $errors = []; + } + $this->analyse([__DIR__ . '/data/bug-4717.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php index 06d0f8a03a..2c7f9d04ab 100644 --- a/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new RandomIntParametersRule($this->createReflectionProvider(), true); - } - - public function testFile(): void - { - $this->analyse([__DIR__ . '/data/random-int.php'], [ - [ - 'Parameter #1 $min (1) of function random_int expects lower number than parameter #2 $max (0).', - 8, - ], - [ - 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (-1).', - 9, - ], - [ - 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (int<-10, -1>).', - 11, - ], - [ - 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (int<-10, 10>).', - 12, - ], - [ - 'Parameter #1 $min (int<1, 10>) of function random_int expects lower number than parameter #2 $max (0).', - 15, - ], - [ - 'Parameter #1 $min (int<-10, 10>) of function random_int expects lower number than parameter #2 $max (0).', - 16, - ], - [ - 'Parameter #1 $min (int<-5, 1>) of function random_int expects lower number than parameter #2 $max (int<0, 5>).', - 19, - ], - [ - 'Parameter #1 $min (int<-5, 0>) of function random_int expects lower number than parameter #2 $max (int<-1, 5>).', - 20, - ], - [ - 'Parameter #1 $min (int<0, 10>) of function random_int expects lower number than parameter #2 $max (int<0, 10>).', - 31, - ], - ]); - } - + public function testFile(): void + { + $this->analyse([__DIR__ . '/data/random-int.php'], [ + [ + 'Parameter #1 $min (1) of function random_int expects lower number than parameter #2 $max (0).', + 8, + ], + [ + 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (-1).', + 9, + ], + [ + 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (int<-10, -1>).', + 11, + ], + [ + 'Parameter #1 $min (0) of function random_int expects lower number than parameter #2 $max (int<-10, 10>).', + 12, + ], + [ + 'Parameter #1 $min (int<1, 10>) of function random_int expects lower number than parameter #2 $max (0).', + 15, + ], + [ + 'Parameter #1 $min (int<-10, 10>) of function random_int expects lower number than parameter #2 $max (0).', + 16, + ], + [ + 'Parameter #1 $min (int<-5, 1>) of function random_int expects lower number than parameter #2 $max (int<0, 5>).', + 19, + ], + [ + 'Parameter #1 $min (int<-5, 0>) of function random_int expects lower number than parameter #2 $max (int<-1, 5>).', + 20, + ], + [ + 'Parameter #1 $min (int<0, 10>) of function random_int expects lower number than parameter #2 $max (int<0, 10>).', + 31, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php index 9d31dc75d2..f232a287a4 100644 --- a/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/return-null-safe-by-ref.php'], [ - [ - 'Nullsafe cannot be returned by reference.', - 15, - ], - [ - 'Nullsafe cannot be returned by reference.', - 25, - ], - [ - 'Nullsafe cannot be returned by reference.', - 36, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/return-null-safe-by-ref.php'], [ + [ + 'Nullsafe cannot be returned by reference.', + 15, + ], + [ + 'Nullsafe cannot be returned by reference.', + 25, + ], + [ + 'Nullsafe cannot be returned by reference.', + 36, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 3567dd7190..37d7f5ee14 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)), $functionReflector); + } - protected function getRule(): \PHPStan\Rules\Rule - { - [, $functionReflector] = self::getReflectors(); - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)), $functionReflector); - } - - public function testReturnTypeRule(): void - { - require_once __DIR__ . '/data/returnTypes.php'; - $this->analyse([__DIR__ . '/data/returnTypes.php'], [ - [ - 'Function ReturnTypes\returnInteger() should return int but returns string.', - 17, - ], - [ - 'Function ReturnTypes\returnObject() should return ReturnTypes\Bar but returns int.', - 27, - ], - [ - 'Function ReturnTypes\returnObject() should return ReturnTypes\Bar but returns ReturnTypes\Foo.', - 31, - ], - [ - 'Function ReturnTypes\returnChild() should return ReturnTypes\Foo but returns ReturnTypes\OtherInterfaceImpl.', - 50, - ], - [ - 'Function ReturnTypes\returnVoid() with return type void returns null but should not return anything.', - 83, - ], - [ - 'Function ReturnTypes\returnVoid() with return type void returns int but should not return anything.', - 87, - ], - [ - 'Function ReturnTypes\returnFromGeneratorString() should return string but empty return statement found.', - 152, - ], - [ - 'Function ReturnTypes\returnFromGeneratorString() should return string but returns int.', - 155, - ], - [ - 'Function ReturnTypes\returnVoidFromGenerator2() with return type void returns int but should not return anything.', - 173, - ], - [ - 'Function ReturnTypes\returnNever() should never return but return statement found.', - 181, - ], - ]); - } - - public function testReturnTypeRulePhp70(): void - { - $this->analyse([__DIR__ . '/data/returnTypes-7.0.php'], [ - [ - 'Function ReturnTypes\Php70\returnInteger() should return int but empty return statement found.', - 7, - ], - ]); - } + public function testReturnTypeRule(): void + { + require_once __DIR__ . '/data/returnTypes.php'; + $this->analyse([__DIR__ . '/data/returnTypes.php'], [ + [ + 'Function ReturnTypes\returnInteger() should return int but returns string.', + 17, + ], + [ + 'Function ReturnTypes\returnObject() should return ReturnTypes\Bar but returns int.', + 27, + ], + [ + 'Function ReturnTypes\returnObject() should return ReturnTypes\Bar but returns ReturnTypes\Foo.', + 31, + ], + [ + 'Function ReturnTypes\returnChild() should return ReturnTypes\Foo but returns ReturnTypes\OtherInterfaceImpl.', + 50, + ], + [ + 'Function ReturnTypes\returnVoid() with return type void returns null but should not return anything.', + 83, + ], + [ + 'Function ReturnTypes\returnVoid() with return type void returns int but should not return anything.', + 87, + ], + [ + 'Function ReturnTypes\returnFromGeneratorString() should return string but empty return statement found.', + 152, + ], + [ + 'Function ReturnTypes\returnFromGeneratorString() should return string but returns int.', + 155, + ], + [ + 'Function ReturnTypes\returnVoidFromGenerator2() with return type void returns int but should not return anything.', + 173, + ], + [ + 'Function ReturnTypes\returnNever() should never return but return statement found.', + 181, + ], + ]); + } - public function testIsGenerator(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } + public function testReturnTypeRulePhp70(): void + { + $this->analyse([__DIR__ . '/data/returnTypes-7.0.php'], [ + [ + 'Function ReturnTypes\Php70\returnInteger() should return int but empty return statement found.', + 7, + ], + ]); + } - $this->analyse([__DIR__ . '/data/is-generator.php'], []); - } + public function testIsGenerator(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } - public function testBug2568(): void - { - require_once __DIR__ . '/data/bug-2568.php'; - $this->analyse([__DIR__ . '/data/bug-2568.php'], []); - } + $this->analyse([__DIR__ . '/data/is-generator.php'], []); + } - public function testBug2723(): void - { - require_once __DIR__ . '/data/bug-2723.php'; - $this->analyse([__DIR__ . '/data/bug-2723.php'], [ - [ - 'Function Bug2723\baz() should return Bug2723\Bar> but returns Bug2723\BarOfFoo.', - 55, - ], - ]); - } + public function testBug2568(): void + { + require_once __DIR__ . '/data/bug-2568.php'; + $this->analyse([__DIR__ . '/data/bug-2568.php'], []); + } + public function testBug2723(): void + { + require_once __DIR__ . '/data/bug-2723.php'; + $this->analyse([__DIR__ . '/data/bug-2723.php'], [ + [ + 'Function Bug2723\baz() should return Bug2723\Bar> but returns Bug2723\BarOfFoo.', + 55, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php b/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php index 8152274a6a..3c43af9f0c 100644 --- a/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider())); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new UnusedClosureUsesRule(new UnusedFunctionParametersCheck($this->createReflectionProvider())); - } - - public function testUnusedClosureUses(): void - { - $this->analyse([__DIR__ . '/data/unused-closure-uses.php'], [ - [ - 'Anonymous function has an unused use $unused.', - 3, - ], - [ - 'Anonymous function has an unused use $anotherUnused.', - 3, - ], - [ - 'Anonymous function has an unused use $usedInClosureUse.', - 10, - ], - ]); - } - + public function testUnusedClosureUses(): void + { + $this->analyse([__DIR__ . '/data/unused-closure-uses.php'], [ + [ + 'Anonymous function has an unused use $unused.', + 3, + ], + [ + 'Anonymous function has an unused use $anotherUnused.', + 3, + ], + [ + 'Anonymous function has an unused use $usedInClosureUse.', + 10, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php b/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php index fa1ec0f17a..af71566ba8 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php @@ -1,33 +1,30 @@ -= 7.4 += 7.4 namespace ArrowFunctionAttributes; #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_FUNCTION)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - public function doFoo() - { - #[Foo] fn () => 1; - #[Bar] fn () => 1; - #[Baz] fn () => 1; - } - + public function doFoo() + { + #[Foo] fn () => 1; + #[Bar] fn () => 1; + #[Baz] fn () => 1; + } } diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php b/tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php index e78b724772..52a0877d2f 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php @@ -3,13 +3,13 @@ namespace ArrowFunctionNullsafeReturnByRef; function (\stdClass $foo): void { - fn &() => $foo?->bar; + fn &() => $foo?->bar; }; function (\stdClass $foo): void { - fn &() => $foo->bar; + fn &() => $foo->bar; }; function (\stdClass $foo): void { - fn () => $foo?->bar; + fn () => $foo?->bar; }; diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-typehints.php b/tests/PHPStan/Rules/Functions/data/arrow-function-typehints.php index c19cb63ba8..914d22bd9b 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-function-typehints.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-typehints.php @@ -4,10 +4,8 @@ class Foo { - - public function doFoo() - { - fn(Bar $bar): Baz => new Baz(); - } - + public function doFoo() + { + fn (Bar $bar): Baz => new Baz(); + } } diff --git a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php index 1ff3081dc5..6dfda76589 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php @@ -1,33 +1,30 @@ -= 7.4 += 7.4 namespace ArrowFunctionsReturnTypes; class Foo { - - public function doFoo(int $i) - { - fn() => $i; - fn(): int => $i; - fn(): string => $i; - fn(int $a): int => $a; - fn(string $a): int => $a; - } - + public function doFoo(int $i) + { + fn () => $i; + fn (): int => $i; + fn (): string => $i; + fn (int $a): int => $a; + fn (string $a): int => $a; + } } class Bar { - - public function doFoo(): void - { - - } - - public function doBar(): void - { - fn () => $this->doFoo(); - fn (?string $value): string => $value ?? '-'; - } - + public function doFoo(): void + { + } + + public function doBar(): void + { + fn () => $this->doFoo(); + fn (?string $value): string => $value ?? '-'; + } } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2268.php b/tests/PHPStan/Rules/Functions/data/bug-2268.php index 9434bce051..11cfb35d72 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2268.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2268.php @@ -4,18 +4,18 @@ abstract class Message implements \ArrayAccess { - /** - * @param string $value - */ - abstract public function offsetSet($key, $value); + /** + * @param string $value + */ + abstract public function offsetSet($key, $value); } function test(Message $data) { - if (isset($data['name'])) { - $data['name'] = 1; - } + if (isset($data['name'])) { + $data['name'] = 1; + } - test($data); + test($data); } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2434.php b/tests/PHPStan/Rules/Functions/data/bug-2434.php index d779d902af..dd7b6868f0 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2434.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2434.php @@ -4,21 +4,21 @@ function foo(int $param): void { - //do something + //do something } function fooWithoutVoid(int $param) { - } function (): void { - register_shutdown_function('Bug2434\\foo', 1); - register_shutdown_function('Bug2434\\fooWithoutVoid', 1); + register_shutdown_function('Bug2434\\foo', 1); + register_shutdown_function('Bug2434\\fooWithoutVoid', 1); - $parameter = new \stdClass(); + $parameter = new \stdClass(); - $shutdown = static function (\stdClass $parameter): void {}; + $shutdown = static function (\stdClass $parameter): void { + }; - register_shutdown_function($shutdown, $parameter); + register_shutdown_function($shutdown, $parameter); }; diff --git a/tests/PHPStan/Rules/Functions/data/bug-2568.php b/tests/PHPStan/Rules/Functions/data/bug-2568.php index 16f4ff0745..000db35042 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2568.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2568.php @@ -8,6 +8,7 @@ * @param array $arr * @return array */ -function my_array_keys($arr) { - return array_keys($arr); +function my_array_keys($arr) +{ + return array_keys($arr); } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2723.php b/tests/PHPStan/Rules/Functions/data/bug-2723.php index 7aed24a439..db4e584697 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2723.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2723.php @@ -1,4 +1,6 @@ -t = $t; - } + /** @param T1 $t */ + public function __construct($t) + { + $this->t = $t; + } } /** @@ -22,14 +24,14 @@ public function __construct($t) */ class Bar { - /** @var T2 */ - public $t; + /** @var T2 */ + public $t; - /** @param T2 $t */ - public function __construct($t) - { - $this->t = $t; - } + /** @param T2 $t */ + public function __construct($t) + { + $this->t = $t; + } } /** @@ -38,11 +40,11 @@ public function __construct($t) */ class BarOfFoo extends Bar { - /** @param T3 $t */ - public function __construct($t) - { - parent::__construct(new Foo($t)); - } + /** @param T3 $t */ + public function __construct($t) + { + parent::__construct(new Foo($t)); + } } /** @@ -52,7 +54,7 @@ public function __construct($t) */ function baz($t) { - return new BarOfFoo("hello"); + return new BarOfFoo("hello"); } /** @@ -62,5 +64,5 @@ function baz($t) */ function baz2($t) { - return new BarOfFoo($t); + return new BarOfFoo($t); } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2846.php b/tests/PHPStan/Rules/Functions/data/bug-2846.php index 5037a04418..8751a7b530 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2846.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2846.php @@ -2,9 +2,13 @@ namespace Bug2846; -class Test { - public static function staticFunc(): void {} - public static function callStatic(): void { - call_user_func([static::class, 'staticFunc']); - } +class Test +{ + public static function staticFunc(): void + { + } + public static function callStatic(): void + { + call_user_func([static::class, 'staticFunc']); + } } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3261.php b/tests/PHPStan/Rules/Functions/data/bug-3261.php index 5e5c5f874e..4de62c1e46 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3261.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3261.php @@ -1,4 +1,6 @@ -= 7.4 += 7.4 namespace Bug3261; @@ -6,15 +8,16 @@ class A { } -class B extends A {} +class B extends A +{ +} function (): void { - /** @var A[] $a */ - $a = []; - - array_filter( - $a, - fn (A $a) => $a instanceof B - ); + /** @var A[] $a */ + $a = []; + array_filter( + $a, + fn (A $a) => $a instanceof B + ); }; diff --git a/tests/PHPStan/Rules/Functions/data/bug-3349.php b/tests/PHPStan/Rules/Functions/data/bug-3349.php index c117ffa193..f89f53f693 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3349.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3349.php @@ -4,5 +4,4 @@ function foo(int $i, string $j = 'str'): void { - } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3608.php b/tests/PHPStan/Rules/Functions/data/bug-3608.php index 29ee31d752..252c01a9bb 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3608.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3608.php @@ -4,26 +4,25 @@ interface IHost { - /** @return IHost[] * */ - public static function getArray(); + /** @return IHost[] * */ + public static function getArray(); } class MyHost implements IHost { - public static function getArray() - { - return [new self()]; - } + public static function getArray() + { + return [new self()]; + } } class HelloWorld { - /** - * @param class-string $name - **/ - public function getConfig(string $name): void - { - call_user_func([$name, 'getArray']); - - } + /** + * @param class-string $name + **/ + public function getConfig(string $name): void + { + call_user_func([$name, 'getArray']); + } } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3891.php b/tests/PHPStan/Rules/Functions/data/bug-3891.php index 7363317ded..1b320aef5f 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3891.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3891.php @@ -4,40 +4,40 @@ class HelloWorld { - /** - * @param bool[] $data - * - * @return string[] - */ - public function doSomething(array $data): array - { - return array_map(static function (bool $value): iterable { - if ($value) { - yield 'something'; - return; - } + /** + * @param bool[] $data + * + * @return string[] + */ + public function doSomething(array $data): array + { + return array_map(static function (bool $value): iterable { + if ($value) { + yield 'something'; + return; + } - yield 'something else'; - }, $data); - } + yield 'something else'; + }, $data); + } } class HelloWorld2 { - /** - * @param bool[] $data - * - * @return string[] - */ - public function doSomething(array $data): array - { - return array_map(static function (bool $value): \Generator { - if ($value) { - yield 'something'; - return; - } + /** + * @param bool[] $data + * + * @return string[] + */ + public function doSomething(array $data): array + { + return array_map(static function (bool $value): \Generator { + if ($value) { + yield 'something'; + return; + } - yield 'something else'; - }, $data); - } + yield 'something else'; + }, $data); + } } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3920.php b/tests/PHPStan/Rules/Functions/data/bug-3920.php index 848ddca7d2..f6c894526c 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3920.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3920.php @@ -4,40 +4,42 @@ class HelloWorld { - /** - * @return class-string - */ - public function sayHello(string $a) - { + /** + * @return class-string + */ + public function sayHello(string $a) + { + $arr = [ + 'a' => Two::class, + 'c' => Two::class, + ]; + return $arr[$a]; + } - $arr = [ - 'a' => Two::class, - 'c' => Two::class, - ]; - return $arr[$a]; - } - - public function sayType(): void - { - call_user_func([One::class, 'isType']); - call_user_func([Two::class, 'isType']); - $class = $this->sayHello('a'); - $type = $class::isType(); - $callable = [$class, 'isType']; - call_user_func($callable); - if (is_callable($callable)) { - call_user_func($callable); - } - } + public function sayType(): void + { + call_user_func([One::class, 'isType']); + call_user_func([Two::class, 'isType']); + $class = $this->sayHello('a'); + $type = $class::isType(); + $callable = [$class, 'isType']; + call_user_func($callable); + if (is_callable($callable)) { + call_user_func($callable); + } + } } -class One { - public static function isType(): bool - { - return true; - } +class One +{ + public static function isType(): bool + { + return true; + } } -class Two extends One { - +class Two extends One +{ +} +class Three +{ } -class Three {} diff --git a/tests/PHPStan/Rules/Functions/data/bug-4455.php b/tests/PHPStan/Rules/Functions/data/bug-4455.php index 76d3e774c7..c7d385134b 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-4455.php +++ b/tests/PHPStan/Rules/Functions/data/bug-4455.php @@ -6,10 +6,11 @@ * @psalm-pure * @return never */ -function nope() { - throw new \Exception(); +function nope() +{ + throw new \Exception(); } function (): void { - nope(); + nope(); }; diff --git a/tests/PHPStan/Rules/Functions/data/bug-4514.php b/tests/PHPStan/Rules/Functions/data/bug-4514.php index 9f53ecb0f6..65878e5d69 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-4514.php +++ b/tests/PHPStan/Rules/Functions/data/bug-4514.php @@ -1,6 +1,8 @@ - $b * @return A[] */ -function f($a, $b): array {} +function f($a, $b): array +{ +} -function test(): void { - f(1, 2); +function test(): void +{ + f(1, 2); } /** @@ -20,10 +23,13 @@ function test(): void { * @param A $a * @return A */ -function g($a) {} +function g($a) +{ +} -function testg(): void { - g(new \DateTimeImmutable()); +function testg(): void +{ + g(new \DateTimeImmutable()); } /** @@ -31,14 +37,15 @@ function testg(): void { * @param (callable(): TReturnType) $callback * @return TReturnType */ -function scope(callable $callback) { - return $callback(); +function scope(callable $callback) +{ + return $callback(); } function (): void { - scope( - function (): void { - throw new \Exception(); - } - ); + scope( + function (): void { + throw new \Exception(); + } + ); }; diff --git a/tests/PHPStan/Rules/Functions/data/call-to-function-with-optional-parameters-definition.php b/tests/PHPStan/Rules/Functions/data/call-to-function-with-optional-parameters-definition.php index 20e29e9c4d..f6c0525ba9 100644 --- a/tests/PHPStan/Rules/Functions/data/call-to-function-with-optional-parameters-definition.php +++ b/tests/PHPStan/Rules/Functions/data/call-to-function-with-optional-parameters-definition.php @@ -2,7 +2,6 @@ namespace CallToFunctionWithOptionalParameters; -function foo($foo, $bar = NULL) +function foo($foo, $bar = null) { - } diff --git a/tests/PHPStan/Rules/Functions/data/call-to-weird-functions.php b/tests/PHPStan/Rules/Functions/data/call-to-weird-functions.php index f826945416..bd5fedf42d 100644 --- a/tests/PHPStan/Rules/Functions/data/call-to-weird-functions.php +++ b/tests/PHPStan/Rules/Functions/data/call-to-weird-functions.php @@ -12,17 +12,19 @@ fputcsv($handle); fputcsv($handle, $data, ',', '""', '\\'); -$resource = imagecreatefrompng('filename'); if ($resource === false) { return; } +$resource = imagecreatefrompng('filename'); if ($resource === false) { + return; +} imagepng(); // should report 1-4 parameters imagepng($resource); // OK imagepng($resource, 'to', 1, 2); // OK imagepng($resource, 'to', 1, 2, 4); // should report 5 parameters given, 1-4 required session_start([ - 'name' => '', - 'cookie_path' => '', - 'cookie_secure' => '', - 'cookie_domain' => '', + 'name' => '', + 'cookie_path' => '', + 'cookie_secure' => '', + 'cookie_domain' => '', ]); locale_get_display_language('cs_CZ'); // OK diff --git a/tests/PHPStan/Rules/Functions/data/callable-or-closure-problem.php b/tests/PHPStan/Rules/Functions/data/callable-or-closure-problem.php index 95ce140ced..82f0a9eaef 100644 --- a/tests/PHPStan/Rules/Functions/data/callable-or-closure-problem.php +++ b/tests/PHPStan/Rules/Functions/data/callable-or-closure-problem.php @@ -4,11 +4,9 @@ function call(callable $callable) { - if ($callable instanceof \Closure) { + if ($callable instanceof \Closure) { + } elseif (\is_array($callable)) { + } - } elseif (\is_array($callable)) { - - } - - return call_user_func_array($callable, []); + return call_user_func_array($callable, []); } diff --git a/tests/PHPStan/Rules/Functions/data/callables-named-arguments.php b/tests/PHPStan/Rules/Functions/data/callables-named-arguments.php index 38e04e86c9..522c03abbe 100644 --- a/tests/PHPStan/Rules/Functions/data/callables-named-arguments.php +++ b/tests/PHPStan/Rules/Functions/data/callables-named-arguments.php @@ -4,25 +4,22 @@ class Foo { + public function doFoo(): void + { + $f = function (int $i, int $j): void { + }; - public function doFoo(): void - { - $f = function (int $i, int $j): void { - - }; - - $f(i: 1); - } - - /** - * @param callable(int, int): void $cb - * @param callable(int $i, int $j): void $cb2 - */ - public function doBar(callable $cb, callable $cb2): void - { - $cb(i: 1); - $cb2(i: 1); - $cb2(i: 1, j: 2, z: 3); - } + $f(i: 1); + } + /** + * @param callable(int, int): void $cb + * @param callable(int $i, int $j): void $cb2 + */ + public function doBar(callable $cb, callable $cb2): void + { + $cb(i: 1); + $cb2(i: 1); + $cb2(i: 1, j: 2, z: 3); + } } diff --git a/tests/PHPStan/Rules/Functions/data/callables.php b/tests/PHPStan/Rules/Functions/data/callables.php index 002a281566..40e2ed3bed 100644 --- a/tests/PHPStan/Rules/Functions/data/callables.php +++ b/tests/PHPStan/Rules/Functions/data/callables.php @@ -4,185 +4,164 @@ class Foo { - - public function doFoo( - $mixed, - callable $callable, - string $string, - \Closure $closure - ) - { - $mixed(); - $callable(); - $string(); - $closure(); - - $date = 'date'; - $date(); - $date('j. n. Y'); - - $nonexistent = 'nonexistent'; - $nonexistent(); - } - - public function doBar( - int $i - ) - { - [$this, 'doBar'](1); - [$this, 'doBar']('string'); - } - - public static function doStaticBaz() - { - ['CallCallables\Foo', 'doStaticBaz'](); - ['CallCallables\Foo', 'doStaticBaz']('foo'); - 'CallCallables\Foo::doStaticBaz'(); - 'CallCallables\Foo::doStaticBaz'('foo'); - } - - private function privateFooMethod() - { - - } - + public function doFoo( + $mixed, + callable $callable, + string $string, + \Closure $closure + ) { + $mixed(); + $callable(); + $string(); + $closure(); + + $date = 'date'; + $date(); + $date('j. n. Y'); + + $nonexistent = 'nonexistent'; + $nonexistent(); + } + + public function doBar( + int $i + ) { + [$this, 'doBar'](1); + [$this, 'doBar']('string'); + } + + public static function doStaticBaz() + { + ['CallCallables\Foo', 'doStaticBaz'](); + ['CallCallables\Foo', 'doStaticBaz']('foo'); + 'CallCallables\Foo::doStaticBaz'(); + 'CallCallables\Foo::doStaticBaz'('foo'); + } + + private function privateFooMethod() + { + } } function (\Closure $closure) { - [new Foo(), 'privateFooMethod'](); - $closure(1, 2, 3); - - $literalClosure = function (int $i, int $j = 1): void { + [new Foo(), 'privateFooMethod'](); + $closure(1, 2, 3); - }; - $literalClosure(); - $result = $literalClosure(1); + $literalClosure = function (int $i, int $j = 1): void { + }; + $literalClosure(); + $result = $literalClosure(1); - $variadicClosure = function (int $i, int ...$j) { - - }; - $variadicClosure(); + $variadicClosure = function (int $i, int ...$j) { + }; + $variadicClosure(); }; function () { - $f = function(int $i) use (&$f) { - $f(1); - $f('foo'); - }; + $f = function (int $i) use (&$f) { + $f(1); + $f('foo'); + }; }; function () { - $foo = new class () { - public function __invoke(string $str) { - - } - }; + $foo = new class() { + public function __invoke(string $str) + { + } + }; - $foo(1); + $foo(1); }; function () { - $emptyString = ''; - $emptyString(1, 2, 3); + $emptyString = ''; + $emptyString(1, 2, 3); }; function (Bar $bar) { - $bar(); + $bar(); }; class Baz { - - /** - * @param Foo[] $foos - */ - public function doFoo( - array $foos - ) - { - $f = function (Foo ...$foo) { - - }; - $f($foos); - $f(...$foos); - } - - public function doBar() - { - $baz = new Baz(); - $baz(); - - if (method_exists($baz, '__invoke')) { - $baz(); - } - } - + /** + * @param Foo[] $foos + */ + public function doFoo( + array $foos + ) { + $f = function (Foo ...$foo) { + }; + $f($foos); + $f(...$foos); + } + + public function doBar() + { + $baz = new Baz(); + $baz(); + + if (method_exists($baz, '__invoke')) { + $baz(); + } + } } class MethodExistsCheckFirst { - - public function doFoo( - object $object - ) - { - if (method_exists($object, 'foo')) { - [$object, 'foo'](); - [$object, 'bar'](); - } - } - + public function doFoo( + object $object + ) { + if (method_exists($object, 'foo')) { + [$object, 'foo'](); + [$object, 'bar'](); + } + } } class RequiredArgumentsAfterDefaultValues { - - public function doFoo() - { - $c = function ($a, $b = 'b', $c) { - - }; - - $c(); - $c('a'); - $c('a', 'b'); - $c('a', 'b', 'c'); - } - + public function doFoo() + { + $c = function ($a, $b = 'b', $c) { + }; + + $c(); + $c('a'); + $c('a', 'b'); + $c('a', 'b', 'c'); + } } class ObjectCallable { - - /** - * @param object $object - */ - public function doFoo($object) - { - $cb = [$object, 'yo']; - $cb(); - if (is_callable($cb)) { - $cb(); - } else { - $cb(); - } - } - + /** + * @param object $object + */ + public function doFoo($object) + { + $cb = [$object, 'yo']; + $cb(); + if (is_callable($cb)) { + $cb(); + } else { + $cb(); + } + } } class CallableInForeach { - - public function doFoo(bool $foo = true): void - { - $callback = [self::class, $foo ? 'foo' : 'bar']; - $callback(); - assert(is_callable($callback)); - $callback(); - - foreach ([0, 1] as $i) { - echo $callback(); - } - } - + public function doFoo(bool $foo = true): void + { + $callback = [self::class, $foo ? 'foo' : 'bar']; + $callback(); + assert(is_callable($callback)); + $callback(); + + foreach ([0, 1] as $i) { + echo $callback(); + } + } } diff --git a/tests/PHPStan/Rules/Functions/data/closure-7.1-typehints.php b/tests/PHPStan/Rules/Functions/data/closure-7.1-typehints.php index 060c90554c..046a410798 100644 --- a/tests/PHPStan/Rules/Functions/data/closure-7.1-typehints.php +++ b/tests/PHPStan/Rules/Functions/data/closure-7.1-typehints.php @@ -4,35 +4,22 @@ class FooFunctionTypehints { - } -function (): void -{ - +function (): void { }; -function (): iterable -{ - +function (): iterable { }; -function (): ?iterable -{ - +function (): ?iterable { }; -function (): ?string -{ - +function (): ?string { }; -function (?FooFunctionTypehints $foo): ?FooFunctionTypehints -{ - +function (?FooFunctionTypehints $foo): ?FooFunctionTypehints { }; -function (?NonexistentClass $bar): ?NonexistentClass -{ - +function (?NonexistentClass $bar): ?NonexistentClass { }; diff --git a/tests/PHPStan/Rules/Functions/data/closure-7.1ReturnTypes.php b/tests/PHPStan/Rules/Functions/data/closure-7.1ReturnTypes.php index 071f1d5f98..772dce3ebd 100644 --- a/tests/PHPStan/Rules/Functions/data/closure-7.1ReturnTypes.php +++ b/tests/PHPStan/Rules/Functions/data/closure-7.1ReturnTypes.php @@ -1,32 +1,25 @@ = 7.2 += 7.2 -function (object $foo): object -{ +namespace TestClosureFunctionTypehintsPhp72; +function (object $foo): object { }; diff --git a/tests/PHPStan/Rules/Functions/data/closure-attributes.php b/tests/PHPStan/Rules/Functions/data/closure-attributes.php index 3fc5cb3556..e327df55de 100644 --- a/tests/PHPStan/Rules/Functions/data/closure-attributes.php +++ b/tests/PHPStan/Rules/Functions/data/closure-attributes.php @@ -5,29 +5,27 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_FUNCTION)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - public function doFoo() - { - #[Foo] function (): void {}; - #[Bar] function (): void {}; - #[Baz] function (): void {}; - } - + public function doFoo() + { + #[Foo] function (): void { + }; + #[Bar] function (): void { + }; + #[Baz] function (): void { + }; + } } diff --git a/tests/PHPStan/Rules/Functions/data/closure-typehints.php b/tests/PHPStan/Rules/Functions/data/closure-typehints.php index ce7b4d0329..190c050376 100644 --- a/tests/PHPStan/Rules/Functions/data/closure-typehints.php +++ b/tests/PHPStan/Rules/Functions/data/closure-typehints.php @@ -4,50 +4,32 @@ class FooFunctionTypehints { - } -$callback = function (FooFunctionTypehints $foo, $bar, array $lorem): NonexistentClass -{ - +$callback = function (FooFunctionTypehints $foo, $bar, array $lorem): NonexistentClass { }; -$callback = function (BarFunctionTypehints $bar): array -{ - +$callback = function (BarFunctionTypehints $bar): array { }; -$callback = function (...$bar): FooFunctionTypehints -{ - +$callback = function (...$bar): FooFunctionTypehints { }; -$callback = function (): parent -{ - +$callback = function (): parent { }; -$callback = function (fOOfUnctionTypehints $foo): FOOfUnctionTypehintS -{ - +$callback = function (fOOfUnctionTypehints $foo): FOOfUnctionTypehintS { }; -$callback = function (\ReturnTypes\FooAliaS $ignoreAlias) -{ - +$callback = function (\ReturnTypes\FooAliaS $ignoreAlias) { }; trait SomeTrait { - } -$callback = function (SomeTrait $trait) -{ - +$callback = function (SomeTrait $trait) { }; -$callback = function (): SomeTrait -{ - +$callback = function (): SomeTrait { }; diff --git a/tests/PHPStan/Rules/Functions/data/closure-uses-this.php b/tests/PHPStan/Rules/Functions/data/closure-uses-this.php index e298303d90..8795bc7de4 100644 --- a/tests/PHPStan/Rules/Functions/data/closure-uses-this.php +++ b/tests/PHPStan/Rules/Functions/data/closure-uses-this.php @@ -4,23 +4,18 @@ class Foo { - - public function doFoo() - { - $f = function () { // ok - - }; - - $that = $this; - $f = function () use ( - $that - ) { // report - - }; - - $f = static function () use ($that) { // ok - - }; - } - + public function doFoo() + { + $f = function () { // ok + }; + + $that = $this; + $f = function () use ( + $that + ) { // report + }; + + $f = static function () use ($that) { // ok + }; + } } diff --git a/tests/PHPStan/Rules/Functions/data/closureReturnTypes-7.0.php b/tests/PHPStan/Rules/Functions/data/closureReturnTypes-7.0.php index 08843fdc3e..9dd689d818 100644 --- a/tests/PHPStan/Rules/Functions/data/closureReturnTypes-7.0.php +++ b/tests/PHPStan/Rules/Functions/data/closureReturnTypes-7.0.php @@ -1,9 +1,9 @@ "bar",]); - - fputcsv($handle, [new Person,]); // Problem on this line - - // This is valid. PersonWithToString should be cast to string by fputcsv - fputcsv($handle, [new PersonWithToString()]); - } + /** @param resource $handle */ + public function writeCsv($handle): void + { + // These are all valid scalers + fputcsv($handle, [ + "string", + 1, + 3.5, + true, + false, + null, // Yes this is accepted by fputcsv, + ]); + + // Arrays can have string for keys (which are ignored) + fputcsv($handle, ["foo" => "bar",]); + + fputcsv($handle, [new Person(),]); // Problem on this line + + // This is valid. PersonWithToString should be cast to string by fputcsv + fputcsv($handle, [new PersonWithToString()]); + } } diff --git a/tests/PHPStan/Rules/Functions/data/function-attributes.php b/tests/PHPStan/Rules/Functions/data/function-attributes.php index 5ed22c10fe..c82c100234 100644 --- a/tests/PHPStan/Rules/Functions/data/function-attributes.php +++ b/tests/PHPStan/Rules/Functions/data/function-attributes.php @@ -5,35 +5,29 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_FUNCTION)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } #[Foo] function doFoo(): void { - } #[Bar] function doBar(): void { - } #[Baz] function doBaz(): void { - } diff --git a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc-definition.php b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc-definition.php index 50fa23a63f..17b47dd772 100644 --- a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc-definition.php +++ b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc-definition.php @@ -2,33 +2,51 @@ namespace FunctionCallStatementNoSideEffectsPhpDoc; -function regular(string $a): string {return $a;} +function regular(string $a): string +{ + return $a; +} /** * @phpstan-pure */ -function pure1(string $a): string {return $a;} +function pure1(string $a): string +{ + return $a; +} /** * @psalm-pure */ -function pure2(string $a): string {return $a;} +function pure2(string $a): string +{ + return $a; +} /** * @pure */ -function pure3(string $a): string {return $a;} +function pure3(string $a): string +{ + return $a; +} /** * @phpstan-pure * @throws void * @return string */ -function pureAndThrowsVoid(): string { return 'aaa'; } +function pureAndThrowsVoid(): string +{ + return 'aaa'; +} /** * @phpstan-pure * @throws \Exception * @return string */ -function pureAndThrowsException(): string { return 'aaa'; } +function pureAndThrowsException(): string +{ + return 'aaa'; +} diff --git a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc.php b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc.php index 61e9d9bae5..c55f14d9b1 100644 --- a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc.php +++ b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects-phpdoc.php @@ -2,12 +2,11 @@ namespace FunctionCallStatementNoSideEffectsPhpDoc; -function(): void -{ - regular('test'); - pure1('test'); - pure2('test'); - pure3('test'); - pureAndThrowsVoid(); - pureAndThrowsException(); +function (): void { + regular('test'); + pure1('test'); + pure2('test'); + pure3('test'); + pureAndThrowsVoid(); + pureAndThrowsException(); }; diff --git a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects.php index ad446a4b38..276a686680 100644 --- a/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Functions/data/function-call-statement-no-side-effects.php @@ -4,11 +4,9 @@ class Foo { - - public function doFoo() - { - printf('%s', 'test'); - sprintf('%s', 'test'); - } - + public function doFoo() + { + printf('%s', 'test'); + sprintf('%s', 'test'); + } } diff --git a/tests/PHPStan/Rules/Functions/data/function-with-int-parameter-that-created-by-addition.php b/tests/PHPStan/Rules/Functions/data/function-with-int-parameter-that-created-by-addition.php index e863ca8726..b218ee3fed 100644 --- a/tests/PHPStan/Rules/Functions/data/function-with-int-parameter-that-created-by-addition.php +++ b/tests/PHPStan/Rules/Functions/data/function-with-int-parameter-that-created-by-addition.php @@ -1,41 +1,43 @@ - 'bar', - 'bar' => new \stdClass(), + 'foo' => 'bar', + 'bar' => new \stdClass(), ]; $keys = array_keys($array); bar($keys[0]); set_error_handler(function ($errno, $errstr, $errfile, $errline): void { - // ... + // ... }); function (string $hash, string $privateKey): void { - $keyId = openssl_pkey_get_private($privateKey); - if ($keyId === false) { - return; - } + $keyId = openssl_pkey_get_private($privateKey); + if ($keyId === false) { + return; + } - $signature = null; - if (!openssl_sign($hash, $signature, $keyId, OPENSSL_ALGO_SHA256)) { - return; - } - openssl_free_key($keyId); + $signature = null; + if (!openssl_sign($hash, $signature, $keyId, OPENSSL_ALGO_SHA256)) { + return; + } + openssl_free_key($keyId); }; diff --git a/tests/PHPStan/Rules/Functions/data/incorrect-function-case-definition.php b/tests/PHPStan/Rules/Functions/data/incorrect-function-case-definition.php index ef5ec45267..78f3d348d9 100644 --- a/tests/PHPStan/Rules/Functions/data/incorrect-function-case-definition.php +++ b/tests/PHPStan/Rules/Functions/data/incorrect-function-case-definition.php @@ -4,5 +4,4 @@ function fooBar() { - } diff --git a/tests/PHPStan/Rules/Functions/data/inner-functions.php b/tests/PHPStan/Rules/Functions/data/inner-functions.php index 9a787c9f3b..64f5d8ddbb 100644 --- a/tests/PHPStan/Rules/Functions/data/inner-functions.php +++ b/tests/PHPStan/Rules/Functions/data/inner-functions.php @@ -4,21 +4,17 @@ function foo() { - function bar() - { - - } + function bar() + { + } } class Foo { - - public function doFoo() - { - function anotherFoo() - { - - } - } - + public function doFoo() + { + function anotherFoo() + { + } + } } diff --git a/tests/PHPStan/Rules/Functions/data/is-generator.php b/tests/PHPStan/Rules/Functions/data/is-generator.php index 0b31553fb1..d649228534 100644 --- a/tests/PHPStan/Rules/Functions/data/is-generator.php +++ b/tests/PHPStan/Rules/Functions/data/is-generator.php @@ -4,25 +4,24 @@ class Foo { - } if (false) { - function doBar(bool $skipThings): iterable - { - if ($skipThings) { - return; - } + function doBar(bool $skipThings): iterable + { + if ($skipThings) { + return; + } - yield 1; - } + yield 1; + } - function doFoo(bool $skipThings): iterable - { - if ($skipThings) { - return; - } + function doFoo(bool $skipThings): iterable + { + if ($skipThings) { + return; + } - yield from array(); - } + yield from array(); + } } diff --git a/tests/PHPStan/Rules/Functions/data/match-expr-analysis.php b/tests/PHPStan/Rules/Functions/data/match-expr-analysis.php index faaa52f617..f3680ac96c 100644 --- a/tests/PHPStan/Rules/Functions/data/match-expr-analysis.php +++ b/tests/PHPStan/Rules/Functions/data/match-expr-analysis.php @@ -1,16 +1,16 @@ -= 8.0 += 8.0 namespace MatchExprAnalysis; class Foo { - - public function doFoo() - { - match (lorem()) { - ipsum() => dolor(), - default => sit(), - }; - } - + public function doFoo() + { + match (lorem()) { + ipsum() => dolor(), + default => sit(), + }; + } } diff --git a/tests/PHPStan/Rules/Functions/data/missing-function-parameter-typehint.php b/tests/PHPStan/Rules/Functions/data/missing-function-parameter-typehint.php index 87bfdff02a..c061b19402 100644 --- a/tests/PHPStan/Rules/Functions/data/missing-function-parameter-typehint.php +++ b/tests/PHPStan/Rules/Functions/data/missing-function-parameter-typehint.php @@ -2,165 +2,145 @@ namespace { - /** - * @param int $a - * @param $b - */ - function globalFunction($a, $b, $c): bool - { - $closure = function($a, $b, $c) { - - }; - - return false; - } + /** + * @param int $a + * @param $b + */ + function globalFunction($a, $b, $c): bool + { + $closure = function ($a, $b, $c) { + }; + + return false; + } } namespace MissingFunctionParameterTypehint { - /** - * @param $d - */ - function namespacedFunction($d, bool $e): int { - return 9; - }; - - /** - * @param array|int[] $a - */ - function intIterableTypehint($a) - { - - } - - function missingArrayTypehint(array $a) - { - - } - - /** - * @param array $a - */ - function missingPhpDocIterableTypehint(array $a) - { - - } - - /** - * @param mixed[] $a - */ - function explicitMixedArrayTypehint(array $a) - { - - } - - /** - * @param \stdClass|array|int|null $a - */ - function unionTypeWithUnknownArrayValueTypehint($a) - { - - } - - /** - * @param iterable&\Traversable $a - */ - function iterableIntersectionTypehint($a) - { - - } - - /** - * @param iterable&\Traversable $a - */ - function iterableIntersectionTypehint2($a) - { - - } - - /** - * @param \PDOStatement $a - */ - function iterableIntersectionTypehint3($a) - { - - } - - /** - * @param \PDOStatement $a - */ - function iterableIntersectionTypehint4($a) - { - - } - - /** - * @template T - * @template U - */ - interface GenericInterface - { - - } - - class NonGenericClass - { - - } - - function acceptsGenericInterface(GenericInterface $i) - { - - } - - function acceptsNonGenericClass(NonGenericClass $c) - { - - } - - /** - * @template A - * @template B - */ - class GenericClass - { - - } - - function acceptsGenericClass(GenericClass $c) - { - - } - - function missingIterableTypehint(iterable $iterable) - { - - } - - /** - * @param iterable $iterable - */ - function missingIterableTypehintPhpDoc($iterable) - { - - } - - function missingTraversableTypehint(\Traversable $traversable) - { - - } - - /** - * @param \Traversable $traversable - */ - function missingTraversableTypehintPhpDoc($traversable) - { - - } - - function missingCallableSignature(callable $cb) - { - - } + /** + * @param $d + */ + function namespacedFunction($d, bool $e): int + { + return 9; + }; + + /** + * @param array|int[] $a + */ + function intIterableTypehint($a) + { + } + + function missingArrayTypehint(array $a) + { + } + + /** + * @param array $a + */ + function missingPhpDocIterableTypehint(array $a) + { + } + + /** + * @param mixed[] $a + */ + function explicitMixedArrayTypehint(array $a) + { + } + + /** + * @param \stdClass|array|int|null $a + */ + function unionTypeWithUnknownArrayValueTypehint($a) + { + } + + /** + * @param iterable&\Traversable $a + */ + function iterableIntersectionTypehint($a) + { + } + + /** + * @param iterable&\Traversable $a + */ + function iterableIntersectionTypehint2($a) + { + } + + /** + * @param \PDOStatement $a + */ + function iterableIntersectionTypehint3($a) + { + } + + /** + * @param \PDOStatement $a + */ + function iterableIntersectionTypehint4($a) + { + } + + /** + * @template T + * @template U + */ + interface GenericInterface + { + } + + class NonGenericClass + { + } + + function acceptsGenericInterface(GenericInterface $i) + { + } + + function acceptsNonGenericClass(NonGenericClass $c) + { + } + + /** + * @template A + * @template B + */ + class GenericClass + { + } + + function acceptsGenericClass(GenericClass $c) + { + } + + function missingIterableTypehint(iterable $iterable) + { + } + + /** + * @param iterable $iterable + */ + function missingIterableTypehintPhpDoc($iterable) + { + } + + function missingTraversableTypehint(\Traversable $traversable) + { + } + + /** + * @param \Traversable $traversable + */ + function missingTraversableTypehintPhpDoc($traversable) + { + } + + function missingCallableSignature(callable $cb) + { + } } diff --git a/tests/PHPStan/Rules/Functions/data/missing-function-return-typehint.php b/tests/PHPStan/Rules/Functions/data/missing-function-return-typehint.php index 6ab9bd061c..62d0910eb7 100644 --- a/tests/PHPStan/Rules/Functions/data/missing-function-return-typehint.php +++ b/tests/PHPStan/Rules/Functions/data/missing-function-return-typehint.php @@ -2,150 +2,140 @@ namespace { - function globalFunction1($a, $b, $c) - { - return false; - } - - function globalFunction2($a, $b, $c): bool - { - $closure = function($a, $b, $c) { - - }; - - return false; - } - - /** - * @return bool - */ - function globalFunction3($a, $b, $c) - { - return false; - } + function globalFunction1($a, $b, $c) + { + return false; + } + + function globalFunction2($a, $b, $c): bool + { + $closure = function ($a, $b, $c) { + }; + + return false; + } + + /** + * @return bool + */ + function globalFunction3($a, $b, $c) + { + return false; + } } namespace MissingFunctionReturnTypehint { - function namespacedFunction1($d, $e) - { - return 9; - }; - - function namespacedFunction2($d, $e): int - { - return 9; - }; - - /** - * @return int - */ - function namespacedFunction3($d, $e) - { - return 9; - }; - - /** - * @return \stdClass|array|int|null - */ - function unionTypeWithUnknownArrayValueTypehint() - { - - } - - /** - * @template T - * @template U - */ - interface GenericInterface - { - - } - - class NonGenericClass - { - - } - - function returnsGenericInterface(): GenericInterface - { - - } - - function returnsNonGenericClass(): NonGenericClass - { - - } - - /** - * @template A - * @template B - */ - class GenericClass - { - - } - - function returnsGenericClass(): GenericClass - { - - } - - /** - * @return GenericClass, GenericClass> - */ - function genericGenericValidArgs(): GenericClass - { - - } - - /** - * @return GenericClass - */ - function genericGenericMissingTemplateArgs(): GenericClass - { - - } - - /** - * @return \Closure - */ - function closureWithNoPrototype() : \Closure{ - - } - - /** - * @return \Closure(int) : void - */ - function closureWithPrototype() : \Closure{ - - } - - /** - * @return callable - */ - function callableWithNoPrototype() : callable{ - - } - - /** - * @return callable(int) : void - */ - function callableWithPrototype() : callable{ - - } - - /** - * @return callable(callable) : void - */ - function callableNestedNoPrototype() : callable{ - - } - - /** - * @return callable(callable(int) : void) : void - */ - function callableNestedWithPrototype() : callable{ - - } + function namespacedFunction1($d, $e) + { + return 9; + }; + + function namespacedFunction2($d, $e): int + { + return 9; + }; + + /** + * @return int + */ + function namespacedFunction3($d, $e) + { + return 9; + }; + + /** + * @return \stdClass|array|int|null + */ + function unionTypeWithUnknownArrayValueTypehint() + { + } + + /** + * @template T + * @template U + */ + interface GenericInterface + { + } + + class NonGenericClass + { + } + + function returnsGenericInterface(): GenericInterface + { + } + + function returnsNonGenericClass(): NonGenericClass + { + } + + /** + * @template A + * @template B + */ + class GenericClass + { + } + + function returnsGenericClass(): GenericClass + { + } + + /** + * @return GenericClass, GenericClass> + */ + function genericGenericValidArgs(): GenericClass + { + } + + /** + * @return GenericClass + */ + function genericGenericMissingTemplateArgs(): GenericClass + { + } + + /** + * @return \Closure + */ + function closureWithNoPrototype(): \Closure + { + } + + /** + * @return \Closure(int) : void + */ + function closureWithPrototype(): \Closure + { + } + + /** + * @return callable + */ + function callableWithNoPrototype(): callable + { + } + + /** + * @return callable(int) : void + */ + function callableWithPrototype(): callable + { + } + + /** + * @return callable(callable) : void + */ + function callableNestedNoPrototype(): callable + { + } + + /** + * @return callable(callable(int) : void) : void + */ + function callableNestedWithPrototype(): callable + { + } } diff --git a/tests/PHPStan/Rules/Functions/data/named-arguments-define.php b/tests/PHPStan/Rules/Functions/data/named-arguments-define.php index 279ca1e656..3d60144d4a 100644 --- a/tests/PHPStan/Rules/Functions/data/named-arguments-define.php +++ b/tests/PHPStan/Rules/Functions/data/named-arguments-define.php @@ -4,10 +4,8 @@ function foo(int $i, int $j): void { - } function variadicFunction(...$arrays): void { - } diff --git a/tests/PHPStan/Rules/Functions/data/named-arguments.php b/tests/PHPStan/Rules/Functions/data/named-arguments.php index 3e349530b4..7e721e5d77 100644 --- a/tests/PHPStan/Rules/Functions/data/named-arguments.php +++ b/tests/PHPStan/Rules/Functions/data/named-arguments.php @@ -4,12 +4,12 @@ function bar(): void { - foo(i: 1); - foo(i: 1, j: 2, z: 3); + foo(i: 1); + foo(i: 1, j: 2, z: 3); } function baz(): void { - variadicFunction(...['a' => ['b', 'c']]); // works - userland - array_merge(...['a' => ['b', 'c']]); // doesn't work - internal + variadicFunction(...['a' => ['b', 'c']]); // works - userland + array_merge(...['a' => ['b', 'c']]); // doesn't work - internal } diff --git a/tests/PHPStan/Rules/Functions/data/native-union-types.php b/tests/PHPStan/Rules/Functions/data/native-union-types.php index 9f8c46574d..e1753fecac 100644 --- a/tests/PHPStan/Rules/Functions/data/native-union-types.php +++ b/tests/PHPStan/Rules/Functions/data/native-union-types.php @@ -1,23 +1,22 @@ -= 8.0 += 8.0 namespace NativeUnionTypesSupport; function foo(int|bool $foo): int|bool { - return 1; + return 1; } function bar(): int|bool { - } function (int|bool $foo): int|bool { - }; function (): int|bool { - }; fn (int|bool $foo): int|bool => 1; diff --git a/tests/PHPStan/Rules/Functions/data/nonexistent-nested-function.php b/tests/PHPStan/Rules/Functions/data/nonexistent-nested-function.php index 995ccca3f1..b063e2b6be 100644 --- a/tests/PHPStan/Rules/Functions/data/nonexistent-nested-function.php +++ b/tests/PHPStan/Rules/Functions/data/nonexistent-nested-function.php @@ -2,6 +2,5 @@ namespace NonexistentNestedFunction; -if (barNonExistentFunction() === NULL) { - +if (barNonExistentFunction() === null) { } diff --git a/tests/PHPStan/Rules/Functions/data/param-attributes.php b/tests/PHPStan/Rules/Functions/data/param-attributes.php index aa852029f7..fa2cf43392 100644 --- a/tests/PHPStan/Rules/Functions/data/param-attributes.php +++ b/tests/PHPStan/Rules/Functions/data/param-attributes.php @@ -5,56 +5,41 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_PARAMETER)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - public function doFoo( - #[Foo] - $foo - ) - { - - } - + public function doFoo( + #[Foo] + $foo + ) { + } } class Ipsum { - - public function doFoo( - #[Bar] - $foo - ) - { - - } - + public function doFoo( + #[Bar] + $foo + ) { + } } class Dolor { - - public function doFoo( - #[Baz] - $foo - ) - { - - } - + public function doFoo( + #[Baz] + $foo + ) { + } } diff --git a/tests/PHPStan/Rules/Functions/data/passed-by-reference.php b/tests/PHPStan/Rules/Functions/data/passed-by-reference.php index e47d8f8cf6..08bde7813c 100644 --- a/tests/PHPStan/Rules/Functions/data/passed-by-reference.php +++ b/tests/PHPStan/Rules/Functions/data/passed-by-reference.php @@ -4,46 +4,43 @@ function foo(&$foo) { - } class Bar { + private $barProperty; - private $barProperty; - - private static $staticBarProperty; - - public function doBar() - { - foo($this->barProperty); // ok - foo(self::$staticBarProperty); // ok - } + private static $staticBarProperty; + public function doBar() + { + foo($this->barProperty); // ok + foo(self::$staticBarProperty); // ok + } } function () { - $i = 0; - foo($i); // ok + $i = 0; + foo($i); // ok - $arr = [1, 2, 3]; - foo($arr[0]); // ok + $arr = [1, 2, 3]; + foo($arr[0]); // ok - foo(rand()); - foo(null); + foo(rand()); + foo(null); - $m = null; - preg_match('a', 'b', $m); + $m = null; + preg_match('a', 'b', $m); - $n = null; - reset($n); + $n = null; + reset($n); }; -function bar(string &$s) { - +function bar(string &$s) +{ } function () { - $i = 1; - bar($i); + $i = 1; + bar($i); }; diff --git a/tests/PHPStan/Rules/Functions/data/printf.php b/tests/PHPStan/Rules/Functions/data/printf.php index 7885441c7d..3beb85dd9e 100644 --- a/tests/PHPStan/Rules/Functions/data/printf.php +++ b/tests/PHPStan/Rules/Functions/data/printf.php @@ -47,7 +47,7 @@ $variousPlaceholderCount = 'foo'; if (rand(0, 1) === 0) { - $variousPlaceholderCount = 'foo %s %s'; + $variousPlaceholderCount = 'foo %s %s'; } sprintf($variousPlaceholderCount, 'bar'); sprintf($variousPlaceholderCount, 'bar', 'baz'); diff --git a/tests/PHPStan/Rules/Functions/data/remove-array-from-iterable.php b/tests/PHPStan/Rules/Functions/data/remove-array-from-iterable.php index b7bac69fe8..31b17b9210 100644 --- a/tests/PHPStan/Rules/Functions/data/remove-array-from-iterable.php +++ b/tests/PHPStan/Rules/Functions/data/remove-array-from-iterable.php @@ -2,18 +2,20 @@ namespace RemoveArrayFromIterable; -function test ($foo) : array { - if (is_iterable($foo)) { - return array_values(is_array($foo) ? $foo : iterator_to_array($foo)); - } +function test($foo): array +{ + if (is_iterable($foo)) { + return array_values(is_array($foo) ? $foo : iterator_to_array($foo)); + } } -function test2($foo) : array { - if (is_iterable($foo)) { - if (is_array($foo)) { - return array_values($foo); - } +function test2($foo): array +{ + if (is_iterable($foo)) { + if (is_array($foo)) { + return array_values($foo); + } - return array_values(iterator_to_array($foo)); - } + return array_values(iterator_to_array($foo)); + } } diff --git a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-arrow.php b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-arrow.php index 39ba53324e..497335c37f 100644 --- a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-arrow.php +++ b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-arrow.php @@ -1,4 +1,6 @@ -= 7.4 += 7.4 namespace RequiredAfterOptional; diff --git a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-closures.php b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-closures.php index fdd7db4709..e9567727f5 100644 --- a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-closures.php +++ b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional-closures.php @@ -2,18 +2,14 @@ namespace RequiredAfterOptional; -function ($foo = null, $bar): void // not OK -{ +function ($foo = null, $bar): void { // not OK }; -function (int $foo = null, $bar): void // is OK -{ +function (int $foo = null, $bar): void { // is OK }; -function (int $foo = 1, $bar): void // not OK -{ +function (int $foo = 1, $bar): void { // not OK }; -function(bool $foo = true, $bar): void // not OK -{ +function (bool $foo = true, $bar): void { // not OK }; diff --git a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional.php b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional.php index 373a441903..a60b5e1477 100644 --- a/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional.php +++ b/tests/PHPStan/Rules/Functions/data/required-parameter-after-optional.php @@ -4,7 +4,6 @@ function doFoo($foo = null, $bar): void // not OK { - } function doBar(int $foo = null, $bar): void // is OK diff --git a/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref.php b/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref.php index 52f06c86d6..dac496dd2f 100644 --- a/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref.php +++ b/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref.php @@ -4,34 +4,32 @@ class Foo { - - public function doFoo(?\stdClass $foo) - { - function &() use ($foo) { - if (rand(0, 1)) { - return $foo; - } - - return $foo?->bar->foo; - }; - } - - public function &doBar() - { - if (rand(0, 1)) { - return $foo; - } - - return $foo?->bar->foo; - } - + public function doFoo(?\stdClass $foo) + { + function &() use ($foo) { + if (rand(0, 1)) { + return $foo; + } + + return $foo?->bar->foo; + }; + } + + public function &doBar() + { + if (rand(0, 1)) { + return $foo; + } + + return $foo?->bar->foo; + } } function &foo(): void { - if (rand(0, 1)) { - return $foo; - } + if (rand(0, 1)) { + return $foo; + } - return $foo?->bar->foo; + return $foo?->bar->foo; } diff --git a/tests/PHPStan/Rules/Functions/data/returnTypes-7.0.php b/tests/PHPStan/Rules/Functions/data/returnTypes-7.0.php index 8dd5c26325..213b398e81 100644 --- a/tests/PHPStan/Rules/Functions/data/returnTypes-7.0.php +++ b/tests/PHPStan/Rules/Functions/data/returnTypes-7.0.php @@ -4,5 +4,5 @@ function returnInteger(): int { - return; + return; } diff --git a/tests/PHPStan/Rules/Functions/data/returnTypes.php b/tests/PHPStan/Rules/Functions/data/returnTypes.php index 3e15651c69..969e05063e 100644 --- a/tests/PHPStan/Rules/Functions/data/returnTypes.php +++ b/tests/PHPStan/Rules/Functions/data/returnTypes.php @@ -4,51 +4,51 @@ function returnNothing() { - return; + return; } function returnInteger(): int { - if (rand(0, 1)) { - return 1; - } + if (rand(0, 1)) { + return 1; + } - if (rand(0, 1)) { - return 'foo'; - } - $foo = function () { - return 'bar'; - }; + if (rand(0, 1)) { + return 'foo'; + } + $foo = function () { + return 'bar'; + }; } function returnObject(): Bar { - if (rand(0, 1)) { - return 1; - } + if (rand(0, 1)) { + return 1; + } - if (rand(0, 1)) { - return new Foo(); - } + if (rand(0, 1)) { + return new Foo(); + } - if (rand(0, 1)) { - return new Bar(); - } + if (rand(0, 1)) { + return new Bar(); + } } function returnChild(): Foo { - if (rand(0, 1)) { - return new Foo(); - } + if (rand(0, 1)) { + return new Foo(); + } - if (rand(0, 1)) { - return new FooChild(); - } + if (rand(0, 1)) { + return new FooChild(); + } - if (rand(0, 1)) { - return new OtherInterfaceImpl(); - } + if (rand(0, 1)) { + return new OtherInterfaceImpl(); + } } /** @@ -56,18 +56,18 @@ function returnChild(): Foo */ function returnNullable() { - if (rand(0, 1)) { - return 'foo'; - } + if (rand(0, 1)) { + return 'foo'; + } - if (rand(0, 1)) { - return null; - } + if (rand(0, 1)) { + return null; + } } function returnInterface(): FooInterface { - return new Foo(); + return new Foo(); } /** @@ -75,27 +75,27 @@ function returnInterface(): FooInterface */ function returnVoid() { - if (rand(0, 1)) { - return; - } + if (rand(0, 1)) { + return; + } - if (rand(0, 1)) { - return null; - } + if (rand(0, 1)) { + return null; + } - if (rand(0, 1)) { - return 1; - } + if (rand(0, 1)) { + return 1; + } } function returnAlias(): Foo { - return new FooAlias(); + return new FooAlias(); } function returnAnotherAlias(): FooAlias { - return new Foo(); + return new Foo(); } /** @@ -103,8 +103,8 @@ function returnAnotherAlias(): FooAlias */ function containsYield() { - yield 1; - return; + yield 1; + return; } /** @@ -112,24 +112,27 @@ function containsYield() */ function returnUnionIterable() { - if (something()) { - return 'foo'; - } + if (something()) { + return 'foo'; + } - return []; + return []; } /** * @param array $arr */ -function arrayMapConservesNonEmptiness(array $arr) : int { - if (!$arr) { - return 5; - } +function arrayMapConservesNonEmptiness(array $arr): int +{ + if (!$arr) { + return 5; + } - $arr = array_map(function($a) : int { return $a; }, $arr); + $arr = array_map(function ($a): int { + return $a; + }, $arr); - return array_shift($arr); + return array_shift($arr); } /** @@ -137,8 +140,8 @@ function arrayMapConservesNonEmptiness(array $arr) : int { */ function returnFromGeneratorMixed(): \Generator { - yield 1; - return 2; + yield 1; + return 2; } /** @@ -146,13 +149,13 @@ function returnFromGeneratorMixed(): \Generator */ function returnFromGeneratorString(): \Generator { - yield 1; + yield 1; - if (rand(0, 1)) { - return; - } + if (rand(0, 1)) { + return; + } - return 2; + return 2; } /** @@ -160,8 +163,8 @@ function returnFromGeneratorString(): \Generator */ function returnVoidFromGenerator(): \Generator { - yield 1; - return; + yield 1; + return; } /** @@ -169,8 +172,8 @@ function returnVoidFromGenerator(): \Generator */ function returnVoidFromGenerator2(): \Generator { - yield 1; - return 2; + yield 1; + return 2; } /** @@ -178,5 +181,5 @@ function returnVoidFromGenerator2(): \Generator */ function returnNever() { - return; + return; } diff --git a/tests/PHPStan/Rules/Functions/data/typehints.php b/tests/PHPStan/Rules/Functions/data/typehints.php index 67ca9b736c..f00e67e037 100644 --- a/tests/PHPStan/Rules/Functions/data/typehints.php +++ b/tests/PHPStan/Rules/Functions/data/typehints.php @@ -4,27 +4,22 @@ class FooFunctionTypehints { - } trait SomeTrait { - } function foo(FooFunctionTypehints $foo, $bar, array $lorem): NonexistentClass { - } function bar(BarFunctionTypehints $bar): array { - } function baz(...$bar): FooFunctionTypehints { - } /** @@ -32,12 +27,10 @@ function baz(...$bar): FooFunctionTypehints */ function returnParent() { - } function badCaseTypehints(fOOFunctionTypehints $foo): fOOFunctionTypehintS { - } /** @@ -46,7 +39,6 @@ function badCaseTypehints(fOOFunctionTypehints $foo): fOOFunctionTypehintS */ function badCaseInNativeAndPhpDoc(FooFunctionTypehints $foo): FooFunctionTypehints { - } /** @@ -55,12 +47,10 @@ function badCaseInNativeAndPhpDoc(FooFunctionTypehints $foo): FooFunctionTypehin */ function anotherBadCaseInNativeAndPhpDoc(FOOFunctionTypehints $foo): FOOFunctionTypehints { - } function referencesTraitsInNative(SomeTrait $trait): SomeTrait { - } /** @@ -69,7 +59,6 @@ function referencesTraitsInNative(SomeTrait $trait): SomeTrait */ function referencesTraitsInPhpDoc($trait) { - } /** @@ -77,7 +66,6 @@ function referencesTraitsInPhpDoc($trait) */ function genericClassString(string $string) { - } /** @@ -86,7 +74,6 @@ function genericClassString(string $string) */ function genericTemplateClassString(string $string) { - } /** @@ -95,5 +82,4 @@ function genericTemplateClassString(string $string) */ function templateTypeMissingInParameter(string $a) { - } diff --git a/tests/PHPStan/Rules/Functions/data/typehintsWithoutNamespace.php b/tests/PHPStan/Rules/Functions/data/typehintsWithoutNamespace.php index 60a47c8b7b..7e2240b842 100644 --- a/tests/PHPStan/Rules/Functions/data/typehintsWithoutNamespace.php +++ b/tests/PHPStan/Rules/Functions/data/typehintsWithoutNamespace.php @@ -2,27 +2,22 @@ class FooFunctionTypehints { - } trait SomeTraitWithoutNamespace { - } function fooWithoutNamespace(FooFunctionTypehints $foo, $bar, array $lorem): NonexistentClass { - } function barWithoutNamespace(BarFunctionTypehints $bar): array { - } function bazWithoutNamespace(...$bar): FooFunctionTypehints { - } /** @@ -30,12 +25,10 @@ function bazWithoutNamespace(...$bar): FooFunctionTypehints */ function returnParentWithoutNamespace() { - } function badCaseTypehintsWithoutNamespace(fOOFunctionTypehints $foo): fOOFunctionTypehintS { - } /** @@ -44,7 +37,6 @@ function badCaseTypehintsWithoutNamespace(fOOFunctionTypehints $foo): fOOFunctio */ function badCaseInNativeAndPhpDocWithoutNamespace(FooFunctionTypehints $foo): FooFunctionTypehints { - } /** @@ -53,12 +45,10 @@ function badCaseInNativeAndPhpDocWithoutNamespace(FooFunctionTypehints $foo): Fo */ function anotherBadCaseInNativeAndPhpDocWithoutNamespace(FOOFunctionTypehints $foo): FOOFunctionTypehints { - } function referencesTraitsInNativeWithoutNamespace(SomeTraitWithoutNamespace $trait): SomeTraitWithoutNamespace { - } /** @@ -67,5 +57,4 @@ function referencesTraitsInNativeWithoutNamespace(SomeTraitWithoutNamespace $tra */ function referencesTraitsInPhpDocWithoutNamespace($trait) { - } diff --git a/tests/PHPStan/Rules/Functions/data/union-iterable-type-issue.php b/tests/PHPStan/Rules/Functions/data/union-iterable-type-issue.php index 3c0d1e83bc..4dd70ca9a0 100644 --- a/tests/PHPStan/Rules/Functions/data/union-iterable-type-issue.php +++ b/tests/PHPStan/Rules/Functions/data/union-iterable-type-issue.php @@ -7,7 +7,6 @@ */ function foo($var) { - } /** @@ -15,5 +14,5 @@ function foo($var) */ function bar($var) { - foo($var); + foo($var); } diff --git a/tests/PHPStan/Rules/Functions/data/unpack-operator.php b/tests/PHPStan/Rules/Functions/data/unpack-operator.php index ee70bd2bf3..b9b5cb14dd 100644 --- a/tests/PHPStan/Rules/Functions/data/unpack-operator.php +++ b/tests/PHPStan/Rules/Functions/data/unpack-operator.php @@ -1,38 +1,34 @@ -createReflectionProvider(), true, false, true, false), true); + } - protected function getRule(): Rule - { - return new YieldFromTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false), true); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/yield-from.php'], [ - [ - 'Argument of an invalid type int passed to yield from, only iterables are supported.', - 15, - ], - [ - 'Generator expects key type DateTimeImmutable, stdClass given.', - 16, - ], - [ - 'Generator expects value type string, int given.', - 16, - ], - [ - 'Generator expects delegated TSend type int, int|null given.', - 41, - ], - [ - 'Generator expects value type array(DateTime, DateTime, stdClass, DateTimeImmutable), array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable) given.', - 74, - ], - [ - 'Result of yield from (void) is used.', - 111, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/yield-from.php'], [ + [ + 'Argument of an invalid type int passed to yield from, only iterables are supported.', + 15, + ], + [ + 'Generator expects key type DateTimeImmutable, stdClass given.', + 16, + ], + [ + 'Generator expects value type string, int given.', + 16, + ], + [ + 'Generator expects delegated TSend type int, int|null given.', + 41, + ], + [ + 'Generator expects value type array(DateTime, DateTime, stdClass, DateTimeImmutable), array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable) given.', + 74, + ], + [ + 'Result of yield from (void) is used.', + 111, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generators/YieldInGeneratorRuleTest.php b/tests/PHPStan/Rules/Generators/YieldInGeneratorRuleTest.php index 95ec184fa4..a6dc343f19 100644 --- a/tests/PHPStan/Rules/Generators/YieldInGeneratorRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldInGeneratorRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/yield-in-generator.php'], [ - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 13, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 14, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 31, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 32, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 37, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 38, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 55, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 56, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 87, - ], - [ - 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', - 88, - ], - ]); - } - - public function testRuleOutsideFunction(): void - { - $this->analyse([__DIR__ . '/data/yield-outside-function.php'], [ - [ - 'Yield can be used only inside a function.', - 5, - ], - [ - 'Yield can be used only inside a function.', - 6, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/yield-in-generator.php'], [ + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 13, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 14, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 31, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 32, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 37, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 38, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 55, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 56, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 87, + ], + [ + 'Yield can be used only with these return types: Generator, Iterator, Traversable, iterable.', + 88, + ], + ]); + } + public function testRuleOutsideFunction(): void + { + $this->analyse([__DIR__ . '/data/yield-outside-function.php'], [ + [ + 'Yield can be used only inside a function.', + 5, + ], + [ + 'Yield can be used only inside a function.', + 6, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php index 58799d2296..e0b2e162b2 100644 --- a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): Rule - { - return new YieldTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/yield.php'], [ - [ - 'Generator expects value type int, string given.', - 14, - ], - [ - 'Generator expects key type string, int given.', - 15, - ], - [ - 'Generator expects value type int, null given.', - 15, - ], - [ - 'Generator expects key type string, int given.', - 16, - ], - [ - 'Generator expects key type string, int given.', - 17, - ], - [ - 'Generator expects value type int, string given.', - 17, - ], - [ - 'Generator expects value type array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable), array(DateTime, DateTime, stdClass, DateTimeImmutable) given.', - 25, - ], - [ - 'Result of yield (void) is used.', - 137, - ], - [ - 'Result of yield (void) is used.', - 138, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/yield.php'], [ + [ + 'Generator expects value type int, string given.', + 14, + ], + [ + 'Generator expects key type string, int given.', + 15, + ], + [ + 'Generator expects value type int, null given.', + 15, + ], + [ + 'Generator expects key type string, int given.', + 16, + ], + [ + 'Generator expects key type string, int given.', + 17, + ], + [ + 'Generator expects value type int, string given.', + 17, + ], + [ + 'Generator expects value type array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable), array(DateTime, DateTime, stdClass, DateTimeImmutable) given.', + 25, + ], + [ + 'Result of yield (void) is used.', + 137, + ], + [ + 'Result of yield (void) is used.', + 138, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generators/data/yield-from.php b/tests/PHPStan/Rules/Generators/data/yield-from.php index 1bab7f130f..90cc97f55a 100644 --- a/tests/PHPStan/Rules/Generators/data/yield-from.php +++ b/tests/PHPStan/Rules/Generators/data/yield-from.php @@ -6,108 +6,111 @@ class Foo { - - /** - * @return \Generator - */ - public function doFoo(): \Generator - { - yield from 1; - yield from $this->doBar(); - } - - /** - * @return \Generator<\stdClass, int> - */ - public function doBar() - { - $stdClass = new \stdClass(); - yield $stdClass => 1; - } - - public function doBaz() - { - $stdClass = new \stdClass(); - yield $stdClass => 1; - } - - /** - * @return \Generator - */ - public function generatorAcceptingIntOrNull(): \Generator - { - yield 1; - yield 2; - yield from $this->generatorAcceptingInt(); - yield from $this->generatorAcceptingIntOrNull(); - } - - /** - * @return \Generator - */ - public function generatorAcceptingInt(): \Generator - { - yield 1; - yield 2; - yield from $this->generatorAcceptingInt(); - yield from $this->generatorAcceptingIntOrNull(); - } - - /** - * @return \Generator - */ - public function doArrayShape(): \Generator - { - yield [ - new \DateTime(), - new \DateTime(), - new \stdClass, - new \DateTimeImmutable('2019-10-26'), - ]; - } - - /** - * @return \Generator - */ - public function doArrayShape2(): \Generator - { - yield from $this->doArrayShape(); - } - - /** - * @return \Generator - */ - public function yieldWithImplicitReturn() : \Generator{ - yield 1; - return 1; - } - - /** - * @return \Generator - */ - public function yieldWithExplicitReturn() : \Generator{ - yield 1; - return 1; - } - - /** - * @return \Generator - */ - public function yieldWithVoidReturn() : \Generator{ - yield 1; - } - - /** - * @return \Generator - */ - public function yieldFromResult() : \Generator{ - yield from $this->yieldWithImplicitReturn(); - $mixed = yield from $this->yieldWithImplicitReturn(); - - yield from $this->yieldWithExplicitReturn(); - $int = yield from $this->yieldWithExplicitReturn(); - - yield from $this->yieldWithVoidReturn(); - $void = yield from $this->yieldWithVoidReturn(); - } + /** + * @return \Generator + */ + public function doFoo(): \Generator + { + yield from 1; + yield from $this->doBar(); + } + + /** + * @return \Generator<\stdClass, int> + */ + public function doBar() + { + $stdClass = new \stdClass(); + yield $stdClass => 1; + } + + public function doBaz() + { + $stdClass = new \stdClass(); + yield $stdClass => 1; + } + + /** + * @return \Generator + */ + public function generatorAcceptingIntOrNull(): \Generator + { + yield 1; + yield 2; + yield from $this->generatorAcceptingInt(); + yield from $this->generatorAcceptingIntOrNull(); + } + + /** + * @return \Generator + */ + public function generatorAcceptingInt(): \Generator + { + yield 1; + yield 2; + yield from $this->generatorAcceptingInt(); + yield from $this->generatorAcceptingIntOrNull(); + } + + /** + * @return \Generator + */ + public function doArrayShape(): \Generator + { + yield [ + new \DateTime(), + new \DateTime(), + new \stdClass(), + new \DateTimeImmutable('2019-10-26'), + ]; + } + + /** + * @return \Generator + */ + public function doArrayShape2(): \Generator + { + yield from $this->doArrayShape(); + } + + /** + * @return \Generator + */ + public function yieldWithImplicitReturn(): \Generator + { + yield 1; + return 1; + } + + /** + * @return \Generator + */ + public function yieldWithExplicitReturn(): \Generator + { + yield 1; + return 1; + } + + /** + * @return \Generator + */ + public function yieldWithVoidReturn(): \Generator + { + yield 1; + } + + /** + * @return \Generator + */ + public function yieldFromResult(): \Generator + { + yield from $this->yieldWithImplicitReturn(); + $mixed = yield from $this->yieldWithImplicitReturn(); + + yield from $this->yieldWithExplicitReturn(); + $int = yield from $this->yieldWithExplicitReturn(); + + yield from $this->yieldWithVoidReturn(); + $void = yield from $this->yieldWithVoidReturn(); + } } diff --git a/tests/PHPStan/Rules/Generators/data/yield-in-generator.php b/tests/PHPStan/Rules/Generators/data/yield-in-generator.php index d896ab4986..99934c117d 100644 --- a/tests/PHPStan/Rules/Generators/data/yield-in-generator.php +++ b/tests/PHPStan/Rules/Generators/data/yield-in-generator.php @@ -1,17 +1,19 @@ - - */ - public function doFoo(): \Generator - { - yield 'foo' => 1; - yield 'foo' => 'bar'; - yield; - yield 1; - yield 'foo'; - } - - /** - * @return\Generator - */ - public function doArrayShape(): \Generator - { - yield [ - new \DateTime(), - new \DateTime(), - new \stdClass, - new \DateTimeImmutable('2019-10-26'), - ]; - } - + /** + * @return \Generator + */ + public function doFoo(): \Generator + { + yield 'foo' => 1; + yield 'foo' => 'bar'; + yield; + yield 1; + yield 'foo'; + } + + /** + * @return\Generator + */ + public function doArrayShape(): \Generator + { + yield [ + new \DateTime(), + new \DateTime(), + new \stdClass(), + new \DateTimeImmutable('2019-10-26'), + ]; + } } /** @@ -38,111 +36,112 @@ public function doArrayShape(): \Generator */ final class Map { - /** @var TKey */ - private $key; - /** @var TValue */ - private $value; - - /** - * @param TKey $key - * @param TValue $value - */ - public function __construct($key, $value) - { - $this->key = $key; - $this->value = $value; - } - - /** @return TKey */ - public function key() - { - return $this->key; - } - - /** @return TValue */ - public function value() - { - return $this->value; - } + /** @var TKey */ + private $key; + /** @var TValue */ + private $value; + + /** + * @param TKey $key + * @param TValue $value + */ + public function __construct($key, $value) + { + $this->key = $key; + $this->value = $value; + } + + /** @return TKey */ + public function key() + { + return $this->key; + } + + /** @return TValue */ + public function value() + { + return $this->value; + } } class TestMap { - - /** - * @template TKey - * @template TValue - * - * @param iterable $iterator - * @param callable(TValue, TKey):(TValue|Map) $callback - * - * @return iterable - */ - function iterator_map(iterable $iterator, callable $callback): iterable - { - foreach ($iterator as $key => $value) { - $result = $callback($value, $key); - if ($result instanceof Map) { - yield $result->key() => $result->value(); - continue; - } - yield $key => $result; - } - } - - /** - * @template T - * @template K - * @param iterable $hash - * @return \Generator - */ - function hash_to_alt_sequence(iterable $hash): iterable - { - foreach ($hash as $k => $v) { - yield $k; - yield $v; - } - } - - /** - * @template T - * @template K - * @param iterable $hash - * @return iterable - */ - function hash_to_alt_sequence2(iterable $hash): iterable - { - foreach ($hash as $k => $v) { - yield $k; - yield $v; - } - } - + /** + * @template TKey + * @template TValue + * + * @param iterable $iterator + * @param callable(TValue, TKey):(TValue|Map) $callback + * + * @return iterable + */ + public function iterator_map(iterable $iterator, callable $callback): iterable + { + foreach ($iterator as $key => $value) { + $result = $callback($value, $key); + if ($result instanceof Map) { + yield $result->key() => $result->value(); + continue; + } + yield $key => $result; + } + } + + /** + * @template T + * @template K + * @param iterable $hash + * @return \Generator + */ + public function hash_to_alt_sequence(iterable $hash): iterable + { + foreach ($hash as $k => $v) { + yield $k; + yield $v; + } + } + + /** + * @template T + * @template K + * @param iterable $hash + * @return iterable + */ + public function hash_to_alt_sequence2(iterable $hash): iterable + { + foreach ($hash as $k => $v) { + yield $k; + yield $v; + } + } } /** * @return \Generator */ -function yieldWithIntSendType(){ - yield 1; - $something = yield 1; - var_dump(yield 1); +function yieldWithIntSendType() +{ + yield 1; + $something = yield 1; + var_dump(yield 1); } /** * @return \Generator */ -function yieldWithVoidSendType(){ - yield 1; - $something = yield 1; - var_dump(yield 1); +function yieldWithVoidSendType() +{ + yield 1; + $something = yield 1; + var_dump(yield 1); } /** * @return \Generator */ -function yieldWithoutSendType(){ - yield 1; - $something = yield 1; - var_dump(yield 1); +function yieldWithoutSendType() +{ + yield 1; + $something = yield 1; + var_dump(yield 1); } diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 042f1ebb93..07d06237cb 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class), + new GenericAncestorsCheck( + $this->createReflectionProvider(), + new GenericObjectTypeCheck(), + new VarianceCheck(), + true + ) + ); + } - protected function getRule(): Rule - { - return new ClassAncestorsRule( - self::getContainer()->getByType(FileTypeMapper::class), - new GenericAncestorsCheck( - $this->createReflectionProvider(), - new GenericObjectTypeCheck(), - new VarianceCheck(), - true - ) - ); - } - - public function testRuleExtends(): void - { - $this->analyse([__DIR__ . '/data/class-ancestors-extends.php'], [ - [ - 'Class ClassAncestorsExtends\FooDoesNotExtendAnything has @extends tag, but does not extend any class.', - 26, - ], - [ - 'The @extends tag of class ClassAncestorsExtends\FooDuplicateExtendsTags describes ClassAncestorsExtends\FooGeneric2 but the class extends ClassAncestorsExtends\FooGeneric.', - 35, - ], - [ - 'The @extends tag of class ClassAncestorsExtends\FooWrongClassExtended describes ClassAncestorsExtends\FooGeneric2 but the class extends ClassAncestorsExtends\FooGeneric.', - 43, - ], - [ - 'Class ClassAncestorsExtends\FooWrongClassExtended extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', - 43, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Class ClassAncestorsExtends\FooWrongTypeInExtendsTag @extends tag contains incompatible type class-string.', - 51, - ], - [ - 'Class ClassAncestorsExtends\FooWrongTypeInExtendsTag extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', - 51, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends does not specify all template types of class ClassAncestorsExtends\FooGeneric: T, U', - 67, - ], - [ - 'Generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends specifies 3 template types, but class ClassAncestorsExtends\FooGeneric supports only 2: T, U', - 75, - ], - [ - 'Type Throwable in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', - 83, - ], - [ - 'Type stdClass in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', - 91, - ], - [ - 'PHPDoc tag @extends has invalid type ClassAncestorsExtends\Zazzuuuu.', - 99, - ], - [ - 'Type mixed in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', - 108, - ], - [ - 'Type Throwable in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', - 117, - ], - [ - 'Type stdClass in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', - 163, - ], - [ - 'Class ClassAncestorsExtends\FooExtendsGenericClass extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', - 174, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Template type T is declared as covariant, but occurs in invariant position in extended type ClassAncestorsExtends\FooGeneric8 of class ClassAncestorsExtends\FooGeneric9.', - 192, - ], - ]); - } - - public function testRuleImplements(): void - { - $this->analyse([__DIR__ . '/data/class-ancestors-implements.php'], [ - [ - 'Class ClassAncestorsImplements\FooDoesNotImplementAnything has @implements tag, but does not implement any interface.', - 35, - ], - [ - 'The @implements tag of class ClassAncestorsImplements\FooInvalidImplementsTags describes ClassAncestorsImplements\FooGeneric2 but the class implements: ClassAncestorsImplements\FooGeneric', - 44, - ], - [ - 'The @implements tag of class ClassAncestorsImplements\FooWrongClassImplemented describes ClassAncestorsImplements\FooGeneric2 but the class implements: ClassAncestorsImplements\FooGeneric, ClassAncestorsImplements\FooGeneric3', - 52, - ], - [ - 'Class ClassAncestorsImplements\FooWrongClassImplemented implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', - 52, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Class ClassAncestorsImplements\FooWrongClassImplemented implements generic interface ClassAncestorsImplements\FooGeneric3 but does not specify its types: T, W', - 52, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Class ClassAncestorsImplements\FooWrongTypeInImplementsTag @implements tag contains incompatible type class-string.', - 60, - ], - [ - 'Class ClassAncestorsImplements\FooWrongTypeInImplementsTag implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', - 60, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements does not specify all template types of interface ClassAncestorsImplements\FooGeneric: T, U', - 76, - ], - [ - 'Generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements specifies 3 template types, but interface ClassAncestorsImplements\FooGeneric supports only 2: T, U', - 84, - ], - [ - 'Type Throwable in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 92, - ], - [ - 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 100, - ], - [ - 'PHPDoc tag @implements has invalid type ClassAncestorsImplements\Zazzuuuu.', - 108, - ], - [ - 'Type mixed in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 117, - ], - [ - 'Type Throwable in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 126, - ], - [ - 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 172, - ], - [ - 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', - 182, - ], - [ - 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric2 in PHPDoc tag @implements is not subtype of template type V of Exception of interface ClassAncestorsImplements\FooGeneric2.', - 182, - ], - [ - 'Class ClassAncestorsImplements\FooImplementsGenericInterface implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', - 198, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Template type T is declared as covariant, but occurs in invariant position in implemented type ClassAncestorsImplements\FooGeneric9 of class ClassAncestorsImplements\FooGeneric10.', - 216, - ], - ]); - } + public function testRuleExtends(): void + { + $this->analyse([__DIR__ . '/data/class-ancestors-extends.php'], [ + [ + 'Class ClassAncestorsExtends\FooDoesNotExtendAnything has @extends tag, but does not extend any class.', + 26, + ], + [ + 'The @extends tag of class ClassAncestorsExtends\FooDuplicateExtendsTags describes ClassAncestorsExtends\FooGeneric2 but the class extends ClassAncestorsExtends\FooGeneric.', + 35, + ], + [ + 'The @extends tag of class ClassAncestorsExtends\FooWrongClassExtended describes ClassAncestorsExtends\FooGeneric2 but the class extends ClassAncestorsExtends\FooGeneric.', + 43, + ], + [ + 'Class ClassAncestorsExtends\FooWrongClassExtended extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', + 43, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Class ClassAncestorsExtends\FooWrongTypeInExtendsTag @extends tag contains incompatible type class-string.', + 51, + ], + [ + 'Class ClassAncestorsExtends\FooWrongTypeInExtendsTag extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', + 51, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends does not specify all template types of class ClassAncestorsExtends\FooGeneric: T, U', + 67, + ], + [ + 'Generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends specifies 3 template types, but class ClassAncestorsExtends\FooGeneric supports only 2: T, U', + 75, + ], + [ + 'Type Throwable in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', + 83, + ], + [ + 'Type stdClass in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', + 91, + ], + [ + 'PHPDoc tag @extends has invalid type ClassAncestorsExtends\Zazzuuuu.', + 99, + ], + [ + 'Type mixed in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', + 108, + ], + [ + 'Type Throwable in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', + 117, + ], + [ + 'Type stdClass in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of class ClassAncestorsExtends\FooGeneric.', + 163, + ], + [ + 'Class ClassAncestorsExtends\FooExtendsGenericClass extends generic class ClassAncestorsExtends\FooGeneric but does not specify its types: T, U', + 174, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Template type T is declared as covariant, but occurs in invariant position in extended type ClassAncestorsExtends\FooGeneric8 of class ClassAncestorsExtends\FooGeneric9.', + 192, + ], + ]); + } - public function testBug3922(): void - { - $this->analyse([__DIR__ . '/data/bug-3922-ancestors.php'], [ - [ - 'Type Bug3922Ancestors\BarQuery in generic type Bug3922Ancestors\QueryHandlerInterface in PHPDoc tag @implements is not subtype of template type TQuery of Bug3922Ancestors\QueryInterface of interface Bug3922Ancestors\QueryHandlerInterface.', - 54, - ], - ]); - } + public function testRuleImplements(): void + { + $this->analyse([__DIR__ . '/data/class-ancestors-implements.php'], [ + [ + 'Class ClassAncestorsImplements\FooDoesNotImplementAnything has @implements tag, but does not implement any interface.', + 35, + ], + [ + 'The @implements tag of class ClassAncestorsImplements\FooInvalidImplementsTags describes ClassAncestorsImplements\FooGeneric2 but the class implements: ClassAncestorsImplements\FooGeneric', + 44, + ], + [ + 'The @implements tag of class ClassAncestorsImplements\FooWrongClassImplemented describes ClassAncestorsImplements\FooGeneric2 but the class implements: ClassAncestorsImplements\FooGeneric, ClassAncestorsImplements\FooGeneric3', + 52, + ], + [ + 'Class ClassAncestorsImplements\FooWrongClassImplemented implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', + 52, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Class ClassAncestorsImplements\FooWrongClassImplemented implements generic interface ClassAncestorsImplements\FooGeneric3 but does not specify its types: T, W', + 52, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Class ClassAncestorsImplements\FooWrongTypeInImplementsTag @implements tag contains incompatible type class-string.', + 60, + ], + [ + 'Class ClassAncestorsImplements\FooWrongTypeInImplementsTag implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', + 60, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements does not specify all template types of interface ClassAncestorsImplements\FooGeneric: T, U', + 76, + ], + [ + 'Generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements specifies 3 template types, but interface ClassAncestorsImplements\FooGeneric supports only 2: T, U', + 84, + ], + [ + 'Type Throwable in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 92, + ], + [ + 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 100, + ], + [ + 'PHPDoc tag @implements has invalid type ClassAncestorsImplements\Zazzuuuu.', + 108, + ], + [ + 'Type mixed in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 117, + ], + [ + 'Type Throwable in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 126, + ], + [ + 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 172, + ], + [ + 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric in PHPDoc tag @implements is not subtype of template type U of Exception of interface ClassAncestorsImplements\FooGeneric.', + 182, + ], + [ + 'Type stdClass in generic type ClassAncestorsImplements\FooGeneric2 in PHPDoc tag @implements is not subtype of template type V of Exception of interface ClassAncestorsImplements\FooGeneric2.', + 182, + ], + [ + 'Class ClassAncestorsImplements\FooImplementsGenericInterface implements generic interface ClassAncestorsImplements\FooGeneric but does not specify its types: T, U', + 198, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Template type T is declared as covariant, but occurs in invariant position in implemented type ClassAncestorsImplements\FooGeneric9 of class ClassAncestorsImplements\FooGeneric10.', + 216, + ], + ]); + } - public function testBug3922Reversed(): void - { - $this->analyse([__DIR__ . '/data/bug-3922-ancestors-reversed.php'], [ - [ - 'Type string in generic type Bug3922AncestorsReversed\QueryHandlerInterface in PHPDoc tag @implements is not subtype of template type int of interface Bug3922AncestorsReversed\QueryHandlerInterface.', - 54, - ], - ]); - } + public function testBug3922(): void + { + $this->analyse([__DIR__ . '/data/bug-3922-ancestors.php'], [ + [ + 'Type Bug3922Ancestors\BarQuery in generic type Bug3922Ancestors\QueryHandlerInterface in PHPDoc tag @implements is not subtype of template type TQuery of Bug3922Ancestors\QueryInterface of interface Bug3922Ancestors\QueryHandlerInterface.', + 54, + ], + ]); + } + public function testBug3922Reversed(): void + { + $this->analyse([__DIR__ . '/data/bug-3922-ancestors-reversed.php'], [ + [ + 'Type string in generic type Bug3922AncestorsReversed\QueryHandlerInterface in PHPDoc tag @implements is not subtype of template type int of interface Bug3922AncestorsReversed\QueryHandlerInterface.', + 54, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index db13da5a03..d3a242522d 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - - return new ClassTemplateTypeRule( - new TemplateTypeCheck( - $broker, - new ClassCaseSensitivityCheck($broker), - new GenericObjectTypeCheck(), - $typeAliasResolver, - true - ) - ); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/class-template.php'; + return new ClassTemplateTypeRule( + new TemplateTypeCheck( + $broker, + new ClassCaseSensitivityCheck($broker), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true + ) + ); + } - $this->analyse([__DIR__ . '/data/class-template.php'], [ - [ - 'PHPDoc tag @template for class ClassTemplateType\Foo cannot have existing class stdClass as its name.', - 8, - ], - [ - 'PHPDoc tag @template T for class ClassTemplateType\Bar has invalid bound type ClassTemplateType\Zazzzu.', - 16, - ], - [ - 'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type float is not supported.', - 24, - ], - [ - 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', - 32, - ], - [ - 'PHPDoc tag @template for class ClassTemplateType\Ipsum cannot have existing type alias TypeAlias as its name.', - 41, - ], - [ - 'PHPDoc tag @template for class ClassTemplateType\Dolor cannot have existing type alias LocalAlias as its name.', - 53, - ], - [ - 'PHPDoc tag @template for class ClassTemplateType\Dolor cannot have existing type alias ImportedAlias as its name.', - 53, - ], - [ - 'PHPDoc tag @template for anonymous class cannot have existing class stdClass as its name.', - 58, - ], - [ - 'PHPDoc tag @template T for anonymous class has invalid bound type ClassTemplateType\Zazzzu.', - 63, - ], - [ - 'PHPDoc tag @template T for anonymous class with bound type float is not supported.', - 68, - ], - [ - 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', - 73, - ], - [ - 'PHPDoc tag @template for anonymous class cannot have existing type alias TypeAlias as its name.', - 78, - ], - ]); - } + public function testRule(): void + { + require_once __DIR__ . '/data/class-template.php'; - public function testNestedGenericTypes(): void - { - $this->analyse([__DIR__ . '/data/nested-generic-types.php'], [ - [ - 'Type mixed in generic type NestedGenericTypesClassCheck\SomeObjectInterface in PHPDoc tag @template U is not subtype of template type T of object of class NestedGenericTypesClassCheck\SomeObjectInterface.', - 32, - ], - [ - 'Type int in generic type NestedGenericTypesClassCheck\SomeObjectInterface in PHPDoc tag @template U is not subtype of template type T of object of class NestedGenericTypesClassCheck\SomeObjectInterface.', - 41, - ], - [ - 'PHPDoc tag @template U bound contains generic type NestedGenericTypesClassCheck\NotGeneric but class NestedGenericTypesClassCheck\NotGeneric is not generic.', - 52, - ], - [ - 'PHPDoc tag @template V bound has type NestedGenericTypesClassCheck\MultipleGenerics which does not specify all template types of class NestedGenericTypesClassCheck\MultipleGenerics: T, U', - 52, - ], - [ - 'PHPDoc tag @template W bound has type NestedGenericTypesClassCheck\MultipleGenerics which specifies 3 template types, but class NestedGenericTypesClassCheck\MultipleGenerics supports only 2: T, U', - 52, - ], - ]); - } + $this->analyse([__DIR__ . '/data/class-template.php'], [ + [ + 'PHPDoc tag @template for class ClassTemplateType\Foo cannot have existing class stdClass as its name.', + 8, + ], + [ + 'PHPDoc tag @template T for class ClassTemplateType\Bar has invalid bound type ClassTemplateType\Zazzzu.', + 16, + ], + [ + 'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type float is not supported.', + 24, + ], + [ + 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', + 32, + ], + [ + 'PHPDoc tag @template for class ClassTemplateType\Ipsum cannot have existing type alias TypeAlias as its name.', + 41, + ], + [ + 'PHPDoc tag @template for class ClassTemplateType\Dolor cannot have existing type alias LocalAlias as its name.', + 53, + ], + [ + 'PHPDoc tag @template for class ClassTemplateType\Dolor cannot have existing type alias ImportedAlias as its name.', + 53, + ], + [ + 'PHPDoc tag @template for anonymous class cannot have existing class stdClass as its name.', + 58, + ], + [ + 'PHPDoc tag @template T for anonymous class has invalid bound type ClassTemplateType\Zazzzu.', + 63, + ], + [ + 'PHPDoc tag @template T for anonymous class with bound type float is not supported.', + 68, + ], + [ + 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', + 73, + ], + [ + 'PHPDoc tag @template for anonymous class cannot have existing type alias TypeAlias as its name.', + 78, + ], + ]); + } + public function testNestedGenericTypes(): void + { + $this->analyse([__DIR__ . '/data/nested-generic-types.php'], [ + [ + 'Type mixed in generic type NestedGenericTypesClassCheck\SomeObjectInterface in PHPDoc tag @template U is not subtype of template type T of object of class NestedGenericTypesClassCheck\SomeObjectInterface.', + 32, + ], + [ + 'Type int in generic type NestedGenericTypesClassCheck\SomeObjectInterface in PHPDoc tag @template U is not subtype of template type T of object of class NestedGenericTypesClassCheck\SomeObjectInterface.', + 41, + ], + [ + 'PHPDoc tag @template U bound contains generic type NestedGenericTypesClassCheck\NotGeneric but class NestedGenericTypesClassCheck\NotGeneric is not generic.', + 52, + ], + [ + 'PHPDoc tag @template V bound has type NestedGenericTypesClassCheck\MultipleGenerics which does not specify all template types of class NestedGenericTypesClassCheck\MultipleGenerics: T, U', + 52, + ], + [ + 'PHPDoc tag @template W bound has type NestedGenericTypesClassCheck\MultipleGenerics which specifies 3 template types, but class NestedGenericTypesClassCheck\MultipleGenerics supports only 2: T, U', + 52, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/FunctionSignatureVarianceRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionSignatureVarianceRuleTest.php index 015bdb9333..0e2e369f91 100644 --- a/tests/PHPStan/Rules/Generics/FunctionSignatureVarianceRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionSignatureVarianceRuleTest.php @@ -1,4 +1,6 @@ -getByType(VarianceCheck::class) + ); + } - protected function getRule(): Rule - { - return new FunctionSignatureVarianceRule( - self::getContainer()->getByType(VarianceCheck::class) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/function-signature-variance.php'], [ - [ - 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type T in in function FunctionSignatureVariance\f().', - 20, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/function-signature-variance.php'], [ + [ + 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type T in in function FunctionSignatureVariance\f().', + 20, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index 7f3975cf63..73bfc3119d 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - - return new FunctionTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/function-template.php'], [ - [ - 'PHPDoc tag @template for function FunctionTemplateType\foo() cannot have existing class stdClass as its name.', - 8, - ], - [ - 'PHPDoc tag @template T for function FunctionTemplateType\bar() has invalid bound type FunctionTemplateType\Zazzzu.', - 16, - ], - [ - 'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type float is not supported.', - 24, - ], - [ - 'PHPDoc tag @template for function FunctionTemplateType\lorem() cannot have existing type alias TypeAlias as its name.', - 32, - ], - ]); - } - - public function testBug3769(): void - { - require_once __DIR__ . '/data/bug-3769.php'; - $this->analyse([__DIR__ . '/data/bug-3769.php'], []); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); + + return new FunctionTemplateTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/function-template.php'], [ + [ + 'PHPDoc tag @template for function FunctionTemplateType\foo() cannot have existing class stdClass as its name.', + 8, + ], + [ + 'PHPDoc tag @template T for function FunctionTemplateType\bar() has invalid bound type FunctionTemplateType\Zazzzu.', + 16, + ], + [ + 'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type float is not supported.', + 24, + ], + [ + 'PHPDoc tag @template for function FunctionTemplateType\lorem() cannot have existing type alias TypeAlias as its name.', + 32, + ], + ]); + } + + public function testBug3769(): void + { + require_once __DIR__ . '/data/bug-3769.php'; + $this->analyse([__DIR__ . '/data/bug-3769.php'], []); + } } diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 6ffca19004..6c6d665f5f 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class), + new GenericAncestorsCheck( + $this->createReflectionProvider(), + new GenericObjectTypeCheck(), + new VarianceCheck(), + true + ) + ); + } - protected function getRule(): Rule - { - return new InterfaceAncestorsRule( - self::getContainer()->getByType(FileTypeMapper::class), - new GenericAncestorsCheck( - $this->createReflectionProvider(), - new GenericObjectTypeCheck(), - new VarianceCheck(), - true - ) - ); - } - - public function testRuleImplements(): void - { - $this->analyse([__DIR__ . '/data/interface-ancestors-implements.php'], [ - [ - 'Interface InterfaceAncestorsImplements\FooDoesNotImplementAnything has @implements tag, but can not implement any interface, must extend from it.', - 35, - ], - [ - 'Interface InterfaceAncestorsImplements\FooInvalidImplementsTags has @implements tag, but can not implement any interface, must extend from it.', - 44, - ], - [ - 'Interface InterfaceAncestorsImplements\FooInvalidImplementsTags has @implements tag, but can not implement any interface, must extend from it.', - 44, - ], - [ - 'Interface InterfaceAncestorsImplements\FooWrongClassImplemented has @implements tag, but can not implement any interface, must extend from it.', - 52, - ], - [ - 'Interface InterfaceAncestorsImplements\FooWrongTypeInImplementsTag @implements tag contains incompatible type class-string.', - 60, - ], - [ - 'Interface InterfaceAncestorsImplements\FooCorrect has @implements tag, but can not implement any interface, must extend from it.', - 68, - ], - [ - 'Interface InterfaceAncestorsImplements\FooNotEnough has @implements tag, but can not implement any interface, must extend from it.', - 76, - ], - [ - 'Interface InterfaceAncestorsImplements\FooExtraTypes has @implements tag, but can not implement any interface, must extend from it.', - 84, - ], - [ - 'Interface InterfaceAncestorsImplements\FooNotSubtype has @implements tag, but can not implement any interface, must extend from it.', - 92, - ], - [ - 'Interface InterfaceAncestorsImplements\FooAlsoNotSubtype has @implements tag, but can not implement any interface, must extend from it.', - 100, - ], - [ - 'Interface InterfaceAncestorsImplements\FooUnknownClass has @implements tag, but can not implement any interface, must extend from it.', - 108, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric has @implements tag, but can not implement any interface, must extend from it.', - 117, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric2 has @implements tag, but can not implement any interface, must extend from it.', - 126, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric3 has @implements tag, but can not implement any interface, must extend from it.', - 136, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric4 has @implements tag, but can not implement any interface, must extend from it.', - 145, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric5 has @implements tag, but can not implement any interface, must extend from it.', - 154, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric6 has @implements tag, but can not implement any interface, must extend from it.', - 163, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric7 has @implements tag, but can not implement any interface, must extend from it.', - 172, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric8 has @implements tag, but can not implement any interface, must extend from it.', - 182, - ], - [ - 'Interface InterfaceAncestorsImplements\FooGenericGeneric8 has @implements tag, but can not implement any interface, must extend from it.', - 182, - ], - ]); - } - - public function testRuleExtends(): void - { - $this->analyse([__DIR__ . '/data/interface-ancestors-extends.php'], [ - [ - 'Interface InterfaceAncestorsExtends\FooDoesNotImplementAnything has @extends tag, but does not extend any interface.', - 35, - ], - [ - 'The @extends tag of interface InterfaceAncestorsExtends\FooInvalidImplementsTags describes InterfaceAncestorsExtends\FooGeneric2 but the interface extends: InterfaceAncestorsExtends\FooGeneric', - 44, - ], - [ - 'The @extends tag of interface InterfaceAncestorsExtends\FooWrongClassImplemented describes InterfaceAncestorsExtends\FooGeneric2 but the interface extends: InterfaceAncestorsExtends\FooGeneric, InterfaceAncestorsExtends\FooGeneric3', - 52, - ], - [ - 'Interface InterfaceAncestorsExtends\FooWrongClassImplemented extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', - 52, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Interface InterfaceAncestorsExtends\FooWrongClassImplemented extends generic interface InterfaceAncestorsExtends\FooGeneric3 but does not specify its types: T, W', - 52, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Interface InterfaceAncestorsExtends\FooWrongTypeInImplementsTag @extends tag contains incompatible type class-string.', - 60, - ], - [ - 'Interface InterfaceAncestorsExtends\FooWrongTypeInImplementsTag extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', - 60, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends does not specify all template types of interface InterfaceAncestorsExtends\FooGeneric: T, U', - 76, - ], - [ - 'Generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends specifies 3 template types, but interface InterfaceAncestorsExtends\FooGeneric supports only 2: T, U', - 84, - ], - [ - 'Type Throwable in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 92, - ], - [ - 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 100, - ], - [ - 'PHPDoc tag @extends has invalid type InterfaceAncestorsExtends\Zazzuuuu.', - 108, - ], - [ - 'Type mixed in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 117, - ], - [ - 'Type Throwable in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 126, - ], - [ - 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 172, - ], - [ - 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', - 182, - ], - [ - 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric2 in PHPDoc tag @extends is not subtype of template type V of Exception of interface InterfaceAncestorsExtends\FooGeneric2.', - 182, - ], - [ - 'Interface InterfaceAncestorsExtends\ExtendsGenericInterface extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', - 197, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Template type T is declared as covariant, but occurs in invariant position in extended type InterfaceAncestorsExtends\FooGeneric9 of interface InterfaceAncestorsExtends\FooGeneric10.', - 215, - ], - ]); - } + public function testRuleImplements(): void + { + $this->analyse([__DIR__ . '/data/interface-ancestors-implements.php'], [ + [ + 'Interface InterfaceAncestorsImplements\FooDoesNotImplementAnything has @implements tag, but can not implement any interface, must extend from it.', + 35, + ], + [ + 'Interface InterfaceAncestorsImplements\FooInvalidImplementsTags has @implements tag, but can not implement any interface, must extend from it.', + 44, + ], + [ + 'Interface InterfaceAncestorsImplements\FooInvalidImplementsTags has @implements tag, but can not implement any interface, must extend from it.', + 44, + ], + [ + 'Interface InterfaceAncestorsImplements\FooWrongClassImplemented has @implements tag, but can not implement any interface, must extend from it.', + 52, + ], + [ + 'Interface InterfaceAncestorsImplements\FooWrongTypeInImplementsTag @implements tag contains incompatible type class-string.', + 60, + ], + [ + 'Interface InterfaceAncestorsImplements\FooCorrect has @implements tag, but can not implement any interface, must extend from it.', + 68, + ], + [ + 'Interface InterfaceAncestorsImplements\FooNotEnough has @implements tag, but can not implement any interface, must extend from it.', + 76, + ], + [ + 'Interface InterfaceAncestorsImplements\FooExtraTypes has @implements tag, but can not implement any interface, must extend from it.', + 84, + ], + [ + 'Interface InterfaceAncestorsImplements\FooNotSubtype has @implements tag, but can not implement any interface, must extend from it.', + 92, + ], + [ + 'Interface InterfaceAncestorsImplements\FooAlsoNotSubtype has @implements tag, but can not implement any interface, must extend from it.', + 100, + ], + [ + 'Interface InterfaceAncestorsImplements\FooUnknownClass has @implements tag, but can not implement any interface, must extend from it.', + 108, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric has @implements tag, but can not implement any interface, must extend from it.', + 117, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric2 has @implements tag, but can not implement any interface, must extend from it.', + 126, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric3 has @implements tag, but can not implement any interface, must extend from it.', + 136, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric4 has @implements tag, but can not implement any interface, must extend from it.', + 145, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric5 has @implements tag, but can not implement any interface, must extend from it.', + 154, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric6 has @implements tag, but can not implement any interface, must extend from it.', + 163, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric7 has @implements tag, but can not implement any interface, must extend from it.', + 172, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric8 has @implements tag, but can not implement any interface, must extend from it.', + 182, + ], + [ + 'Interface InterfaceAncestorsImplements\FooGenericGeneric8 has @implements tag, but can not implement any interface, must extend from it.', + 182, + ], + ]); + } + public function testRuleExtends(): void + { + $this->analyse([__DIR__ . '/data/interface-ancestors-extends.php'], [ + [ + 'Interface InterfaceAncestorsExtends\FooDoesNotImplementAnything has @extends tag, but does not extend any interface.', + 35, + ], + [ + 'The @extends tag of interface InterfaceAncestorsExtends\FooInvalidImplementsTags describes InterfaceAncestorsExtends\FooGeneric2 but the interface extends: InterfaceAncestorsExtends\FooGeneric', + 44, + ], + [ + 'The @extends tag of interface InterfaceAncestorsExtends\FooWrongClassImplemented describes InterfaceAncestorsExtends\FooGeneric2 but the interface extends: InterfaceAncestorsExtends\FooGeneric, InterfaceAncestorsExtends\FooGeneric3', + 52, + ], + [ + 'Interface InterfaceAncestorsExtends\FooWrongClassImplemented extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', + 52, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Interface InterfaceAncestorsExtends\FooWrongClassImplemented extends generic interface InterfaceAncestorsExtends\FooGeneric3 but does not specify its types: T, W', + 52, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Interface InterfaceAncestorsExtends\FooWrongTypeInImplementsTag @extends tag contains incompatible type class-string.', + 60, + ], + [ + 'Interface InterfaceAncestorsExtends\FooWrongTypeInImplementsTag extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', + 60, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends does not specify all template types of interface InterfaceAncestorsExtends\FooGeneric: T, U', + 76, + ], + [ + 'Generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends specifies 3 template types, but interface InterfaceAncestorsExtends\FooGeneric supports only 2: T, U', + 84, + ], + [ + 'Type Throwable in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 92, + ], + [ + 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 100, + ], + [ + 'PHPDoc tag @extends has invalid type InterfaceAncestorsExtends\Zazzuuuu.', + 108, + ], + [ + 'Type mixed in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 117, + ], + [ + 'Type Throwable in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 126, + ], + [ + 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 172, + ], + [ + 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric in PHPDoc tag @extends is not subtype of template type U of Exception of interface InterfaceAncestorsExtends\FooGeneric.', + 182, + ], + [ + 'Type stdClass in generic type InterfaceAncestorsExtends\FooGeneric2 in PHPDoc tag @extends is not subtype of template type V of Exception of interface InterfaceAncestorsExtends\FooGeneric2.', + 182, + ], + [ + 'Interface InterfaceAncestorsExtends\ExtendsGenericInterface extends generic interface InterfaceAncestorsExtends\FooGeneric but does not specify its types: T, U', + 197, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Template type T is declared as covariant, but occurs in invariant position in extended type InterfaceAncestorsExtends\FooGeneric9 of interface InterfaceAncestorsExtends\FooGeneric10.', + 215, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index 231e2eb0c1..142060dd16 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - - return new InterfaceTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) - ); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/interface-template.php'; - - $this->analyse([__DIR__ . '/data/interface-template.php'], [ - [ - 'PHPDoc tag @template for interface InterfaceTemplateType\Foo cannot have existing class stdClass as its name.', - 8, - ], - [ - 'PHPDoc tag @template T for interface InterfaceTemplateType\Bar has invalid bound type InterfaceTemplateType\Zazzzu.', - 16, - ], - [ - 'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type float is not supported.', - 24, - ], - [ - 'PHPDoc tag @template for interface InterfaceTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', - 33, - ], - [ - 'PHPDoc tag @template for interface InterfaceTemplateType\Ipsum cannot have existing type alias LocalAlias as its name.', - 45, - ], - [ - 'PHPDoc tag @template for interface InterfaceTemplateType\Ipsum cannot have existing type alias ImportedAlias as its name.', - 45, - ], - ]); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); + + return new InterfaceTemplateTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + ); + } + + public function testRule(): void + { + require_once __DIR__ . '/data/interface-template.php'; + + $this->analyse([__DIR__ . '/data/interface-template.php'], [ + [ + 'PHPDoc tag @template for interface InterfaceTemplateType\Foo cannot have existing class stdClass as its name.', + 8, + ], + [ + 'PHPDoc tag @template T for interface InterfaceTemplateType\Bar has invalid bound type InterfaceTemplateType\Zazzzu.', + 16, + ], + [ + 'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type float is not supported.', + 24, + ], + [ + 'PHPDoc tag @template for interface InterfaceTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', + 33, + ], + [ + 'PHPDoc tag @template for interface InterfaceTemplateType\Ipsum cannot have existing type alias LocalAlias as its name.', + 45, + ], + [ + 'PHPDoc tag @template for interface InterfaceTemplateType\Ipsum cannot have existing type alias ImportedAlias as its name.', + 45, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php index 3facb614dd..b7c431b6a4 100644 --- a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php @@ -1,4 +1,6 @@ -getByType(VarianceCheck::class) + ); + } - protected function getRule(): Rule - { - return new MethodSignatureVarianceRule( - self::getContainer()->getByType(VarianceCheck::class) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/method-signature-variance.php'], [ - [ - 'Template type T is declared as covariant, but occurs in contravariant position in parameter a of method MethodSignatureVariance\C::a().', - 25, - ], - [ - 'Template type T is declared as covariant, but occurs in invariant position in parameter b of method MethodSignatureVariance\C::a().', - 25, - ], - [ - 'Template type T is declared as covariant, but occurs in contravariant position in parameter c of method MethodSignatureVariance\C::a().', - 25, - ], - [ - 'Template type W is declared as covariant, but occurs in contravariant position in parameter d of method MethodSignatureVariance\C::a().', - 25, - ], - [ - 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type U in in method MethodSignatureVariance\C::b().', - 35, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/method-signature-variance.php'], [ + [ + 'Template type T is declared as covariant, but occurs in contravariant position in parameter a of method MethodSignatureVariance\C::a().', + 25, + ], + [ + 'Template type T is declared as covariant, but occurs in invariant position in parameter b of method MethodSignatureVariance\C::a().', + 25, + ], + [ + 'Template type T is declared as covariant, but occurs in contravariant position in parameter c of method MethodSignatureVariance\C::a().', + 25, + ], + [ + 'Template type W is declared as covariant, but occurs in contravariant position in parameter d of method MethodSignatureVariance\C::a().', + 25, + ], + [ + 'Variance annotation is only allowed for type parameters of classes and interfaces, but occurs in template type U in in method MethodSignatureVariance\C::b().', + 35, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php index d8f2c90422..28304f7083 100644 --- a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - - return new MethodTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) - ); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/method-template.php'; - - $this->analyse([__DIR__ . '/data/method-template.php'], [ - [ - 'PHPDoc tag @template for method MethodTemplateType\Foo::doFoo() cannot have existing class stdClass as its name.', - 11, - ], - [ - 'PHPDoc tag @template T for method MethodTemplateType\Foo::doBar() has invalid bound type MethodTemplateType\Zazzzu.', - 19, - ], - [ - 'PHPDoc tag @template T for method MethodTemplateType\Bar::doFoo() shadows @template T of Exception for class MethodTemplateType\Bar.', - 37, - ], - [ - 'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type float is not supported.', - 50, - ], - [ - 'PHPDoc tag @template for method MethodTemplateType\Lorem::doFoo() cannot have existing type alias TypeAlias as its name.', - 66, - ], - [ - 'PHPDoc tag @template for method MethodTemplateType\Ipsum::doFoo() cannot have existing type alias LocalAlias as its name.', - 85, - ], - [ - 'PHPDoc tag @template for method MethodTemplateType\Ipsum::doFoo() cannot have existing type alias ImportedAlias as its name.', - 85, - ], - ]); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); + + return new MethodTemplateTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + ); + } + + public function testRule(): void + { + require_once __DIR__ . '/data/method-template.php'; + + $this->analyse([__DIR__ . '/data/method-template.php'], [ + [ + 'PHPDoc tag @template for method MethodTemplateType\Foo::doFoo() cannot have existing class stdClass as its name.', + 11, + ], + [ + 'PHPDoc tag @template T for method MethodTemplateType\Foo::doBar() has invalid bound type MethodTemplateType\Zazzzu.', + 19, + ], + [ + 'PHPDoc tag @template T for method MethodTemplateType\Bar::doFoo() shadows @template T of Exception for class MethodTemplateType\Bar.', + 37, + ], + [ + 'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type float is not supported.', + 50, + ], + [ + 'PHPDoc tag @template for method MethodTemplateType\Lorem::doFoo() cannot have existing type alias TypeAlias as its name.', + 66, + ], + [ + 'PHPDoc tag @template for method MethodTemplateType\Ipsum::doFoo() cannot have existing type alias LocalAlias as its name.', + 85, + ], + [ + 'PHPDoc tag @template for method MethodTemplateType\Ipsum::doFoo() cannot have existing type alias ImportedAlias as its name.', + 85, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php index 5ae1a3b1aa..5054f2e709 100644 --- a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); - - return new TraitTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) - ); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/trait-template.php'; - - $this->analyse([__DIR__ . '/data/trait-template.php'], [ - [ - 'PHPDoc tag @template for trait TraitTemplateType\Foo cannot have existing class stdClass as its name.', - 8, - ], - [ - 'PHPDoc tag @template T for trait TraitTemplateType\Bar has invalid bound type TraitTemplateType\Zazzzu.', - 16, - ], - [ - 'PHPDoc tag @template T for trait TraitTemplateType\Baz with bound type float is not supported.', - 24, - ], - [ - 'PHPDoc tag @template for trait TraitTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', - 33, - ], - [ - 'PHPDoc tag @template for trait TraitTemplateType\Ipsum cannot have existing type alias LocalAlias as its name.', - 45, - ], - [ - 'PHPDoc tag @template for trait TraitTemplateType\Ipsum cannot have existing type alias ImportedAlias as its name.', - 45, - ], - ]); - } - + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $broker); + + return new TraitTemplateTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + ); + } + + public function testRule(): void + { + require_once __DIR__ . '/data/trait-template.php'; + + $this->analyse([__DIR__ . '/data/trait-template.php'], [ + [ + 'PHPDoc tag @template for trait TraitTemplateType\Foo cannot have existing class stdClass as its name.', + 8, + ], + [ + 'PHPDoc tag @template T for trait TraitTemplateType\Bar has invalid bound type TraitTemplateType\Zazzzu.', + 16, + ], + [ + 'PHPDoc tag @template T for trait TraitTemplateType\Baz with bound type float is not supported.', + 24, + ], + [ + 'PHPDoc tag @template for trait TraitTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', + 33, + ], + [ + 'PHPDoc tag @template for trait TraitTemplateType\Ipsum cannot have existing type alias LocalAlias as its name.', + 45, + ], + [ + 'PHPDoc tag @template for trait TraitTemplateType\Ipsum cannot have existing type alias ImportedAlias as its name.', + 45, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 340d663e34..ed908c77ee 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class), + new GenericAncestorsCheck( + $this->createReflectionProvider(), + new GenericObjectTypeCheck(), + new VarianceCheck(), + true + ) + ); + } - protected function getRule(): Rule - { - return new UsedTraitsRule( - self::getContainer()->getByType(FileTypeMapper::class), - new GenericAncestorsCheck( - $this->createReflectionProvider(), - new GenericObjectTypeCheck(), - new VarianceCheck(), - true - ) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/used-traits.php'], [ - [ - 'PHPDoc tag @use contains generic type UsedTraits\NongenericTrait but trait UsedTraits\NongenericTrait is not generic.', - 20, - ], - [ - 'Type int in generic type UsedTraits\GenericTrait in PHPDoc tag @use is not subtype of template type T of object of trait UsedTraits\GenericTrait.', - 31, - ], - [ - 'Class UsedTraits\Baz uses generic trait UsedTraits\GenericTrait but does not specify its types: T', - 38, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Generic type UsedTraits\GenericTrait in PHPDoc tag @use specifies 2 template types, but trait UsedTraits\GenericTrait supports only 1: T', - 46, - ], - [ - 'The @use tag of trait UsedTraits\NestedTrait describes UsedTraits\NongenericTrait but the trait uses UsedTraits\GenericTrait.', - 54, - ], - [ - 'Trait UsedTraits\NestedTrait uses generic trait UsedTraits\GenericTrait but does not specify its types: T', - 54, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/used-traits.php'], [ + [ + 'PHPDoc tag @use contains generic type UsedTraits\NongenericTrait but trait UsedTraits\NongenericTrait is not generic.', + 20, + ], + [ + 'Type int in generic type UsedTraits\GenericTrait in PHPDoc tag @use is not subtype of template type T of object of trait UsedTraits\GenericTrait.', + 31, + ], + [ + 'Class UsedTraits\Baz uses generic trait UsedTraits\GenericTrait but does not specify its types: T', + 38, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Generic type UsedTraits\GenericTrait in PHPDoc tag @use specifies 2 template types, but trait UsedTraits\GenericTrait supports only 1: T', + 46, + ], + [ + 'The @use tag of trait UsedTraits\NestedTrait describes UsedTraits\NongenericTrait but the trait uses UsedTraits\GenericTrait.', + 54, + ], + [ + 'Trait UsedTraits\NestedTrait uses generic trait UsedTraits\GenericTrait but does not specify its types: T', + 54, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Generics/data/bug-3769.php b/tests/PHPStan/Rules/Generics/data/bug-3769.php index 1101ad3547..0a4b19800a 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-3769.php +++ b/tests/PHPStan/Rules/Generics/data/bug-3769.php @@ -9,11 +9,12 @@ * @param array $in * @return array */ -function stringValues(array $in): array { - $a = assertType('array', $in); - return array_map(function (int $int): string { - return (string) $int; - }, $in); +function stringValues(array $in): array +{ + $a = assertType('array', $in); + return array_map(function (int $int): string { + return (string) $int; + }, $in); } /** @@ -22,21 +23,22 @@ function stringValues(array $in): array { * @param array $baz */ function foo( - array $foo, - array $bar, - array $baz + array $foo, + array $bar, + array $baz ): void { - $a = assertType('array', stringValues($foo)); - $a = assertType('array', stringValues($bar)); - $a = assertType('array', stringValues($baz)); + $a = assertType('array', stringValues($foo)); + $a = assertType('array', stringValues($bar)); + $a = assertType('array', stringValues($baz)); }; /** * @template T of \stdClass|\Exception * @param T $foo */ -function fooUnion($foo): void { - $a = assertType('T of Exception|stdClass (function Bug3769\fooUnion(), argument)', $foo); +function fooUnion($foo): void +{ + $a = assertType('T of Exception|stdClass (function Bug3769\fooUnion(), argument)', $foo); } /** @@ -46,7 +48,7 @@ function fooUnion($foo): void { */ function mixedBound($a) { - return $a; + return $a; } /** @@ -56,7 +58,7 @@ function mixedBound($a) */ function intBound(int $a) { - return $a; + return $a; } /** @@ -66,45 +68,44 @@ function intBound(int $a) */ function stringBound(string $a) { - return $a; + return $a; } function (): void { - $a = assertType('int', mixedBound(1)); - $a = assertType('string', mixedBound('str')); - $a = assertType('1', intBound(1)); - $a = assertType('\'str\'', stringBound('str')); + $a = assertType('int', mixedBound(1)); + $a = assertType('string', mixedBound('str')); + $a = assertType('1', intBound(1)); + $a = assertType('\'str\'', stringBound('str')); }; /** @template T of string */ class Foo { - - /** @var T */ - private $value; - - /** - * @param T $value - */ - public function __construct($value) - { - $this->value = $value; - } - - /** - * @return T - */ - public function getValue() - { - return $this->value; - } - + /** @var T */ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return T + */ + public function getValue() + { + return $this->value; + } } /** @param Foo<'bar'> $foo */ -function testTofString(Foo $foo): void { - $a = assertType('\'bar\'', $foo->getValue()); +function testTofString(Foo $foo): void +{ + $a = assertType('\'bar\'', $foo->getValue()); - $baz = new Foo('baz'); - $a = assertType('\'baz\'', $baz->getValue()); + $baz = new Foo('baz'); + $a = assertType('\'baz\'', $baz->getValue()); }; diff --git a/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors-reversed.php b/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors-reversed.php index acd2fbd6da..979182a83b 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors-reversed.php +++ b/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors-reversed.php @@ -8,12 +8,12 @@ */ interface QueryHandlerInterface { - /** - * @param TQuery $query - * - * @return TResult - */ - public function handle(QueryInterface $query); + /** + * @param TQuery $query + * + * @return TResult + */ + public function handle(QueryInterface $query); } /** @@ -42,10 +42,10 @@ final class BarQuery implements QueryInterface */ final class FooQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query): string - { - return 'foo'; - } + public function handle(QueryInterface $query): string + { + return 'foo'; + } } /** @@ -53,8 +53,8 @@ public function handle(QueryInterface $query): string */ final class BarQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query): int - { - return 10; - } + public function handle(QueryInterface $query): int + { + return 10; + } } diff --git a/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors.php b/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors.php index 90a960e388..bb809860fb 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors.php +++ b/tests/PHPStan/Rules/Generics/data/bug-3922-ancestors.php @@ -8,12 +8,12 @@ */ interface QueryHandlerInterface { - /** - * @param TQuery $query - * - * @return TResult - */ - public function handle(QueryInterface $query); + /** + * @param TQuery $query + * + * @return TResult + */ + public function handle(QueryInterface $query); } /** @@ -42,10 +42,10 @@ final class BarQuery implements QueryInterface */ final class FooQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query): string - { - return 'foo'; - } + public function handle(QueryInterface $query): string + { + return 'foo'; + } } /** @@ -53,8 +53,8 @@ public function handle(QueryInterface $query): string */ final class BarQueryHandler implements QueryHandlerInterface { - public function handle(QueryInterface $query): int - { - return 10; - } + public function handle(QueryInterface $query): int + { + return 10; + } } diff --git a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php index 62a1cd9303..677bc70396 100644 --- a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php +++ b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php @@ -8,7 +8,6 @@ */ class FooGeneric { - } /** @@ -17,7 +16,6 @@ class FooGeneric */ class FooGeneric2 { - } /** @@ -25,7 +23,6 @@ class FooGeneric2 */ class FooDoesNotExtendAnything { - } /** @@ -34,7 +31,6 @@ class FooDoesNotExtendAnything */ class FooDuplicateExtendsTags extends FooGeneric { - } /** @@ -42,7 +38,6 @@ class FooDuplicateExtendsTags extends FooGeneric */ class FooWrongClassExtended extends FooGeneric { - } /** @@ -50,7 +45,6 @@ class FooWrongClassExtended extends FooGeneric */ class FooWrongTypeInExtendsTag extends FooGeneric { - } /** @@ -58,7 +52,6 @@ class FooWrongTypeInExtendsTag extends FooGeneric */ class FooCorrect extends FooGeneric { - } /** @@ -66,7 +59,6 @@ class FooCorrect extends FooGeneric */ class FooNotEnough extends FooGeneric { - } /** @@ -74,7 +66,6 @@ class FooNotEnough extends FooGeneric */ class FooExtraTypes extends FooGeneric { - } /** @@ -82,7 +73,6 @@ class FooExtraTypes extends FooGeneric */ class FooNotSubtype extends FooGeneric { - } /** @@ -90,7 +80,6 @@ class FooNotSubtype extends FooGeneric */ class FooAlsoNotSubtype extends FooGeneric { - } /** @@ -98,7 +87,6 @@ class FooAlsoNotSubtype extends FooGeneric */ class FooUnknownClass extends FooGeneric { - } /** @@ -107,7 +95,6 @@ class FooUnknownClass extends FooGeneric */ class FooGenericGeneric extends FooGeneric { - } /** @@ -116,7 +103,6 @@ class FooGenericGeneric extends FooGeneric */ class FooGenericGeneric2 extends FooGeneric { - } @@ -126,7 +112,6 @@ class FooGenericGeneric2 extends FooGeneric */ class FooGenericGeneric3 extends FooGeneric { - } /** @@ -135,7 +120,6 @@ class FooGenericGeneric3 extends FooGeneric */ class FooGenericGeneric4 extends FooGeneric { - } /** @@ -144,7 +128,6 @@ class FooGenericGeneric4 extends FooGeneric */ class FooGenericGeneric5 extends FooGeneric { - } /** @@ -153,7 +136,6 @@ class FooGenericGeneric5 extends FooGeneric */ class FooGenericGeneric6 extends FooGeneric { - } /** @@ -162,18 +144,15 @@ class FooGenericGeneric6 extends FooGeneric */ class FooGenericGeneric7 extends FooGeneric { - } class FooExtendsNonGenericClass extends FooDoesNotExtendAnything { - } class FooExtendsGenericClass extends FooGeneric { - } /** @@ -182,7 +161,6 @@ class FooExtendsGenericClass extends FooGeneric */ class FooGeneric8 { - } /** @@ -191,5 +169,4 @@ class FooGeneric8 */ class FooGeneric9 extends FooGeneric8 { - } diff --git a/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php b/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php index 716c58c2b7..981e1f0415 100644 --- a/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php +++ b/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php @@ -8,7 +8,6 @@ */ interface FooGeneric { - } /** @@ -17,7 +16,6 @@ interface FooGeneric */ interface FooGeneric2 { - } /** @@ -26,7 +24,6 @@ interface FooGeneric2 */ interface FooGeneric3 { - } /** @@ -34,7 +31,6 @@ interface FooGeneric3 */ class FooDoesNotImplementAnything { - } /** @@ -43,7 +39,6 @@ class FooDoesNotImplementAnything */ class FooInvalidImplementsTags implements FooGeneric { - } /** @@ -51,7 +46,6 @@ class FooInvalidImplementsTags implements FooGeneric */ class FooWrongClassImplemented implements FooGeneric, FooGeneric3 { - } /** @@ -59,7 +53,6 @@ class FooWrongClassImplemented implements FooGeneric, FooGeneric3 */ class FooWrongTypeInImplementsTag implements FooGeneric { - } /** @@ -67,7 +60,6 @@ class FooWrongTypeInImplementsTag implements FooGeneric */ class FooCorrect implements FooGeneric { - } /** @@ -75,7 +67,6 @@ class FooCorrect implements FooGeneric */ class FooNotEnough implements FooGeneric { - } /** @@ -83,7 +74,6 @@ class FooNotEnough implements FooGeneric */ class FooExtraTypes implements FooGeneric { - } /** @@ -91,7 +81,6 @@ class FooExtraTypes implements FooGeneric */ class FooNotSubtype implements FooGeneric { - } /** @@ -99,7 +88,6 @@ class FooNotSubtype implements FooGeneric */ class FooAlsoNotSubtype implements FooGeneric { - } /** @@ -107,7 +95,6 @@ class FooAlsoNotSubtype implements FooGeneric */ class FooUnknownClass implements FooGeneric { - } /** @@ -116,7 +103,6 @@ class FooUnknownClass implements FooGeneric */ class FooGenericGeneric implements FooGeneric { - } /** @@ -125,7 +111,6 @@ class FooGenericGeneric implements FooGeneric */ class FooGenericGeneric2 implements FooGeneric { - } @@ -135,7 +120,6 @@ class FooGenericGeneric2 implements FooGeneric */ class FooGenericGeneric3 implements FooGeneric { - } /** @@ -144,7 +128,6 @@ class FooGenericGeneric3 implements FooGeneric */ class FooGenericGeneric4 implements FooGeneric { - } /** @@ -153,7 +136,6 @@ class FooGenericGeneric4 implements FooGeneric */ class FooGenericGeneric5 implements FooGeneric { - } /** @@ -162,7 +144,6 @@ class FooGenericGeneric5 implements FooGeneric */ class FooGenericGeneric6 implements FooGeneric { - } /** @@ -171,7 +152,6 @@ class FooGenericGeneric6 implements FooGeneric */ class FooGenericGeneric7 implements FooGeneric { - } /** @@ -181,23 +161,19 @@ class FooGenericGeneric7 implements FooGeneric */ class FooGenericGeneric8 implements FooGeneric, FooGeneric2 { - } interface NonGenericInterface { - } class FooImplementsNonGenericInterface implements NonGenericInterface { - } class FooImplementsGenericInterface implements FooGeneric { - } /** @@ -206,7 +182,6 @@ class FooImplementsGenericInterface implements FooGeneric */ interface FooGeneric9 { - } /** @@ -215,5 +190,4 @@ interface FooGeneric9 */ class FooGeneric10 implements FooGeneric9 { - } diff --git a/tests/PHPStan/Rules/Generics/data/class-template.php b/tests/PHPStan/Rules/Generics/data/class-template.php index 752983b26a..6a4003e67b 100644 --- a/tests/PHPStan/Rules/Generics/data/class-template.php +++ b/tests/PHPStan/Rules/Generics/data/class-template.php @@ -7,7 +7,6 @@ */ class Foo { - } /** @@ -15,7 +14,6 @@ class Foo */ class Bar { - } /** @@ -23,7 +21,6 @@ class Bar */ class Baz { - } /** @@ -31,7 +28,6 @@ class Baz */ class Lorem { - } /** @@ -40,7 +36,6 @@ class Lorem */ class Ipsum { - } /** @@ -52,30 +47,19 @@ class Ipsum */ class Dolor { - } -new /** @template stdClass */ class -{ - +new /** @template stdClass */ class() { }; -new /** @template T of Zazzzu */ class -{ - +new /** @template T of Zazzzu */ class() { }; -new /** @template T of float */ class -{ - +new /** @template T of float */ class() { }; -new /** @template T of baz */ class -{ - +new /** @template T of baz */ class() { }; -new /** @template TypeAlias */ class -{ - +new /** @template TypeAlias */ class() { }; diff --git a/tests/PHPStan/Rules/Generics/data/function-signature-variance.php b/tests/PHPStan/Rules/Generics/data/function-signature-variance.php index 2414849eb3..5d17c94609 100644 --- a/tests/PHPStan/Rules/Generics/data/function-signature-variance.php +++ b/tests/PHPStan/Rules/Generics/data/function-signature-variance.php @@ -3,11 +3,13 @@ namespace FunctionSignatureVariance; /** @template-covariant T */ -interface Out { +interface Out +{ } /** @template T */ -interface Invariant { +interface Invariant +{ } /** @@ -17,6 +19,7 @@ interface Invariant { * @param T $c * @return T */ -function f($a, $b, $c) { - return $c; +function f($a, $b, $c) +{ + return $c; } diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 994865f88f..bbbaf07b84 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -7,7 +7,6 @@ */ function foo() { - } /** @@ -15,7 +14,6 @@ function foo() */ function bar() { - } /** @@ -23,7 +21,6 @@ function bar() */ function baz() { - } /** @@ -31,5 +28,4 @@ function baz() */ function lorem() { - } diff --git a/tests/PHPStan/Rules/Generics/data/interface-ancestors-extends.php b/tests/PHPStan/Rules/Generics/data/interface-ancestors-extends.php index 510c9451b8..405cc77a3a 100644 --- a/tests/PHPStan/Rules/Generics/data/interface-ancestors-extends.php +++ b/tests/PHPStan/Rules/Generics/data/interface-ancestors-extends.php @@ -8,7 +8,6 @@ */ interface FooGeneric { - } /** @@ -17,7 +16,6 @@ interface FooGeneric */ interface FooGeneric2 { - } /** @@ -26,7 +24,6 @@ interface FooGeneric2 */ interface FooGeneric3 { - } /** @@ -34,7 +31,6 @@ interface FooGeneric3 */ interface FooDoesNotImplementAnything { - } /** @@ -43,7 +39,6 @@ interface FooDoesNotImplementAnything */ interface FooInvalidImplementsTags extends FooGeneric { - } /** @@ -51,7 +46,6 @@ interface FooInvalidImplementsTags extends FooGeneric */ interface FooWrongClassImplemented extends FooGeneric, FooGeneric3 { - } /** @@ -59,7 +53,6 @@ interface FooWrongClassImplemented extends FooGeneric, FooGeneric3 */ interface FooWrongTypeInImplementsTag extends FooGeneric { - } /** @@ -67,7 +60,6 @@ interface FooWrongTypeInImplementsTag extends FooGeneric */ interface FooCorrect extends FooGeneric { - } /** @@ -75,7 +67,6 @@ interface FooCorrect extends FooGeneric */ interface FooNotEnough extends FooGeneric { - } /** @@ -83,7 +74,6 @@ interface FooNotEnough extends FooGeneric */ interface FooExtraTypes extends FooGeneric { - } /** @@ -91,7 +81,6 @@ interface FooExtraTypes extends FooGeneric */ interface FooNotSubtype extends FooGeneric { - } /** @@ -99,7 +88,6 @@ interface FooNotSubtype extends FooGeneric */ interface FooAlsoNotSubtype extends FooGeneric { - } /** @@ -107,7 +95,6 @@ interface FooAlsoNotSubtype extends FooGeneric */ interface FooUnknowninterface extends FooGeneric { - } /** @@ -116,7 +103,6 @@ interface FooUnknowninterface extends FooGeneric */ interface FooGenericGeneric extends FooGeneric { - } /** @@ -125,7 +111,6 @@ interface FooGenericGeneric extends FooGeneric */ interface FooGenericGeneric2 extends FooGeneric { - } @@ -135,7 +120,6 @@ interface FooGenericGeneric2 extends FooGeneric */ interface FooGenericGeneric3 extends FooGeneric { - } /** @@ -144,7 +128,6 @@ interface FooGenericGeneric3 extends FooGeneric */ interface FooGenericGeneric4 extends FooGeneric { - } /** @@ -153,7 +136,6 @@ interface FooGenericGeneric4 extends FooGeneric */ interface FooGenericGeneric5 extends FooGeneric { - } /** @@ -162,7 +144,6 @@ interface FooGenericGeneric5 extends FooGeneric */ interface FooGenericGeneric6 extends FooGeneric { - } /** @@ -171,7 +152,6 @@ interface FooGenericGeneric6 extends FooGeneric */ interface FooGenericGeneric7 extends FooGeneric { - } /** @@ -181,22 +161,18 @@ interface FooGenericGeneric7 extends FooGeneric */ interface FooGenericGeneric8 extends FooGeneric, FooGeneric2 { - } interface NonGenericInterface { - } interface ExtendsNonGenericInterface extends NonGenericInterface { - } interface ExtendsGenericInterface extends FooGeneric { - } /** @@ -205,7 +181,6 @@ interface ExtendsGenericInterface extends FooGeneric */ interface FooGeneric9 { - } /** @@ -214,5 +189,4 @@ interface FooGeneric9 */ interface FooGeneric10 extends FooGeneric9 { - } diff --git a/tests/PHPStan/Rules/Generics/data/interface-ancestors-implements.php b/tests/PHPStan/Rules/Generics/data/interface-ancestors-implements.php index 262e07e56a..824a323a9c 100644 --- a/tests/PHPStan/Rules/Generics/data/interface-ancestors-implements.php +++ b/tests/PHPStan/Rules/Generics/data/interface-ancestors-implements.php @@ -8,7 +8,6 @@ */ interface FooGeneric { - } /** @@ -17,7 +16,6 @@ interface FooGeneric */ interface FooGeneric2 { - } /** @@ -26,7 +24,6 @@ interface FooGeneric2 */ interface FooGeneric3 { - } /** @@ -34,7 +31,6 @@ interface FooGeneric3 */ interface FooDoesNotImplementAnything { - } /** @@ -43,7 +39,6 @@ interface FooDoesNotImplementAnything */ interface FooInvalidImplementsTags { - } /** @@ -51,7 +46,6 @@ interface FooInvalidImplementsTags */ interface FooWrongClassImplemented { - } /** @@ -59,7 +53,6 @@ interface FooWrongClassImplemented */ interface FooWrongTypeInImplementsTag { - } /** @@ -67,7 +60,6 @@ interface FooWrongTypeInImplementsTag */ interface FooCorrect { - } /** @@ -75,7 +67,6 @@ interface FooCorrect */ interface FooNotEnough { - } /** @@ -83,7 +74,6 @@ interface FooNotEnough */ interface FooExtraTypes { - } /** @@ -91,7 +81,6 @@ interface FooExtraTypes */ interface FooNotSubtype { - } /** @@ -99,7 +88,6 @@ interface FooNotSubtype */ interface FooAlsoNotSubtype { - } /** @@ -107,7 +95,6 @@ interface FooAlsoNotSubtype */ interface FooUnknownClass { - } /** @@ -116,7 +103,6 @@ interface FooUnknownClass */ interface FooGenericGeneric { - } /** @@ -125,7 +111,6 @@ interface FooGenericGeneric */ interface FooGenericGeneric2 { - } @@ -135,7 +120,6 @@ interface FooGenericGeneric2 */ interface FooGenericGeneric3 { - } /** @@ -144,7 +128,6 @@ interface FooGenericGeneric3 */ interface FooGenericGeneric4 { - } /** @@ -153,7 +136,6 @@ interface FooGenericGeneric4 */ interface FooGenericGeneric5 { - } /** @@ -162,7 +144,6 @@ interface FooGenericGeneric5 */ interface FooGenericGeneric6 { - } /** @@ -171,7 +152,6 @@ interface FooGenericGeneric6 */ interface FooGenericGeneric7 { - } /** @@ -181,5 +161,4 @@ interface FooGenericGeneric7 */ interface FooGenericGeneric8 { - } diff --git a/tests/PHPStan/Rules/Generics/data/interface-template.php b/tests/PHPStan/Rules/Generics/data/interface-template.php index ed7f3ef667..04ef50e941 100644 --- a/tests/PHPStan/Rules/Generics/data/interface-template.php +++ b/tests/PHPStan/Rules/Generics/data/interface-template.php @@ -7,7 +7,6 @@ */ interface Foo { - } /** @@ -15,7 +14,6 @@ interface Foo */ interface Bar { - } /** @@ -23,7 +21,6 @@ interface Bar */ interface Baz { - } /** @@ -32,7 +29,6 @@ interface Baz */ interface Lorem { - } /** @@ -44,17 +40,14 @@ interface Lorem */ interface Ipsum { - } /** @template T */ interface NormalT { - } /** @template T of NormalT<\stdClass>|\stdClass */ interface UnionBound { - } diff --git a/tests/PHPStan/Rules/Generics/data/method-signature-variance.php b/tests/PHPStan/Rules/Generics/data/method-signature-variance.php index c783c69191..c614056500 100644 --- a/tests/PHPStan/Rules/Generics/data/method-signature-variance.php +++ b/tests/PHPStan/Rules/Generics/data/method-signature-variance.php @@ -3,36 +3,41 @@ namespace MethodSignatureVariance; /** @template-covariant T */ -interface Out { +interface Out +{ } /** @template T */ -interface Invariant { +interface Invariant +{ } /** * @template-covariant T * @template-covariant W of \DateTimeInterface */ -class C { - /** - * @param Out $a - * @param Invariant $b - * @param T $c - * @param W $d - * @return T - */ - function a($a, $b, $c, $d) { - return $c; - } - /** - * @template-covariant U - * @param Out $a - * @param Invariant $b - * @param U $c - * @return U - */ - function b($a, $b, $c) { - return $c; - } +class C +{ + /** + * @param Out $a + * @param Invariant $b + * @param T $c + * @param W $d + * @return T + */ + public function a($a, $b, $c, $d) + { + return $c; + } + /** + * @template-covariant U + * @param Out $a + * @param Invariant $b + * @param U $c + * @return U + */ + public function b($a, $b, $c) + { + return $c; + } } diff --git a/tests/PHPStan/Rules/Generics/data/method-template.php b/tests/PHPStan/Rules/Generics/data/method-template.php index fc6c4c87e2..74a4c7ebf2 100644 --- a/tests/PHPStan/Rules/Generics/data/method-template.php +++ b/tests/PHPStan/Rules/Generics/data/method-template.php @@ -4,23 +4,19 @@ class Foo { - - /** - * @template stdClass - */ - public function doFoo() - { - - } - - /** - * @template T of Zazzzu - */ - public function doBar() - { - - } - + /** + * @template stdClass + */ + public function doFoo() + { + } + + /** + * @template T of Zazzzu + */ + public function doBar() + { + } } /** @@ -29,29 +25,23 @@ public function doBar() */ class Bar { - - /** - * @template T - * @template U - */ - public function doFoo() - { - - } - + /** + * @template T + * @template U + */ + public function doFoo() + { + } } class Baz { - - /** - * @template T of float - */ - public function doFoo() - { - - } - + /** + * @template T of float + */ + public function doFoo() + { + } } /** @@ -59,15 +49,12 @@ public function doFoo() */ class Lorem { - - /** - * @template TypeAlias - */ - public function doFoo() - { - - } - + /** + * @template TypeAlias + */ + public function doFoo() + { + } } /** @@ -76,15 +63,12 @@ public function doFoo() */ class Ipsum { - - /** - * @template LocalAlias - * @template ExportedAlias - * @template ImportedAlias - */ - public function doFoo() - { - - } - + /** + * @template LocalAlias + * @template ExportedAlias + * @template ImportedAlias + */ + public function doFoo() + { + } } diff --git a/tests/PHPStan/Rules/Generics/data/nested-generic-types.php b/tests/PHPStan/Rules/Generics/data/nested-generic-types.php index 2b96f54bd8..b5b0aca3c2 100644 --- a/tests/PHPStan/Rules/Generics/data/nested-generic-types.php +++ b/tests/PHPStan/Rules/Generics/data/nested-generic-types.php @@ -2,19 +2,27 @@ namespace NestedGenericTypesClassCheck; -interface NotGeneric {} +interface NotGeneric +{ +} /** @template T */ -interface SomeInterface {} +interface SomeInterface +{ +} /** @template T of object */ -interface SomeObjectInterface {} +interface SomeObjectInterface +{ +} /** * @template T * @template U */ -interface MultipleGenerics {} +interface MultipleGenerics +{ +} /** * @template T @@ -22,7 +30,6 @@ interface MultipleGenerics {} */ class Foo { - } /** @@ -31,7 +38,6 @@ class Foo */ class Bar { - } /** @@ -40,7 +46,6 @@ class Bar */ class Baz { - } /** @@ -51,5 +56,4 @@ class Baz */ class Lorem { - } diff --git a/tests/PHPStan/Rules/Generics/data/trait-template.php b/tests/PHPStan/Rules/Generics/data/trait-template.php index 8870b4dd98..7ff7a1adee 100644 --- a/tests/PHPStan/Rules/Generics/data/trait-template.php +++ b/tests/PHPStan/Rules/Generics/data/trait-template.php @@ -7,7 +7,6 @@ */ trait Foo { - } /** @@ -15,7 +14,6 @@ trait Foo */ trait Bar { - } /** @@ -23,7 +21,6 @@ trait Bar */ trait Baz { - } /** @@ -32,7 +29,6 @@ trait Baz */ trait Lorem { - } /** @@ -44,5 +40,4 @@ trait Lorem */ trait Ipsum { - } diff --git a/tests/PHPStan/Rules/Generics/data/used-traits.php b/tests/PHPStan/Rules/Generics/data/used-traits.php index 855d38aa02..0d7fb7f5ed 100644 --- a/tests/PHPStan/Rules/Generics/data/used-traits.php +++ b/tests/PHPStan/Rules/Generics/data/used-traits.php @@ -4,60 +4,46 @@ trait NongenericTrait { - } /** @template T of object */ trait GenericTrait { - } class Foo { + /** @use NongenericTrait<\stdClass> */ + use NongenericTrait; - /** @use NongenericTrait<\stdClass> */ - use NongenericTrait; - - /** @use GenericTrait<\stdClass> */ - use GenericTrait; - + /** @use GenericTrait<\stdClass> */ + use GenericTrait; } class Bar { - - /** @use GenericTrait */ - use GenericTrait; - + /** @use GenericTrait */ + use GenericTrait; } class Baz { - - use GenericTrait; - + use GenericTrait; } class Lorem { - - /** @use GenericTrait<\stdClass, \Exception> */ - use GenericTrait; - + /** @use GenericTrait<\stdClass, \Exception> */ + use GenericTrait; } trait NestedTrait { - - /** @use NongenericTrait */ - use GenericTrait; - + /** @use NongenericTrait */ + use GenericTrait; } class Ipsum { - - use NestedTrait; - + use NestedTrait; } diff --git a/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php b/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php index 107d77fb07..de06669d15 100644 --- a/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/continue-break.php'], [ - [ - 'Keyword break used outside of a loop or a switch statement.', - 67, - ], - [ - 'Keyword break used outside of a loop or a switch statement.', - 69, - ], - [ - 'Keyword break used outside of a loop or a switch statement.', - 77, - ], - [ - 'Keyword continue used outside of a loop or a switch statement.', - 79, - ], - [ - 'Keyword break used outside of a loop or a switch statement.', - 87, - ], - [ - 'Keyword break used outside of a loop or a switch statement.', - 95, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/continue-break.php'], [ + [ + 'Keyword break used outside of a loop or a switch statement.', + 67, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 69, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 77, + ], + [ + 'Keyword continue used outside of a loop or a switch statement.', + 79, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 87, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 95, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Keywords/data/continue-break.php b/tests/PHPStan/Rules/Keywords/data/continue-break.php index c702597a23..44b389c07b 100644 --- a/tests/PHPStan/Rules/Keywords/data/continue-break.php +++ b/tests/PHPStan/Rules/Keywords/data/continue-break.php @@ -4,93 +4,91 @@ class Foo { + public function doFoo($foo): void + { + switch ($foo) { + case 1: + break; + default: + break; + } - public function doFoo($foo): void - { - switch ($foo) { - case 1: - break; - default: - break; - } + foreach ([1, 2, 3] as $val) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } - foreach ([1, 2, 3] as $val) { - if (rand(0, 1)) { - break; - } else { - continue; - } - } + for ($i = 0; $i < 5; $i++) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } - for ($i = 0; $i < 5; $i++) { - if (rand(0, 1)) { - break; - } else { - continue; - } - } + while (true) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } - while (true) { - if (rand(0, 1)) { - break; - } else { - continue; - } - } + do { + if (rand(0, 1)) { + break; + } else { + continue; + } + } while (true); + } - do { - if (rand(0, 1)) { - break; - } else { - continue; - } - } while (true); - } + public function doLorem($foo) + { + foreach ([1, 2, 3] as $val) { + switch ($foo) { + case 1: + break 2; + default: + break 2; + } + } + } - public function doLorem($foo) - { - foreach ([1, 2, 3] as $val) { - switch ($foo) { - case 1: - break 2; - default: - break 2; - } - } - } + public function doBar($foo) + { + foreach ([1, 2, 3] as $val) { + switch ($foo) { + case 1: + break 3; + default: + break 3; + } + } + } - public function doBar($foo) - { - foreach ([1, 2, 3] as $val) { - switch ($foo) { - case 1: - break 3; - default: - break 3; - } - } - } - - public function doBaz() - { - if (rand(0, 1)) { - break; - } else { - continue; - } - } - - public function doIpsum($foo) - { - foreach ([1, 2, 3] as $val) { - function (): void { - break; - }; - } - } + public function doBaz() + { + if (rand(0, 1)) { + break; + } else { + continue; + } + } + public function doIpsum($foo) + { + foreach ([1, 2, 3] as $val) { + function (): void { + break; + }; + } + } } if (rand(0, 1)) { - break; + break; } diff --git a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php index 0717ed9914..aa655e1342 100644 --- a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php +++ b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - $this->analyse([__DIR__ . '/data/abstract-method.php'], [ - [ - 'Non-abstract class AbstractMethod\Bar contains abstract method doBar().', - 15, - ], - [ - 'Non-abstract class AbstractMethod\Baz contains abstract method doBar().', - 22, - ], - ]); - } - - public function testTraitProblem(): void - { - $this->analyse([__DIR__ . '/data/trait-method-problem.php'], []); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/abstract-method.php'], [ + [ + 'Non-abstract class AbstractMethod\Bar contains abstract method doBar().', + 15, + ], + [ + 'Non-abstract class AbstractMethod\Baz contains abstract method doBar().', + 22, + ], + ]); + } - public function testBug3406(): void - { - $this->analyse([__DIR__ . '/data/bug-3406.php'], []); - } + public function testTraitProblem(): void + { + $this->analyse([__DIR__ . '/data/trait-method-problem.php'], []); + } - public function testBug3406ReflectionCheck(): void - { - $this->createBroker(); - $reflectionProvider = $this->createReflectionProvider(); - $reflection = $reflectionProvider->getClass(ClassFoo::class); - $this->assertSame(AbstractFoo::class, $reflection->getNativeMethod('myFoo')->getDeclaringClass()->getName()); - $this->assertSame(ClassFoo::class, $reflection->getNativeMethod('myBar')->getDeclaringClass()->getName()); - } + public function testBug3406(): void + { + $this->analyse([__DIR__ . '/data/bug-3406.php'], []); + } - public function testbug3406AnotherCase(): void - { - $this->analyse([__DIR__ . '/data/bug-3406_2.php'], []); - } + public function testBug3406ReflectionCheck(): void + { + $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); + $reflection = $reflectionProvider->getClass(ClassFoo::class); + $this->assertSame(AbstractFoo::class, $reflection->getNativeMethod('myFoo')->getDeclaringClass()->getName()); + $this->assertSame(ClassFoo::class, $reflection->getNativeMethod('myBar')->getDeclaringClass()->getName()); + } - public function testBug4214(): void - { - $this->analyse([__DIR__ . '/data/bug-4214.php'], []); - } + public function testbug3406AnotherCase(): void + { + $this->analyse([__DIR__ . '/data/bug-3406_2.php'], []); + } + public function testBug4214(): void + { + $this->analyse([__DIR__ . '/data/bug-4214.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 717fe521fb..010e0ed3ef 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); - return new CallMethodsRule( - $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), true, true, true, true), - $ruleLevelHelper, - true, - true - ); - } - - public function testCallMethods(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([ __DIR__ . '/data/call-methods.php'], [ - [ - 'Call to an undefined method Test\Foo::protectedMethodFromChild().', - 10, - ], - [ - 'Call to an undefined method Test\Bar::loremipsum().', - 40, - ], - [ - 'Call to private method foo() of class Test\Foo.', - 41, - ], - [ - 'Method Test\Foo::foo() invoked with 1 parameter, 0 required.', - 41, - ], - [ - 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', - 46, - ], - [ - 'Cannot call method method() on string.', - 49, - ], - [ - 'Call to method doFoo() on an unknown class Test\UnknownClass.', - 63, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 66, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 68, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 70, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 72, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 75, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 76, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 77, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 78, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 79, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 81, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 83, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 84, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 85, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 86, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 90, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 91, - ], - [ - 'Call to an undefined method ArrayObject::doFoo().', - 108, - ], - [ - 'Method PDO::query() invoked with 0 parameters, 1-4 required.', - 113, - ], - [ - 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', - 167, - ], - [ - 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', - 168, - ], - [ - 'Cannot call method ipsum() on Test\Foo|null.', - 183, - ], - [ - 'Cannot call method ipsum() on Test\Bar|null.', - 184, - ], - [ - 'Cannot call method ipsum() on Test\Foo|null.', - 201, - ], - [ - 'Cannot call method ipsum() on Test\Bar|null.', - 202, - ], - [ - 'Method DateTimeZone::getTransitions() invoked with 3 parameters, 0-2 required.', - 214, - ], - [ - 'Result of method Test\ReturningSomethingFromConstructor::__construct() (void) is used.', - 234, - ], - [ - 'Cannot call method foo() on int|string.', - 254, - ], - [ - 'Method Test\FirstInterface::firstMethod() invoked with 1 parameter, 0 required.', - 281, - ], - [ - 'Method Test\SecondInterface::secondMethod() invoked with 1 parameter, 0 required.', - 282, - ], - [ - 'Cannot call method foo() on null.', - 299, - ], - [ - 'Call to method test() on an unknown class Test\FirstUnknownClass.', - 312, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Call to method test() on an unknown class Test\SecondUnknownClass.', - 312, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Cannot call method ipsum() on Test\Foo|null.', - 325, - ], - [ - 'Call to an undefined method Test\WithFooAndBarMethod|Test\WithFooMethod::bar().', - 355, - ], - [ - 'Call to an undefined method Test\SomeInterface&Test\WithFooMethod::bar().', - 372, - ], - [ - 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', - 373, - ], - [ - 'Parameter #1 $foo of method Test\ObjectTypehint::doBar() expects Test\Foo, object given.', - 385, - ], - [ - 'Cannot call method test() on array.', - 399, - ], - [ - 'Method Test\Foo::ipsum() invoked with 1 parameter, 0 required.', - 409, - ], - [ - 'Parameter #1 $test of method Test\NullableInPhpDoc::doFoo() expects string, null given.', - 427, - ], - [ - 'Parameter #1 $globalTitle of method Test\ThreeTypesCall::threeTypes() expects string, float given.', - 446, - ], - [ - 'Cannot call method find() on Test\NullCoalesce|null.', - 516, - ], - [ - 'Cannot call method find() on Test\NullCoalesce|null.', - 518, - ], - [ - 'Cannot call method find() on Test\NullCoalesce|null.', - 522, - ], - [ - 'Cannot call method find() on Test\NullCoalesce|null.', - 524, - ], - [ - 'Cannot call method find() on Test\NullCoalesce|null.', - 524, - ], - [ - 'Parameter #1 $param of method Test\IncompatiblePhpDocNullableTypeIssue::doFoo() expects string|null, int given.', - 551, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', - 565, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, Test\Foo given.', - 567, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', - 568, - ], - [ - 'Parameter #1 $s of method Test\ForeachSituation::takesInt() expects int|null, string|null given.', - 595, - ], - [ - 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, int given.', - 632, - ], - [ - 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, true given.', - 633, - ], - [ - 'Cannot call method add() on null.', - 647, - ], - [ - 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'nonexistentFunction\' given.', - 658, - ], - [ - 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'Test…\' given.', - 660, - ], - [ - 'Method Test\VariadicAnnotationMethod::definedInPhpDoc() invoked with 0 parameters, at least 1 required.', - 714, - ], - [ - 'Parameter #2 $str of method Test\PreIncString::doFoo() expects string, int given.', - 725, - ], - [ - 'Cannot call method bar() on string.', - 747, - ], - [ - 'Cannot call method bar() on string.', - 748, - ], - [ - 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, (int|string) given.', - 791, - ], - [ - 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|stdClass|string given.', - 797, - ], - [ - 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|stdClass|string given.', - 798, - ], - [ - 'Parameter #1 $intOrString of method Test\CheckDefaultArrayKeys::doLorem() expects int|string, int|stdClass|string given.', - 799, - ], - [ - 'Parameter #1 $stdOrInt of method Test\CheckDefaultArrayKeys::doIpsum() expects int|stdClass, int|stdClass|string given.', // should not expect this - 800, - ], - [ - 'Parameter #1 $stdOrString of method Test\CheckDefaultArrayKeys::doDolor() expects stdClass|string, int|stdClass|string given.', // should not expect this - 801, - ], - [ - 'Parameter #1 $dateOrString of method Test\CheckDefaultArrayKeys::doSit() expects DateTimeImmutable|string, int|stdClass|string given.', - 802, - ], - [ - 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, int|stdClass|string given.', - 803, - ], - [ - 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|string given.', - 866, - ], - [ - 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|string given.', - 867, - ], - [ - 'Cannot call method test() on string.', - 885, - ], - [ - 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', - 886, - ], - [ - 'Call to an undefined method ReflectionType::getName().', - 896, - ], - [ - 'Call to an undefined method ReflectionType::getName().', - 897, - ], - [ - 'Call to an undefined method Test\Foo::lorem().', - 907, - ], - [ - 'Call to an undefined method Test\Foo::lorem().', - 911, - ], - [ - 'Cannot call method foo() on class-string|object.', - 914, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', - 915, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', - 916, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', - 921, - ], - [ - 'Parameter #1 $namespaceOrPrefix of method SimpleXMLElement::children() expects string|null, int given.', - 942, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 964, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 987, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 1005, - ], - [ - 'Call to an undefined method Test\CallAfterPropertyEmpty::doBar().', - 1072, - ], - [ - 'Call to an undefined method Test\ArraySliceWithNonEmptyArray::doesNotExist().', - 1092, - ], - [ - 'Call to an undefined method Test\AssertInFor::doBar().', - 1207, - ], - [ - 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', - 1277, - ], - [ - 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', - 1284, - ], - [ - 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', - 1285, - ], - [ - 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', - 1378, - ], - [ - 'Parameter #1 $foo of method Test\ExpectsExceptionGenerics::requiresFoo() expects Test\Foo, Exception given.', - 1379, - ], - [ - 'Only iterables can be unpacked, array|null given in argument #5.', - 1459, - ], - [ - 'Only iterables can be unpacked, int given in argument #6.', - 1460, - ], - [ - 'Only iterables can be unpacked, string given in argument #7.', - 1461, - ], - [ - 'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string, string given.', - 1490, - ], - [ - 'Parameter #2 $object of method Test\ClassStringWithUpperBounds::doFoo() expects Exception, Throwable given.', - 1490, - ], - [ - 'Unable to resolve the template type T in call to method Test\ClassStringWithUpperBounds::doFoo()', - 1490, - 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', - ], - [ - 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', - 1533, - ], - [ - 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', - 1589, - ], - [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', - 1657, - ], - [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', - 1658, - ], - ]); - } - - public function testCallMethodsOnThisOnly(): void - { - $this->checkThisOnly = true; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([ __DIR__ . '/data/call-methods.php'], [ - [ - 'Call to an undefined method Test\Foo::protectedMethodFromChild().', - 10, - ], - [ - 'Call to an undefined method Test\Bar::loremipsum().', - 40, - ], - [ - 'Call to private method foo() of class Test\Foo.', - 41, - ], - [ - 'Method Test\Foo::foo() invoked with 1 parameter, 0 required.', - 41, - ], - [ - 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', - 46, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 66, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 68, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 70, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 72, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 75, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 76, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 77, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 78, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 79, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 81, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 83, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 84, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 85, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 86, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 90, - ], - [ - 'Result of method Test\Bar::returnsVoid() (void) is used.', - 91, - ], - [ - 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', - 167, - ], - [ - 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', - 168, - ], - [ - 'Parameter #1 $foo of method Test\ObjectTypehint::doBar() expects Test\Foo, object given.', - 385, - ], - [ - 'Parameter #1 $test of method Test\NullableInPhpDoc::doFoo() expects string, null given.', - 427, - ], - [ - 'Parameter #1 $globalTitle of method Test\ThreeTypesCall::threeTypes() expects string, float given.', - 446, - ], - [ - 'Parameter #1 $param of method Test\IncompatiblePhpDocNullableTypeIssue::doFoo() expects string|null, int given.', - 551, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', - 565, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, Test\Foo given.', - 567, - ], - [ - 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', - 568, - ], - [ - 'Parameter #1 $s of method Test\ForeachSituation::takesInt() expects int|null, string|null given.', - 595, - ], - [ - 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, int given.', - 632, - ], - [ - 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, true given.', - 633, - ], - [ - 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'nonexistentFunction\' given.', - 658, - ], - [ - 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'Test…\' given.', - 660, - ], - [ - 'Method Test\VariadicAnnotationMethod::definedInPhpDoc() invoked with 0 parameters, at least 1 required.', - 714, - ], - [ - 'Parameter #2 $str of method Test\PreIncString::doFoo() expects string, int given.', - 725, - ], - [ - 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, (int|string) given.', - 791, - ], - [ - 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|stdClass|string given.', - 797, - ], - [ - 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|stdClass|string given.', - 798, - ], - [ - 'Parameter #1 $intOrString of method Test\CheckDefaultArrayKeys::doLorem() expects int|string, int|stdClass|string given.', - 799, - ], - [ - 'Parameter #1 $stdOrInt of method Test\CheckDefaultArrayKeys::doIpsum() expects int|stdClass, int|stdClass|string given.', // should not expect this - 800, - ], - [ - 'Parameter #1 $stdOrString of method Test\CheckDefaultArrayKeys::doDolor() expects stdClass|string, int|stdClass|string given.', // should not expect this - 801, - ], - [ - 'Parameter #1 $dateOrString of method Test\CheckDefaultArrayKeys::doSit() expects DateTimeImmutable|string, int|stdClass|string given.', - 802, - ], - [ - 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, int|stdClass|string given.', - 803, - ], - [ - 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|string given.', - 866, - ], - [ - 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|string given.', - 867, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', - 915, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', - 916, - ], - [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', - 921, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 964, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 987, - ], - [ - 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', - 1005, - ], - [ - 'Call to an undefined method Test\CallAfterPropertyEmpty::doBar().', - 1072, - ], - [ - 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', - 1277, - ], - [ - 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', - 1284, - ], - [ - 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', - 1285, - ], - [ - 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', - 1378, - ], - [ - 'Parameter #1 $foo of method Test\ExpectsExceptionGenerics::requiresFoo() expects Test\Foo, Exception given.', - 1379, - ], - [ - 'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string, string given.', - 1490, - ], - [ - 'Parameter #2 $object of method Test\ClassStringWithUpperBounds::doFoo() expects Exception, Throwable given.', - 1490, - ], - [ - 'Unable to resolve the template type T in call to method Test\ClassStringWithUpperBounds::doFoo()', - 1490, - 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', - ], - [ - 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', - 1533, - ], - [ - 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', - 1589, - ], - [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', - 1657, - ], - [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', - 1658, - ], - ]); - } - - public function testCallTraitMethods(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-trait-methods.php'], [ - [ - 'Call to an undefined method CallTraitMethods\Baz::unexistentMethod().', - 26, - ], - ]); - } - - public function testCallTraitOverridenMethods(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-trait-overridden-methods.php'], []); - } - - public function testCallInterfaceMethods(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-interface-methods.php'], [ - [ - 'Call to an undefined method InterfaceMethods\Baz::barMethod().', - 25, - ], - ]); - } - - public function testClosureBind(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/closure-bind.php'], [ - [ - 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 12, - ], - [ - 'Call to an undefined method CallClosureBind\Bar::barMethod().', - 16, - ], - [ - 'Call to private method privateMethod() of class CallClosureBind\Foo.', - 18, - ], - [ - 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 19, - ], - [ - 'Call to an undefined method CallClosureBind\Bar::barMethod().', - 23, - ], - [ - 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 28, - ], - [ - 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 33, - ], - [ - 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 39, - ], - ]); - } - - public function testArrowFunctionClosureBind(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/arrow-function-bind.php'], [ - [ - 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', - 27, - ], - [ - 'Call to an undefined method CallArrowFunctionBind\Bar::barMethod().', - 29, - ], - [ - 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', - 31, - ], - [ - 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', - 33, - ], - [ - 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', - 35, - ], - ]); - } - - public function testCallVariadicMethods(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-variadic-methods.php'], [ - [ - 'Method CallVariadicMethods\Foo::baz() invoked with 0 parameters, at least 1 required.', - 10, - ], - [ - 'Method CallVariadicMethods\Foo::lorem() invoked with 0 parameters, at least 2 required.', - 11, - ], - [ - 'Parameter #2 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 32, - ], - [ - 'Parameter #3 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 32, - ], - [ - 'Parameter #1 $int of method CallVariadicMethods\Foo::doVariadicString() expects int, string given.', - 34, - ], - [ - 'Parameter #3 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 42, - ], - [ - 'Parameter #4 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 42, - ], - [ - 'Parameter #5 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 42, - ], - [ - 'Parameter #6 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', - 42, - ], - [ - 'Method CallVariadicMethods\Foo::doIntegerParameters() invoked with 3 parameters, 2 required.', - 43, - ], - [ - 'Parameter #1 $foo of method CallVariadicMethods\Foo::doIntegerParameters() expects int, string given.', - 43, - ], - [ - 'Parameter #2 $bar of method CallVariadicMethods\Foo::doIntegerParameters() expects int, string given.', - 43, - ], - [ - 'Method CallVariadicMethods\Foo::doIntegerParameters() invoked with 3 parameters, 2 required.', - 44, - ], - [ - 'Parameter #1 ...$strings of method CallVariadicMethods\Bar::variadicStrings() expects string, int given.', - 85, - ], - [ - 'Parameter #2 ...$strings of method CallVariadicMethods\Bar::variadicStrings() expects string, int given.', - 85, - ], - [ - 'Parameter #1 ...$strings of method CallVariadicMethods\Bar::anotherVariadicStrings() expects string, int given.', - 88, - ], - [ - 'Parameter #2 ...$strings of method CallVariadicMethods\Bar::anotherVariadicStrings() expects string, int given.', - 88, - ], - ]); - } - - public function testCallToIncorrectCaseMethodName(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/incorrect-method-case.php'], [ - [ - 'Call to method IncorrectMethodCase\Foo::fooBar() with incorrect case: foobar', - 10, - ], - ]); - } - - public function testNullableParameters(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/nullable-parameters.php'], [ - [ - 'Method NullableParameters\Foo::doFoo() invoked with 0 parameters, 2 required.', - 6, - ], - [ - 'Method NullableParameters\Foo::doFoo() invoked with 1 parameter, 2 required.', - 7, - ], - [ - 'Method NullableParameters\Foo::doFoo() invoked with 3 parameters, 2 required.', - 10, - ], - ]); - } - - public function testProtectedMethodCallFromParent(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/protected-method-call-from-parent.php'], []); - } - - public function testSiblingMethodPrototype(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/sibling-method-prototype.php'], []); - } - - public function testOverridenMethodPrototype(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/overriden-method-prototype.php'], []); - } - - public function testCallMethodWithInheritDoc(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/calling-method-with-inheritdoc.php'], [ - [ - 'Parameter #1 $i of method MethodWithInheritDoc\Baz::doFoo() expects int, string given.', - 65, - ], - [ - 'Parameter #1 $str of method MethodWithInheritDoc\Foo::doBar() expects string, int given.', - 67, - ], - ]); - } - - public function testCallMethodWithInheritDocWithoutCurlyBraces(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/calling-method-with-inheritdoc-without-curly-braces.php'], [ - [ - 'Parameter #1 $i of method MethodWithInheritDocWithoutCurlyBraces\Baz::doFoo() expects int, string given.', - 65, - ], - [ - 'Parameter #1 $str of method MethodWithInheritDocWithoutCurlyBraces\Foo::doBar() expects string, int given.', - 67, - ], - ]); - } - - public function testCallMethodWithPhpDocsImplicitInheritance(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/calling-method-with-phpDocs-implicit-inheritance.php'], [ - [ - 'Parameter #1 $i of method MethodWithPhpDocsImplicitInheritance\Baz::doFoo() expects int, string given.', - 56, - ], - [ - 'Parameter #1 $str of method MethodWithPhpDocsImplicitInheritance\Foo::doBar() expects string, int given.', - 58, - ], - [ - 'Parameter #1 $x of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\A, int given.', - 89, - ], - [ - 'Parameter #2 $y of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\B, int given.', - 89, - ], - [ - 'Parameter #3 $z of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\C, int given.', - 89, - ], - [ - 'Parameter #4 $d of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\D, int given.', - 89, - ], - [ - 'Parameter #1 $g of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\A, int given.', - 104, - ], - [ - 'Parameter #2 $h of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\B, int given.', - 104, - ], - [ - 'Parameter #3 $i of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\C, int given.', - 104, - ], - [ - 'Parameter #4 $d of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\D, int given.', - 104, - ], - [ - 'Parameter #1 $value of method ArrayObject::append() expects stdClass, Exception given.', - 115, - ], - [ - 'Parameter #1 $value of method ArrayObject::append() expects stdClass, Exception given.', - 129, - ], - [ - 'Parameter #1 $someValue of method MethodWithPhpDocsImplicitInheritance\TestArrayObject3::append() expects stdClass, Exception given.', - 146, - ], - ]); - } - - public function testNegatedInstanceof(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/negated-instanceof.php'], []); - } - - public function testInvokeMagicInvokeMethod(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/invoke-magic-method.php'], [ - [ - 'Parameter #1 $foo of method InvokeMagicInvokeMethod\ClassForCallable::doFoo() expects callable(): mixed, InvokeMagicInvokeMethod\ClassForCallable given.', - 27, - ], - ]); - } - - public function testCheckNullables(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/check-nullables.php'], [ - [ - 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, null given.', - 11, - ], - [ - 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, string|null given.', - 15, - ], - ]); - } - - public function testDoNotCheckNullables(): void - { - $this->checkThisOnly = false; - $this->checkNullables = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/check-nullables.php'], [ - [ - 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, null given.', - 11, - ], - ]); - } - - public function testMysqliQuery(): void - { - $this->checkThisOnly = false; - $this->checkNullables = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/mysqli-query.php'], [ - [ - 'Method mysqli::query() invoked with 0 parameters, 1-2 required.', - 4, - ], - ]); - } - - public function testCallMethodsNullIssue(): void - { - $this->checkThisOnly = false; - $this->checkNullables = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/order.php'], []); - } - - public function dataIterable(): array - { - return [ - [ - true, - ], - [ - false, - ], - ]; - } - - /** - * @dataProvider dataIterable - * @param bool $checkNullables - */ - public function testIterables(bool $checkNullables): void - { - $this->checkThisOnly = false; - $this->checkNullables = $checkNullables; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-methods-iterable.php'], [ - [ - 'Parameter #1 $ids of method CallMethodsIterables\Uuid::bar() expects iterable, array given.', - 14, - ], - [ - 'Parameter #1 $iterable of method CallMethodsIterables\Foo::acceptsSelfIterable() expects iterable, iterable given.', - 59, - ], - [ - 'Parameter #1 $iterable of method CallMethodsIterables\Foo::acceptsSelfIterable() expects iterable, string given.', - 60, - ], - [ - 'Parameter #1 $iterableWithoutTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', - 62, - ], - [ - 'Parameter #2 $iterableWithIterableTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', - 62, - ], - [ - 'Parameter #3 $iterableWithConcreteTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', - 62, - ], - [ - 'Parameter #4 $arrayWithIterableTypehint of method CallMethodsIterables\Foo::doFoo() expects array, int given.', - 62, - ], - [ - 'Parameter #5 $unionIterableType of method CallMethodsIterables\Foo::doFoo() expects CallMethodsIterables\Collection&iterable, int given.', - 62, - ], - [ - 'Parameter #6 $mixedUnionIterableType of method CallMethodsIterables\Foo::doFoo() expects array, int given.', - 62, - ], - [ - 'Parameter #7 $unionIterableIterableType of method CallMethodsIterables\Foo::doFoo() expects CallMethodsIterables\Collection&iterable, int given.', - 62, - ], - [ - 'Parameter #9 $integers of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', - 62, - ], - [ - 'Parameter #10 $mixeds of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', - 62, - ], - ]); - } - - public function testAcceptThrowable(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/accept-throwable.php'], [ - [ - 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\SomeInterface&Throwable given.', - 41, - ], - [ - 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\InterfaceExtendingThrowable given.', - 44, - ], - [ - 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\NonExceptionClass&Throwable given.', - 47, - ], - [ - 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, Exception given.', - 50, - ], - ]); - } - - public function testWithoutCheckUnionTypes(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = false; - $this->analyse([__DIR__ . '/data/without-union-types.php'], [ - [ - 'Method CallMethodsWithoutUnionTypes\Foo::doFoo() invoked with 3 parameters, 0 required.', - 14, - ], - ]); - } - - public function testStrictTypes(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/call-methods-strict.php'], [ - [ - 'Parameter #1 $foo of method Test\ClassWithToString::acceptsString() expects string, Test\ClassWithToString given.', - 7, - ], - ]); - } - - public function testAliasedTraitsProblem(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/aliased-traits-problem.php'], []); - } - - public function testClosureCallInvocations(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/closure-call.php'], [ - [ - 'Method Closure::call() invoked with 0 parameters, 2 required.', - 9, - ], - [ - 'Method Closure::call() invoked with 1 parameter, 2 required.', - 10, - ], - [ - 'Method Closure::call() invoked with 1 parameter, 2 required.', - 11, - ], - [ - 'Parameter #1 $newThis of method Closure::call() expects object, int given.', - 11, - ], - [ - 'Parameter #2 $thing of method Closure::call() expects object, int given.', - 12, - ], - [ - 'Parameter #1 $newThis of method Closure::call() expects object, int given.', - 13, - ], - [ - 'Method Closure::call() invoked with 3 parameters, 2 required.', - 14, - ], - [ - 'Result of method Closure::call() (void) is used.', - 18, - ], - ]); - } - - public function testMixin(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/mixin.php'], [ - [ - 'Method MixinMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', - 30, - ], - [ - 'Method MixinMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', - 40, - ], - [ - 'Method Exception::getMessage() invoked with 1 parameter, 0 required.', - 61, - ], - [ - 'Call to an undefined method MixinMethods\GenericFoo::getMessagee().', - 62, - ], - ]); - } - - public function testRecursiveIteratorIterator(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/recursive-iterator-iterator.php'], [ - [ - 'Method RecursiveDirectoryIterator::getSubPathname() invoked with 1 parameter, 0 required.', - 14, - ], - ]); - } - - public function testMergeInheritedPhpDocs(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/merge-inherited-param.php'], [ - [ - 'Parameter #1 $uno of method CallMethodsPhpDocMergeParamInherited\ParentClass::method() expects CallMethodsPhpDocMergeParamInherited\A, CallMethodsPhpDocMergeParamInherited\D given.', - 37, - ], - [ - 'Parameter #2 $dos of method CallMethodsPhpDocMergeParamInherited\ParentClass::method() expects CallMethodsPhpDocMergeParamInherited\B, CallMethodsPhpDocMergeParamInherited\D given.', - 37, - ], - [ - 'Parameter #1 $one of method CallMethodsPhpDocMergeParamInherited\ChildClass::method() expects CallMethodsPhpDocMergeParamInherited\C, CallMethodsPhpDocMergeParamInherited\B given.', - 42, - ], - [ - 'Parameter #2 $two of method CallMethodsPhpDocMergeParamInherited\ChildClass::method() expects CallMethodsPhpDocMergeParamInherited\B, CallMethodsPhpDocMergeParamInherited\D given.', - 42, - ], - ]); - } - - public function testShadowedTraitMethod(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/shadowed-trait-method.php'], []); - } - - public function testExplicitMixed(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/check-explicit-mixed.php'], [ - [ - 'Cannot call method foo() on mixed.', - 17, - ], - [ - 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, mixed given.', - 43, - ], - [ - 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, T given.', - 65, - ], - ]); - } - - public function testBug3409(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3409.php'], []); - } - - public function testBug2600(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-2600.php'], [ - [ - 'Method Bug2600\Foo::doBar() invoked with 3 parameters, 0-1 required.', - 10, - ], - ]); - } - - public function testBug3415(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3415.php'], []); - } - - public function testBug3415Two(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3415-2.php'], []); - } - - public function testBug3445(): void - { - if (!self::$useStaticReflectionProvider) { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('PHP looks at the parameter value non-lazily before PHP 7.3.'); - } - } - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3445.php'], [ - [ - 'Parameter #1 $test of method Bug3445\Foo::doFoo() expects Bug3445\Foo, $this(Bug3445\Bar) given.', - 26, - ], - ]); - } - - public function testBug3481(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3481.php'], [ - [ - 'Method Bug3481\Foo::doSomething() invoked with 2 parameters, 3 required.', - 34, - ], - [ - 'Parameter #1 $a of method Bug3481\Foo::doSomething() expects string, int|string given.', - 44, - ], - ]); - } - - public function testBug3683(): void - { - if (self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires hybrid reflection.'); - } - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3683.php'], [ - [ - 'Parameter #1 $exception of method Generator::throw() expects Throwable, int given.', - 7, - ], - ]); - } - - public function testStringable(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/stringable.php'], []); - } - - public function testStringableStrictTypes(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/stringable-strict.php'], [ - [ - 'Parameter #1 $s of method TestStringables\Dolor::doFoo() expects string, TestStringables\Bar given.', - 15, - ], - ]); - } - - public function testMatchExpressionVoidIsUsed(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/match-expr-void-used.php'], [ - [ - 'Result of method MatchExprVoidUsed\Foo::doLorem() (void) is used.', - 10, - ], - [ - 'Result of method MatchExprVoidUsed\Foo::doBar() (void) is used.', - 11, - ], - ]); - } - - public function testNullSafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - - $this->analyse([__DIR__ . '/data/nullsafe-method-call.php'], [ - [ - 'Method NullsafeMethodCall\Foo::doBar() invoked with 1 parameter, 0 required.', - 11, - ], - [ - 'Parameter #1 $passedByRef of method NullsafeMethodCall\Foo::doBaz() is passed by reference, so it expects variables only.', - 26, - ], - [ - 'Parameter #1 $passedByRef of method NullsafeMethodCall\Foo::doBaz() is passed by reference, so it expects variables only.', - 27, - ], - ]); - } - - public function testDisallowNamedArguments(): void - { - if (PHP_VERSION_ID >= 80000) { - $this->markTestSkipped('Test requires PHP earlier than 8.0.'); - } - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - - $this->analyse([__DIR__ . '/data/disallow-named-arguments.php'], [ - [ - 'Named arguments are supported only on PHP 8.0 and later.', - 10, - ], - ]); - } - - public function testNamedArguments(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->phpVersion = 80000; - - $this->analyse([__DIR__ . '/data/named-arguments.php'], [ - [ - 'Named argument cannot be followed by a positional argument.', - 21, - ], - [ - 'Named argument cannot be followed by a positional argument.', - 22, - ], - [ - 'Missing parameter $j (int) in call to method NamedArgumentsMethod\Foo::doFoo().', - 19, - ], - [ - 'Missing parameter $k (int) in call to method NamedArgumentsMethod\Foo::doFoo().', - 19, - ], - [ - 'Argument for parameter $i has already been passed.', - 26, - ], - [ - 'Argument for parameter $i has already been passed.', - 32, - ], - [ - 'Missing parameter $k (int) in call to method NamedArgumentsMethod\Foo::doFoo().', - 37, - ], - [ - 'Unknown parameter $z in call to method NamedArgumentsMethod\Foo::doFoo().', - 46, - ], - [ - 'Parameter #1 $i of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', - 50, - ], - [ - 'Parameter $j of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', - 57, - ], - [ - 'Parameter $i of method NamedArgumentsMethod\Foo::doBaz() is passed by reference, so it expects variables only.', - 70, - ], - [ - 'Parameter $i of method NamedArgumentsMethod\Foo::doBaz() is passed by reference, so it expects variables only.', - 71, - ], - [ - 'Named argument cannot be followed by an unpacked (...) argument.', - 73, - ], - [ - 'Parameter $j of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', - 75, - ], - [ - 'Named argument cannot be followed by a positional argument.', - 77, - ], - [ - 'Missing parameter $j (int) in call to method NamedArgumentsMethod\Foo::doFoo().', - 77, - ], - [ - 'Parameter #3 ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', - 87, - ], - [ - 'Parameter $b of method NamedArgumentsMethod\Foo::doIpsum() expects int, string given.', - 90, - ], - [ - 'Parameter $b of method NamedArgumentsMethod\Foo::doIpsum() expects int, string given.', - 91, - ], - [ - 'Parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', - 91, - ], - [ - 'Missing parameter $b (int) in call to method NamedArgumentsMethod\Foo::doIpsum().', - 92, - ], - [ - 'Missing parameter $a (int) in call to method NamedArgumentsMethod\Foo::doIpsum().', - 93, - ], - [ - 'Unpacked argument (...) cannot be followed by a non-unpacked argument.', - 94, - ], - ]); - } - - public function testBug4199(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - - $this->analyse([__DIR__ . '/data/bug-4199.php'], [ - [ - 'Cannot call method answer() on Bug4199\Baz|null.', - 37, - ], - ]); - } - - public function testBug4188(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - - $this->analyse([__DIR__ . '/data/bug-4188.php'], []); - } - - public function testOnlyRelevantUnableToResolveTemplateType(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/only-relevant-unable-to-resolve-template-type.php'], [ - [ - 'Parameter #1 $a of method OnlyRelevantUnableToResolve\Foo::doBaz() expects array, int given.', - 41, - ], - [ - 'Unable to resolve the template type T in call to method OnlyRelevantUnableToResolve\Foo::doBaz()', - 41, - 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', - ], - ]); - } - - public function testBug4552(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-4552.php'], []); - } - - public function testBug2837(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-2837.php'], []); - } - - public function testBug2298(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-2298.php'], []); - } - - public function testBug1661(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-1661.php'], []); - } - - public function testBug1656(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-1656.php'], []); - } - - public function testBug3534(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3534.php'], []); - } - - public function testBug4557(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4557.php'], []); - } - - public function testBug4209(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4209.php'], []); - } - - public function testBug4209Two(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4209-2.php'], []); - } - - public function testBug3321(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-3321.php'], []); - } - - public function testBug4498(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4498.php'], []); - } - - public function testBug3922(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-3922.php'], [ - [ - 'Parameter #1 $query of method Bug3922\FooQueryHandler::handle() expects Bug3922\FooQuery, Bug3922\BarQuery given.', - 63, - ], - ]); - } - - public function testBug4642(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4642.php'], []); - } - - public function testBug4008(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-4008.php'], []); - } - - public function testBug3546(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3546.php'], []); - } - - public function testBug4800(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->phpVersion = 80000; - $this->analyse([__DIR__ . '/data/bug-4800.php'], [ - [ - 'Missing parameter $bar (string) in call to method Bug4800\HelloWorld2::a().', - 36, - ], - ]); - } - + /** @var bool */ + private $checkThisOnly; + + /** @var bool */ + private $checkNullables; + + /** @var bool */ + private $checkUnionTypes; + + /** @var bool */ + private $checkExplicitMixed = false; + + /** @var int */ + private $phpVersion = PHP_VERSION_ID; + + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); + return new CallMethodsRule( + $broker, + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), true, true, true, true), + $ruleLevelHelper, + true, + true + ); + } + + public function testCallMethods(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([ __DIR__ . '/data/call-methods.php'], [ + [ + 'Call to an undefined method Test\Foo::protectedMethodFromChild().', + 10, + ], + [ + 'Call to an undefined method Test\Bar::loremipsum().', + 40, + ], + [ + 'Call to private method foo() of class Test\Foo.', + 41, + ], + [ + 'Method Test\Foo::foo() invoked with 1 parameter, 0 required.', + 41, + ], + [ + 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', + 46, + ], + [ + 'Cannot call method method() on string.', + 49, + ], + [ + 'Call to method doFoo() on an unknown class Test\UnknownClass.', + 63, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 66, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 68, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 70, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 72, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 75, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 76, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 77, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 78, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 79, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 81, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 83, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 84, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 85, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 86, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 90, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 91, + ], + [ + 'Call to an undefined method ArrayObject::doFoo().', + 108, + ], + [ + 'Method PDO::query() invoked with 0 parameters, 1-4 required.', + 113, + ], + [ + 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', + 167, + ], + [ + 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', + 168, + ], + [ + 'Cannot call method ipsum() on Test\Foo|null.', + 183, + ], + [ + 'Cannot call method ipsum() on Test\Bar|null.', + 184, + ], + [ + 'Cannot call method ipsum() on Test\Foo|null.', + 201, + ], + [ + 'Cannot call method ipsum() on Test\Bar|null.', + 202, + ], + [ + 'Method DateTimeZone::getTransitions() invoked with 3 parameters, 0-2 required.', + 214, + ], + [ + 'Result of method Test\ReturningSomethingFromConstructor::__construct() (void) is used.', + 234, + ], + [ + 'Cannot call method foo() on int|string.', + 254, + ], + [ + 'Method Test\FirstInterface::firstMethod() invoked with 1 parameter, 0 required.', + 281, + ], + [ + 'Method Test\SecondInterface::secondMethod() invoked with 1 parameter, 0 required.', + 282, + ], + [ + 'Cannot call method foo() on null.', + 299, + ], + [ + 'Call to method test() on an unknown class Test\FirstUnknownClass.', + 312, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Call to method test() on an unknown class Test\SecondUnknownClass.', + 312, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Cannot call method ipsum() on Test\Foo|null.', + 325, + ], + [ + 'Call to an undefined method Test\WithFooAndBarMethod|Test\WithFooMethod::bar().', + 355, + ], + [ + 'Call to an undefined method Test\SomeInterface&Test\WithFooMethod::bar().', + 372, + ], + [ + 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', + 373, + ], + [ + 'Parameter #1 $foo of method Test\ObjectTypehint::doBar() expects Test\Foo, object given.', + 385, + ], + [ + 'Cannot call method test() on array.', + 399, + ], + [ + 'Method Test\Foo::ipsum() invoked with 1 parameter, 0 required.', + 409, + ], + [ + 'Parameter #1 $test of method Test\NullableInPhpDoc::doFoo() expects string, null given.', + 427, + ], + [ + 'Parameter #1 $globalTitle of method Test\ThreeTypesCall::threeTypes() expects string, float given.', + 446, + ], + [ + 'Cannot call method find() on Test\NullCoalesce|null.', + 516, + ], + [ + 'Cannot call method find() on Test\NullCoalesce|null.', + 518, + ], + [ + 'Cannot call method find() on Test\NullCoalesce|null.', + 522, + ], + [ + 'Cannot call method find() on Test\NullCoalesce|null.', + 524, + ], + [ + 'Cannot call method find() on Test\NullCoalesce|null.', + 524, + ], + [ + 'Parameter #1 $param of method Test\IncompatiblePhpDocNullableTypeIssue::doFoo() expects string|null, int given.', + 551, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', + 565, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, Test\Foo given.', + 567, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', + 568, + ], + [ + 'Parameter #1 $s of method Test\ForeachSituation::takesInt() expects int|null, string|null given.', + 595, + ], + [ + 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, int given.', + 632, + ], + [ + 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, true given.', + 633, + ], + [ + 'Cannot call method add() on null.', + 647, + ], + [ + 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'nonexistentFunction\' given.', + 658, + ], + [ + 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'Test…\' given.', + 660, + ], + [ + 'Method Test\VariadicAnnotationMethod::definedInPhpDoc() invoked with 0 parameters, at least 1 required.', + 714, + ], + [ + 'Parameter #2 $str of method Test\PreIncString::doFoo() expects string, int given.', + 725, + ], + [ + 'Cannot call method bar() on string.', + 747, + ], + [ + 'Cannot call method bar() on string.', + 748, + ], + [ + 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, (int|string) given.', + 791, + ], + [ + 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|stdClass|string given.', + 797, + ], + [ + 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|stdClass|string given.', + 798, + ], + [ + 'Parameter #1 $intOrString of method Test\CheckDefaultArrayKeys::doLorem() expects int|string, int|stdClass|string given.', + 799, + ], + [ + 'Parameter #1 $stdOrInt of method Test\CheckDefaultArrayKeys::doIpsum() expects int|stdClass, int|stdClass|string given.', // should not expect this + 800, + ], + [ + 'Parameter #1 $stdOrString of method Test\CheckDefaultArrayKeys::doDolor() expects stdClass|string, int|stdClass|string given.', // should not expect this + 801, + ], + [ + 'Parameter #1 $dateOrString of method Test\CheckDefaultArrayKeys::doSit() expects DateTimeImmutable|string, int|stdClass|string given.', + 802, + ], + [ + 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, int|stdClass|string given.', + 803, + ], + [ + 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|string given.', + 866, + ], + [ + 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|string given.', + 867, + ], + [ + 'Cannot call method test() on string.', + 885, + ], + [ + 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', + 886, + ], + [ + 'Call to an undefined method ReflectionType::getName().', + 896, + ], + [ + 'Call to an undefined method ReflectionType::getName().', + 897, + ], + [ + 'Call to an undefined method Test\Foo::lorem().', + 907, + ], + [ + 'Call to an undefined method Test\Foo::lorem().', + 911, + ], + [ + 'Cannot call method foo() on class-string|object.', + 914, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', + 915, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', + 916, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', + 921, + ], + [ + 'Parameter #1 $namespaceOrPrefix of method SimpleXMLElement::children() expects string|null, int given.', + 942, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 964, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 987, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 1005, + ], + [ + 'Call to an undefined method Test\CallAfterPropertyEmpty::doBar().', + 1072, + ], + [ + 'Call to an undefined method Test\ArraySliceWithNonEmptyArray::doesNotExist().', + 1092, + ], + [ + 'Call to an undefined method Test\AssertInFor::doBar().', + 1207, + ], + [ + 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', + 1277, + ], + [ + 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', + 1284, + ], + [ + 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', + 1285, + ], + [ + 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', + 1378, + ], + [ + 'Parameter #1 $foo of method Test\ExpectsExceptionGenerics::requiresFoo() expects Test\Foo, Exception given.', + 1379, + ], + [ + 'Only iterables can be unpacked, array|null given in argument #5.', + 1459, + ], + [ + 'Only iterables can be unpacked, int given in argument #6.', + 1460, + ], + [ + 'Only iterables can be unpacked, string given in argument #7.', + 1461, + ], + [ + 'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string, string given.', + 1490, + ], + [ + 'Parameter #2 $object of method Test\ClassStringWithUpperBounds::doFoo() expects Exception, Throwable given.', + 1490, + ], + [ + 'Unable to resolve the template type T in call to method Test\ClassStringWithUpperBounds::doFoo()', + 1490, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + [ + 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', + 1533, + ], + [ + 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', + 1589, + ], + [ + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', + 1657, + ], + [ + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', + 1658, + ], + ]); + } + + public function testCallMethodsOnThisOnly(): void + { + $this->checkThisOnly = true; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([ __DIR__ . '/data/call-methods.php'], [ + [ + 'Call to an undefined method Test\Foo::protectedMethodFromChild().', + 10, + ], + [ + 'Call to an undefined method Test\Bar::loremipsum().', + 40, + ], + [ + 'Call to private method foo() of class Test\Foo.', + 41, + ], + [ + 'Method Test\Foo::foo() invoked with 1 parameter, 0 required.', + 41, + ], + [ + 'Method Test\Foo::test() invoked with 0 parameters, 1 required.', + 46, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 66, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 68, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 70, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 72, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 75, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 76, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 77, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 78, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 79, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 81, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 83, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 84, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 85, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 86, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 90, + ], + [ + 'Result of method Test\Bar::returnsVoid() (void) is used.', + 91, + ], + [ + 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', + 167, + ], + [ + 'Parameter #1 $bar of method Test\ClassWithNullableProperty::doBar() is passed by reference, so it expects variables only.', + 168, + ], + [ + 'Parameter #1 $foo of method Test\ObjectTypehint::doBar() expects Test\Foo, object given.', + 385, + ], + [ + 'Parameter #1 $test of method Test\NullableInPhpDoc::doFoo() expects string, null given.', + 427, + ], + [ + 'Parameter #1 $globalTitle of method Test\ThreeTypesCall::threeTypes() expects string, float given.', + 446, + ], + [ + 'Parameter #1 $param of method Test\IncompatiblePhpDocNullableTypeIssue::doFoo() expects string|null, int given.', + 551, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', + 565, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, Test\Foo given.', + 567, + ], + [ + 'Parameter #1 $i of method Test\TernaryEvaluation::doBar() expects int, false given.', + 568, + ], + [ + 'Parameter #1 $s of method Test\ForeachSituation::takesInt() expects int|null, string|null given.', + 595, + ], + [ + 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, int given.', + 632, + ], + [ + 'Parameter #1 $str of method Test\LiteralArrayTypeCheck::test() expects string, true given.', + 633, + ], + [ + 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'nonexistentFunction\' given.', + 658, + ], + [ + 'Parameter #1 $str of method Test\CheckIsCallable::test() expects callable(): mixed, \'Test…\' given.', + 660, + ], + [ + 'Method Test\VariadicAnnotationMethod::definedInPhpDoc() invoked with 0 parameters, at least 1 required.', + 714, + ], + [ + 'Parameter #2 $str of method Test\PreIncString::doFoo() expects string, int given.', + 725, + ], + [ + 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, (int|string) given.', + 791, + ], + [ + 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|stdClass|string given.', + 797, + ], + [ + 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|stdClass|string given.', + 798, + ], + [ + 'Parameter #1 $intOrString of method Test\CheckDefaultArrayKeys::doLorem() expects int|string, int|stdClass|string given.', + 799, + ], + [ + 'Parameter #1 $stdOrInt of method Test\CheckDefaultArrayKeys::doIpsum() expects int|stdClass, int|stdClass|string given.', // should not expect this + 800, + ], + [ + 'Parameter #1 $stdOrString of method Test\CheckDefaultArrayKeys::doDolor() expects stdClass|string, int|stdClass|string given.', // should not expect this + 801, + ], + [ + 'Parameter #1 $dateOrString of method Test\CheckDefaultArrayKeys::doSit() expects DateTimeImmutable|string, int|stdClass|string given.', + 802, + ], + [ + 'Parameter #1 $std of method Test\CheckDefaultArrayKeys::doAmet() expects stdClass, int|stdClass|string given.', + 803, + ], + [ + 'Parameter #1 $i of method Test\CheckDefaultArrayKeys::doBar() expects int, int|string given.', + 866, + ], + [ + 'Parameter #1 $str of method Test\CheckDefaultArrayKeys::doBaz() expects string, int|string given.', + 867, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', + 915, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', + 916, + ], + [ + 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', + 921, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 964, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 987, + ], + [ + 'Parameter #1 $s of method Test\IssetCumulativeArray::doBar() expects string, int given.', + 1005, + ], + [ + 'Call to an undefined method Test\CallAfterPropertyEmpty::doBar().', + 1072, + ], + [ + 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', + 1277, + ], + [ + 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', + 1284, + ], + [ + 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', + 1285, + ], + [ + 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', + 1378, + ], + [ + 'Parameter #1 $foo of method Test\ExpectsExceptionGenerics::requiresFoo() expects Test\Foo, Exception given.', + 1379, + ], + [ + 'Parameter #1 $s of method Test\ClassStringWithUpperBounds::doFoo() expects class-string, string given.', + 1490, + ], + [ + 'Parameter #2 $object of method Test\ClassStringWithUpperBounds::doFoo() expects Exception, Throwable given.', + 1490, + ], + [ + 'Unable to resolve the template type T in call to method Test\ClassStringWithUpperBounds::doFoo()', + 1490, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + [ + 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', + 1533, + ], + [ + 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', + 1589, + ], + [ + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', + 1657, + ], + [ + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', + 1658, + ], + ]); + } + + public function testCallTraitMethods(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-trait-methods.php'], [ + [ + 'Call to an undefined method CallTraitMethods\Baz::unexistentMethod().', + 26, + ], + ]); + } + + public function testCallTraitOverridenMethods(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-trait-overridden-methods.php'], []); + } + + public function testCallInterfaceMethods(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-interface-methods.php'], [ + [ + 'Call to an undefined method InterfaceMethods\Baz::barMethod().', + 25, + ], + ]); + } + + public function testClosureBind(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/closure-bind.php'], [ + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 12, + ], + [ + 'Call to an undefined method CallClosureBind\Bar::barMethod().', + 16, + ], + [ + 'Call to private method privateMethod() of class CallClosureBind\Foo.', + 18, + ], + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 19, + ], + [ + 'Call to an undefined method CallClosureBind\Bar::barMethod().', + 23, + ], + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 28, + ], + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 33, + ], + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 39, + ], + ]); + } + + public function testArrowFunctionClosureBind(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/arrow-function-bind.php'], [ + [ + 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', + 27, + ], + [ + 'Call to an undefined method CallArrowFunctionBind\Bar::barMethod().', + 29, + ], + [ + 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', + 31, + ], + [ + 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', + 33, + ], + [ + 'Call to an undefined method CallArrowFunctionBind\Foo::nonexistentMethod().', + 35, + ], + ]); + } + + public function testCallVariadicMethods(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-variadic-methods.php'], [ + [ + 'Method CallVariadicMethods\Foo::baz() invoked with 0 parameters, at least 1 required.', + 10, + ], + [ + 'Method CallVariadicMethods\Foo::lorem() invoked with 0 parameters, at least 2 required.', + 11, + ], + [ + 'Parameter #2 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 32, + ], + [ + 'Parameter #3 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 32, + ], + [ + 'Parameter #1 $int of method CallVariadicMethods\Foo::doVariadicString() expects int, string given.', + 34, + ], + [ + 'Parameter #3 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 42, + ], + [ + 'Parameter #4 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 42, + ], + [ + 'Parameter #5 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 42, + ], + [ + 'Parameter #6 ...$strings of method CallVariadicMethods\Foo::doVariadicString() expects string, int given.', + 42, + ], + [ + 'Method CallVariadicMethods\Foo::doIntegerParameters() invoked with 3 parameters, 2 required.', + 43, + ], + [ + 'Parameter #1 $foo of method CallVariadicMethods\Foo::doIntegerParameters() expects int, string given.', + 43, + ], + [ + 'Parameter #2 $bar of method CallVariadicMethods\Foo::doIntegerParameters() expects int, string given.', + 43, + ], + [ + 'Method CallVariadicMethods\Foo::doIntegerParameters() invoked with 3 parameters, 2 required.', + 44, + ], + [ + 'Parameter #1 ...$strings of method CallVariadicMethods\Bar::variadicStrings() expects string, int given.', + 85, + ], + [ + 'Parameter #2 ...$strings of method CallVariadicMethods\Bar::variadicStrings() expects string, int given.', + 85, + ], + [ + 'Parameter #1 ...$strings of method CallVariadicMethods\Bar::anotherVariadicStrings() expects string, int given.', + 88, + ], + [ + 'Parameter #2 ...$strings of method CallVariadicMethods\Bar::anotherVariadicStrings() expects string, int given.', + 88, + ], + ]); + } + + public function testCallToIncorrectCaseMethodName(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/incorrect-method-case.php'], [ + [ + 'Call to method IncorrectMethodCase\Foo::fooBar() with incorrect case: foobar', + 10, + ], + ]); + } + + public function testNullableParameters(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/nullable-parameters.php'], [ + [ + 'Method NullableParameters\Foo::doFoo() invoked with 0 parameters, 2 required.', + 6, + ], + [ + 'Method NullableParameters\Foo::doFoo() invoked with 1 parameter, 2 required.', + 7, + ], + [ + 'Method NullableParameters\Foo::doFoo() invoked with 3 parameters, 2 required.', + 10, + ], + ]); + } + + public function testProtectedMethodCallFromParent(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/protected-method-call-from-parent.php'], []); + } + + public function testSiblingMethodPrototype(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/sibling-method-prototype.php'], []); + } + + public function testOverridenMethodPrototype(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/overriden-method-prototype.php'], []); + } + + public function testCallMethodWithInheritDoc(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/calling-method-with-inheritdoc.php'], [ + [ + 'Parameter #1 $i of method MethodWithInheritDoc\Baz::doFoo() expects int, string given.', + 65, + ], + [ + 'Parameter #1 $str of method MethodWithInheritDoc\Foo::doBar() expects string, int given.', + 67, + ], + ]); + } + + public function testCallMethodWithInheritDocWithoutCurlyBraces(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/calling-method-with-inheritdoc-without-curly-braces.php'], [ + [ + 'Parameter #1 $i of method MethodWithInheritDocWithoutCurlyBraces\Baz::doFoo() expects int, string given.', + 65, + ], + [ + 'Parameter #1 $str of method MethodWithInheritDocWithoutCurlyBraces\Foo::doBar() expects string, int given.', + 67, + ], + ]); + } + + public function testCallMethodWithPhpDocsImplicitInheritance(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/calling-method-with-phpDocs-implicit-inheritance.php'], [ + [ + 'Parameter #1 $i of method MethodWithPhpDocsImplicitInheritance\Baz::doFoo() expects int, string given.', + 56, + ], + [ + 'Parameter #1 $str of method MethodWithPhpDocsImplicitInheritance\Foo::doBar() expects string, int given.', + 58, + ], + [ + 'Parameter #1 $x of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\A, int given.', + 89, + ], + [ + 'Parameter #2 $y of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\B, int given.', + 89, + ], + [ + 'Parameter #3 $z of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\C, int given.', + 89, + ], + [ + 'Parameter #4 $d of method MethodWithPhpDocsImplicitInheritance\Ipsum::doLorem() expects MethodWithPhpDocsImplicitInheritance\D, int given.', + 89, + ], + [ + 'Parameter #1 $g of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\A, int given.', + 104, + ], + [ + 'Parameter #2 $h of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\B, int given.', + 104, + ], + [ + 'Parameter #3 $i of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\C, int given.', + 104, + ], + [ + 'Parameter #4 $d of method MethodWithPhpDocsImplicitInheritance\Dolor::doLorem() expects MethodWithPhpDocsImplicitInheritance\D, int given.', + 104, + ], + [ + 'Parameter #1 $value of method ArrayObject::append() expects stdClass, Exception given.', + 115, + ], + [ + 'Parameter #1 $value of method ArrayObject::append() expects stdClass, Exception given.', + 129, + ], + [ + 'Parameter #1 $someValue of method MethodWithPhpDocsImplicitInheritance\TestArrayObject3::append() expects stdClass, Exception given.', + 146, + ], + ]); + } + + public function testNegatedInstanceof(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/negated-instanceof.php'], []); + } + + public function testInvokeMagicInvokeMethod(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/invoke-magic-method.php'], [ + [ + 'Parameter #1 $foo of method InvokeMagicInvokeMethod\ClassForCallable::doFoo() expects callable(): mixed, InvokeMagicInvokeMethod\ClassForCallable given.', + 27, + ], + ]); + } + + public function testCheckNullables(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/check-nullables.php'], [ + [ + 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, null given.', + 11, + ], + [ + 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, string|null given.', + 15, + ], + ]); + } + + public function testDoNotCheckNullables(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/check-nullables.php'], [ + [ + 'Parameter #1 $foo of method CheckNullables\Foo::doFoo() expects string, null given.', + 11, + ], + ]); + } + + public function testMysqliQuery(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/mysqli-query.php'], [ + [ + 'Method mysqli::query() invoked with 0 parameters, 1-2 required.', + 4, + ], + ]); + } + + public function testCallMethodsNullIssue(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/order.php'], []); + } + + public function dataIterable(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } + + /** + * @dataProvider dataIterable + * @param bool $checkNullables + */ + public function testIterables(bool $checkNullables): void + { + $this->checkThisOnly = false; + $this->checkNullables = $checkNullables; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-methods-iterable.php'], [ + [ + 'Parameter #1 $ids of method CallMethodsIterables\Uuid::bar() expects iterable, array given.', + 14, + ], + [ + 'Parameter #1 $iterable of method CallMethodsIterables\Foo::acceptsSelfIterable() expects iterable, iterable given.', + 59, + ], + [ + 'Parameter #1 $iterable of method CallMethodsIterables\Foo::acceptsSelfIterable() expects iterable, string given.', + 60, + ], + [ + 'Parameter #1 $iterableWithoutTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', + 62, + ], + [ + 'Parameter #2 $iterableWithIterableTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', + 62, + ], + [ + 'Parameter #3 $iterableWithConcreteTypehint of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', + 62, + ], + [ + 'Parameter #4 $arrayWithIterableTypehint of method CallMethodsIterables\Foo::doFoo() expects array, int given.', + 62, + ], + [ + 'Parameter #5 $unionIterableType of method CallMethodsIterables\Foo::doFoo() expects CallMethodsIterables\Collection&iterable, int given.', + 62, + ], + [ + 'Parameter #6 $mixedUnionIterableType of method CallMethodsIterables\Foo::doFoo() expects array, int given.', + 62, + ], + [ + 'Parameter #7 $unionIterableIterableType of method CallMethodsIterables\Foo::doFoo() expects CallMethodsIterables\Collection&iterable, int given.', + 62, + ], + [ + 'Parameter #9 $integers of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', + 62, + ], + [ + 'Parameter #10 $mixeds of method CallMethodsIterables\Foo::doFoo() expects iterable, int given.', + 62, + ], + ]); + } + + public function testAcceptThrowable(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/accept-throwable.php'], [ + [ + 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\SomeInterface&Throwable given.', + 41, + ], + [ + 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\InterfaceExtendingThrowable given.', + 44, + ], + [ + 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, AcceptThrowable\NonExceptionClass&Throwable given.', + 47, + ], + [ + 'Parameter #1 $i of method AcceptThrowable\Foo::doBar() expects int, Exception given.', + 50, + ], + ]); + } + + public function testWithoutCheckUnionTypes(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = false; + $this->analyse([__DIR__ . '/data/without-union-types.php'], [ + [ + 'Method CallMethodsWithoutUnionTypes\Foo::doFoo() invoked with 3 parameters, 0 required.', + 14, + ], + ]); + } + + public function testStrictTypes(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/call-methods-strict.php'], [ + [ + 'Parameter #1 $foo of method Test\ClassWithToString::acceptsString() expects string, Test\ClassWithToString given.', + 7, + ], + ]); + } + + public function testAliasedTraitsProblem(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/aliased-traits-problem.php'], []); + } + + public function testClosureCallInvocations(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/closure-call.php'], [ + [ + 'Method Closure::call() invoked with 0 parameters, 2 required.', + 9, + ], + [ + 'Method Closure::call() invoked with 1 parameter, 2 required.', + 10, + ], + [ + 'Method Closure::call() invoked with 1 parameter, 2 required.', + 11, + ], + [ + 'Parameter #1 $newThis of method Closure::call() expects object, int given.', + 11, + ], + [ + 'Parameter #2 $thing of method Closure::call() expects object, int given.', + 12, + ], + [ + 'Parameter #1 $newThis of method Closure::call() expects object, int given.', + 13, + ], + [ + 'Method Closure::call() invoked with 3 parameters, 2 required.', + 14, + ], + [ + 'Result of method Closure::call() (void) is used.', + 18, + ], + ]); + } + + public function testMixin(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/mixin.php'], [ + [ + 'Method MixinMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', + 30, + ], + [ + 'Method MixinMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', + 40, + ], + [ + 'Method Exception::getMessage() invoked with 1 parameter, 0 required.', + 61, + ], + [ + 'Call to an undefined method MixinMethods\GenericFoo::getMessagee().', + 62, + ], + ]); + } + + public function testRecursiveIteratorIterator(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/recursive-iterator-iterator.php'], [ + [ + 'Method RecursiveDirectoryIterator::getSubPathname() invoked with 1 parameter, 0 required.', + 14, + ], + ]); + } + + public function testMergeInheritedPhpDocs(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/merge-inherited-param.php'], [ + [ + 'Parameter #1 $uno of method CallMethodsPhpDocMergeParamInherited\ParentClass::method() expects CallMethodsPhpDocMergeParamInherited\A, CallMethodsPhpDocMergeParamInherited\D given.', + 37, + ], + [ + 'Parameter #2 $dos of method CallMethodsPhpDocMergeParamInherited\ParentClass::method() expects CallMethodsPhpDocMergeParamInherited\B, CallMethodsPhpDocMergeParamInherited\D given.', + 37, + ], + [ + 'Parameter #1 $one of method CallMethodsPhpDocMergeParamInherited\ChildClass::method() expects CallMethodsPhpDocMergeParamInherited\C, CallMethodsPhpDocMergeParamInherited\B given.', + 42, + ], + [ + 'Parameter #2 $two of method CallMethodsPhpDocMergeParamInherited\ChildClass::method() expects CallMethodsPhpDocMergeParamInherited\B, CallMethodsPhpDocMergeParamInherited\D given.', + 42, + ], + ]); + } + + public function testShadowedTraitMethod(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/shadowed-trait-method.php'], []); + } + + public function testExplicitMixed(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/check-explicit-mixed.php'], [ + [ + 'Cannot call method foo() on mixed.', + 17, + ], + [ + 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, mixed given.', + 43, + ], + [ + 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, T given.', + 65, + ], + ]); + } + + public function testBug3409(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3409.php'], []); + } + + public function testBug2600(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-2600.php'], [ + [ + 'Method Bug2600\Foo::doBar() invoked with 3 parameters, 0-1 required.', + 10, + ], + ]); + } + + public function testBug3415(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3415.php'], []); + } + + public function testBug3415Two(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3415-2.php'], []); + } + + public function testBug3445(): void + { + if (!self::$useStaticReflectionProvider) { + if (PHP_VERSION_ID < 70300) { + $this->markTestSkipped('PHP looks at the parameter value non-lazily before PHP 7.3.'); + } + } + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3445.php'], [ + [ + 'Parameter #1 $test of method Bug3445\Foo::doFoo() expects Bug3445\Foo, $this(Bug3445\Bar) given.', + 26, + ], + ]); + } + + public function testBug3481(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3481.php'], [ + [ + 'Method Bug3481\Foo::doSomething() invoked with 2 parameters, 3 required.', + 34, + ], + [ + 'Parameter #1 $a of method Bug3481\Foo::doSomething() expects string, int|string given.', + 44, + ], + ]); + } + + public function testBug3683(): void + { + if (self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires hybrid reflection.'); + } + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3683.php'], [ + [ + 'Parameter #1 $exception of method Generator::throw() expects Throwable, int given.', + 7, + ], + ]); + } + + public function testStringable(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/stringable.php'], []); + } + + public function testStringableStrictTypes(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/stringable-strict.php'], [ + [ + 'Parameter #1 $s of method TestStringables\Dolor::doFoo() expects string, TestStringables\Bar given.', + 15, + ], + ]); + } + + public function testMatchExpressionVoidIsUsed(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/match-expr-void-used.php'], [ + [ + 'Result of method MatchExprVoidUsed\Foo::doLorem() (void) is used.', + 10, + ], + [ + 'Result of method MatchExprVoidUsed\Foo::doBar() (void) is used.', + 11, + ], + ]); + } + + public function testNullSafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + $this->analyse([__DIR__ . '/data/nullsafe-method-call.php'], [ + [ + 'Method NullsafeMethodCall\Foo::doBar() invoked with 1 parameter, 0 required.', + 11, + ], + [ + 'Parameter #1 $passedByRef of method NullsafeMethodCall\Foo::doBaz() is passed by reference, so it expects variables only.', + 26, + ], + [ + 'Parameter #1 $passedByRef of method NullsafeMethodCall\Foo::doBaz() is passed by reference, so it expects variables only.', + 27, + ], + ]); + } + + public function testDisallowNamedArguments(): void + { + if (PHP_VERSION_ID >= 80000) { + $this->markTestSkipped('Test requires PHP earlier than 8.0.'); + } + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + $this->analyse([__DIR__ . '/data/disallow-named-arguments.php'], [ + [ + 'Named arguments are supported only on PHP 8.0 and later.', + 10, + ], + ]); + } + + public function testNamedArguments(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->phpVersion = 80000; + + $this->analyse([__DIR__ . '/data/named-arguments.php'], [ + [ + 'Named argument cannot be followed by a positional argument.', + 21, + ], + [ + 'Named argument cannot be followed by a positional argument.', + 22, + ], + [ + 'Missing parameter $j (int) in call to method NamedArgumentsMethod\Foo::doFoo().', + 19, + ], + [ + 'Missing parameter $k (int) in call to method NamedArgumentsMethod\Foo::doFoo().', + 19, + ], + [ + 'Argument for parameter $i has already been passed.', + 26, + ], + [ + 'Argument for parameter $i has already been passed.', + 32, + ], + [ + 'Missing parameter $k (int) in call to method NamedArgumentsMethod\Foo::doFoo().', + 37, + ], + [ + 'Unknown parameter $z in call to method NamedArgumentsMethod\Foo::doFoo().', + 46, + ], + [ + 'Parameter #1 $i of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', + 50, + ], + [ + 'Parameter $j of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', + 57, + ], + [ + 'Parameter $i of method NamedArgumentsMethod\Foo::doBaz() is passed by reference, so it expects variables only.', + 70, + ], + [ + 'Parameter $i of method NamedArgumentsMethod\Foo::doBaz() is passed by reference, so it expects variables only.', + 71, + ], + [ + 'Named argument cannot be followed by an unpacked (...) argument.', + 73, + ], + [ + 'Parameter $j of method NamedArgumentsMethod\Foo::doFoo() expects int, string given.', + 75, + ], + [ + 'Named argument cannot be followed by a positional argument.', + 77, + ], + [ + 'Missing parameter $j (int) in call to method NamedArgumentsMethod\Foo::doFoo().', + 77, + ], + [ + 'Parameter #3 ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', + 87, + ], + [ + 'Parameter $b of method NamedArgumentsMethod\Foo::doIpsum() expects int, string given.', + 90, + ], + [ + 'Parameter $b of method NamedArgumentsMethod\Foo::doIpsum() expects int, string given.', + 91, + ], + [ + 'Parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', + 91, + ], + [ + 'Missing parameter $b (int) in call to method NamedArgumentsMethod\Foo::doIpsum().', + 92, + ], + [ + 'Missing parameter $a (int) in call to method NamedArgumentsMethod\Foo::doIpsum().', + 93, + ], + [ + 'Unpacked argument (...) cannot be followed by a non-unpacked argument.', + 94, + ], + ]); + } + + public function testBug4199(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + $this->analyse([__DIR__ . '/data/bug-4199.php'], [ + [ + 'Cannot call method answer() on Bug4199\Baz|null.', + 37, + ], + ]); + } + + public function testBug4188(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + $this->analyse([__DIR__ . '/data/bug-4188.php'], []); + } + + public function testOnlyRelevantUnableToResolveTemplateType(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/only-relevant-unable-to-resolve-template-type.php'], [ + [ + 'Parameter #1 $a of method OnlyRelevantUnableToResolve\Foo::doBaz() expects array, int given.', + 41, + ], + [ + 'Unable to resolve the template type T in call to method OnlyRelevantUnableToResolve\Foo::doBaz()', + 41, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + ]); + } + + public function testBug4552(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4552.php'], []); + } + + public function testBug2837(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-2837.php'], []); + } + + public function testBug2298(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-2298.php'], []); + } + + public function testBug1661(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-1661.php'], []); + } + + public function testBug1656(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-1656.php'], []); + } + + public function testBug3534(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3534.php'], []); + } + + public function testBug4557(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4557.php'], []); + } + + public function testBug4209(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4209.php'], []); + } + + public function testBug4209Two(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4209-2.php'], []); + } + + public function testBug3321(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-3321.php'], []); + } + + public function testBug4498(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4498.php'], []); + } + + public function testBug3922(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-3922.php'], [ + [ + 'Parameter #1 $query of method Bug3922\FooQueryHandler::handle() expects Bug3922\FooQuery, Bug3922\BarQuery given.', + 63, + ], + ]); + } + + public function testBug4642(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4642.php'], []); + } + + public function testBug4008(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4008.php'], []); + } + + public function testBug3546(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3546.php'], []); + } + + public function testBug4800(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->phpVersion = 80000; + $this->analyse([__DIR__ . '/data/bug-4800.php'], [ + [ + 'Missing parameter $bar (string) in call to method Bug4800\HelloWorld2::a().', + 36, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 99bdc60099..c5f6784463 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false); - return new CallStaticMethodsRule( - $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), true, true, true, true), - $ruleLevelHelper, - new ClassCaseSensitivityCheck($broker), - true, - true - ); - } - - public function testCallStaticMethods(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); - } - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/call-static-methods.php'], [ - [ - 'Call to an undefined static method CallStaticMethods\Foo::bar().', - 39, - ], - [ - 'Call to an undefined static method CallStaticMethods\Bar::bar().', - 40, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::bar().', - 41, - ], - [ - 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', - 42, - ], - [ - 'Call to private static method dolor() of class CallStaticMethods\Foo.', - 43, - ], - [ - 'CallStaticMethods\Ipsum::ipsumTest() calls parent::lorem() but CallStaticMethods\Ipsum does not extend any class.', - 63, - ], - [ - 'Static method CallStaticMethods\Foo::test() invoked with 1 parameter, 0 required.', - 65, - ], - [ - 'Call to protected static method baz() of class CallStaticMethods\Foo.', - 66, - ], - [ - 'Call to static method loremIpsum() on an unknown class CallStaticMethods\UnknownStaticMethodClass.', - 67, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Call to private method __construct() of class CallStaticMethods\ClassWithConstructor.', - 87, - ], - [ - 'Method CallStaticMethods\ClassWithConstructor::__construct() invoked with 0 parameters, 1 required.', - 87, - ], - [ - 'Calling self::someStaticMethod() outside of class scope.', - 93, - ], - [ - 'Calling static::someStaticMethod() outside of class scope.', - 94, - ], - [ - 'Calling parent::someStaticMethod() outside of class scope.', - 95, - ], - [ - 'Call to protected static method baz() of class CallStaticMethods\Foo.', - 97, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::bar().', - 98, - ], - [ - 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', - 99, - ], - [ - 'Call to private static method dolor() of class CallStaticMethods\Foo.', - 100, - ], - [ - 'Static method Locale::getDisplayLanguage() invoked with 3 parameters, 1-2 required.', - 104, - ], - [ - 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', - 115, - ], - [ - 'Cannot call static method foo() on int|string.', - 120, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 127, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::unknownMethod().', - 127, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 128, - ], - [ - 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', - 128, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 129, - ], - [ - 'Call to private static method dolor() of class CallStaticMethods\Foo.', - 129, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 130, - ], - [ - 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', - 130, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 131, - ], - [ - 'Call to static method CallStaticMethods\Foo::test() with incorrect case: TEST', - 131, - ], - [ - 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', - 132, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::__construct().', - 144, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::nonexistent().', - 154, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::nonexistent().', - 159, - ], - [ - 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', - 160, - ], - [ - 'Static method CallStaticMethods\ClassOrString::calledMethod() invoked with 1 parameter, 0 required.', - 173, - ], - [ - 'Cannot call abstract static method CallStaticMethods\InterfaceWithStaticMethod::doFoo().', - 208, - ], - [ - 'Call to an undefined static method CallStaticMethods\InterfaceWithStaticMethod::doBar().', - 209, - ], - [ - 'Static call to instance method CallStaticMethods\InterfaceWithStaticMethod::doInstanceFoo().', - 212, - ], - [ - 'Static call to instance method CallStaticMethods\InterfaceWithStaticMethod::doInstanceFoo().', - 213, - ], - [ - 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', - 298, - ], - [ - 'Call to an undefined static method T of CallStaticMethods\Foo::nonexistentMethod().', - 299, - ], - [ - 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', - 302, - ], - [ - 'Call to an undefined static method CallStaticMethods\Foo::nonexistentMethod().', - 303, - ], - [ - 'Call to static method doFoo() on an unknown class CallStaticMethods\TraitWithStaticMethod.', - 328, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false); + return new CallStaticMethodsRule( + $broker, + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), true, true, true, true), + $ruleLevelHelper, + new ClassCaseSensitivityCheck($broker), + true, + true + ); + } - public function testCallInterfaceMethods(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/call-interface-methods.php'], [ - [ - 'Cannot call abstract static method InterfaceMethods\Foo::fooStaticMethod().', - 26, - ], - [ - 'Call to an undefined static method InterfaceMethods\Baz::barStaticMethod().', - 27, - ], - ]); - } + public function testCallStaticMethods(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); + } + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/call-static-methods.php'], [ + [ + 'Call to an undefined static method CallStaticMethods\Foo::bar().', + 39, + ], + [ + 'Call to an undefined static method CallStaticMethods\Bar::bar().', + 40, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::bar().', + 41, + ], + [ + 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', + 42, + ], + [ + 'Call to private static method dolor() of class CallStaticMethods\Foo.', + 43, + ], + [ + 'CallStaticMethods\Ipsum::ipsumTest() calls parent::lorem() but CallStaticMethods\Ipsum does not extend any class.', + 63, + ], + [ + 'Static method CallStaticMethods\Foo::test() invoked with 1 parameter, 0 required.', + 65, + ], + [ + 'Call to protected static method baz() of class CallStaticMethods\Foo.', + 66, + ], + [ + 'Call to static method loremIpsum() on an unknown class CallStaticMethods\UnknownStaticMethodClass.', + 67, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Call to private method __construct() of class CallStaticMethods\ClassWithConstructor.', + 87, + ], + [ + 'Method CallStaticMethods\ClassWithConstructor::__construct() invoked with 0 parameters, 1 required.', + 87, + ], + [ + 'Calling self::someStaticMethod() outside of class scope.', + 93, + ], + [ + 'Calling static::someStaticMethod() outside of class scope.', + 94, + ], + [ + 'Calling parent::someStaticMethod() outside of class scope.', + 95, + ], + [ + 'Call to protected static method baz() of class CallStaticMethods\Foo.', + 97, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::bar().', + 98, + ], + [ + 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', + 99, + ], + [ + 'Call to private static method dolor() of class CallStaticMethods\Foo.', + 100, + ], + [ + 'Static method Locale::getDisplayLanguage() invoked with 3 parameters, 1-2 required.', + 104, + ], + [ + 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', + 115, + ], + [ + 'Cannot call static method foo() on int|string.', + 120, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 127, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::unknownMethod().', + 127, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 128, + ], + [ + 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', + 128, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 129, + ], + [ + 'Call to private static method dolor() of class CallStaticMethods\Foo.', + 129, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 130, + ], + [ + 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', + 130, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 131, + ], + [ + 'Call to static method CallStaticMethods\Foo::test() with incorrect case: TEST', + 131, + ], + [ + 'Class CallStaticMethods\Foo referenced with incorrect case: CallStaticMethods\FOO.', + 132, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::__construct().', + 144, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::nonexistent().', + 154, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::nonexistent().', + 159, + ], + [ + 'Static call to instance method CallStaticMethods\Foo::loremIpsum().', + 160, + ], + [ + 'Static method CallStaticMethods\ClassOrString::calledMethod() invoked with 1 parameter, 0 required.', + 173, + ], + [ + 'Cannot call abstract static method CallStaticMethods\InterfaceWithStaticMethod::doFoo().', + 208, + ], + [ + 'Call to an undefined static method CallStaticMethods\InterfaceWithStaticMethod::doBar().', + 209, + ], + [ + 'Static call to instance method CallStaticMethods\InterfaceWithStaticMethod::doInstanceFoo().', + 212, + ], + [ + 'Static call to instance method CallStaticMethods\InterfaceWithStaticMethod::doInstanceFoo().', + 213, + ], + [ + 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', + 298, + ], + [ + 'Call to an undefined static method T of CallStaticMethods\Foo::nonexistentMethod().', + 299, + ], + [ + 'Static method CallStaticMethods\Foo::test() invoked with 3 parameters, 0 required.', + 302, + ], + [ + 'Call to an undefined static method CallStaticMethods\Foo::nonexistentMethod().', + 303, + ], + [ + 'Call to static method doFoo() on an unknown class CallStaticMethods\TraitWithStaticMethod.', + 328, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - public function testCallToIncorrectCaseMethodName(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/incorrect-static-method-case.php'], [ - [ - 'Call to static method IncorrectStaticMethodCase\Foo::fooBar() with incorrect case: foobar', - 10, - ], - ]); - } + public function testCallInterfaceMethods(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/call-interface-methods.php'], [ + [ + 'Cannot call abstract static method InterfaceMethods\Foo::fooStaticMethod().', + 26, + ], + [ + 'Call to an undefined static method InterfaceMethods\Baz::barStaticMethod().', + 27, + ], + ]); + } - public function testStaticCallsToInstanceMethods(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/static-calls-to-instance-methods.php'], [ - [ - 'Static call to instance method StaticCallsToInstanceMethods\Foo::doFoo().', - 10, - ], - [ - 'Static call to instance method StaticCallsToInstanceMethods\Bar::doBar().', - 16, - ], - [ - 'Static call to instance method StaticCallsToInstanceMethods\Foo::doFoo().', - 36, - ], - [ - 'Call to method StaticCallsToInstanceMethods\Foo::doFoo() with incorrect case: dofoo', - 42, - ], - [ - 'Method StaticCallsToInstanceMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', - 43, - ], - [ - 'Call to private method doPrivateFoo() of class StaticCallsToInstanceMethods\Foo.', - 45, - ], - [ - 'Method StaticCallsToInstanceMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', - 48, - ], - ]); - } + public function testCallToIncorrectCaseMethodName(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/incorrect-static-method-case.php'], [ + [ + 'Call to static method IncorrectStaticMethodCase\Foo::fooBar() with incorrect case: foobar', + 10, + ], + ]); + } - public function testStaticCallOnExpression(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/static-call-on-expression.php'], [ - [ - 'Call to an undefined static method StaticCallOnExpression\Foo::doBar().', - 16, - ], - ]); - } + public function testStaticCallsToInstanceMethods(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/static-calls-to-instance-methods.php'], [ + [ + 'Static call to instance method StaticCallsToInstanceMethods\Foo::doFoo().', + 10, + ], + [ + 'Static call to instance method StaticCallsToInstanceMethods\Bar::doBar().', + 16, + ], + [ + 'Static call to instance method StaticCallsToInstanceMethods\Foo::doFoo().', + 36, + ], + [ + 'Call to method StaticCallsToInstanceMethods\Foo::doFoo() with incorrect case: dofoo', + 42, + ], + [ + 'Method StaticCallsToInstanceMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', + 43, + ], + [ + 'Call to private method doPrivateFoo() of class StaticCallsToInstanceMethods\Foo.', + 45, + ], + [ + 'Method StaticCallsToInstanceMethods\Foo::doFoo() invoked with 1 parameter, 0 required.', + 48, + ], + ]); + } - public function testStaticCallOnExpressionWithCheckDisabled(): void - { - $this->checkThisOnly = true; - $this->analyse([__DIR__ . '/data/static-call-on-expression.php'], []); - } + public function testStaticCallOnExpression(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/static-call-on-expression.php'], [ + [ + 'Call to an undefined static method StaticCallOnExpression\Foo::doBar().', + 16, + ], + ]); + } - public function testReturnStatic(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/return-static-static-method.php'], [ - [ - 'Call to an undefined static method ReturnStaticStaticMethod\Bar::doBaz().', - 24, - ], - ]); - } + public function testStaticCallOnExpressionWithCheckDisabled(): void + { + $this->checkThisOnly = true; + $this->analyse([__DIR__ . '/data/static-call-on-expression.php'], []); + } - public function testCallParentAbstractMethod(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/call-parent-abstract-method.php'], [ - [ - 'Cannot call abstract method CallParentAbstractMethod\Baz::uninstall().', - 21, - ], - [ - 'Cannot call abstract method CallParentAbstractMethod\Lorem::doFoo().', - 38, - ], - [ - 'Cannot call abstract method CallParentAbstractMethod\Lorem::doFoo().', - 48, - ], - [ - 'Cannot call abstract static method CallParentAbstractMethod\SitAmet::doFoo().', - 61, - ], - ]); - } + public function testReturnStatic(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/return-static-static-method.php'], [ + [ + 'Call to an undefined static method ReturnStaticStaticMethod\Bar::doBaz().', + 24, + ], + ]); + } - public function testClassExists(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/static-methods-class-exists.php'], []); - } + public function testCallParentAbstractMethod(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/call-parent-abstract-method.php'], [ + [ + 'Cannot call abstract method CallParentAbstractMethod\Baz::uninstall().', + 21, + ], + [ + 'Cannot call abstract method CallParentAbstractMethod\Lorem::doFoo().', + 38, + ], + [ + 'Cannot call abstract method CallParentAbstractMethod\Lorem::doFoo().', + 48, + ], + [ + 'Cannot call abstract static method CallParentAbstractMethod\SitAmet::doFoo().', + 61, + ], + ]); + } - public function testBug3448(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-3448.php'], [ - [ - 'Parameter #1 $lall of static method Bug3448\Foo::add() expects int, string given.', - 21, - ], - [ - 'Parameter #1 $lall of static method Bug3448\Foo::add() expects int, string given.', - 22, - ], - ]); - } + public function testClassExists(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/static-methods-class-exists.php'], []); + } - public function testBug3641(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-3641.php'], [ - [ - 'Static method Bug3641\Foo::bar() invoked with 1 parameter, 0 required.', - 32, - ], - ]); - } + public function testBug3448(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-3448.php'], [ + [ + 'Parameter #1 $lall of static method Bug3448\Foo::add() expects int, string given.', + 21, + ], + [ + 'Parameter #1 $lall of static method Bug3448\Foo::add() expects int, string given.', + 22, + ], + ]); + } - public function testBug2164(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-2164.php'], [ - [ - 'Parameter #1 $arg of static method Bug2164\A::staticTest() expects static(Bug2164\B)|string, Bug2164\B|string given.', - 24, - ], - ]); - } + public function testBug3641(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-3641.php'], [ + [ + 'Static method Bug3641\Foo::bar() invoked with 1 parameter, 0 required.', + 32, + ], + ]); + } - public function testNamedArguments(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testBug2164(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-2164.php'], [ + [ + 'Parameter #1 $arg of static method Bug2164\A::staticTest() expects static(Bug2164\B)|string, Bug2164\B|string given.', + 24, + ], + ]); + } - $this->checkThisOnly = false; + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - $this->analyse([__DIR__ . '/data/static-method-named-arguments.php'], [ - [ - 'Missing parameter $j (int) in call to static method StaticMethodNamedArguments\Foo::doFoo().', - 15, - ], - [ - 'Unknown parameter $z in call to static method StaticMethodNamedArguments\Foo::doFoo().', - 16, - ], - ]); - } + $this->checkThisOnly = false; - public function testBug577(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-577.php'], []); - } + $this->analyse([__DIR__ . '/data/static-method-named-arguments.php'], [ + [ + 'Missing parameter $j (int) in call to static method StaticMethodNamedArguments\Foo::doFoo().', + 15, + ], + [ + 'Unknown parameter $z in call to static method StaticMethodNamedArguments\Foo::doFoo().', + 16, + ], + ]); + } - public function testBug4550(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-4550.php'], [ - [ - 'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string, string given.', - 34, - ], - [ - 'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string, string given.', - 44, - ], - ]); - } + public function testBug577(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-577.php'], []); + } - public function testBug1971(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/bug-1971.php'], [ - [ - 'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array(class-string, \'sayHello2\') given.', - 16, - ], - ]); - } + public function testBug4550(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-4550.php'], [ + [ + 'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string, string given.', + 34, + ], + [ + 'Parameter #1 $class of static method Bug4550\Test::valuesOf() expects class-string, string given.', + 44, + ], + ]); + } + public function testBug1971(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-1971.php'], [ + [ + 'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array(class-string, \'sayHello2\') given.', + 16, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php index 917cc77f8b..b7de13321d 100644 --- a/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider()); + } - protected function getRule(): Rule - { - return new CallToConstructorStatementWithoutSideEffectsRule($this->createReflectionProvider()); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/constructor-statement-no-side-effects.php'], [ - [ - 'Call to Exception::__construct() on a separate line has no effect.', - 6, - ], - [ - 'Call to ConstructorStatementNoSideEffects\ConstructorWithPure::__construct() on a separate line has no effect.', - 57, - ], - [ - 'Call to ConstructorStatementNoSideEffects\ConstructorWithPureAndThrowsVoid::__construct() on a separate line has no effect.', - 58, - ], - ]); - } - - public function testBug4455(): void - { - $this->analyse([__DIR__ . '/data/bug-4455-constructor.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/constructor-statement-no-side-effects.php'], [ + [ + 'Call to Exception::__construct() on a separate line has no effect.', + 6, + ], + [ + 'Call to ConstructorStatementNoSideEffects\ConstructorWithPure::__construct() on a separate line has no effect.', + 57, + ], + [ + 'Call to ConstructorStatementNoSideEffects\ConstructorWithPureAndThrowsVoid::__construct() on a separate line has no effect.', + 58, + ], + ]); + } + public function testBug4455(): void + { + $this->analyse([__DIR__ . '/data/bug-4455-constructor.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php index 01ac6f193d..6d4c349071 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): Rule - { - return new CallToMethodStamentWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/method-call-statement-no-side-effects.php'], [ - [ - 'Call to method DateTimeImmutable::modify() on a separate line has no effect.', - 15, - ], - [ - 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', - 16, - ], - [ - 'Call to method Exception::getCode() on a separate line has no effect.', - 21, - ], - [ - 'Call to method MethodCallStatementNoSideEffects\Bar::doPure() on a separate line has no effect.', - 63, - ], - [ - 'Call to method MethodCallStatementNoSideEffects\Bar::doPureWithThrowsVoid() on a separate line has no effect.', - 64, - ], - ]); - } - - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/method-call-statement-no-side-effects.php'], [ + [ + 'Call to method DateTimeImmutable::modify() on a separate line has no effect.', + 15, + ], + [ + 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', + 16, + ], + [ + 'Call to method Exception::getCode() on a separate line has no effect.', + 21, + ], + [ + 'Call to method MethodCallStatementNoSideEffects\Bar::doPure() on a separate line has no effect.', + 63, + ], + [ + 'Call to method MethodCallStatementNoSideEffects\Bar::doPureWithThrowsVoid() on a separate line has no effect.', + 64, + ], + ]); + } - $this->analyse([__DIR__ . '/data/nullsafe-method-call-statement-no-side-effects.php'], [ - [ - 'Call to method Exception::getMessage() on a separate line has no effect.', - 10, - ], - ]); - } + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function testBug4232(): void - { - $this->analyse([__DIR__ . '/data/bug-4232.php'], []); - } + $this->analyse([__DIR__ . '/data/nullsafe-method-call-statement-no-side-effects.php'], [ + [ + 'Call to method Exception::getMessage() on a separate line has no effect.', + 10, + ], + ]); + } - public function testPhpDoc(): void - { - $this->analyse([__DIR__ . '/data/method-call-statement-no-side-effects-phpdoc.php'], [ - [ - 'Call to method MethodCallStatementNoSideEffects\Bzz::pure1() on a separate line has no effect.', - 39, - ], - [ - 'Call to method MethodCallStatementNoSideEffects\Bzz::pure2() on a separate line has no effect.', - 40, - ], - [ - 'Call to method MethodCallStatementNoSideEffects\Bzz::pure3() on a separate line has no effect.', - 41, - ], - ]); - } + public function testBug4232(): void + { + $this->analyse([__DIR__ . '/data/bug-4232.php'], []); + } - public function testBug4455(): void - { - $this->analyse([__DIR__ . '/data/bug-4455.php'], []); - } + public function testPhpDoc(): void + { + $this->analyse([__DIR__ . '/data/method-call-statement-no-side-effects-phpdoc.php'], [ + [ + 'Call to method MethodCallStatementNoSideEffects\Bzz::pure1() on a separate line has no effect.', + 39, + ], + [ + 'Call to method MethodCallStatementNoSideEffects\Bzz::pure2() on a separate line has no effect.', + 40, + ], + [ + 'Call to method MethodCallStatementNoSideEffects\Bzz::pure3() on a separate line has no effect.', + 41, + ], + ]); + } + public function testBug4455(): void + { + $this->analyse([__DIR__ . '/data/bug-4455.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php index ec5ade3b82..642aa9a693 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new CallToStaticMethodStamentWithoutSideEffectsRule( + new RuleLevelHelper($broker, true, false, true, false), + $broker + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new CallToStaticMethodStamentWithoutSideEffectsRule( - new RuleLevelHelper($broker, true, false, true, false), - $broker - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/static-method-call-statement-no-side-effects.php'], [ - [ - 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', - 12, - ], - [ - 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', - 13, - ], - [ - 'Call to method DateTime::format() on a separate line has no effect.', - 23, - ], - ]); - } - - public function testPhpDoc(): void - { - $this->analyse([__DIR__ . '/data/static-method-call-statement-no-side-effects-phpdoc.php'], [ - [ - 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure1() on a separate line has no effect.', - 39, - ], - [ - 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure2() on a separate line has no effect.', - 40, - ], - [ - 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure3() on a separate line has no effect.', - 41, - ], - [ - 'Call to static method StaticMethodCallStatementNoSideEffects\PureThrows::pureAndThrowsVoid() on a separate line has no effect.', - 67, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/static-method-call-statement-no-side-effects.php'], [ + [ + 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', + 12, + ], + [ + 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', + 13, + ], + [ + 'Call to method DateTime::format() on a separate line has no effect.', + 23, + ], + ]); + } - public function testBug4455(): void - { - $this->analyse([__DIR__ . '/data/bug-4455-static.php'], []); - } + public function testPhpDoc(): void + { + $this->analyse([__DIR__ . '/data/static-method-call-statement-no-side-effects-phpdoc.php'], [ + [ + 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure1() on a separate line has no effect.', + 39, + ], + [ + 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure2() on a separate line has no effect.', + 40, + ], + [ + 'Call to static method StaticMethodCallStatementNoSideEffects\BzzStatic::pure3() on a separate line has no effect.', + 41, + ], + [ + 'Call to static method StaticMethodCallStatementNoSideEffects\PureThrows::pureAndThrowsVoid() on a separate line has no effect.', + 67, + ], + ]); + } + public function testBug4455(): void + { + $this->analyse([__DIR__ . '/data/bug-4455-static.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index a35359ee77..99b66d8937 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); - } - - public function testExistingClassInTypehint(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); - } - $this->analyse([__DIR__ . '/data/typehints.php'], [ - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::foo() has invalid type TestMethodTypehints\NonexistentClass.', - 8, - ], - [ - 'Parameter $bar of method TestMethodTypehints\FooMethodTypehints::bar() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', - 13, - ], - [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', - 28, - ], - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid type TestMethodTypehints\BazMethodTypehints.', - 28, - ], - [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', - 38, - ], - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid type TestMethodTypehints\BazMethodTypehints.', - 38, - ], - [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', - 48, - ], - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid type TestMethodTypehints\BazMethodTypehints.', - 48, - ], - [ - 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid typehint type parent.', - 53, - ], - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid type parent.', - 53, - ], - [ - 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid typehint type parent.', - 62, - ], - [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid type parent.', - 62, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehints.', - 67, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', - 67, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehints.', - 76, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', - 76, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', - 85, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', - 85, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', - 94, - ], - [ - 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', - 94, - ], - [ - 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid typehint type TestMethodTypehints\NonexistentClass.', - 102, - ], - [ - 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', - 102, - ], - [ - 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Bla.', - 113, - ], - [ - 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Ble.', - 113, - ], - [ - 'Template type U of method TestMethodTypehints\TemplateTypeMissingInParameter::doFoo() is not referenced in a parameter.', - 130, - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + } - public function testExistingClassInIterableTypehint(): void - { - $this->analyse([__DIR__ . '/data/typehints-iterable.php'], [ - [ - 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\NonexistentClass.', - 11, - ], - [ - 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', - 11, - ], - ]); - } + public function testExistingClassInTypehint(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); + } + $this->analyse([__DIR__ . '/data/typehints.php'], [ + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::foo() has invalid type TestMethodTypehints\NonexistentClass.', + 8, + ], + [ + 'Parameter $bar of method TestMethodTypehints\FooMethodTypehints::bar() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 13, + ], + [ + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 28, + ], + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid type TestMethodTypehints\BazMethodTypehints.', + 28, + ], + [ + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 38, + ], + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid type TestMethodTypehints\BazMethodTypehints.', + 38, + ], + [ + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 48, + ], + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid type TestMethodTypehints\BazMethodTypehints.', + 48, + ], + [ + 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid typehint type parent.', + 53, + ], + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid type parent.', + 53, + ], + [ + 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid typehint type parent.', + 62, + ], + [ + 'Return typehint of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid type parent.', + 62, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehints.', + 67, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', + 67, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehints.', + 76, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', + 76, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', + 85, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', + 85, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', + 94, + ], + [ + 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\FOOMethodTypehints.', + 94, + ], + [ + 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid typehint type TestMethodTypehints\NonexistentClass.', + 102, + ], + [ + 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', + 102, + ], + [ + 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Bla.', + 113, + ], + [ + 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Ble.', + 113, + ], + [ + 'Template type U of method TestMethodTypehints\TemplateTypeMissingInParameter::doFoo() is not referenced in a parameter.', + 130, + ], + ]); + } - public function testVoidParameterTypehint(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection'); - } - $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ - [ - 'Parameter $param of method VoidParameterTypehintMethod\Foo::doFoo() has invalid typehint type void.', - 8, - ], - ]); - } + public function testExistingClassInIterableTypehint(): void + { + $this->analyse([__DIR__ . '/data/typehints-iterable.php'], [ + [ + 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\NonexistentClass.', + 11, + ], + [ + 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', + 11, + ], + ]); + } - public function dataNativeUnionTypes(): array - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - return []; - } + public function testVoidParameterTypehint(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection'); + } + $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ + [ + 'Parameter $param of method VoidParameterTypehintMethod\Foo::doFoo() has invalid typehint type void.', + 8, + ], + ]); + } - return [ - [ - 70400, - [ - [ - 'Method NativeUnionTypesSupport\Foo::doFoo() uses native union types but they\'re supported only on PHP 8.0 and later.', - 8, - ], - [ - 'Method NativeUnionTypesSupport\Foo::doBar() uses native union types but they\'re supported only on PHP 8.0 and later.', - 13, - ], - ], - ], - [ - 80000, - [], - ], - ]; - } + public function dataNativeUnionTypes(): array + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + return []; + } - /** - * @dataProvider dataNativeUnionTypes - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testNativeUnionTypes(int $phpVersionId, array $errors): void - { - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); - } + return [ + [ + 70400, + [ + [ + 'Method NativeUnionTypesSupport\Foo::doFoo() uses native union types but they\'re supported only on PHP 8.0 and later.', + 8, + ], + [ + 'Method NativeUnionTypesSupport\Foo::doBar() uses native union types but they\'re supported only on PHP 8.0 and later.', + 13, + ], + ], + ], + [ + 80000, + [], + ], + ]; + } - public function dataRequiredParameterAfterOptional(): array - { - return [ - [ - 70400, - PHP_VERSION_ID < 80000 || self::$useStaticReflectionProvider ? [] : [ - [ - 'Required parameter $bar follows optional parameter $foo', - 8, - ], - [ - 'Required parameter $bar follows optional parameter $foo', - 17, - ], - [ - 'Required parameter $bar follows optional parameter $foo', - 21, - ], - ], - ], - [ - 80000, - [ - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 8, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 17, - ], - [ - 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', - 21, - ], - ], - ], - ]; - } + /** + * @dataProvider dataNativeUnionTypes + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testNativeUnionTypes(int $phpVersionId, array $errors): void + { + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/native-union-types.php'], $errors); + } - /** - * @dataProvider dataRequiredParameterAfterOptional - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void - { - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); - } + public function dataRequiredParameterAfterOptional(): array + { + return [ + [ + 70400, + PHP_VERSION_ID < 80000 || self::$useStaticReflectionProvider ? [] : [ + [ + 'Required parameter $bar follows optional parameter $foo', + 8, + ], + [ + 'Required parameter $bar follows optional parameter $foo', + 17, + ], + [ + 'Required parameter $bar follows optional parameter $foo', + 21, + ], + ], + ], + [ + 80000, + [ + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 8, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 17, + ], + [ + 'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.', + 21, + ], + ], + ], + ]; + } - public function testBug4641(): void - { - $this->analyse([__DIR__ . '/data/bug-4641.php'], [ - [ - 'Template type U of method Bug4641\I::getRepository() is not referenced in a parameter.', - 26, - ], - ]); - } + /** + * @dataProvider dataRequiredParameterAfterOptional + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void + { + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); + } + public function testBug4641(): void + { + $this->analyse([__DIR__ . '/data/bug-4641.php'], [ + [ + 'Template type U of method Bug4641\I::getRepository() is not referenced in a parameter.', + 26, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php index 4999bce4d2..a77d39cb6c 100644 --- a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/incompatible-default-parameter-type-methods.php'], [ - [ - 'Default value of the parameter #6 $resource (false) of method IncompatibleDefaultParameter\Foo::baz() is incompatible with type resource.', - 45, - ], - [ - 'Default value of the parameter #6 $resource (false) of method IncompatibleDefaultParameter\Foo::bar() is incompatible with type resource.', - 55, - ], - ]); - } - - public function testTraitCrash(): void - { - $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-trait-crash.php'], []); - } - - public function testBug4011(): void - { - $this->analyse([__DIR__ . '/data/bug-4011.php'], []); - } - + protected function getRule(): Rule + { + return new IncompatibleDefaultParameterTypeRule(); + } + + public function testMethods(): void + { + $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-methods.php'], [ + [ + 'Default value of the parameter #6 $resource (false) of method IncompatibleDefaultParameter\Foo::baz() is incompatible with type resource.', + 45, + ], + [ + 'Default value of the parameter #6 $resource (false) of method IncompatibleDefaultParameter\Foo::bar() is incompatible with type resource.', + 55, + ], + ]); + } + + public function testTraitCrash(): void + { + $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-trait-crash.php'], []); + } + + public function testBug4011(): void + { + $this->analyse([__DIR__ . '/data/bug-4011.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 799f02c24a..6f93f22d1d 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MethodAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new MethodAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/method-attributes.php'], [ - [ - 'Attribute class MethodAttributes\Foo does not have the method target.', - 26, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/method-attributes.php'], [ + [ + 'Attribute class MethodAttributes\Foo does not have the method target.', + 26, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index f42226d2d5..76d17dd8d1 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -1,4 +1,6 @@ -reportMaybes, $this->reportStatic), - true - ); - } + /** @var bool */ + private $reportStatic; - public function testReturnTypeRule(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse( - [ - __DIR__ . '/data/method-signature.php', - ], - [ - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseClass::parameterTypeTest4()', - 298, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseInterface::parameterTypeTest4()', - 298, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', - 305, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', - 305, - ], - [ - 'Return type (MethodSignature\Animal) of method MethodSignature\SubClass::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest4()', - 351, - ], - [ - 'Return type (MethodSignature\Animal) of method MethodSignature\SubClass::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest4()', - 351, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', - 358, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', - 358, - ], - [ - 'Parameter #1 $node (PhpParser\Node\Expr\StaticCall) of method MethodSignature\Rule::processNode() should be contravariant with parameter $node (PhpParser\Node) of method MethodSignature\GenericRule::processNode()', - 454, - ], - ] - ); - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new OverridingMethodRule( + new PhpVersion(PHP_VERSION_ID), + new MethodSignatureRule($this->reportMaybes, $this->reportStatic), + true + ); + } - public function testReturnTypeRuleTrait(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse( - [ - __DIR__ . '/data/method-signature-trait.php', - ], - [ - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseClass::parameterTypeTest4()', - 43, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseInterface::parameterTypeTest4()', - 43, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', - 50, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', - 50, - ], - [ - 'Return type (MethodSignature\Animal) of method MethodSignature\SubClassUsingTrait::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest4()', - 96, - ], - [ - 'Return type (MethodSignature\Animal) of method MethodSignature\SubClassUsingTrait::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest4()', - 96, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', - 103, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', - 103, - ], - ] - ); - } + public function testReturnTypeRule(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse( + [ + __DIR__ . '/data/method-signature.php', + ], + [ + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseClass::parameterTypeTest4()', + 298, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseInterface::parameterTypeTest4()', + 298, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', + 305, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', + 305, + ], + [ + 'Return type (MethodSignature\Animal) of method MethodSignature\SubClass::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest4()', + 351, + ], + [ + 'Return type (MethodSignature\Animal) of method MethodSignature\SubClass::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest4()', + 351, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', + 358, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', + 358, + ], + [ + 'Parameter #1 $node (PhpParser\Node\Expr\StaticCall) of method MethodSignature\Rule::processNode() should be contravariant with parameter $node (PhpParser\Node) of method MethodSignature\GenericRule::processNode()', + 454, + ], + ] + ); + } - public function testReturnTypeRuleTraitWithoutMaybes(): void - { - $this->reportMaybes = false; - $this->reportStatic = true; - $this->analyse( - [ - __DIR__ . '/data/method-signature-trait.php', - ], - [ - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', - 50, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', - 50, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', - 103, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', - 103, - ], - ] - ); - } + public function testReturnTypeRuleTrait(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse( + [ + __DIR__ . '/data/method-signature-trait.php', + ], + [ + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseClass::parameterTypeTest4()', + 43, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest4() should be contravariant with parameter $animal (MethodSignature\Animal) of method MethodSignature\BaseInterface::parameterTypeTest4()', + 43, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', + 50, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', + 50, + ], + [ + 'Return type (MethodSignature\Animal) of method MethodSignature\SubClassUsingTrait::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest4()', + 96, + ], + [ + 'Return type (MethodSignature\Animal) of method MethodSignature\SubClassUsingTrait::returnTypeTest4() should be covariant with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest4()', + 96, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', + 103, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', + 103, + ], + ] + ); + } - public function testReturnTypeRuleWithoutMaybes(): void - { - $this->reportMaybes = false; - $this->reportStatic = true; - $this->analyse( - [ - __DIR__ . '/data/method-signature.php', - ], - [ - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', - 305, - ], - [ - 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', - 305, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', - 358, - ], - [ - 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', - 358, - ], - ] - ); - } + public function testReturnTypeRuleTraitWithoutMaybes(): void + { + $this->reportMaybes = false; + $this->reportStatic = true; + $this->analyse( + [ + __DIR__ . '/data/method-signature-trait.php', + ], + [ + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', + 50, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClassUsingTrait::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', + 50, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', + 103, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClassUsingTrait::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', + 103, + ], + ] + ); + } - public function testRuleWithoutStaticMethods(): void - { - $this->reportMaybes = true; - $this->reportStatic = false; - $this->analyse([__DIR__ . '/data/method-signature-static.php'], []); - } + public function testReturnTypeRuleWithoutMaybes(): void + { + $this->reportMaybes = false; + $this->reportStatic = true; + $this->analyse( + [ + __DIR__ . '/data/method-signature.php', + ], + [ + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseClass::parameterTypeTest5()', + 305, + ], + [ + 'Parameter #1 $animal (MethodSignature\Dog) of method MethodSignature\SubClass::parameterTypeTest5() should be compatible with parameter $animal (MethodSignature\Cat) of method MethodSignature\BaseInterface::parameterTypeTest5()', + 305, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseClass::returnTypeTest5()', + 358, + ], + [ + 'Return type (MethodSignature\Cat) of method MethodSignature\SubClass::returnTypeTest5() should be compatible with return type (MethodSignature\Dog) of method MethodSignature\BaseInterface::returnTypeTest5()', + 358, + ], + ] + ); + } - public function testRuleWithStaticMethods(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/method-signature-static.php'], [ - [ - 'Parameter #1 $value (string) of method MethodSignature\Bar::doFoo() should be compatible with parameter $value (int) of method MethodSignature\Foo::doFoo()', - 24, - ], - ]); - } + public function testRuleWithoutStaticMethods(): void + { + $this->reportMaybes = true; + $this->reportStatic = false; + $this->analyse([__DIR__ . '/data/method-signature-static.php'], []); + } - public function testBug2950(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-2950.php'], []); - } + public function testRuleWithStaticMethods(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/method-signature-static.php'], [ + [ + 'Parameter #1 $value (string) of method MethodSignature\Bar::doFoo() should be compatible with parameter $value (int) of method MethodSignature\Foo::doFoo()', + 24, + ], + ]); + } - public function testBug3997(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-3997.php'], [ - [ - 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - 59, - ], - ]); - } + public function testBug2950(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-2950.php'], []); + } - public function testBug4003(): void - { - if (PHP_VERSION_ID < 70200 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 7.2 or later.'); - } - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4003.php'], [ - [ - 'Return type (string) of method Bug4003\Baz::foo() should be compatible with return type (int) of method Bug4003\Boo::foo()', - 15, - ], - [ - PHP_VERSION_ID < 70200 ? 'Parameter #1 $test (mixed) of method Bug4003\Ipsum::doFoo() does not match parameter #1 $test (int) of method Bug4003\Lorem::doFoo().' : 'Parameter #1 $test (string) of method Bug4003\Ipsum::doFoo() should be compatible with parameter $test (int) of method Bug4003\Lorem::doFoo()', - 38, - ], - ]); - } + public function testBug3997(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-3997.php'], [ + [ + 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', + 59, + ], + ]); + } - public function testBug4017(): void - { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4017.php'], []); - } + public function testBug4003(): void + { + if (PHP_VERSION_ID < 70200 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.2 or later.'); + } + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4003.php'], [ + [ + 'Return type (string) of method Bug4003\Baz::foo() should be compatible with return type (int) of method Bug4003\Boo::foo()', + 15, + ], + [ + PHP_VERSION_ID < 70200 ? 'Parameter #1 $test (mixed) of method Bug4003\Ipsum::doFoo() does not match parameter #1 $test (int) of method Bug4003\Lorem::doFoo().' : 'Parameter #1 $test (string) of method Bug4003\Ipsum::doFoo() should be compatible with parameter $test (int) of method Bug4003\Lorem::doFoo()', + 38, + ], + ]); + } - public function testBug4017Two(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4017_2.php'], [ - [ - 'Parameter #1 $a (Bug4017_2\Foo) of method Bug4017_2\Lorem::doFoo() should be compatible with parameter $a (stdClass) of method Bug4017_2\Bar::doFoo()', - 51, - ], - ]); - } + public function testBug4017(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4017.php'], []); + } - public function testBug4017Three(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4017_3.php'], [ - [ - 'Parameter #1 $a (T of stdClass) of method Bug4017_3\Lorem::doFoo() should be compatible with parameter $a (Bug4017_3\Foo) of method Bug4017_3\Bar::doFoo()', - 45, - ], - ]); - } + public function testBug4017Two(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4017_2.php'], [ + [ + 'Parameter #1 $a (Bug4017_2\Foo) of method Bug4017_2\Lorem::doFoo() should be compatible with parameter $a (stdClass) of method Bug4017_2\Bar::doFoo()', + 51, + ], + ]); + } - public function testBug4023(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4023.php'], []); - } + public function testBug4017Three(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4017_3.php'], [ + [ + 'Parameter #1 $a (T of stdClass) of method Bug4017_3\Lorem::doFoo() should be compatible with parameter $a (Bug4017_3\Foo) of method Bug4017_3\Bar::doFoo()', + 45, + ], + ]); + } - public function testBug4006(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4006.php'], []); - } + public function testBug4023(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4023.php'], []); + } - public function testBug4084(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4084.php'], []); - } + public function testBug4006(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4006.php'], []); + } - public function testBug3523(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-3523.php'], [ - [ - 'Return type (Bug3523\Baz) of method Bug3523\Baz::deserialize() should be covariant with return type (static(Bug3523\FooInterface)) of method Bug3523\FooInterface::deserialize()', - 40, - ], - ]); - } + public function testBug4084(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4084.php'], []); + } - public function testBug4707(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4707.php'], [ - [ - 'Return type (array) of method Bug4707\Block2::getChildren() should be compatible with return type (array>) of method Bug4707\ParentNodeInterface::getChildren()', - 38, - ], - ]); - } + public function testBug3523(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-3523.php'], [ + [ + 'Return type (Bug3523\Baz) of method Bug3523\Baz::deserialize() should be covariant with return type (static(Bug3523\FooInterface)) of method Bug3523\FooInterface::deserialize()', + 40, + ], + ]); + } - public function testBug4707Covariant(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4707-covariant.php'], [ - [ - 'Return type (array) of method Bug4707Covariant\Block2::getChildren() should be covariant with return type (array>) of method Bug4707Covariant\ParentNodeInterface::getChildren()', - 38, - ], - ]); - } + public function testBug4707(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4707.php'], [ + [ + 'Return type (array) of method Bug4707\Block2::getChildren() should be compatible with return type (array>) of method Bug4707\ParentNodeInterface::getChildren()', + 38, + ], + ]); + } - public function testBug4707Two(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4707-two.php'], []); - } + public function testBug4707Covariant(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4707-covariant.php'], [ + [ + 'Return type (array) of method Bug4707Covariant\Block2::getChildren() should be covariant with return type (array>) of method Bug4707Covariant\ParentNodeInterface::getChildren()', + 38, + ], + ]); + } - public function testBug4729(): void - { - $this->reportMaybes = true; - $this->reportStatic = true; - $this->analyse([__DIR__ . '/data/bug-4729.php'], []); - } + public function testBug4707Two(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4707-two.php'], []); + } + public function testBug4729(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4729.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php index bdd6304f97..48083c1fbf 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/missing-method-impl.php'], [ - [ - 'Non-abstract class MissingMethodImpl\Baz contains abstract method doBaz() from class MissingMethodImpl\Baz.', - 24, - ], - [ - 'Non-abstract class MissingMethodImpl\Baz contains abstract method doFoo() from interface MissingMethodImpl\Foo.', - 24, - ], - [ - 'Non-abstract class class@anonymous/tests/PHPStan/Rules/Methods/data/missing-method-impl.php:41 contains abstract method doFoo() from interface MissingMethodImpl\Foo.', - 41, - ], - ]); - } - - public function testBug3469(): void - { - $this->analyse([__DIR__ . '/data/bug-3469.php'], []); - } - - public function testBug3958(): void - { - $this->analyse([__DIR__ . '/data/bug-3958.php'], []); - } - + protected function getRule(): Rule + { + return new MissingMethodImplementationRule(); + } + + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/missing-method-impl.php'], [ + [ + 'Non-abstract class MissingMethodImpl\Baz contains abstract method doBaz() from class MissingMethodImpl\Baz.', + 24, + ], + [ + 'Non-abstract class MissingMethodImpl\Baz contains abstract method doFoo() from interface MissingMethodImpl\Foo.', + 24, + ], + [ + 'Non-abstract class class@anonymous/tests/PHPStan/Rules/Methods/data/missing-method-impl.php:41 contains abstract method doFoo() from interface MissingMethodImpl\Foo.', + 41, + ], + ]); + } + + public function testBug3469(): void + { + $this->analyse([__DIR__ . '/data/bug-3469.php'], []); + } + + public function testBug3958(): void + { + $this->analyse([__DIR__ . '/data/bug-3958.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index c4d4a257ee..0a7aff464e 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MissingMethodParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new MissingMethodParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true)); - } - - public function testRule(): void - { - $errors = [ - [ - 'Method MissingMethodParameterTypehint\FooInterface::getFoo() has parameter $p1 with no typehint specified.', - 8, - ], - [ - 'Method MissingMethodParameterTypehint\FooParent::getBar() has parameter $p2 with no typehint specified.', - 15, - ], - [ - 'Method MissingMethodParameterTypehint\Foo::getFoo() has parameter $p1 with no typehint specified.', - 25, - ], - [ - 'Method MissingMethodParameterTypehint\Foo::getBar() has parameter $p2 with no typehint specified.', - 33, - ], - [ - 'Method MissingMethodParameterTypehint\Foo::getBaz() has parameter $p3 with no typehint specified.', - 42, - ], - [ - 'Method MissingMethodParameterTypehint\Foo::unionTypeWithUnknownArrayValueTypehint() has parameter $a with no value type specified in iterable type array.', - 58, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Method MissingMethodParameterTypehint\Bar::acceptsGenericInterface() has parameter $i with generic interface MissingMethodParameterTypehint\GenericInterface but does not specify its types: T, U', - 91, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodParameterTypehint\Bar::acceptsGenericClass() has parameter $c with generic class MissingMethodParameterTypehint\GenericClass but does not specify its types: A, B', - 101, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodParameterTypehint\CollectionIterableAndGeneric::acceptsCollection() has parameter $collection with generic interface DoctrineIntersectionTypeIsSupertypeOf\Collection but does not specify its types: TKey, T', - 111, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodParameterTypehint\CollectionIterableAndGeneric::acceptsCollection2() has parameter $collection with generic interface DoctrineIntersectionTypeIsSupertypeOf\Collection but does not specify its types: TKey, T', - 119, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodParameterTypehint\CallableSignature::doFoo() has parameter $cb with no signature specified for callable.', - 180, - ], - ]; - - $this->analyse([__DIR__ . '/data/missing-method-parameter-typehint.php'], $errors); - } + public function testRule(): void + { + $errors = [ + [ + 'Method MissingMethodParameterTypehint\FooInterface::getFoo() has parameter $p1 with no typehint specified.', + 8, + ], + [ + 'Method MissingMethodParameterTypehint\FooParent::getBar() has parameter $p2 with no typehint specified.', + 15, + ], + [ + 'Method MissingMethodParameterTypehint\Foo::getFoo() has parameter $p1 with no typehint specified.', + 25, + ], + [ + 'Method MissingMethodParameterTypehint\Foo::getBar() has parameter $p2 with no typehint specified.', + 33, + ], + [ + 'Method MissingMethodParameterTypehint\Foo::getBaz() has parameter $p3 with no typehint specified.', + 42, + ], + [ + 'Method MissingMethodParameterTypehint\Foo::unionTypeWithUnknownArrayValueTypehint() has parameter $a with no value type specified in iterable type array.', + 58, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Method MissingMethodParameterTypehint\Bar::acceptsGenericInterface() has parameter $i with generic interface MissingMethodParameterTypehint\GenericInterface but does not specify its types: T, U', + 91, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodParameterTypehint\Bar::acceptsGenericClass() has parameter $c with generic class MissingMethodParameterTypehint\GenericClass but does not specify its types: A, B', + 101, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodParameterTypehint\CollectionIterableAndGeneric::acceptsCollection() has parameter $collection with generic interface DoctrineIntersectionTypeIsSupertypeOf\Collection but does not specify its types: TKey, T', + 111, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodParameterTypehint\CollectionIterableAndGeneric::acceptsCollection2() has parameter $collection with generic interface DoctrineIntersectionTypeIsSupertypeOf\Collection but does not specify its types: TKey, T', + 119, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodParameterTypehint\CallableSignature::doFoo() has parameter $cb with no signature specified for callable.', + 180, + ], + ]; - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - $this->analyse([__DIR__ . '/data/missing-typehint-promoted-properties.php'], [ - [ - 'Method MissingTypehintPromotedProperties\Foo::__construct() has parameter $foo with no value type specified in iterable type array.', - 8, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Method MissingTypehintPromotedProperties\Bar::__construct() has parameter $foo with no value type specified in iterable type array.', - 21, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - ]); - } + $this->analyse([__DIR__ . '/data/missing-method-parameter-typehint.php'], $errors); + } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/missing-typehint-promoted-properties.php'], [ + [ + 'Method MissingTypehintPromotedProperties\Foo::__construct() has parameter $foo with no value type specified in iterable type array.', + 8, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Method MissingTypehintPromotedProperties\Bar::__construct() has parameter $foo with no value type specified in iterable type array.', + 21, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 6b96e41be4..0600746917 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MissingMethodReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new MissingMethodReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/missing-method-return-typehint.php'], [ - [ - 'Method MissingMethodReturnTypehint\FooInterface::getFoo() has no return typehint specified.', - 8, - ], - [ - 'Method MissingMethodReturnTypehint\FooParent::getBar() has no return typehint specified.', - 15, - ], - [ - 'Method MissingMethodReturnTypehint\Foo::getFoo() has no return typehint specified.', - 25, - ], - [ - 'Method MissingMethodReturnTypehint\Foo::getBar() has no return typehint specified.', - 33, - ], - [ - 'Method MissingMethodReturnTypehint\Foo::unionTypeWithUnknownArrayValueTypehint() return type has no value type specified in iterable type array.', - 46, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Method MissingMethodReturnTypehint\Bar::returnsGenericInterface() return type with generic interface MissingMethodReturnTypehint\GenericInterface does not specify its types: T, U', - 79, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodReturnTypehint\Bar::returnsGenericClass() return type with generic class MissingMethodReturnTypehint\GenericClass does not specify its types: A, B', - 89, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Method MissingMethodReturnTypehint\CallableSignature::doFoo() return type has no signature specified for callable.', - 99, - ], - ]); - } - - public function testIndirectInheritanceBug2740(): void - { - $this->analyse([__DIR__ . '/data/bug2740.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-method-return-typehint.php'], [ + [ + 'Method MissingMethodReturnTypehint\FooInterface::getFoo() has no return typehint specified.', + 8, + ], + [ + 'Method MissingMethodReturnTypehint\FooParent::getBar() has no return typehint specified.', + 15, + ], + [ + 'Method MissingMethodReturnTypehint\Foo::getFoo() has no return typehint specified.', + 25, + ], + [ + 'Method MissingMethodReturnTypehint\Foo::getBar() has no return typehint specified.', + 33, + ], + [ + 'Method MissingMethodReturnTypehint\Foo::unionTypeWithUnknownArrayValueTypehint() return type has no value type specified in iterable type array.', + 46, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Method MissingMethodReturnTypehint\Bar::returnsGenericInterface() return type with generic interface MissingMethodReturnTypehint\GenericInterface does not specify its types: T, U', + 79, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodReturnTypehint\Bar::returnsGenericClass() return type with generic class MissingMethodReturnTypehint\GenericClass does not specify its types: A, B', + 89, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Method MissingMethodReturnTypehint\CallableSignature::doFoo() return type has no signature specified for callable.', + 99, + ], + ]); + } - public function testArrayTypehintWithoutNullInPhpDoc(): void - { - $this->analyse([__DIR__ . '/../../Analyser/data/array-typehint-without-null-in-phpdoc.php'], []); - } + public function testIndirectInheritanceBug2740(): void + { + $this->analyse([__DIR__ . '/data/bug2740.php'], []); + } - public function testBug4415(): void - { - $this->analyse([__DIR__ . '/data/bug-4415.php'], [ - [ - 'Method Bug4415Rule\CategoryCollection::getIterator() return type has no value type specified in iterable type Iterator.', - 76, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - ]); - } + public function testArrayTypehintWithoutNullInPhpDoc(): void + { + $this->analyse([__DIR__ . '/../../Analyser/data/array-typehint-without-null-in-phpdoc.php'], []); + } + public function testBug4415(): void + { + $this->analyse([__DIR__ . '/data/bug-4415.php'], [ + [ + 'Method Bug4415Rule\CategoryCollection::getIterator() return type has no value type specified in iterable type Iterator.', + 76, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php index 55fb808a0d..b19226a44a 100644 --- a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/nullsafe-method-call-rule.php'], [ - [ - 'Using nullsafe method call on non-nullable type Exception. Use -> instead.', - 16, - ], - ]); - } + public function testRule(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/nullsafe-method-call-rule.php'], [ + [ + 'Using nullsafe method call on non-nullable type Exception. Use -> instead.', + 16, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 06083ccdf0..41e03a401f 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -1,4 +1,6 @@ -phpVersionId), - new MethodSignatureRule(true, true), - false - ); - } - - public function dataOverridingFinalMethod(): array - { - return [ - [ - 70300, - 'compatible', - 'compatible', - ], - [ - 70400, - 'contravariant', - 'covariant', - ], - ]; - } - - /** - * @dataProvider dataOverridingFinalMethod - * @param int $phpVersion - * @param string $contravariantMessage - */ - public function testOverridingFinalMethod(int $phpVersion, string $contravariantMessage): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $errors = [ - [ - 'Method OverridingFinalMethod\Bar::doFoo() overrides final method OverridingFinalMethod\Foo::doFoo().', - 43, - ], - [ - 'Private method OverridingFinalMethod\Bar::doBar() overriding public method OverridingFinalMethod\Foo::doBar() should also be public.', - 48, - ], - [ - 'Protected method OverridingFinalMethod\Bar::doBaz() overriding public method OverridingFinalMethod\Foo::doBaz() should also be public.', - 53, - ], - [ - 'Private method OverridingFinalMethod\Bar::doLorem() overriding protected method OverridingFinalMethod\Foo::doLorem() should be protected or public.', - 58, - ], - [ - 'Non-static method OverridingFinalMethod\Bar::doIpsum() overrides static method OverridingFinalMethod\Foo::doIpsum().', - 63, - ], - [ - 'Static method OverridingFinalMethod\Bar::doDolor() overrides non-static method OverridingFinalMethod\Foo::doDolor().', - 68, - ], - [ - 'Parameter #1 $s (string) of method OverridingFinalMethod\Dolor::__construct() is not ' . $contravariantMessage . ' with parameter #1 $i (int) of method OverridingFinalMethod\Ipsum::__construct().', - 110, - ], - [ - 'Method OverridingFinalMethod\Dolor::doFoo() overrides method OverridingFinalMethod\Ipsum::doFoo() but misses parameter #1 $i.', - 115, - ], - [ - 'Parameter #1 $size (int) of method OverridingFinalMethod\FixedArray::setSize() is not ' . $contravariantMessage . ' with parameter #1 $size (mixed) of method SplFixedArray::setSize().', - 125, - ], - [ - 'Parameter #1 $j of method OverridingFinalMethod\Amet::doBar() is not variadic but parameter #1 $j of method OverridingFinalMethod\Sit::doBar() is variadic.', - 160, - ], - [ - 'Parameter #1 $j of method OverridingFinalMethod\Amet::doBaz() is variadic but parameter #1 $j of method OverridingFinalMethod\Sit::doBaz() is not variadic.', - 165, - ], - [ - 'Parameter #2 $j of method OverridingFinalMethod\Consecteur::doFoo() is required but parameter #2 $j of method OverridingFinalMethod\Sit::doFoo() is optional.', - 175, - ], - [ - 'Parameter #1 $i of method OverridingFinalMethod\Lacus::doFoo() is not passed by reference but parameter #1 $i of method OverridingFinalMethod\Etiam::doFoo() is passed by reference.', - 195, - ], - [ - 'Parameter #2 $j of method OverridingFinalMethod\Lacus::doFoo() is passed by reference but parameter #2 $j of method OverridingFinalMethod\Etiam::doFoo() is not passed by reference.', - 195, - ], - [ - 'Parameter #1 $i of method OverridingFinalMethod\BazBaz::doBar() is not optional.', - 205, - ], - [ - 'Parameter #2 $j of method OverridingFinalMethod\FooFoo::doFoo() is not optional.', - 225, - ], - [ - 'Method OverridingFinalMethod\SomeOtherException::__construct() overrides final method OverridingFinalMethod\OtherException::__construct().', - 280, - ], - [ - 'Parameter #1 $index (int) of method OverridingFinalMethod\FixedArrayOffsetExists::offsetExists() is not ' . $contravariantMessage . ' with parameter #1 $offset (mixed) of method ArrayAccess::offsetExists().', - 313, - ], - ]; - - if (PHP_VERSION_ID >= 80000) { - $errors = array_values(array_filter($errors, static function (array $error): bool { - return $error[1] !== 125; - })); - } - - $this->phpVersionId = $phpVersion; - $this->analyse([__DIR__ . '/data/overriding-method.php'], $errors); - } - - public function dataParameterContravariance(): array - { - return [ - [ - __DIR__ . '/data/parameter-contravariance-array.php', - 70300, - [ - [ - 'Parameter #1 $a (iterable) of method ParameterContravarianceArray\Baz::doBar() is not compatible with parameter #1 $a (array|null) of method ParameterContravarianceArray\Foo::doBar().', - 43, - ], - ], - ], - [ - __DIR__ . '/data/parameter-contravariance-array.php', - 70400, - [ - [ - 'Parameter #1 $a (iterable) of method ParameterContravarianceArray\Baz::doBar() is not contravariant with parameter #1 $a (array|null) of method ParameterContravarianceArray\Foo::doBar().', - 43, - ], - ], - ], - [ - __DIR__ . '/data/parameter-contravariance-traversable.php', - 70300, - [ - [ - 'Parameter #1 $a (iterable) of method ParameterContravarianceTraversable\Baz::doBar() is not compatible with parameter #1 $a (Traversable|null) of method ParameterContravarianceTraversable\Foo::doBar().', - 43, - ], - ], - ], - [ - __DIR__ . '/data/parameter-contravariance-traversable.php', - 70400, - [ - [ - 'Parameter #1 $a (iterable) of method ParameterContravarianceTraversable\Baz::doBar() is not contravariant with parameter #1 $a (Traversable|null) of method ParameterContravarianceTraversable\Foo::doBar().', - 43, - ], - ], - ], - [ - __DIR__ . '/data/parameter-contravariance.php', - 70300, - [ - [ - 'Parameter #1 $e (Exception) of method ParameterContravariance\Bar::doBar() is not compatible with parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Foo::doBar().', - 28, - ], - [ - 'Parameter #1 $e (Exception|null) of method ParameterContravariance\Baz::doBar() is not compatible with parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Foo::doBar().', - 38, - ], - [ - 'Parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Lorem::doFoo() is not compatible with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', - 48, - ], - [ - 'Parameter #1 $e (InvalidArgumentException|null) of method ParameterContravariance\Ipsum::doFoo() is not compatible with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', - 58, - ], - ], - ], - [ - __DIR__ . '/data/parameter-contravariance.php', - 70400, - [ - [ - 'Parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Lorem::doFoo() is not contravariant with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', - 48, - ], - [ - 'Parameter #1 $e (InvalidArgumentException|null) of method ParameterContravariance\Ipsum::doFoo() is not contravariant with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', - 58, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataParameterContravariance - * @param string $file - * @param int $phpVersion - * @param mixed[] $expectedErrors - */ - public function testParameterContravariance( - string $file, - int $phpVersion, - array $expectedErrors - ): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->phpVersionId = $phpVersion; - $this->analyse([$file], $expectedErrors); - } - - public function dataReturnTypeCovariance(): array - { - return [ - [ - 70300, - [ - [ - 'Return type iterable of method ReturnTypeCovariance\Bar::doBar() is not compatible with return type array of method ReturnTypeCovariance\Foo::doBar().', - 38, - ], - [ - 'Return type InvalidArgumentException of method ReturnTypeCovariance\Bar::doBaz() is not compatible with return type Exception of method ReturnTypeCovariance\Foo::doBaz().', - 43, - ], - [ - 'Return type Exception of method ReturnTypeCovariance\Bar::doLorem() is not compatible with return type InvalidArgumentException of method ReturnTypeCovariance\Foo::doLorem().', - 48, - ], - [ - 'Return type mixed of method ReturnTypeCovariance\B::foo() is not compatible with return type stdClass|null of method ReturnTypeCovariance\A::foo().', - 66, - ], - ], - ], - [ - 70400, - [ - [ - 'Return type iterable of method ReturnTypeCovariance\Bar::doBar() is not covariant with return type array of method ReturnTypeCovariance\Foo::doBar().', - 38, - ], - [ - 'Return type Exception of method ReturnTypeCovariance\Bar::doLorem() is not covariant with return type InvalidArgumentException of method ReturnTypeCovariance\Foo::doLorem().', - 48, - ], - [ - 'Return type mixed of method ReturnTypeCovariance\B::foo() is not covariant with return type stdClass|null of method ReturnTypeCovariance\A::foo().', - 66, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataReturnTypeCovariance - * @param int $phpVersion - * @param mixed[] $expectedErrors - */ - public function testReturnTypeCovariance( - int $phpVersion, - array $expectedErrors - ): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->phpVersionId = $phpVersion; - $this->analyse([__DIR__ . '/data/return-type-covariance.php'], $expectedErrors); - } - - /** - * @dataProvider dataOverridingFinalMethod - * @param int $phpVersion - * @param string $contravariantMessage - * @param string $covariantMessage - */ - public function testParle(int $phpVersion, string $contravariantMessage, string $covariantMessage): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->phpVersionId = $phpVersion; - $this->analyse([__DIR__ . '/data/parle.php'], [ - [ - 'Parameter #1 $state (int) of method OverridingParle\Foo::pushState() is not ' . $contravariantMessage . ' with parameter #1 $state (string) of method Parle\RLexer::pushState().', - 8, - ], - [ - 'Return type string of method OverridingParle\Foo::pushState() is not ' . $covariantMessage . ' with return type int of method Parle\RLexer::pushState().', - 8, - ], - ]); - } - - public function testVariadicParameterIsAlwaysOptional(): void - { - $this->phpVersionId = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/variadic-always-optional.php'], []); - } - - /** - * @dataProvider dataOverridingFinalMethod - * @param int $phpVersion - */ - public function testBug3403(int $phpVersion): void - { - $this->phpVersionId = $phpVersion; - $this->analyse([__DIR__ . '/data/bug-3403.php'], []); - } - - public function testBug3443(): void - { - $this->phpVersionId = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/bug-3443.php'], []); - } - - public function testBug3478(): void - { - $this->phpVersionId = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/bug-3478.php'], []); - } - - public function testBug3629(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test require static reflection.'); - } - $this->phpVersionId = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/bug-3629.php'], []); - } - - public function testVariadics(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->phpVersionId = PHP_VERSION_ID; - $errors = []; - if (PHP_VERSION_ID < 70200) { - $errors[] = [ - 'Parameter #2 $lang (mixed) of method OverridingVariadics\Translator::translate() does not match parameter #2 $parameters (string) of method OverridingVariadics\ITranslator::translate().', - 24, - ]; - } - - $errors = array_merge($errors, [ - [ - 'Parameter #2 $lang of method OverridingVariadics\OtherTranslator::translate() is not optional.', - 34, - ], - [ - 'Parameter #2 $lang of method OverridingVariadics\AnotherTranslator::translate() is not optional.', - 44, - ], - [ - 'Parameter #3 $parameters of method OverridingVariadics\AnotherTranslator::translate() is not variadic.', - 44, - ], - [ - 'Parameter #2 $lang of method OverridingVariadics\YetAnotherTranslator::translate() is not variadic.', - 54, - ], - ]); - - if (PHP_VERSION_ID < 70200) { - $errors[] = [ - 'Parameter #2 $lang (mixed) of method OverridingVariadics\YetAnotherTranslator::translate() does not match parameter #2 $parameters (string) of method OverridingVariadics\ITranslator::translate().', - 54, - ]; - } - - $this->analyse([__DIR__ . '/data/overriding-variadics.php'], $errors); - } - - public function dataLessOverridenParametersWithVariadic(): array - { - return [ - [ - 70400, - [ - [ - 'Parameter #1 $everything of method LessParametersVariadics\Bar::doFoo() is variadic but parameter #1 $many of method LessParametersVariadics\Foo::doFoo() is not variadic.', - 18, - ], - [ - 'Method LessParametersVariadics\Bar::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #2 $parameters.', - 18, - ], - [ - 'Method LessParametersVariadics\Bar::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', - 18, - ], - [ - 'Parameter #1 $everything of method LessParametersVariadics\Baz::doFoo() is variadic but parameter #1 $many of method LessParametersVariadics\Foo::doFoo() is not variadic.', - 28, - ], - [ - 'Method LessParametersVariadics\Baz::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #2 $parameters.', - 28, - ], - [ - 'Method LessParametersVariadics\Baz::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', - 28, - ], - [ - 'Parameter #2 $everything of method LessParametersVariadics\Lorem::doFoo() is variadic but parameter #2 $parameters of method LessParametersVariadics\Foo::doFoo() is not variadic.', - 38, - ], - [ - 'Method LessParametersVariadics\Lorem::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', - 38, - ], - ], - ], - [ - 80000, - [ - [ - 'Parameter #1 ...$everything (int) of method LessParametersVariadics\Baz::doFoo() is not contravariant with parameter #2 $parameters (string) of method LessParametersVariadics\Foo::doFoo().', - 28, - ], - [ - 'Parameter #1 ...$everything (int) of method LessParametersVariadics\Baz::doFoo() is not contravariant with parameter #3 $here (string) of method LessParametersVariadics\Foo::doFoo().', - 28, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataLessOverridenParametersWithVariadic - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testLessOverridenParametersWithVariadic(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/less-parameters-variadics.php'], $errors); - } - - public function dataParameterTypeWidening(): array - { - return [ - [ - 70100, - [ - [ - 'Parameter #1 $foo (mixed) of method ParameterTypeWidening\Bar::doFoo() does not match parameter #1 $foo (string) of method ParameterTypeWidening\Foo::doFoo().', - 18, - ], - ], - ], - [ - 70200, - [], - ], - ]; - } - - /** - * @dataProvider dataParameterTypeWidening - * @param int $phpVersionId - * @param mixed[] $errors - */ - public function testParameterTypeWidening(int $phpVersionId, array $errors): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->phpVersionId = $phpVersionId; - $this->analyse([__DIR__ . '/data/parameter-type-widening.php'], $errors); - } - + /** @var int */ + private $phpVersionId; + + protected function getRule(): Rule + { + return new OverridingMethodRule( + new PhpVersion($this->phpVersionId), + new MethodSignatureRule(true, true), + false + ); + } + + public function dataOverridingFinalMethod(): array + { + return [ + [ + 70300, + 'compatible', + 'compatible', + ], + [ + 70400, + 'contravariant', + 'covariant', + ], + ]; + } + + /** + * @dataProvider dataOverridingFinalMethod + * @param int $phpVersion + * @param string $contravariantMessage + */ + public function testOverridingFinalMethod(int $phpVersion, string $contravariantMessage): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $errors = [ + [ + 'Method OverridingFinalMethod\Bar::doFoo() overrides final method OverridingFinalMethod\Foo::doFoo().', + 43, + ], + [ + 'Private method OverridingFinalMethod\Bar::doBar() overriding public method OverridingFinalMethod\Foo::doBar() should also be public.', + 48, + ], + [ + 'Protected method OverridingFinalMethod\Bar::doBaz() overriding public method OverridingFinalMethod\Foo::doBaz() should also be public.', + 53, + ], + [ + 'Private method OverridingFinalMethod\Bar::doLorem() overriding protected method OverridingFinalMethod\Foo::doLorem() should be protected or public.', + 58, + ], + [ + 'Non-static method OverridingFinalMethod\Bar::doIpsum() overrides static method OverridingFinalMethod\Foo::doIpsum().', + 63, + ], + [ + 'Static method OverridingFinalMethod\Bar::doDolor() overrides non-static method OverridingFinalMethod\Foo::doDolor().', + 68, + ], + [ + 'Parameter #1 $s (string) of method OverridingFinalMethod\Dolor::__construct() is not ' . $contravariantMessage . ' with parameter #1 $i (int) of method OverridingFinalMethod\Ipsum::__construct().', + 110, + ], + [ + 'Method OverridingFinalMethod\Dolor::doFoo() overrides method OverridingFinalMethod\Ipsum::doFoo() but misses parameter #1 $i.', + 115, + ], + [ + 'Parameter #1 $size (int) of method OverridingFinalMethod\FixedArray::setSize() is not ' . $contravariantMessage . ' with parameter #1 $size (mixed) of method SplFixedArray::setSize().', + 125, + ], + [ + 'Parameter #1 $j of method OverridingFinalMethod\Amet::doBar() is not variadic but parameter #1 $j of method OverridingFinalMethod\Sit::doBar() is variadic.', + 160, + ], + [ + 'Parameter #1 $j of method OverridingFinalMethod\Amet::doBaz() is variadic but parameter #1 $j of method OverridingFinalMethod\Sit::doBaz() is not variadic.', + 165, + ], + [ + 'Parameter #2 $j of method OverridingFinalMethod\Consecteur::doFoo() is required but parameter #2 $j of method OverridingFinalMethod\Sit::doFoo() is optional.', + 175, + ], + [ + 'Parameter #1 $i of method OverridingFinalMethod\Lacus::doFoo() is not passed by reference but parameter #1 $i of method OverridingFinalMethod\Etiam::doFoo() is passed by reference.', + 195, + ], + [ + 'Parameter #2 $j of method OverridingFinalMethod\Lacus::doFoo() is passed by reference but parameter #2 $j of method OverridingFinalMethod\Etiam::doFoo() is not passed by reference.', + 195, + ], + [ + 'Parameter #1 $i of method OverridingFinalMethod\BazBaz::doBar() is not optional.', + 205, + ], + [ + 'Parameter #2 $j of method OverridingFinalMethod\FooFoo::doFoo() is not optional.', + 225, + ], + [ + 'Method OverridingFinalMethod\SomeOtherException::__construct() overrides final method OverridingFinalMethod\OtherException::__construct().', + 280, + ], + [ + 'Parameter #1 $index (int) of method OverridingFinalMethod\FixedArrayOffsetExists::offsetExists() is not ' . $contravariantMessage . ' with parameter #1 $offset (mixed) of method ArrayAccess::offsetExists().', + 313, + ], + ]; + + if (PHP_VERSION_ID >= 80000) { + $errors = array_values(array_filter($errors, static function (array $error): bool { + return $error[1] !== 125; + })); + } + + $this->phpVersionId = $phpVersion; + $this->analyse([__DIR__ . '/data/overriding-method.php'], $errors); + } + + public function dataParameterContravariance(): array + { + return [ + [ + __DIR__ . '/data/parameter-contravariance-array.php', + 70300, + [ + [ + 'Parameter #1 $a (iterable) of method ParameterContravarianceArray\Baz::doBar() is not compatible with parameter #1 $a (array|null) of method ParameterContravarianceArray\Foo::doBar().', + 43, + ], + ], + ], + [ + __DIR__ . '/data/parameter-contravariance-array.php', + 70400, + [ + [ + 'Parameter #1 $a (iterable) of method ParameterContravarianceArray\Baz::doBar() is not contravariant with parameter #1 $a (array|null) of method ParameterContravarianceArray\Foo::doBar().', + 43, + ], + ], + ], + [ + __DIR__ . '/data/parameter-contravariance-traversable.php', + 70300, + [ + [ + 'Parameter #1 $a (iterable) of method ParameterContravarianceTraversable\Baz::doBar() is not compatible with parameter #1 $a (Traversable|null) of method ParameterContravarianceTraversable\Foo::doBar().', + 43, + ], + ], + ], + [ + __DIR__ . '/data/parameter-contravariance-traversable.php', + 70400, + [ + [ + 'Parameter #1 $a (iterable) of method ParameterContravarianceTraversable\Baz::doBar() is not contravariant with parameter #1 $a (Traversable|null) of method ParameterContravarianceTraversable\Foo::doBar().', + 43, + ], + ], + ], + [ + __DIR__ . '/data/parameter-contravariance.php', + 70300, + [ + [ + 'Parameter #1 $e (Exception) of method ParameterContravariance\Bar::doBar() is not compatible with parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Foo::doBar().', + 28, + ], + [ + 'Parameter #1 $e (Exception|null) of method ParameterContravariance\Baz::doBar() is not compatible with parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Foo::doBar().', + 38, + ], + [ + 'Parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Lorem::doFoo() is not compatible with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', + 48, + ], + [ + 'Parameter #1 $e (InvalidArgumentException|null) of method ParameterContravariance\Ipsum::doFoo() is not compatible with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', + 58, + ], + ], + ], + [ + __DIR__ . '/data/parameter-contravariance.php', + 70400, + [ + [ + 'Parameter #1 $e (InvalidArgumentException) of method ParameterContravariance\Lorem::doFoo() is not contravariant with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', + 48, + ], + [ + 'Parameter #1 $e (InvalidArgumentException|null) of method ParameterContravariance\Ipsum::doFoo() is not contravariant with parameter #1 $e (Exception) of method ParameterContravariance\Foo::doFoo().', + 58, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataParameterContravariance + * @param string $file + * @param int $phpVersion + * @param mixed[] $expectedErrors + */ + public function testParameterContravariance( + string $file, + int $phpVersion, + array $expectedErrors + ): void { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersion; + $this->analyse([$file], $expectedErrors); + } + + public function dataReturnTypeCovariance(): array + { + return [ + [ + 70300, + [ + [ + 'Return type iterable of method ReturnTypeCovariance\Bar::doBar() is not compatible with return type array of method ReturnTypeCovariance\Foo::doBar().', + 38, + ], + [ + 'Return type InvalidArgumentException of method ReturnTypeCovariance\Bar::doBaz() is not compatible with return type Exception of method ReturnTypeCovariance\Foo::doBaz().', + 43, + ], + [ + 'Return type Exception of method ReturnTypeCovariance\Bar::doLorem() is not compatible with return type InvalidArgumentException of method ReturnTypeCovariance\Foo::doLorem().', + 48, + ], + [ + 'Return type mixed of method ReturnTypeCovariance\B::foo() is not compatible with return type stdClass|null of method ReturnTypeCovariance\A::foo().', + 66, + ], + ], + ], + [ + 70400, + [ + [ + 'Return type iterable of method ReturnTypeCovariance\Bar::doBar() is not covariant with return type array of method ReturnTypeCovariance\Foo::doBar().', + 38, + ], + [ + 'Return type Exception of method ReturnTypeCovariance\Bar::doLorem() is not covariant with return type InvalidArgumentException of method ReturnTypeCovariance\Foo::doLorem().', + 48, + ], + [ + 'Return type mixed of method ReturnTypeCovariance\B::foo() is not covariant with return type stdClass|null of method ReturnTypeCovariance\A::foo().', + 66, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataReturnTypeCovariance + * @param int $phpVersion + * @param mixed[] $expectedErrors + */ + public function testReturnTypeCovariance( + int $phpVersion, + array $expectedErrors + ): void { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersion; + $this->analyse([__DIR__ . '/data/return-type-covariance.php'], $expectedErrors); + } + + /** + * @dataProvider dataOverridingFinalMethod + * @param int $phpVersion + * @param string $contravariantMessage + * @param string $covariantMessage + */ + public function testParle(int $phpVersion, string $contravariantMessage, string $covariantMessage): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersion; + $this->analyse([__DIR__ . '/data/parle.php'], [ + [ + 'Parameter #1 $state (int) of method OverridingParle\Foo::pushState() is not ' . $contravariantMessage . ' with parameter #1 $state (string) of method Parle\RLexer::pushState().', + 8, + ], + [ + 'Return type string of method OverridingParle\Foo::pushState() is not ' . $covariantMessage . ' with return type int of method Parle\RLexer::pushState().', + 8, + ], + ]); + } + + public function testVariadicParameterIsAlwaysOptional(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/variadic-always-optional.php'], []); + } + + /** + * @dataProvider dataOverridingFinalMethod + * @param int $phpVersion + */ + public function testBug3403(int $phpVersion): void + { + $this->phpVersionId = $phpVersion; + $this->analyse([__DIR__ . '/data/bug-3403.php'], []); + } + + public function testBug3443(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-3443.php'], []); + } + + public function testBug3478(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-3478.php'], []); + } + + public function testBug3629(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test require static reflection.'); + } + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-3629.php'], []); + } + + public function testVariadics(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = PHP_VERSION_ID; + $errors = []; + if (PHP_VERSION_ID < 70200) { + $errors[] = [ + 'Parameter #2 $lang (mixed) of method OverridingVariadics\Translator::translate() does not match parameter #2 $parameters (string) of method OverridingVariadics\ITranslator::translate().', + 24, + ]; + } + + $errors = array_merge($errors, [ + [ + 'Parameter #2 $lang of method OverridingVariadics\OtherTranslator::translate() is not optional.', + 34, + ], + [ + 'Parameter #2 $lang of method OverridingVariadics\AnotherTranslator::translate() is not optional.', + 44, + ], + [ + 'Parameter #3 $parameters of method OverridingVariadics\AnotherTranslator::translate() is not variadic.', + 44, + ], + [ + 'Parameter #2 $lang of method OverridingVariadics\YetAnotherTranslator::translate() is not variadic.', + 54, + ], + ]); + + if (PHP_VERSION_ID < 70200) { + $errors[] = [ + 'Parameter #2 $lang (mixed) of method OverridingVariadics\YetAnotherTranslator::translate() does not match parameter #2 $parameters (string) of method OverridingVariadics\ITranslator::translate().', + 54, + ]; + } + + $this->analyse([__DIR__ . '/data/overriding-variadics.php'], $errors); + } + + public function dataLessOverridenParametersWithVariadic(): array + { + return [ + [ + 70400, + [ + [ + 'Parameter #1 $everything of method LessParametersVariadics\Bar::doFoo() is variadic but parameter #1 $many of method LessParametersVariadics\Foo::doFoo() is not variadic.', + 18, + ], + [ + 'Method LessParametersVariadics\Bar::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #2 $parameters.', + 18, + ], + [ + 'Method LessParametersVariadics\Bar::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', + 18, + ], + [ + 'Parameter #1 $everything of method LessParametersVariadics\Baz::doFoo() is variadic but parameter #1 $many of method LessParametersVariadics\Foo::doFoo() is not variadic.', + 28, + ], + [ + 'Method LessParametersVariadics\Baz::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #2 $parameters.', + 28, + ], + [ + 'Method LessParametersVariadics\Baz::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', + 28, + ], + [ + 'Parameter #2 $everything of method LessParametersVariadics\Lorem::doFoo() is variadic but parameter #2 $parameters of method LessParametersVariadics\Foo::doFoo() is not variadic.', + 38, + ], + [ + 'Method LessParametersVariadics\Lorem::doFoo() overrides method LessParametersVariadics\Foo::doFoo() but misses parameter #3 $here.', + 38, + ], + ], + ], + [ + 80000, + [ + [ + 'Parameter #1 ...$everything (int) of method LessParametersVariadics\Baz::doFoo() is not contravariant with parameter #2 $parameters (string) of method LessParametersVariadics\Foo::doFoo().', + 28, + ], + [ + 'Parameter #1 ...$everything (int) of method LessParametersVariadics\Baz::doFoo() is not contravariant with parameter #3 $here (string) of method LessParametersVariadics\Foo::doFoo().', + 28, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataLessOverridenParametersWithVariadic + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testLessOverridenParametersWithVariadic(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/less-parameters-variadics.php'], $errors); + } + + public function dataParameterTypeWidening(): array + { + return [ + [ + 70100, + [ + [ + 'Parameter #1 $foo (mixed) of method ParameterTypeWidening\Bar::doFoo() does not match parameter #1 $foo (string) of method ParameterTypeWidening\Foo::doFoo().', + 18, + ], + ], + ], + [ + 70200, + [], + ], + ]; + } + + /** + * @dataProvider dataParameterTypeWidening + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testParameterTypeWidening(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/parameter-type-widening.php'], $errors); + } } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 69e4c8efe2..f2eff6ba51 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false))); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false))); - } - - public function testReturnTypeRule(): void - { - $this->analyse([__DIR__ . '/data/returnTypes.php'], [ - [ - 'Method ReturnTypes\Foo::returnInteger() should return int but returns string.', - 20, - ], - [ - 'Method ReturnTypes\Foo::returnObject() should return ReturnTypes\Bar but returns int.', - 30, - ], - [ - 'Method ReturnTypes\Foo::returnObject() should return ReturnTypes\Bar but returns ReturnTypes\Foo.', - 34, - ], - [ - 'Method ReturnTypes\Foo::returnChild() should return ReturnTypes\Foo but returns ReturnTypes\OtherInterfaceImpl.', - 53, - ], - [ - 'Method ReturnTypes\Foo::returnVoid() with return type void returns null but should not return anything.', - 86, - ], - [ - 'Method ReturnTypes\Foo::returnVoid() with return type void returns int but should not return anything.', - 90, - ], - [ - 'Method ReturnTypes\Foo::returnStatic() should return static(ReturnTypes\Foo) but returns ReturnTypes\FooParent.', - 106, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns ReturnTypes\Foo.', - 139, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns ReturnTypes\Bar.', - 147, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns array.', - 151, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns int.', - 155, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but empty return statement found.', - 159, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\Collection.', - 166, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\AnotherCollection.', - 173, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns (iterable&ReturnTypes\AnotherCollection)|(iterable&ReturnTypes\Collection).', - 180, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\AnotherCollection.', - 187, - ], - [ - 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns null.', - 191, - ], - [ - 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns ReturnTypes\Foo.', - 219, - ], - [ - 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns int.', - 222, - ], - [ - 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns static(ReturnTypes\Foo).', - 225, - ], - [ - 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns null.', - 229, - ], - [ - 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns ReturnTypes\Foo.', - 247, - ], - [ - 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns int.', - 250, - ], - [ - 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns static(ReturnTypes\Foo).', - 259, - ], - [ - 'Method ReturnTypes\Foo::returnsParent() should return ReturnTypes\FooParent but returns int.', - 282, - ], - [ - 'Method ReturnTypes\Foo::returnsParent() should return ReturnTypes\FooParent but returns null.', - 285, - ], - [ - 'Method ReturnTypes\Foo::returnsPhpDocParent() should return ReturnTypes\FooParent but returns int.', - 298, - ], - [ - 'Method ReturnTypes\Foo::returnsPhpDocParent() should return ReturnTypes\FooParent but returns null.', - 301, - ], - [ - 'Method ReturnTypes\Foo::returnScalar() should return bool|float|int|string but returns stdClass.', - 323, - ], - [ - 'Method ReturnTypes\Foo::returnsNullInTernary() should return int but returns int|null.', - 342, - ], - [ - 'Method ReturnTypes\Foo::returnsNullInTernary() should return int but returns int|null.', - 348, - ], - [ - 'Method ReturnTypes\Foo::misleadingBoolReturnType() should return ReturnTypes\boolean but returns true.', - 355, - ], - [ - 'Method ReturnTypes\Foo::misleadingBoolReturnType() should return ReturnTypes\boolean but returns int.', - 358, - ], - [ - 'Method ReturnTypes\Foo::misleadingIntReturnType() should return ReturnTypes\integer but returns int.', - 368, - ], - [ - 'Method ReturnTypes\Foo::misleadingIntReturnType() should return ReturnTypes\integer but returns true.', - 371, - ], - [ - 'Method ReturnTypes\Stock::getAnotherStock() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', - 429, - ], - [ - 'Method ReturnTypes\Stock::returnSelfAgainError() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', - 484, - ], - [ - 'Method ReturnTypes\Stock::returnYetSelfAgainError() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', - 508, - ], - [ - 'Method ReturnTypes\ReturningSomethingFromConstructor::__construct() with return type void returns ReturnTypes\Foo but should not return anything.', - 552, - ], - [ - 'Method ReturnTypes\ReturnTernary::returnTernary() should return ReturnTypes\Foo but returns false.', - 625, - ], - [ - 'Method ReturnTypes\TrickyVoid::returnVoidOrInt() should return int|void but returns string.', - 656, - ], - [ - 'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.', - 687, - ], - [ - 'Method ReturnTypes\AppendedArrayReturnType::foo() should return array but returns array.', - 700, - ], - [ - 'Method ReturnTypes\AppendedArrayReturnType::bar() should return array but returns array.', - 710, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__toString() should return string but returns true.', - 720, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__isset() should return bool but returns int.', - 725, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__destruct() with return type void returns int but should not return anything.', - 730, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__unset() with return type void returns int but should not return anything.', - 735, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__sleep() should return array but returns array.', - 740, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__wakeup() with return type void returns int but should not return anything.', - 747, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__set_state() should return object but returns array.', - 752, - ], - [ - 'Method ReturnTypes\WrongMagicMethods::__clone() with return type void returns int but should not return anything.', - 757, - ], - [ - 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', - 815, - ], - [ - 'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.', - 838, - ], - [ - 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', - 858, - ], - [ - 'Method ReturnTypes\NestedArrayCheck::doBar() should return array but returns array>.', - 873, - ], - [ - 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns string.', - 948, - ], - [ - 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns ReturnTypes\integer.', - 951, - ], - [ - 'Method ReturnTypes\VariableOverwrittenInForeach::doFoo() should return int but returns int|string.', - 1009, - ], - [ - 'Method ReturnTypes\VariableOverwrittenInForeach::doBar() should return int but returns int|string.', - 1024, - ], - [ - 'Method ReturnTypes\ReturnStaticGeneric::instanceReturnsStatic() should return static(ReturnTypes\ReturnStaticGeneric) but returns ReturnTypes\ReturnStaticGeneric.', - 1064, - ], - [ - 'Method ReturnTypes\NeverReturn::doFoo() should never return but return statement found.', - 1238, - ], - ]); - } - - public function testMisleadingTypehintsInClassWithoutNamespace(): void - { - $this->analyse([__DIR__ . '/data/misleadingTypehints.php'], [ - [ - 'Method FooWithoutNamespace::misleadingBoolReturnType() should return boolean but returns true.', - 9, - ], - [ - 'Method FooWithoutNamespace::misleadingBoolReturnType() should return boolean but returns int.', - 13, - ], - [ - 'Method FooWithoutNamespace::misleadingIntReturnType() should return integer but returns int.', - 24, - ], - [ - 'Method FooWithoutNamespace::misleadingIntReturnType() should return integer but returns true.', - 28, - ], - ]); - } + public function testReturnTypeRule(): void + { + $this->analyse([__DIR__ . '/data/returnTypes.php'], [ + [ + 'Method ReturnTypes\Foo::returnInteger() should return int but returns string.', + 20, + ], + [ + 'Method ReturnTypes\Foo::returnObject() should return ReturnTypes\Bar but returns int.', + 30, + ], + [ + 'Method ReturnTypes\Foo::returnObject() should return ReturnTypes\Bar but returns ReturnTypes\Foo.', + 34, + ], + [ + 'Method ReturnTypes\Foo::returnChild() should return ReturnTypes\Foo but returns ReturnTypes\OtherInterfaceImpl.', + 53, + ], + [ + 'Method ReturnTypes\Foo::returnVoid() with return type void returns null but should not return anything.', + 86, + ], + [ + 'Method ReturnTypes\Foo::returnVoid() with return type void returns int but should not return anything.', + 90, + ], + [ + 'Method ReturnTypes\Foo::returnStatic() should return static(ReturnTypes\Foo) but returns ReturnTypes\FooParent.', + 106, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns ReturnTypes\Foo.', + 139, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns ReturnTypes\Bar.', + 147, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns array.', + 151, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns int.', + 155, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but empty return statement found.', + 159, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\Collection.', + 166, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\AnotherCollection.', + 173, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns (iterable&ReturnTypes\AnotherCollection)|(iterable&ReturnTypes\Collection).', + 180, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns iterable&ReturnTypes\AnotherCollection.', + 187, + ], + [ + 'Method ReturnTypes\Foo::returnUnionIterableType() should return array|(iterable&ReturnTypes\Collection) but returns null.', + 191, + ], + [ + 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns ReturnTypes\Foo.', + 219, + ], + [ + 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns int.', + 222, + ], + [ + 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns static(ReturnTypes\Foo).', + 225, + ], + [ + 'Method ReturnTypes\Foo::returnThis() should return $this(ReturnTypes\Foo) but returns null.', + 229, + ], + [ + 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns ReturnTypes\Foo.', + 247, + ], + [ + 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns int.', + 250, + ], + [ + 'Method ReturnTypes\Foo::returnThisOrNull() should return $this(ReturnTypes\Foo)|null but returns static(ReturnTypes\Foo).', + 259, + ], + [ + 'Method ReturnTypes\Foo::returnsParent() should return ReturnTypes\FooParent but returns int.', + 282, + ], + [ + 'Method ReturnTypes\Foo::returnsParent() should return ReturnTypes\FooParent but returns null.', + 285, + ], + [ + 'Method ReturnTypes\Foo::returnsPhpDocParent() should return ReturnTypes\FooParent but returns int.', + 298, + ], + [ + 'Method ReturnTypes\Foo::returnsPhpDocParent() should return ReturnTypes\FooParent but returns null.', + 301, + ], + [ + 'Method ReturnTypes\Foo::returnScalar() should return bool|float|int|string but returns stdClass.', + 323, + ], + [ + 'Method ReturnTypes\Foo::returnsNullInTernary() should return int but returns int|null.', + 342, + ], + [ + 'Method ReturnTypes\Foo::returnsNullInTernary() should return int but returns int|null.', + 348, + ], + [ + 'Method ReturnTypes\Foo::misleadingBoolReturnType() should return ReturnTypes\boolean but returns true.', + 355, + ], + [ + 'Method ReturnTypes\Foo::misleadingBoolReturnType() should return ReturnTypes\boolean but returns int.', + 358, + ], + [ + 'Method ReturnTypes\Foo::misleadingIntReturnType() should return ReturnTypes\integer but returns int.', + 368, + ], + [ + 'Method ReturnTypes\Foo::misleadingIntReturnType() should return ReturnTypes\integer but returns true.', + 371, + ], + [ + 'Method ReturnTypes\Stock::getAnotherStock() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', + 429, + ], + [ + 'Method ReturnTypes\Stock::returnSelfAgainError() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', + 484, + ], + [ + 'Method ReturnTypes\Stock::returnYetSelfAgainError() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', + 508, + ], + [ + 'Method ReturnTypes\ReturningSomethingFromConstructor::__construct() with return type void returns ReturnTypes\Foo but should not return anything.', + 552, + ], + [ + 'Method ReturnTypes\ReturnTernary::returnTernary() should return ReturnTypes\Foo but returns false.', + 625, + ], + [ + 'Method ReturnTypes\TrickyVoid::returnVoidOrInt() should return int|void but returns string.', + 656, + ], + [ + 'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.', + 687, + ], + [ + 'Method ReturnTypes\AppendedArrayReturnType::foo() should return array but returns array.', + 700, + ], + [ + 'Method ReturnTypes\AppendedArrayReturnType::bar() should return array but returns array.', + 710, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__toString() should return string but returns true.', + 720, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__isset() should return bool but returns int.', + 725, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__destruct() with return type void returns int but should not return anything.', + 730, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__unset() with return type void returns int but should not return anything.', + 735, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__sleep() should return array but returns array.', + 740, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__wakeup() with return type void returns int but should not return anything.', + 747, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__set_state() should return object but returns array.', + 752, + ], + [ + 'Method ReturnTypes\WrongMagicMethods::__clone() with return type void returns int but should not return anything.', + 757, + ], + [ + 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', + 815, + ], + [ + 'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.', + 838, + ], + [ + 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', + 858, + ], + [ + 'Method ReturnTypes\NestedArrayCheck::doBar() should return array but returns array>.', + 873, + ], + [ + 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns string.', + 948, + ], + [ + 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns ReturnTypes\integer.', + 951, + ], + [ + 'Method ReturnTypes\VariableOverwrittenInForeach::doFoo() should return int but returns int|string.', + 1009, + ], + [ + 'Method ReturnTypes\VariableOverwrittenInForeach::doBar() should return int but returns int|string.', + 1024, + ], + [ + 'Method ReturnTypes\ReturnStaticGeneric::instanceReturnsStatic() should return static(ReturnTypes\ReturnStaticGeneric) but returns ReturnTypes\ReturnStaticGeneric.', + 1064, + ], + [ + 'Method ReturnTypes\NeverReturn::doFoo() should never return but return statement found.', + 1238, + ], + ]); + } - public function testOverridenTypeFromIfConditionShouldNotBeMixedAfterBranch(): void - { - $this->analyse([__DIR__ . '/data/returnTypes-overridenTypeInIfCondition.php'], [ - [ - 'Method ReturnTypes\OverridenTypeInIfCondition::getAnotherAnotherStock() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', - 15, - ], - ]); - } + public function testMisleadingTypehintsInClassWithoutNamespace(): void + { + $this->analyse([__DIR__ . '/data/misleadingTypehints.php'], [ + [ + 'Method FooWithoutNamespace::misleadingBoolReturnType() should return boolean but returns true.', + 9, + ], + [ + 'Method FooWithoutNamespace::misleadingBoolReturnType() should return boolean but returns int.', + 13, + ], + [ + 'Method FooWithoutNamespace::misleadingIntReturnType() should return integer but returns int.', + 24, + ], + [ + 'Method FooWithoutNamespace::misleadingIntReturnType() should return integer but returns true.', + 28, + ], + ]); + } - public function testReturnStaticFromParent(): void - { - $this->analyse([__DIR__ . '/data/return-static-from-parent.php'], []); - } + public function testOverridenTypeFromIfConditionShouldNotBeMixedAfterBranch(): void + { + $this->analyse([__DIR__ . '/data/returnTypes-overridenTypeInIfCondition.php'], [ + [ + 'Method ReturnTypes\OverridenTypeInIfCondition::getAnotherAnotherStock() should return ReturnTypes\Stock but returns ReturnTypes\Stock|null.', + 15, + ], + ]); + } - public function testReturnIterable(): void - { - $this->analyse([__DIR__ . '/data/returnTypes-iterable.php'], [ - [ - 'Method ReturnTypesIterable\Foo::stringIterable() should return iterable but returns array.', - 27, - ], - [ - 'Method ReturnTypesIterable\Foo::stringIterablePipe() should return iterable but returns array.', - 36, - ], - ]); - } + public function testReturnStaticFromParent(): void + { + $this->analyse([__DIR__ . '/data/return-static-from-parent.php'], []); + } - public function testBug2676(): void - { - $this->analyse([__DIR__ . '/data/bug-2676.php'], []); - } + public function testReturnIterable(): void + { + $this->analyse([__DIR__ . '/data/returnTypes-iterable.php'], [ + [ + 'Method ReturnTypesIterable\Foo::stringIterable() should return iterable but returns array.', + 27, + ], + [ + 'Method ReturnTypesIterable\Foo::stringIterablePipe() should return iterable but returns array.', + 36, + ], + ]); + } - public function testBug2885(): void - { - $this->analyse([__DIR__ . '/data/bug-2885.php'], []); - } + public function testBug2676(): void + { + $this->analyse([__DIR__ . '/data/bug-2676.php'], []); + } - public function testMergeInheritedPhpDocs(): void - { - $this->analyse([__DIR__ . '/data/merge-inherited-return.php'], [ - [ - 'Method ReturnTypePhpDocMergeReturnInherited\ParentClass::method() should return ReturnTypePhpDocMergeReturnInherited\B but returns ReturnTypePhpDocMergeReturnInherited\A.', - 33, - ], - [ - 'Method ReturnTypePhpDocMergeReturnInherited\ChildClass::method() should return ReturnTypePhpDocMergeReturnInherited\B but returns ReturnTypePhpDocMergeReturnInherited\A.', - 41, - ], - [ - 'Method ReturnTypePhpDocMergeReturnInherited\ChildClass2::method() should return ReturnTypePhpDocMergeReturnInherited\D but returns ReturnTypePhpDocMergeReturnInherited\B.', - 52, - ], - ]); - } + public function testBug2885(): void + { + $this->analyse([__DIR__ . '/data/bug-2885.php'], []); + } - public function testReturnTypeRulePhp70(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->analyse([__DIR__ . '/data/returnTypes-7.0.php'], [ - [ - 'Method ReturnTypes\FooPhp70::returnInteger() should return int but empty return statement found.', - 10, - ], - ]); - } + public function testMergeInheritedPhpDocs(): void + { + $this->analyse([__DIR__ . '/data/merge-inherited-return.php'], [ + [ + 'Method ReturnTypePhpDocMergeReturnInherited\ParentClass::method() should return ReturnTypePhpDocMergeReturnInherited\B but returns ReturnTypePhpDocMergeReturnInherited\A.', + 33, + ], + [ + 'Method ReturnTypePhpDocMergeReturnInherited\ChildClass::method() should return ReturnTypePhpDocMergeReturnInherited\B but returns ReturnTypePhpDocMergeReturnInherited\A.', + 41, + ], + [ + 'Method ReturnTypePhpDocMergeReturnInherited\ChildClass2::method() should return ReturnTypePhpDocMergeReturnInherited\D but returns ReturnTypePhpDocMergeReturnInherited\B.', + 52, + ], + ]); + } - public function testBug3997(): void - { - $this->analyse([__DIR__ . '/data/bug-3997.php'], [ - [ - 'Method Bug3997\Foo::count() should return int but returns string.', - 12, - ], - [ - 'Method Bug3997\Bar::count() should return int but returns string.', - 22, - ], - [ - 'Method Bug3997\Baz::count() should return int but returns string.', - 35, - ], - [ - 'Method Bug3997\Lorem::count() should return int but returns string.', - 48, - ], - ]); - } + public function testReturnTypeRulePhp70(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/returnTypes-7.0.php'], [ + [ + 'Method ReturnTypes\FooPhp70::returnInteger() should return int but empty return statement found.', + 10, + ], + ]); + } - public function testBug1903(): void - { - $this->analyse([__DIR__ . '/data/bug-1903.php'], [ - [ - 'Method Bug1903\Test::doFoo() should return array but returns int.', - 19, - ], - ]); - } + public function testBug3997(): void + { + $this->analyse([__DIR__ . '/data/bug-3997.php'], [ + [ + 'Method Bug3997\Foo::count() should return int but returns string.', + 12, + ], + [ + 'Method Bug3997\Bar::count() should return int but returns string.', + 22, + ], + [ + 'Method Bug3997\Baz::count() should return int but returns string.', + 35, + ], + [ + 'Method Bug3997\Lorem::count() should return int but returns string.', + 48, + ], + ]); + } - public function testBug3117(): void - { - $this->analyse([__DIR__ . '/data/bug-3117.php'], [ - [ - 'Method Bug3117\SimpleTemporal::adjustInto() should return T of Bug3117\Temporal but returns $this(Bug3117\SimpleTemporal).', - 35, - ], - ]); - } + public function testBug1903(): void + { + $this->analyse([__DIR__ . '/data/bug-1903.php'], [ + [ + 'Method Bug1903\Test::doFoo() should return array but returns int.', + 19, + ], + ]); + } - public function testBug3034(): void - { - $this->analyse([__DIR__ . '/data/bug-3034.php'], []); - } + public function testBug3117(): void + { + $this->analyse([__DIR__ . '/data/bug-3117.php'], [ + [ + 'Method Bug3117\SimpleTemporal::adjustInto() should return T of Bug3117\Temporal but returns $this(Bug3117\SimpleTemporal).', + 35, + ], + ]); + } - public function testInferArrayKey(): void - { - $this->analyse([__DIR__ . '/data/infer-array-key.php'], []); - } + public function testBug3034(): void + { + $this->analyse([__DIR__ . '/data/bug-3034.php'], []); + } - public function testBug4590(): void - { - $this->analyse([__DIR__ . '/data/bug-4590.php'], [ - [ - 'Method Bug4590\Controller::test1() should return Bug4590\OkResponse> but returns Bug4590\OkResponse string)>.', - 39, - ], - [ - 'Method Bug4590\Controller::test2() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', - 47, - ], - [ - 'Method Bug4590\Controller::test3() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', - 55, - ], - ]); - } + public function testInferArrayKey(): void + { + $this->analyse([__DIR__ . '/data/infer-array-key.php'], []); + } - public function testTemplateStringBound(): void - { - $this->analyse([__DIR__ . '/data/template-string-bound.php'], []); - } + public function testBug4590(): void + { + $this->analyse([__DIR__ . '/data/bug-4590.php'], [ + [ + 'Method Bug4590\Controller::test1() should return Bug4590\OkResponse> but returns Bug4590\OkResponse string)>.', + 39, + ], + [ + 'Method Bug4590\Controller::test2() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', + 47, + ], + [ + 'Method Bug4590\Controller::test3() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', + 55, + ], + ]); + } - public function testBug4605(): void - { - $this->analyse([__DIR__ . '/data/bug-4605.php'], []); - } + public function testTemplateStringBound(): void + { + $this->analyse([__DIR__ . '/data/template-string-bound.php'], []); + } - public function testReturnStatic(): void - { - $this->analyse([__DIR__ . '/data/return-static.php'], []); - } + public function testBug4605(): void + { + $this->analyse([__DIR__ . '/data/bug-4605.php'], []); + } - public function testBug4648(): void - { - $this->analyse([__DIR__ . '/data/bug-4648.php'], []); - } + public function testReturnStatic(): void + { + $this->analyse([__DIR__ . '/data/return-static.php'], []); + } - public function testBug3523(): void - { - $this->analyse([__DIR__ . '/data/bug-3523.php'], [ - [ - 'Method Bug3523\Bar::deserialize() should return static(Bug3523\Bar) but returns Bug3523\Bar.', - 31, - ], - ]); - } + public function testBug4648(): void + { + $this->analyse([__DIR__ . '/data/bug-4648.php'], []); + } - public function testBug3120(): void - { - $this->analyse([__DIR__ . '/data/bug-3120.php'], []); - } + public function testBug3523(): void + { + $this->analyse([__DIR__ . '/data/bug-3523.php'], [ + [ + 'Method Bug3523\Bar::deserialize() should return static(Bug3523\Bar) but returns Bug3523\Bar.', + 31, + ], + ]); + } - public function testBug3118(): void - { - $this->analyse([__DIR__ . '/data/bug-3118.php'], [ - [ - 'Method Bug3118\CustomEnum2::all() should return Bug3118\EnumSet but returns Bug3118\CustomEnumSet.', - 56, - ], - ]); - } + public function testBug3120(): void + { + $this->analyse([__DIR__ . '/data/bug-3120.php'], []); + } - public function testBug4795(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0'); - } - $this->analyse([__DIR__ . '/data/bug-4795.php'], []); - } + public function testBug3118(): void + { + $this->analyse([__DIR__ . '/data/bug-3118.php'], [ + [ + 'Method Bug3118\CustomEnum2::all() should return Bug3118\EnumSet but returns Bug3118\CustomEnumSet.', + 56, + ], + ]); + } - public function testBug4803(): void - { - $this->analyse([__DIR__ . '/../../Analyser/data/bug-4803.php'], []); - } + public function testBug4795(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/bug-4795.php'], []); + } + public function testBug4803(): void + { + $this->analyse([__DIR__ . '/../../Analyser/data/bug-4803.php'], []); + } } diff --git a/tests/PHPStan/Rules/Methods/data/abstract-method.php b/tests/PHPStan/Rules/Methods/data/abstract-method.php index 49f2a33d14..49769153e9 100644 --- a/tests/PHPStan/Rules/Methods/data/abstract-method.php +++ b/tests/PHPStan/Rules/Methods/data/abstract-method.php @@ -4,21 +4,15 @@ abstract class Foo { - - abstract public function doFoo(): void; - + abstract public function doFoo(): void; } class Bar { - - abstract public function doBar(): void; - + abstract public function doBar(): void; } interface Baz { - - abstract public function doBar(): void; - + abstract public function doBar(): void; } diff --git a/tests/PHPStan/Rules/Methods/data/accept-throwable.php b/tests/PHPStan/Rules/Methods/data/accept-throwable.php index aa40e1b99c..046f83c725 100644 --- a/tests/PHPStan/Rules/Methods/data/accept-throwable.php +++ b/tests/PHPStan/Rules/Methods/data/accept-throwable.php @@ -4,49 +4,42 @@ interface SomeInterface { - } interface InterfaceExtendingThrowable extends \Throwable { - } class NonExceptionClass { - } class Foo { + public function doFoo(\Throwable $e) + { + } - public function doFoo(\Throwable $e) - { - - } - - public function doBar(int $i) - { - - } - + public function doBar(int $i) + { + } } function () { - $foo = new Foo(); - try { - maybeThrows(); - } catch (SomeInterface $e) { - $foo->doFoo($e); - $foo->doBar($e); - } catch (InterfaceExtendingThrowable $e) { - $foo->doFoo($e); - $foo->doBar($e); - } catch (NonExceptionClass $e) { - $foo->doFoo($e); // fine, the feasibility must be checked by a different rule - $foo->doBar($e); - } catch (\Exception $e) { - $foo->doFoo($e); - $foo->doBar($e); - } + $foo = new Foo(); + try { + maybeThrows(); + } catch (SomeInterface $e) { + $foo->doFoo($e); + $foo->doBar($e); + } catch (InterfaceExtendingThrowable $e) { + $foo->doFoo($e); + $foo->doBar($e); + } catch (NonExceptionClass $e) { + $foo->doFoo($e); // fine, the feasibility must be checked by a different rule + $foo->doBar($e); + } catch (\Exception $e) { + $foo->doFoo($e); + $foo->doBar($e); + } }; diff --git a/tests/PHPStan/Rules/Methods/data/aliased-traits-problem.php b/tests/PHPStan/Rules/Methods/data/aliased-traits-problem.php index c922840740..d022c7efbc 100644 --- a/tests/PHPStan/Rules/Methods/data/aliased-traits-problem.php +++ b/tests/PHPStan/Rules/Methods/data/aliased-traits-problem.php @@ -4,34 +4,28 @@ trait Foo { - - public function fooBaz($var = null) - { - - } - + public function fooBaz($var = null) + { + } } trait Baz { + use Foo { + fooBaz as protected fooBazFromFoo; + } - use Foo { - fooBaz as protected fooBazFromFoo; - } - - public function fooBaz($var, $var2 = null) - { - - } + public function fooBaz($var, $var2 = null) + { + } } class Test { + use Baz; - use Baz; - - public function test() - { - $this->fooBaz('foo', 'baz'); - } + public function test() + { + $this->fooBaz('foo', 'baz'); + } } diff --git a/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php b/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php index 449d409b2c..204d4d8a4e 100644 --- a/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php +++ b/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php @@ -1,60 +1,52 @@ -= 7.4 += 7.4 namespace CallArrowFunctionBind; class Foo { + private function privateMethod() + { + } - private function privateMethod() - { - - } - - public function publicMethod() - { - - } - + public function publicMethod() + { + } } class Bar { - - public function fooMethod(): Foo - { - \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, Foo::class); - \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, Foo::class); - \Closure::bind(fn () => $this->fooMethod(), $nonexistent, self::class); - \Closure::bind(fn () => $this->barMethod(), $nonexistent, self::class); - \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, 'CallArrowFunctionBind\Foo'); - \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, 'CallArrowFunctionBind\Foo'); - \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, new Foo()); - \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, new Foo()); - \Closure::bind(fn () => $this->privateMethod(), $this->fooMethod(), Foo::class); - \Closure::bind(fn () => $this->nonexistentMethod(), $this->fooMethod(), Foo::class); - - (fn () => $this->publicMethod())->call(new Foo()); - } - + public function fooMethod(): Foo + { + \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, Foo::class); + \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, Foo::class); + \Closure::bind(fn () => $this->fooMethod(), $nonexistent, self::class); + \Closure::bind(fn () => $this->barMethod(), $nonexistent, self::class); + \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, 'CallArrowFunctionBind\Foo'); + \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, 'CallArrowFunctionBind\Foo'); + \Closure::bind(fn (Foo $foo) => $foo->privateMethod(), null, new Foo()); + \Closure::bind(fn (Foo $foo) => $foo->nonexistentMethod(), null, new Foo()); + \Closure::bind(fn () => $this->privateMethod(), $this->fooMethod(), Foo::class); + \Closure::bind(fn () => $this->nonexistentMethod(), $this->fooMethod(), Foo::class); + + (fn () => $this->publicMethod())->call(new Foo()); + } } class BazVoid { - - public function doFoo(): void - { - - } - - public function doBar(): void - { - $this->doBaz(fn () => $this->doFoo()); - } - - public function doBaz(callable $cb): void - { - - } - + public function doFoo(): void + { + } + + public function doBar(): void + { + $this->doBaz(fn () => $this->doFoo()); + } + + public function doBaz(callable $cb): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-1656.php b/tests/PHPStan/Rules/Methods/data/bug-1656.php index 8a66acd00a..e30b320980 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-1656.php +++ b/tests/PHPStan/Rules/Methods/data/bug-1656.php @@ -4,13 +4,13 @@ class HelloWorld { - public function test(): void - { - return; - } + public function test(): void + { + return; + } - public function testVoidResult(): void - { - true or $this->test(); - } + public function testVoidResult(): void + { + true or $this->test(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-1661.php b/tests/PHPStan/Rules/Methods/data/bug-1661.php index ef6b04750a..f26938148a 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-1661.php +++ b/tests/PHPStan/Rules/Methods/data/bug-1661.php @@ -2,41 +2,46 @@ namespace Bug1661; -interface I1 { - public function someMethod(); +interface I1 +{ + public function someMethod(); } -interface I2 { - public function someOtherMethod(); +interface I2 +{ + public function someOtherMethod(); } -class Foo { - /** - * @return I1&static - */ - public function bar() { - if ($this instanceof I1) { - return $this; - } - - throw new \Exception('bad'); - } - - /** - * @return I2&static - */ - public function bat() { - if ($this instanceof I2) { - return $this; - } - - throw new \Exception('bad'); - } +class Foo +{ + /** + * @return I1&static + */ + public function bar() + { + if ($this instanceof I1) { + return $this; + } + + throw new \Exception('bad'); + } + + /** + * @return I2&static + */ + public function bat() + { + if ($this instanceof I2) { + return $this; + } + + throw new \Exception('bad'); + } } function (): void { - $a = (new Foo)->bar(); - $b = $a->bat(); - $b->someMethod(); - $b->someOtherMethod(); + $a = (new Foo())->bar(); + $b = $a->bat(); + $b->someMethod(); + $b->someOtherMethod(); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-1903.php b/tests/PHPStan/Rules/Methods/data/bug-1903.php index 5317a00547..93bc78c037 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-1903.php +++ b/tests/PHPStan/Rules/Methods/data/bug-1903.php @@ -4,19 +4,17 @@ class Test { + private $answersOrder = []; - private $answersOrder = []; + public function doFoo(string $qId): array + { + if (null !== $this->answersOrder[$qId]) { + return $this->answersOrder[$qId]; + } - public function doFoo(string $qId): array - { - if (null !== $this->answersOrder[$qId]) { - return $this->answersOrder[$qId]; - } + $this->answersOrder[$qId] = 5; - $this->answersOrder[$qId] = 5; - - return $this->answersOrder[$qId]; - } - + return $this->answersOrder[$qId]; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-1971.php b/tests/PHPStan/Rules/Methods/data/bug-1971.php index 0002418488..cf7191725e 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-1971.php +++ b/tests/PHPStan/Rules/Methods/data/bug-1971.php @@ -4,15 +4,15 @@ class HelloWorld { - public function sayHello(): void - { - echo 'Hello'; - } + public function sayHello(): void + { + echo 'Hello'; + } - public function getClosure(): void - { - $closure1 = \Closure::fromCallable([self::class, 'sayHello']); - $closure2 = \Closure::fromCallable([static::class, 'sayHello']); - $closure2 = \Closure::fromCallable([static::class, 'sayHello2']); - } + public function getClosure(): void + { + $closure1 = \Closure::fromCallable([self::class, 'sayHello']); + $closure2 = \Closure::fromCallable([static::class, 'sayHello']); + $closure2 = \Closure::fromCallable([static::class, 'sayHello2']); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2164.php b/tests/PHPStan/Rules/Methods/data/bug-2164.php index 95bfd393f3..2d63ad5101 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2164.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2164.php @@ -4,35 +4,35 @@ class A { - /** - * @param static|string $arg - * @return void - */ - public static function staticTest($arg) - { - } + /** + * @param static|string $arg + * @return void + */ + public static function staticTest($arg) + { + } } class B extends A { - /** - * @param B|string $arg - * @return void - */ - public function test($arg) - { - B::staticTest($arg); - } + /** + * @param B|string $arg + * @return void + */ + public function test($arg) + { + B::staticTest($arg); + } } final class B2 extends A { - /** - * @param B2|string $arg - * @return void - */ - public function test($arg) - { - B2::staticTest($arg); - } + /** + * @param B2|string $arg + * @return void + */ + public function test($arg) + { + B2::staticTest($arg); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2298.php b/tests/PHPStan/Rules/Methods/data/bug-2298.php index 88e907629d..7c3d7ca267 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2298.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2298.php @@ -4,37 +4,31 @@ abstract class BitbucketDriver { + /** + * @var VcsDriver + */ + protected $fallbackDriver; - /** - * @var VcsDriver - */ - protected $fallbackDriver; - - protected $rootIdentifier; - + protected $rootIdentifier; } class HgBitbucketDriver extends BitbucketDriver { - - public function getRootIdentifier(): string - { - if ($this->fallbackDriver) { - return $this->fallbackDriver->getRootIdentifier(); - } - - if (null === $this->rootIdentifier) { - return $this->fallbackDriver->getRootIdentifier(); - } - - return 'foo'; - } - + public function getRootIdentifier(): string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getRootIdentifier(); + } + + if (null === $this->rootIdentifier) { + return $this->fallbackDriver->getRootIdentifier(); + } + + return 'foo'; + } } interface VcsDriver { - - public function getRootIdentifier(): string; - + public function getRootIdentifier(): string; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2600.php b/tests/PHPStan/Rules/Methods/data/bug-2600.php index 3fcd0a1092..e22f541d10 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2600.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2600.php @@ -3,18 +3,18 @@ namespace Bug2600; function (Foo $foo): void { - $foo->doFoo(); - $foo->doFoo(1, 2, 3); + $foo->doFoo(); + $foo->doFoo(1, 2, 3); - $foo->doBar(); - $foo->doBar(1, 2, 3); + $foo->doBar(); + $foo->doBar(1, 2, 3); - $foo->doBaz(); - $foo->doBaz(1, 2, 3); + $foo->doBaz(); + $foo->doBaz(1, 2, 3); - $foo->doLorem(); - $foo->doLorem(1, 2, 3); + $foo->doLorem(); + $foo->doLorem(1, 2, 3); - $foo->doIpsum(); - $foo->doIpsum(1, 2, 3); + $foo->doIpsum(); + $foo->doIpsum(1, 2, 3); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-2676.php b/tests/PHPStan/Rules/Methods/data/bug-2676.php index 0f095acc36..6492cdc5e9 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2676.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2676.php @@ -6,7 +6,6 @@ class BankAccount { - } /** @@ -15,19 +14,19 @@ class BankAccount */ class Wallet { - /** - * @var Collection - * - * @ORM\OneToMany(targetEntity=BankAccount::class, mappedBy="wallet") - * @ORM\OrderBy({"id" = "ASC"}) - */ - private $bankAccountList; + /** + * @var Collection + * + * @ORM\OneToMany(targetEntity=BankAccount::class, mappedBy="wallet") + * @ORM\OrderBy({"id" = "ASC"}) + */ + private $bankAccountList; - /** - * @return Collection - */ - public function getBankAccountList(): Collection - { - return $this->bankAccountList; - } + /** + * @return Collection + */ + public function getBankAccountList(): Collection + { + return $this->bankAccountList; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2837.php b/tests/PHPStan/Rules/Methods/data/bug-2837.php index 286d8450bf..ba8f2325e3 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2837.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2837.php @@ -1,55 +1,55 @@ -make($class); - } - - + use X; + /** + * @phpstan-param class-string $class + */ + public function demo(string $class): void + { + $this->make($class); + } } -trait X { - /** - * @phpstan-template T of object - * @phpstan-param class-string $class - * @phpstan-return T - */ - protected function make(string $class) : object - { - $reflection = new \ReflectionClass($class); - - return $reflection->newInstanceWithoutConstructor(); - } +trait X +{ + /** + * @phpstan-template T of object + * @phpstan-param class-string $class + * @phpstan-return T + */ + protected function make(string $class): object + { + $reflection = new \ReflectionClass($class); + + return $reflection->newInstanceWithoutConstructor(); + } } class HelloWorld2 { - - /** - * @phpstan-param class-string $class - */ - public function demo(string $class): void - { - $this->make($class); - } - - /** - * @phpstan-template T of object - * @phpstan-param class-string $class - * @phpstan-return T - */ - protected function make(string $class) : object - { - $reflection = new \ReflectionClass($class); - - return $reflection->newInstanceWithoutConstructor(); - } + /** + * @phpstan-param class-string $class + */ + public function demo(string $class): void + { + $this->make($class); + } + + /** + * @phpstan-template T of object + * @phpstan-param class-string $class + * @phpstan-return T + */ + protected function make(string $class): object + { + $reflection = new \ReflectionClass($class); + + return $reflection->newInstanceWithoutConstructor(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2885.php b/tests/PHPStan/Rules/Methods/data/bug-2885.php index 982c0a0d46..220680927c 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2885.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2885.php @@ -4,11 +4,11 @@ class Test { - /** - * @return static - */ - function do() - { - return $this->do()->do(); - } + /** + * @return static + */ + public function do() + { + return $this->do()->do(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-2950.php b/tests/PHPStan/Rules/Methods/data/bug-2950.php index c1a3da07c7..d0d422650b 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-2950.php +++ b/tests/PHPStan/Rules/Methods/data/bug-2950.php @@ -4,44 +4,42 @@ class BlockFactory { - - /** @var \ArrayObject */ - private static $fullList = null; - - public static function isRegistered(int $id): bool - { - $b = self::$fullList[$id << 4]; - return $b !== null; - } + /** @var \ArrayObject */ + private static $fullList = null; + + public static function isRegistered(int $id): bool + { + $b = self::$fullList[$id << 4]; + return $b !== null; + } } class Attribute { - - public function getId(): int - { - return 0; - } - - public function isSyncable(): bool - { - return false; - } - - public function isDesynchronized(): bool - { - return false; - } - - public function getValue(): float - { - return 0.0; - } - - public function setValue(float $f) : self - { - return $this; - } + public function getId(): int + { + return 0; + } + + public function isSyncable(): bool + { + return false; + } + + public function isDesynchronized(): bool + { + return false; + } + + public function getValue(): float + { + return 0.0; + } + + public function setValue(float $f): self + { + return $this; + } } @@ -51,69 +49,67 @@ public function setValue(float $f) : self */ class AttributeMap implements \ArrayAccess { - - /** @var Attribute[] */ - private $attributes = []; - - public function addAttribute(Attribute $attribute): void - { - $this->attributes[$attribute->getId()] = $attribute; - } - - public function getAttribute(int $id): ?Attribute - { - return $this->attributes[$id] ?? null; - } - - /** - * @return Attribute[] - */ - public function getAll(): array - { - return $this->attributes; - } - - /** - * @return Attribute[] - */ - public function needSend(): array - { - return array_filter($this->attributes, function(Attribute $attribute){ - return $attribute->isSyncable() and $attribute->isDesynchronized(); - }); - } - - /** - * @param int $offset - */ - public function offsetExists($offset): bool - { - return isset($this->attributes[$offset]); - } - - /** - * @param int $offset - */ - public function offsetGet($offset): float - { - return $this->attributes[$offset]->getValue(); - } - - /** - * @param int|null $offset - * @param float $value - */ - public function offsetSet($offset, $value): void - { - $this->attributes[$offset]->setValue($value); - } - - /** - * @param int $offset - */ - public function offsetUnset($offset): void - { - throw new \RuntimeException("Could not unset an attribute from an attribute map"); - } - + /** @var Attribute[] */ + private $attributes = []; + + public function addAttribute(Attribute $attribute): void + { + $this->attributes[$attribute->getId()] = $attribute; + } + + public function getAttribute(int $id): ?Attribute + { + return $this->attributes[$id] ?? null; + } + + /** + * @return Attribute[] + */ + public function getAll(): array + { + return $this->attributes; + } + + /** + * @return Attribute[] + */ + public function needSend(): array + { + return array_filter($this->attributes, function (Attribute $attribute) { + return $attribute->isSyncable() and $attribute->isDesynchronized(); + }); + } + + /** + * @param int $offset + */ + public function offsetExists($offset): bool + { + return isset($this->attributes[$offset]); + } + + /** + * @param int $offset + */ + public function offsetGet($offset): float + { + return $this->attributes[$offset]->getValue(); + } + + /** + * @param int|null $offset + * @param float $value + */ + public function offsetSet($offset, $value): void + { + $this->attributes[$offset]->setValue($value); + } + + /** + * @param int $offset + */ + public function offsetUnset($offset): void + { + throw new \RuntimeException("Could not unset an attribute from an attribute map"); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3034.php b/tests/PHPStan/Rules/Methods/data/bug-3034.php index a83ebe47a5..ac1e1abdc5 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3034.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3034.php @@ -7,16 +7,16 @@ */ class HelloWorld implements \IteratorAggregate { - /** - * @var array - */ - private $list; + /** + * @var array + */ + private $list; - /** - * @return \ArrayIterator - */ - public function getIterator() - { - return new \ArrayIterator($this->list); - } + /** + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->list); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3117.php b/tests/PHPStan/Rules/Methods/data/bug-3117.php index 07670f436b..7727643f96 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3117.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3117.php @@ -4,46 +4,40 @@ interface Temporal { - - /** - * @return static - */ - public function adjustedWith(TemporalAdjuster $adjuster): Temporal; - + /** + * @return static + */ + public function adjustedWith(TemporalAdjuster $adjuster): Temporal; } interface TemporalAdjuster { - - /** - * @template T of Temporal - * - * @param T $temporal - * - * @return T - */ - public function adjustInto(Temporal $temporal) : Temporal; - + /** + * @template T of Temporal + * + * @param T $temporal + * + * @return T + */ + public function adjustInto(Temporal $temporal): Temporal; } final class SimpleTemporal implements Temporal, TemporalAdjuster { - - public function adjustInto(Temporal $temporal): Temporal - { - if ($temporal instanceof self) { - return $this; - } - - return $temporal->adjustedWith($this); - } - - /** - * @return static - */ - public function adjustedWith(TemporalAdjuster $adjuster): Temporal - { - return $this; - } - + public function adjustInto(Temporal $temporal): Temporal + { + if ($temporal instanceof self) { + return $this; + } + + return $temporal->adjustedWith($this); + } + + /** + * @return static + */ + public function adjustedWith(TemporalAdjuster $adjuster): Temporal + { + return $this; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3118.php b/tests/PHPStan/Rules/Methods/data/bug-3118.php index b8d7a930bf..6e6eae7485 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3118.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3118.php @@ -7,26 +7,26 @@ */ class EnumSet { - private $type; - - /** - * @param class-string $type - */ - public function __construct(string $type) - { - $this->type = $type; - } + private $type; + + /** + * @param class-string $type + */ + public function __construct(string $type) + { + $this->type = $type; + } } abstract class Enum { - /** - * @return EnumSet - */ - public static function all(): EnumSet - { - return new EnumSet(static::class); - } + /** + * @return EnumSet + */ + public static function all(): EnumSet + { + return new EnumSet(static::class); + } } /** @@ -34,25 +34,24 @@ public static function all(): EnumSet */ final class CustomEnumSet extends EnumSet { - - public function __construct() - { - parent::__construct(CustomEnum::class); - } + public function __construct() + { + parent::__construct(CustomEnum::class); + } } final class CustomEnum extends Enum { - public static function all(): EnumSet - { - return new CustomEnumSet(); - } + public static function all(): EnumSet + { + return new CustomEnumSet(); + } } class CustomEnum2 extends Enum { - public static function all(): EnumSet - { - return new CustomEnumSet(); - } + public static function all(): EnumSet + { + return new CustomEnumSet(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3120.php b/tests/PHPStan/Rules/Methods/data/bug-3120.php index 41039c47aa..5fc21412a7 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3120.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3120.php @@ -2,41 +2,45 @@ namespace Bug3120; -class A { - /** @return static */ - public static function getInstance() { - $class = static::class; - return new $class(); - } +class A +{ + /** @return static */ + public static function getInstance() + { + $class = static::class; + return new $class(); + } } -final class AChild extends A { - public static function getInstance() { - return new AChild(); - } +final class AChild extends A +{ + public static function getInstance() + { + return new AChild(); + } } class Test { - final public function __construct() - {} - - /** - * @return static - */ - public function foo(): self - { - return self::bar(new static()); - } + final public function __construct() + { + } - /** - * @phpstan-template T of Test - * @phpstan-param T $object - * @phpstan-return T - */ - public function bar(Test $object): self - { - return $object; - } + /** + * @return static + */ + public function foo(): self + { + return self::bar(new static()); + } + /** + * @phpstan-template T of Test + * @phpstan-param T $object + * @phpstan-return T + */ + public function bar(Test $object): self + { + return $object; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3403.php b/tests/PHPStan/Rules/Methods/data/bug-3403.php index 2222208736..4f74872505 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3403.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3403.php @@ -4,17 +4,12 @@ interface Foo { - - public function bar(...$baz): void; - + public function bar(...$baz): void; } class AFoo implements Foo { - - public function bar(...$baz): void - { - - } - + public function bar(...$baz): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3406.php b/tests/PHPStan/Rules/Methods/data/bug-3406.php index 49ae9ed5b0..4a639c99cf 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3406.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3406.php @@ -4,34 +4,25 @@ abstract class AbstractFoo { + public function myFoo(): void + { + } - public function myFoo(): void - { - - } - - public function myBar(): void - { - - } - + public function myBar(): void + { + } } trait TraitFoo { + abstract public function myFoo(): void; - abstract public function myFoo(): void; - - public function myBar(): void - { - - } - + public function myBar(): void + { + } } final class ClassFoo extends AbstractFoo { - - use TraitFoo; - + use TraitFoo; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3406_2.php b/tests/PHPStan/Rules/Methods/data/bug-3406_2.php index e119e64027..6c5f97e29e 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3406_2.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3406_2.php @@ -4,33 +4,24 @@ trait AbstractTrait { - - abstract public function test(): void; - + abstract public function test(): void; } trait ImplTrait { - - public function test(): void - { - - } - + public function test(): void + { + } } class Test { - - use AbstractTrait; - use ImplTrait; - + use AbstractTrait; + use ImplTrait; } class Test2 { - - use ImplTrait; - use AbstractTrait; - + use ImplTrait; + use AbstractTrait; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3409.php b/tests/PHPStan/Rules/Methods/data/bug-3409.php index 99875ebbf9..06e02212c2 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3409.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3409.php @@ -4,15 +4,12 @@ class Foo { - - public function doFoo() - { - $this->doBar(); - } - - public function doBar(?callable $callback = null, ...$args): void - { - - } - + public function doFoo() + { + $this->doBar(); + } + + public function doBar(?callable $callback = null, ...$args): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3415-2.php b/tests/PHPStan/Rules/Methods/data/bug-3415-2.php index 492a0775e5..852120e6d8 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3415-2.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3415-2.php @@ -1,21 +1,26 @@ -bar(); - $this->baz(); - } + public function __construct() + { + $this->bar(); + $this->baz(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3415.php b/tests/PHPStan/Rules/Methods/data/bug-3415.php index f7246641ee..01fecde932 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3415.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3415.php @@ -1,25 +1,31 @@ -bar(); - $this->baz(); - } + public function __construct() + { + $this->bar(); + $this->baz(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3443.php b/tests/PHPStan/Rules/Methods/data/bug-3443.php index ecc9612f36..2a93d5987c 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3443.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3443.php @@ -7,15 +7,13 @@ */ interface CollectionInterface { - - /** - * @param mixed $data - * @param mixed ...$parameters - * - * @return mixed - */ - public static function with($data = [], ...$parameters); - + /** + * @param mixed $data + * @param mixed ...$parameters + * + * @return mixed + */ + public static function with($data = [], ...$parameters); } @@ -24,27 +22,24 @@ public static function with($data = [], ...$parameters); */ final class Collection implements CollectionInterface { - - public static function with($data = [], ...$parameters) - { - return new self(); - } - + public static function with($data = [], ...$parameters) + { + return new self(); + } } -interface TranslatorInterface{ - - /** - * @param array $additionalParametersToInjectIntoTranslation - */ - public function translate(string $translationKey, bool $upperCaseFirst = true, ...$additionalParametersToInjectIntoTranslation) : string; +interface TranslatorInterface +{ + /** + * @param array $additionalParametersToInjectIntoTranslation + */ + public function translate(string $translationKey, bool $upperCaseFirst = true, ...$additionalParametersToInjectIntoTranslation): string; } class Translator implements TranslatorInterface { - - public function translate(string $translationKey, bool $upperCaseFirst = true, ...$additionalParametersToInjectIntoTranslation) : string - { - return 'some fancy translation with possibly some parameters injected'; - } + public function translate(string $translationKey, bool $upperCaseFirst = true, ...$additionalParametersToInjectIntoTranslation): string + { + return 'some fancy translation with possibly some parameters injected'; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3445.php b/tests/PHPStan/Rules/Methods/data/bug-3445.php index eb641dd6ac..d57c1707d3 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3445.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3445.php @@ -4,28 +4,22 @@ class Foo { + public function doFoo(self $test): void + { + } - public function doFoo(self $test): void - { - - } - - public function doBar($test = UnknownClass::BAR): void - { - - } - + public function doBar($test = UnknownClass::BAR): void + { + } } class Bar { + public function doFoo(Foo $foo) + { + $foo->doFoo(new Foo()); + $foo->doFoo($this); - public function doFoo(Foo $foo) - { - $foo->doFoo(new Foo()); - $foo->doFoo($this); - - $foo->doBar(new \stdClass()); - } - + $foo->doBar(new \stdClass()); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3448.php b/tests/PHPStan/Rules/Methods/data/bug-3448.php index d4053edf8c..698c59b1ed 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3448.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3448.php @@ -6,19 +6,19 @@ final class Foo { - public static function add(int $lall): void - { - $args = func_get_args(); - } + public static function add(int $lall): void + { + $args = func_get_args(); + } } final class UseFoo { - public static function do(): void - { - Foo::add(1, [new \stdClass()]); + public static function do(): void + { + Foo::add(1, [new \stdClass()]); - Foo::add('foo'); - Foo::add('foo', [new \stdClass()]); - } + Foo::add('foo'); + Foo::add('foo', [new \stdClass()]); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3469.php b/tests/PHPStan/Rules/Methods/data/bug-3469.php index 911d1929c4..31d755597e 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3469.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3469.php @@ -2,28 +2,33 @@ namespace Bug3469; -abstract class Tile{ - abstract protected function readSaveData() : void; +abstract class Tile +{ + abstract protected function readSaveData(): void; - abstract protected function writeSaveData() : void; + abstract protected function writeSaveData(): void; } -abstract class Spawnable extends Tile{ +abstract class Spawnable extends Tile +{ } -trait NameableTrait{ +trait NameableTrait +{ + protected function loadName(): void + { + } - protected function loadName() : void{ - } - - protected function saveName() : void{ - } + protected function saveName(): void + { + } } -class EnchantTable extends Spawnable{ - use NameableTrait { - loadName as readSaveData; - saveName as writeSaveData; - } +class EnchantTable extends Spawnable +{ + use NameableTrait { + loadName as readSaveData; + saveName as writeSaveData; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3478.php b/tests/PHPStan/Rules/Methods/data/bug-3478.php index 68b6993ec5..e688954adb 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3478.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3478.php @@ -4,8 +4,8 @@ class ExtendedDocument extends \DOMDocument { - public function saveHTML(\DOMNode $node = null) - { - return parent::saveHTML($node); - } + public function saveHTML(\DOMNode $node = null) + { + return parent::saveHTML($node); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3481.php b/tests/PHPStan/Rules/Methods/data/bug-3481.php index ab473711e0..2e38eed727 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3481.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3481.php @@ -4,42 +4,42 @@ class Foo { - /** - * @param string $a - * @param int $b - * @param string $c - */ - public function doSomething($a, $b, $c): void - { - } + /** + * @param string $a + * @param int $b + * @param string $c + */ + public function doSomething($a, $b, $c): void + { + } } function (): void { - $args = [ - 'foo', - 1, - 'bar', - ]; - $foo = new Foo(); - $foo->doSomething(...$args); + $args = [ + 'foo', + 1, + 'bar', + ]; + $foo = new Foo(); + $foo->doSomething(...$args); }; function (): void { - $args = ['foo', 1]; - if (rand(0, 1)) { - $args[] = 'bar'; - } + $args = ['foo', 1]; + if (rand(0, 1)) { + $args[] = 'bar'; + } - $foo = new Foo(); - $foo->doSomething(...$args); + $foo = new Foo(); + $foo->doSomething(...$args); }; function (): void { - $args = ['foo', 1, 'string']; - if (rand(0, 1)) { - $args[0] = 1; - } + $args = ['foo', 1, 'string']; + if (rand(0, 1)) { + $args[0] = 1; + } - $foo = new Foo(); - $foo->doSomething(...$args); + $foo = new Foo(); + $foo->doSomething(...$args); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-3523.php b/tests/PHPStan/Rules/Methods/data/bug-3523.php index 99d5762446..0b82cbee61 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3523.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3523.php @@ -4,41 +4,41 @@ interface FooInterface { - /** - * @return static - */ - public function deserialize(); + /** + * @return static + */ + public function deserialize(); } final class Foo implements FooInterface { - /** - * @return static - */ - public function deserialize(): self - { - return new self(); - } + /** + * @return static + */ + public function deserialize(): self + { + return new self(); + } } class Bar implements FooInterface { - /** - * @return static - */ - public function deserialize(): self - { - return new self(); - } + /** + * @return static + */ + public function deserialize(): self + { + return new self(); + } } class Baz implements FooInterface { - /** - * @return self - */ - public function deserialize(): self - { - return new self(); - } + /** + * @return self + */ + public function deserialize(): self + { + return new self(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3534.php b/tests/PHPStan/Rules/Methods/data/bug-3534.php index 11a9508ad2..91377c666a 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3534.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3534.php @@ -4,19 +4,19 @@ class MyClassWithErrorInPHPDocs { + /** + * The PHP docs return type below is wrong + * @return void + */ + public function terminate(): bool + { + return true; + } - /** - * The PHP docs return type below is wrong - * @return void - */ - public function terminate(): bool - { - return true; - } - - public function foo(){ - if ($this->terminate()) { - echo "C'est fini"; - } - } + public function foo() + { + if ($this->terminate()) { + echo "C'est fini"; + } + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3546.php b/tests/PHPStan/Rules/Methods/data/bug-3546.php index 939600c450..1976a8b1e1 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3546.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3546.php @@ -2,27 +2,33 @@ namespace Bug3546; -interface MyInterface{} -class MyClass implements MyInterface{} +interface MyInterface +{ +} +class MyClass implements MyInterface +{ +} /** @phpstan-template T of MyInterface */ -interface SomeInterface{} +interface SomeInterface +{ +} /** * @phpstan-template T of MyInterface */ abstract class MyAbstractService { - /** @phpstan-var SomeInterface */ - private $someInterface; + /** @phpstan-var SomeInterface */ + private $someInterface; - /** - * @phpstan-param SomeInterface $someInterface - */ - public function __construct(SomeInterface $someInterface) - { - $this->someInterface = $someInterface; - } + /** + * @phpstan-param SomeInterface $someInterface + */ + public function __construct(SomeInterface $someInterface) + { + $this->someInterface = $someInterface; + } } /** @@ -30,11 +36,11 @@ public function __construct(SomeInterface $someInterface) */ class MyService extends MyAbstractService { - /** - * @phpstan-param SomeInterface $someInterface - */ - public function __construct(SomeInterface $someInterface) - { - parent::__construct($someInterface); - } + /** + * @phpstan-param SomeInterface $someInterface + */ + public function __construct(SomeInterface $someInterface) + { + parent::__construct($someInterface); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3629.php b/tests/PHPStan/Rules/Methods/data/bug-3629.php index 320197ad09..85a4f5b1e6 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3629.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3629.php @@ -4,8 +4,8 @@ class HelloWorld extends \Thread { - public function start(int $options = PTHREADS_INHERIT_ALL) - { - return parent::start($options); - } + public function start(int $options = PTHREADS_INHERIT_ALL) + { + return parent::start($options); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3641.php b/tests/PHPStan/Rules/Methods/data/bug-3641.php index 8480c30ac6..9a92781a31 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3641.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3641.php @@ -4,10 +4,10 @@ class Foo { - public function bar(): int - { - return 5; - } + public function bar(): int + { + return 5; + } } /** @@ -15,19 +15,19 @@ public function bar(): int */ class Bar { - /** - * @param mixed[] $args - * @return mixed - */ - public static function __callStatic(string $method, $args) - { - $instance = new Foo; + /** + * @param mixed[] $args + * @return mixed + */ + public static function __callStatic(string $method, $args) + { + $instance = new Foo(); - return $instance->$method(...$args); - } + return $instance->$method(...$args); + } } function (): void { - Bar::bar(); - Bar::bar(1); + Bar::bar(); + Bar::bar(1); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-3683.php b/tests/PHPStan/Rules/Methods/data/bug-3683.php index 34c834dd44..2c99e4f281 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3683.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3683.php @@ -3,6 +3,6 @@ namespace Bug3683; function (\Generator $g): void { - $g->throw(new \Exception()); - $g->throw(1); + $g->throw(new \Exception()); + $g->throw(1); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-3997.php b/tests/PHPStan/Rules/Methods/data/bug-3997.php index 83362ddc2c..93e4d31541 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3997.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3997.php @@ -6,59 +6,49 @@ class Foo implements Countable { - - public function count() - { - return 'foo'; - } - + public function count() + { + return 'foo'; + } } class Bar implements Countable { - - public function count(): int - { - return 'foo'; - } - + public function count(): int + { + return 'foo'; + } } class Baz implements Countable { - - /** - * @return int - */ - public function count(): int - { - return 'foo'; - } - + /** + * @return int + */ + public function count(): int + { + return 'foo'; + } } class Lorem implements Countable { - - /** - * @return int - */ - public function count() - { - return 'foo'; - } - + /** + * @return int + */ + public function count() + { + return 'foo'; + } } class Ipsum implements Countable { - - /** - * @return string - */ - public function count() - { - return 'foo'; - } - + /** + * @return string + */ + public function count() + { + return 'foo'; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4003.php b/tests/PHPStan/Rules/Methods/data/bug-4003.php index 6906e7c7d4..6cc5bfa79b 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4003.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4003.php @@ -4,54 +4,52 @@ class Boo { - /** @return int */ - public function foo() - { - return 1; - } + /** @return int */ + public function foo() + { + return 1; + } } -class Baz extends Boo { - public function foo(): string - { - return 'test'; - } +class Baz extends Boo +{ + public function foo(): string + { + return 'test'; + } } class Lorem { - - public function doFoo(int $test) - { - - } - + public function doFoo(int $test) + { + } } class Ipsum extends Lorem { - - /** - * @param string $test - */ - public function doFoo($test) - { - - } - + /** + * @param string $test + */ + public function doFoo($test) + { + } } -interface Dolor { - /** - * @return void - * @phpstan-return never - */ - public function bar(); +interface Dolor +{ + /** + * @return void + * @phpstan-return never + */ + public function bar(); } -class Amet implements Dolor { - public function bar(): void { - throw new \Exception(); - } +class Amet implements Dolor +{ + public function bar(): void + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4006.php b/tests/PHPStan/Rules/Methods/data/bug-4006.php index 079f34d1ae..cfd3385900 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4006.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4006.php @@ -4,20 +4,16 @@ interface Foo { - - /** - * @return never - */ - public function bar(); - + /** + * @return never + */ + public function bar(); } class Bar implements Foo { - - public function bar(): void - { - throw new \Exception(); - } - + public function bar(): void + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4008.php b/tests/PHPStan/Rules/Methods/data/bug-4008.php index 7d93147d5f..771872dda5 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4008.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4008.php @@ -5,27 +5,35 @@ /** @template TModlel of BaseModel */ abstract class GenericClass { - /** @var OtherGenericClass */ - private $otherGenericClass; + /** @var OtherGenericClass */ + private $otherGenericClass; - /** @param OtherGenericClass $otherGenericClass */ - public function __construct(OtherGenericClass $otherGenericClass){ - $this->otherGenericClass = $otherGenericClass; - } + /** @param OtherGenericClass $otherGenericClass */ + public function __construct(OtherGenericClass $otherGenericClass) + { + $this->otherGenericClass = $otherGenericClass; + } } /** @extends GenericClass */ class ChildGenericClass extends GenericClass { - /** @param OtherGenericClass $otherGenericClass */ - public function __construct(OtherGenericClass $otherGenericClass){ - parent::__construct($otherGenericClass); - } + /** @param OtherGenericClass $otherGenericClass */ + public function __construct(OtherGenericClass $otherGenericClass) + { + parent::__construct($otherGenericClass); + } } /** @template TModlel of BaseModel */ -class OtherGenericClass{} +class OtherGenericClass +{ +} -abstract class BaseModel{} +abstract class BaseModel +{ +} -class Model extends BaseModel{} +class Model extends BaseModel +{ +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-4011.php b/tests/PHPStan/Rules/Methods/data/bug-4011.php index 6ec3c6cd66..3c71a2b309 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4011.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4011.php @@ -4,8 +4,7 @@ class ParseException extends \Exception { - public function __construct(string $message = '', $code = '') - { - - } + public function __construct(string $message = '', $code = '') + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4017.php b/tests/PHPStan/Rules/Methods/data/bug-4017.php index c374328688..65a2117255 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4017.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4017.php @@ -7,17 +7,16 @@ */ interface DoctrineEntityRepository { - } interface DoctrineEntityManagerInterface { - /** - * @template T - * @param class-string $className - * @return DoctrineEntityRepository - */ - public function getRepository(string $className): DoctrineEntityRepository; + /** + * @template T + * @param class-string $className + * @return DoctrineEntityRepository + */ + public function getRepository(string $className): DoctrineEntityRepository; } @@ -31,10 +30,10 @@ interface MyEntityRepositoryInterface extends DoctrineEntityRepository interface MyEntityManagerInterface extends DoctrineEntityManagerInterface { - /** - * @template T - * @param class-string $className - * @return MyEntityRepositoryInterface - */ - public function getRepository(string $className): MyEntityRepositoryInterface; + /** + * @template T + * @param class-string $className + * @return MyEntityRepositoryInterface + */ + public function getRepository(string $className): MyEntityRepositoryInterface; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4017_2.php b/tests/PHPStan/Rules/Methods/data/bug-4017_2.php index 30adaa35ca..58e4d85c2e 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4017_2.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4017_2.php @@ -4,7 +4,6 @@ class Foo { - } /** @@ -12,15 +11,12 @@ class Foo */ class Bar { - - /** - * @param T $a - */ - public function doFoo($a) - { - - } - + /** + * @param T $a + */ + public function doFoo($a) + { + } } /** @@ -28,15 +24,12 @@ public function doFoo($a) */ class Baz extends Bar { - - /** - * @param Foo $a - */ - public function doFoo($a) - { - - } - + /** + * @param Foo $a + */ + public function doFoo($a) + { + } } /** @@ -44,13 +37,10 @@ public function doFoo($a) */ class Lorem extends Bar { - - /** - * @param Foo $a - */ - public function doFoo($a) - { - - } - + /** + * @param Foo $a + */ + public function doFoo($a) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4017_3.php b/tests/PHPStan/Rules/Methods/data/bug-4017_3.php index f458c85bf2..b14da519e3 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4017_3.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4017_3.php @@ -4,47 +4,37 @@ class Foo { - } class Bar { - - /** - * @template T of Foo - * @param T $a - */ - public function doFoo($a) - { - - } - + /** + * @template T of Foo + * @param T $a + */ + public function doFoo($a) + { + } } class Baz extends Bar { - - /** - * @template T of Foo - * @param T $a - */ - public function doFoo($a) - { - - } - + /** + * @template T of Foo + * @param T $a + */ + public function doFoo($a) + { + } } class Lorem extends Bar { - - /** - * @template T of \stdClass - * @param T $a - */ - public function doFoo($a) - { - - } - + /** + * @template T of \stdClass + * @param T $a + */ + public function doFoo($a) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4023.php b/tests/PHPStan/Rules/Methods/data/bug-4023.php index ad1df962a7..83bb7c3274 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4023.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4023.php @@ -4,27 +4,27 @@ interface A { - /** - * @template T of object - * - * @param mixed[]|T $data - * - * @return T - */ - public function x($data): object; + /** + * @template T of object + * + * @param mixed[]|T $data + * + * @return T + */ + public function x($data): object; } final class B implements A { - /** - * @template T of object - * - * @param mixed[]|T $data - * - * @return T - */ - public function x($data): object - { - throw new \Exception(); - } + /** + * @template T of object + * + * @param mixed[]|T $data + * + * @return T + */ + public function x($data): object + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4084.php b/tests/PHPStan/Rules/Methods/data/bug-4084.php index 068ef3733b..f376028382 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4084.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4084.php @@ -4,14 +4,20 @@ class Handler implements \SessionUpdateTimestampHandlerInterface { - /** - * @param string $sessionId - * @param string $data - */ - public function updateTimestamp($sessionId, $data) { return true; } + /** + * @param string $sessionId + * @param string $data + */ + public function updateTimestamp($sessionId, $data) + { + return true; + } - /** - * @param string $sessionId The session id - */ - public function validateId($sessionId) { return true; } + /** + * @param string $sessionId The session id + */ + public function validateId($sessionId) + { + return true; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4188.php b/tests/PHPStan/Rules/Methods/data/bug-4188.php index 6b0744876c..5bf30c399a 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4188.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4188.php @@ -1,32 +1,40 @@ -= 7.4 += 7.4 namespace Bug4188; -interface A {} -interface B {} +interface A +{ +} +interface B +{ +} class Test { - /** @param array $data */ - public function set(array $data): void - { - $this->onlyB(array_filter( - $data, - function ($param): bool { - return $param instanceof B; - }, - )); - } + /** @param array $data */ + public function set(array $data): void + { + $this->onlyB(array_filter( + $data, + function ($param): bool { + return $param instanceof B; + }, + )); + } - /** @param array $data */ - public function setShort(array $data): void - { - $this->onlyB(array_filter( - $data, - fn($param): bool => $param instanceof B, - )); - } + /** @param array $data */ + public function setShort(array $data): void + { + $this->onlyB(array_filter( + $data, + fn ($param): bool => $param instanceof B, + )); + } - /** @param B[] $data */ - public function onlyB(array $data): void {} + /** @param B[] $data */ + public function onlyB(array $data): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4199.php b/tests/PHPStan/Rules/Methods/data/bug-4199.php index e9c307c6c8..5505a5edfc 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4199.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4199.php @@ -1,38 +1,40 @@ -= 8.0 += 8.0 namespace Bug4199; class Foo { - public function getBar(): ?Bar - { - return null; - } + public function getBar(): ?Bar + { + return null; + } } class Bar { - public function getBaz(): Baz - { - return new Baz(); - } - public function getBazOrNull(): ?Baz - { - return null; - } + public function getBaz(): Baz + { + return new Baz(); + } + public function getBazOrNull(): ?Baz + { + return null; + } } class Baz { - public function answer(): int - { - return 42; - } + public function answer(): int + { + return 42; + } } function (): void { - $foo = new Foo; - $answer = $foo->getBar()?->getBaz()->answer(); + $foo = new Foo(); + $answer = $foo->getBar()?->getBaz()->answer(); - $answer2 = $foo->getBar()?->getBazOrNull()->answer(); + $answer2 = $foo->getBar()?->getBazOrNull()->answer(); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-4214.php b/tests/PHPStan/Rules/Methods/data/bug-4214.php index d741ffeb4d..5650d27363 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4214.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4214.php @@ -2,10 +2,12 @@ namespace Bug4214; -trait AbstractTrait { - abstract public function getMessage(); +trait AbstractTrait +{ + abstract public function getMessage(); } -class Test extends \Exception { - use AbstractTrait; +class Test extends \Exception +{ + use AbstractTrait; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4232.php b/tests/PHPStan/Rules/Methods/data/bug-4232.php index 050e548128..51ac13d92c 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4232.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4232.php @@ -2,12 +2,11 @@ namespace Bug4232; -function (\ReflectionMethod $m): bool -{ - try { - $m->getPrototype(); - return true; - } catch (\ReflectionException $e) { - return false; - } +function (\ReflectionMethod $m): bool { + try { + $m->getPrototype(); + return true; + } catch (\ReflectionException $e) { + return false; + } }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-4415.php b/tests/PHPStan/Rules/Methods/data/bug-4415.php index e440b56e79..02ed04f3b0 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4415.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4415.php @@ -8,15 +8,15 @@ */ interface CollectionInterface extends \IteratorAggregate { - /** - * @param T $item - */ - public function has($item): bool; + /** + * @param T $item + */ + public function has($item): bool; - /** - * @return self - */ - public function sort(): self; + /** + * @return self + */ + public function sort(): self; } /** @@ -25,11 +25,11 @@ public function sort(): self; */ interface MutableCollectionInterface extends CollectionInterface { - /** - * @param T $item - * @phpstan-return self - */ - public function add($item): self; + /** + * @param T $item + * @phpstan-return self + */ + public function add($item): self; } /** @@ -37,12 +37,12 @@ public function add($item): self; */ interface CategoryCollectionInterface extends CollectionInterface { - public function has($item): bool; + public function has($item): bool; - /** - * @phpstan-return \Iterator - */ - public function getIterator(): \Iterator; + /** + * @phpstan-return \Iterator + */ + public function getIterator(): \Iterator; } /** @@ -54,34 +54,35 @@ interface MutableCategoryCollectionInterface extends CategoryCollectionInterface class CategoryCollection implements MutableCategoryCollectionInterface { - /** @var array */ - private $categories = []; + /** @var array */ + private $categories = []; - public function add($item): MutableCollectionInterface - { - $this->categories[$item->getName()] = $item; - return $this; - } + public function add($item): MutableCollectionInterface + { + $this->categories[$item->getName()] = $item; + return $this; + } - public function has($item): bool - { - return isset($this->categories[$item->getName()]); - } + public function has($item): bool + { + return isset($this->categories[$item->getName()]); + } - public function sort(): CollectionInterface - { - return $this; - } + public function sort(): CollectionInterface + { + return $this; + } - public function getIterator(): \Iterator - { - return new \ArrayIterator($this->categories); - } + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->categories); + } } -class Category { - public function getName(): string - { - return ''; - } +class Category +{ + public function getName(): string + { + return ''; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4455-constructor.php b/tests/PHPStan/Rules/Methods/data/bug-4455-constructor.php index d5d91501ea..480560f61b 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4455-constructor.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4455-constructor.php @@ -4,16 +4,17 @@ class HelloWorld { - public function sayHello(string $_): bool - { - new self(); - } + public function sayHello(string $_): bool + { + new self(); + } - /** - * @psalm-pure - * @return never - */ - public function __construct() { - throw new \Exception(); - } + /** + * @psalm-pure + * @return never + */ + public function __construct() + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4455-static.php b/tests/PHPStan/Rules/Methods/data/bug-4455-static.php index 3ec30e9aba..7eba144826 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4455-static.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4455-static.php @@ -4,20 +4,21 @@ class HelloWorld { - public function sayHello(string $_): bool - { - if($_ ===''){ - return true; - } + public function sayHello(string $_): bool + { + if ($_ ==='') { + return true; + } - self::nope(); - } + self::nope(); + } - /** - * @psalm-pure - * @return never - */ - public static function nope() { - throw new \Exception(); - } + /** + * @psalm-pure + * @return never + */ + public static function nope() + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4455.php b/tests/PHPStan/Rules/Methods/data/bug-4455.php index 2ad299cf86..e46a724cf7 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4455.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4455.php @@ -4,20 +4,21 @@ class HelloWorld { - public function sayHello(string $_): bool - { - if($_ ===''){ - return true; - } + public function sayHello(string $_): bool + { + if ($_ ==='') { + return true; + } - $this->nope(); - } + $this->nope(); + } - /** - * @psalm-pure - * @return never - */ - function nope() { - throw new \Exception(); - } + /** + * @psalm-pure + * @return never + */ + public function nope() + { + throw new \Exception(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4550.php b/tests/PHPStan/Rules/Methods/data/bug-4550.php index d0c4139957..e0bb06267b 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4550.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4550.php @@ -4,43 +4,43 @@ class Test { - /** - * @template T - * @param class-string $class - */ - public static function valuesOf(string $class): void - { - (new $class())->values(); - assert(method_exists($class, 'values')); - $class::values(); - } + /** + * @template T + * @param class-string $class + */ + public static function valuesOf(string $class): void + { + (new $class())->values(); + assert(method_exists($class, 'values')); + $class::values(); + } - /** - * @template T - * @param class-string $class - */ - public static function doBar(string $class): void - { - (new $class())->values(); - $class::values(); - } + /** + * @template T + * @param class-string $class + */ + public static function doBar(string $class): void + { + (new $class())->values(); + $class::values(); + } - /** - * @param class-string $s - */ - public function doBaz(string $s): void - { - $s::valuesOf(\stdClass::class); - $s::valuesOf('Person'); - } + /** + * @param class-string $s + */ + public function doBaz(string $s): void + { + $s::valuesOf(\stdClass::class); + $s::valuesOf('Person'); + } - /** - * @template T of self - * @param class-string $s - */ - public function doLorem(string $s): void - { - $s::valuesOf(\stdClass::class); - $s::valuesOf('Person'); - } + /** + * @template T of self + * @param class-string $s + */ + public function doLorem(string $s): void + { + $s::valuesOf(\stdClass::class); + $s::valuesOf('Person'); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4552.php b/tests/PHPStan/Rules/Methods/data/bug-4552.php index f2d5413622..0e4e4f628b 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4552.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4552.php @@ -1,36 +1,42 @@ - */ -class SimpleOptionDefinition implements OptionDefinition { - public function presenter() - { - return new SimpleOptionPresenter(); - } +class SimpleOptionDefinition implements OptionDefinition +{ + public function presenter() + { + return new SimpleOptionPresenter(); + } } /** @@ -40,8 +46,9 @@ public function presenter() * * @return T */ -function present($definition) { - return instantiate($definition)->presenter(); +function present($definition) +{ + return instantiate($definition)->presenter(); } @@ -52,12 +59,13 @@ function present($definition) { * * @return T */ -function instantiate($definition) { - return new $definition; +function instantiate($definition) +{ + return new $definition(); } function (): void { - $p = present(SimpleOptionDefinition::class); - assertType(SimpleOptionPresenter::class, $p); - $p->test(); + $p = present(SimpleOptionDefinition::class); + assertType(SimpleOptionPresenter::class, $p); + $p->test(); }; diff --git a/tests/PHPStan/Rules/Methods/data/bug-4590.php b/tests/PHPStan/Rules/Methods/data/bug-4590.php index a3db4a3445..9032f0dba7 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4590.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4590.php @@ -7,69 +7,68 @@ */ class OkResponse { - /** - * @phpstan-var T - */ - private $body; + /** + * @phpstan-var T + */ + private $body; - /** - * @phpstan-param T $body - */ - public function __construct($body) - { - $this->body = $body; - } + /** + * @phpstan-param T $body + */ + public function __construct($body) + { + $this->body = $body; + } - /** - * @phpstan-return T - */ - public function getBody() - { - return $this->body; - } + /** + * @phpstan-return T + */ + public function getBody() + { + return $this->body; + } } class Controller { - /** - * @return OkResponse> - */ - public function test1(): OkResponse - { - return new OkResponse(["ok" => "hello"]); - } + /** + * @return OkResponse> + */ + public function test1(): OkResponse + { + return new OkResponse(["ok" => "hello"]); + } - /** - * @return OkResponse> - */ - public function test2(): OkResponse - { - return new OkResponse([0 => "hello"]); - } + /** + * @return OkResponse> + */ + public function test2(): OkResponse + { + return new OkResponse([0 => "hello"]); + } - /** - * @return OkResponse - */ - public function test3(): OkResponse - { - return new OkResponse(["hello"]); - } + /** + * @return OkResponse + */ + public function test3(): OkResponse + { + return new OkResponse(["hello"]); + } - /** - * @return OkResponse - */ - public function test4(): OkResponse - { - return new OkResponse("hello"); - } - - /** - * @param array $a - * @return OkResponse> - */ - public function test5(array $a): OkResponse - { - return new OkResponse($a); - } + /** + * @return OkResponse + */ + public function test4(): OkResponse + { + return new OkResponse("hello"); + } + /** + * @param array $a + * @return OkResponse> + */ + public function test5(array $a): OkResponse + { + return new OkResponse($a); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4605.php b/tests/PHPStan/Rules/Methods/data/bug-4605.php index c8fd31428a..98fca763d4 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4605.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4605.php @@ -9,24 +9,27 @@ * @template-extends IteratorAggregate * @template-extends ArrayAccess */ -interface Collection extends \Countable, \IteratorAggregate, \ArrayAccess {} +interface Collection extends \Countable, \IteratorAggregate, \ArrayAccess +{ +} -class Boo { - /** - * @param Collection $collection - * @return Collection - */ - public function foo(Collection $collection): Collection - { - return $collection; - } +class Boo +{ + /** + * @param Collection $collection + * @return Collection + */ + public function foo(Collection $collection): Collection + { + return $collection; + } - /** - * @param Collection $collection - * @return Collection - */ - public function boo(Collection $collection): Collection - { - return $collection; - } + /** + * @param Collection $collection + * @return Collection + */ + public function boo(Collection $collection): Collection + { + return $collection; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4641.php b/tests/PHPStan/Rules/Methods/data/bug-4641.php index e99a071c52..7e55c89be6 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4641.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4641.php @@ -4,25 +4,21 @@ interface IEntity { - } /** @template E of IEntity */ interface IRepository { - } interface I { - - /** - * @template E of IEntity - * @template T of IRepository - * @template U - * @phpstan-param class-string $className - * @phpstan-return T - */ - function getRepository(string $className): IRepository; - + /** + * @template E of IEntity + * @template T of IRepository + * @template U + * @phpstan-param class-string $className + * @phpstan-return T + */ + public function getRepository(string $className): IRepository; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4648.php b/tests/PHPStan/Rules/Methods/data/bug-4648.php index b4b9d1c6d2..3b27e55850 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4648.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4648.php @@ -4,24 +4,24 @@ interface ClassInterface { - /** - * @return static - */ - public static function convert(); + /** + * @return static + */ + public static function convert(); } trait ClassDefaultLogic { - /** - * @return static - */ - public static function convert() - { - return new self(); - } + /** + * @return static + */ + public static function convert() + { + return new self(); + } } final class ClassImplementation implements ClassInterface { - use ClassDefaultLogic; + use ClassDefaultLogic; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4707-covariant.php b/tests/PHPStan/Rules/Methods/data/bug-4707-covariant.php index 40af389187..3fe3b186b7 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4707-covariant.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4707-covariant.php @@ -7,60 +7,60 @@ */ interface ChildNodeInterface { - /** @return TParent */ - public function getParent(): ParentNodeInterface; + /** @return TParent */ + public function getParent(): ParentNodeInterface; } interface ParentNodeInterface { - /** @return list> */ - public function getChildren(): array; + /** @return list> */ + public function getChildren(): array; } final class Block implements ParentNodeInterface { - /** @var list */ - private $rows = []; + /** @var list */ + private $rows = []; - /** @return list */ - public function getChildren(): array - { - return $this->rows; - } + /** @return list */ + public function getChildren(): array + { + return $this->rows; + } } class Block2 implements ParentNodeInterface { - /** @var list */ - private $rows = []; + /** @var list */ + private $rows = []; - /** @return list */ - public function getChildren(): array - { - return $this->rows; - } + /** @return list */ + public function getChildren(): array + { + return $this->rows; + } } /** @implements ChildNodeInterface */ final class Row implements ChildNodeInterface { - /** @var Block $parent */ - private $parent; + /** @var Block $parent */ + private $parent; - public function getParent(): ParentNodeInterface - { - return $this->parent; - } + public function getParent(): ParentNodeInterface + { + return $this->parent; + } } /** @implements ChildNodeInterface */ final class Row2 implements ChildNodeInterface { - /** @var Block2 $parent */ - private $parent; + /** @var Block2 $parent */ + private $parent; - public function getParent(): ParentNodeInterface - { - return $this->parent; - } + public function getParent(): ParentNodeInterface + { + return $this->parent; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4707-three.php b/tests/PHPStan/Rules/Methods/data/bug-4707-three.php index 82b97a0018..25d7df8bcd 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4707-three.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4707-three.php @@ -6,21 +6,25 @@ * @template TParent of ParentNodeInterface */ interface ChildNodeInterface -{} +{ +} interface ParentNodeInterface { - /** @return ChildNodeInterface */ - public function getChildren(); + /** @return ChildNodeInterface */ + public function getChildren(); } /** @implements ChildNodeInterface */ -final class Row implements ChildNodeInterface {} +final class Row implements ChildNodeInterface +{ +} class Block implements ParentNodeInterface { - /** @return Row */ - public function getChildren() { - return new Row(); - } + /** @return Row */ + public function getChildren() + { + return new Row(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4707-two.php b/tests/PHPStan/Rules/Methods/data/bug-4707-two.php index c0a2d4f0be..f20b59d6e1 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4707-two.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4707-two.php @@ -6,21 +6,25 @@ * @template TParent of ParentNodeInterface */ interface ChildNodeInterface -{} +{ +} interface ParentNodeInterface { - /** @return ChildNodeInterface */ - public function getChildren(); + /** @return ChildNodeInterface */ + public function getChildren(); } /** @implements ChildNodeInterface */ -final class Row implements ChildNodeInterface {} +final class Row implements ChildNodeInterface +{ +} final class Block implements ParentNodeInterface { - /** @return Row */ - public function getChildren() { - return new Row(); - } + /** @return Row */ + public function getChildren() + { + return new Row(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4707.php b/tests/PHPStan/Rules/Methods/data/bug-4707.php index 46091671d2..ae37c1bbc5 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4707.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4707.php @@ -7,60 +7,60 @@ */ interface ChildNodeInterface { - /** @return TParent */ - public function getParent(): ParentNodeInterface; + /** @return TParent */ + public function getParent(): ParentNodeInterface; } interface ParentNodeInterface { - /** @return list> */ - public function getChildren(): array; + /** @return list> */ + public function getChildren(): array; } final class Block implements ParentNodeInterface { - /** @var list */ - private $rows = []; + /** @var list */ + private $rows = []; - /** @return list */ - public function getChildren(): array - { - return $this->rows; - } + /** @return list */ + public function getChildren(): array + { + return $this->rows; + } } class Block2 implements ParentNodeInterface { - /** @var list */ - private $rows = []; + /** @var list */ + private $rows = []; - /** @return list */ - public function getChildren(): array - { - return $this->rows; - } + /** @return list */ + public function getChildren(): array + { + return $this->rows; + } } /** @implements ChildNodeInterface */ final class Row implements ChildNodeInterface { - /** @var Block $parent */ - private $parent; + /** @var Block $parent */ + private $parent; - public function getParent(): ParentNodeInterface - { - return $this->parent; - } + public function getParent(): ParentNodeInterface + { + return $this->parent; + } } /** @implements ChildNodeInterface */ final class Row2 implements ChildNodeInterface { - /** @var Block2 $parent */ - private $parent; + /** @var Block2 $parent */ + private $parent; - public function getParent(): ParentNodeInterface - { - return $this->parent; - } + public function getParent(): ParentNodeInterface + { + return $this->parent; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4729.php b/tests/PHPStan/Rules/Methods/data/bug-4729.php index 001bb88617..6d02495fa9 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4729.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4729.php @@ -5,10 +5,10 @@ /** @template T of int */ interface I { - /** - * @return static - */ - function get(): I; + /** + * @return static + */ + public function get(): I; } /** @@ -17,10 +17,10 @@ function get(): I; */ final class B implements I { - function get(): I - { - return $this; - } + public function get(): I + { + return $this; + } } /** @@ -29,8 +29,8 @@ function get(): I */ class C implements I { - function get(): I - { - return $this; - } + public function get(): I + { + return $this; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4795.php b/tests/PHPStan/Rules/Methods/data/bug-4795.php index fd7320c30f..a8b69faa30 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4795.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4795.php @@ -1,47 +1,45 @@ -= 8.0 += 8.0 namespace Bug4795; abstract class BaseDTO { + final protected static function create(): static + { + $class = static::class; - final protected static function create(): static - { - $class = static::class; - - return new $class(); - } - - abstract public static function parse(): static; + return new $class(); + } + abstract public static function parse(): static; } final class ConcreteDTO extends BaseDTO { + public string $foo; - public string $foo; + public static function parse(): static + { + $instance = self::create(); - public static function parse(): static - { - $instance = self::create(); + $instance->foo = 'bar'; - $instance->foo = 'bar'; - - return $instance; - } + return $instance; + } } class NonFinalConcreteDTO extends BaseDTO { + public string $foo; - public string $foo; - - public static function parse(): static - { - $instance = self::create(); + public static function parse(): static + { + $instance = self::create(); - $instance->foo = 'bar'; + $instance->foo = 'bar'; - return $instance; - } + return $instance; + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4800.php b/tests/PHPStan/Rules/Methods/data/bug-4800.php index d4f725d9e4..60fd36eab2 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4800.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4800.php @@ -1,38 +1,40 @@ -= 8.0 += 8.0 namespace Bug4800; class HelloWorld { - /** - * @param string|int ...$arguments - */ - public function a(string $bar = '', ...$arguments): string - { - return ''; - } + /** + * @param string|int ...$arguments + */ + public function a(string $bar = '', ...$arguments): string + { + return ''; + } - public function b(): void - { - $this->a(bar: 'baz', foo: 'bar', c: 3); - $this->a(foo: 'bar', c: 3); - } + public function b(): void + { + $this->a(bar: 'baz', foo: 'bar', c: 3); + $this->a(foo: 'bar', c: 3); + } } class HelloWorld2 { - /** - * @param string|int ...$arguments - */ - public function a(string $bar, ...$arguments): string - { - return ''; - } + /** + * @param string|int ...$arguments + */ + public function a(string $bar, ...$arguments): string + { + return ''; + } - public function b(): void - { - $this->a(bar: 'baz', foo: 'bar', c: 3); - $this->a(foo: 'baz', bar: 'bar', c: 3); - $this->a(foo: 'bar', c: 3); - } + public function b(): void + { + $this->a(bar: 'baz', foo: 'bar', c: 3); + $this->a(foo: 'baz', bar: 'bar', c: 3); + $this->a(foo: 'bar', c: 3); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-577.php b/tests/PHPStan/Rules/Methods/data/bug-577.php index 25077f01bf..1c1a205cdd 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-577.php +++ b/tests/PHPStan/Rules/Methods/data/bug-577.php @@ -1,18 +1,20 @@ - 1) { - static::step2($foo); - } - } + public static function step1(?int $foo): void + { + if ($foo > 1) { + static::step2($foo); + } + } - public static function step2(int $bar): void - { - var_dump($bar); - } + public static function step2(int $bar): void + { + var_dump($bar); + } } diff --git a/tests/PHPStan/Rules/Methods/data/bug2740.php b/tests/PHPStan/Rules/Methods/data/bug2740.php index 731a62d0ed..e5f4eb3f42 100644 --- a/tests/PHPStan/Rules/Methods/data/bug2740.php +++ b/tests/PHPStan/Rules/Methods/data/bug2740.php @@ -18,49 +18,44 @@ interface Collection extends \IteratorAggregate */ interface Member extends Collection { - } class MemberImpl implements Member { - - /** - * @return \Iterator - */ - public function getIterator(): \Iterator - { - return new \ArrayIterator([$this]); - } - + /** + * @return \Iterator + */ + public function getIterator(): \Iterator + { + return new \ArrayIterator([$this]); + } } class CollectionImpl implements Collection { - - /** - * @var array - */ - private $members; - - public function __construct(Member ...$members) - { - $this->members = $members; - } - - /** - * @return Member - */ - public function getMember(): Member - { - return new MemberImpl(); - } - - /** - * @return \Iterator - */ - public function getIterator(): \Iterator - { - return new \ArrayIterator($this->members); - } - + /** + * @var array + */ + private $members; + + public function __construct(Member ...$members) + { + $this->members = $members; + } + + /** + * @return Member + */ + public function getMember(): Member + { + return new MemberImpl(); + } + + /** + * @return \Iterator + */ + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->members); + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-interface-methods.php b/tests/PHPStan/Rules/Methods/data/call-interface-methods.php index d150640b79..5f86a3dea1 100644 --- a/tests/PHPStan/Rules/Methods/data/call-interface-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-interface-methods.php @@ -4,27 +4,22 @@ interface Foo { + public function fooMethod(); - public function fooMethod(); - - public static function fooStaticMethod(); - + public static function fooStaticMethod(); } abstract class Bar implements Foo { - } abstract class Baz extends Bar { - - public function bazMethod() - { - $this->fooMethod(); - $this->barMethod(); - self::fooStaticMethod(); - self::barStaticMethod(); - } - + public function bazMethod() + { + $this->fooMethod(); + $this->barMethod(); + self::fooStaticMethod(); + self::barStaticMethod(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods-iterable.php b/tests/PHPStan/Rules/Methods/data/call-methods-iterable.php index f957b0d228..a6b45da8c8 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods-iterable.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods-iterable.php @@ -4,98 +4,86 @@ class Uuid { - - /** - * @param Uuid[] $ids - */ - public function bar(iterable $ids) - { - $id = new self(); - $id->bar([null]); - - } + /** + * @param Uuid[] $ids + */ + public function bar(iterable $ids) + { + $id = new self(); + $id->bar([null]); + } } class Foo { - - /** - * @return self[]|iterable - */ - public function getIterable(): iterable - { - - } - - /** - * @return Bar[]|iterable - */ - public function getOtherIterable(): iterable - { - - } - - /** - * @param array|\Traversable $iterable - */ - public function acceptsArrayOrTraversable($iterable) - { - - } - - /** - * @param self[]|iterable $iterable - */ - public function acceptsSelfIterable(iterable $iterable) - { - - } - - public function test() - { - $this->acceptsArrayOrTraversable($this->getIterable()); - $this->acceptsSelfIterable($this->getIterable()); - $this->acceptsArrayOrTraversable($this->getOtherIterable()); - $this->acceptsSelfIterable($this->getOtherIterable()); - $this->acceptsSelfIterable('foo'); - - $this->doFoo(1, 1, 1, 1, 1, 1, 1, 1, 1, 1); - } - - /** - * @param iterable $iterableWithIterableTypehint - * @param Bar[] $iterableWithConcreteTypehint - * @param iterable $arrayWithIterableTypehint - * @param Bar[]|Collection $unionIterableType - * @param Foo[]|Bar[]|Collection|array $mixedUnionIterableType - * @param Bar[]|Collection $unionIterableIterableType - * @param int[]|iterable $integers - * @param mixed[]|iterable $mixeds - */ - public function doFoo( - iterable $iterableWithoutTypehint, - iterable $iterableWithIterableTypehint, - iterable $iterableWithConcreteTypehint, - array $arrayWithIterableTypehint, - Collection $unionIterableType, - array $mixedUnionIterableType, - iterable $unionIterableIterableType, - $iterableSpecifiedLater, - iterable $integers, - iterable $mixeds - ) - { - - } - + /** + * @return self[]|iterable + */ + public function getIterable(): iterable + { + } + + /** + * @return Bar[]|iterable + */ + public function getOtherIterable(): iterable + { + } + + /** + * @param array|\Traversable $iterable + */ + public function acceptsArrayOrTraversable($iterable) + { + } + + /** + * @param self[]|iterable $iterable + */ + public function acceptsSelfIterable(iterable $iterable) + { + } + + public function test() + { + $this->acceptsArrayOrTraversable($this->getIterable()); + $this->acceptsSelfIterable($this->getIterable()); + $this->acceptsArrayOrTraversable($this->getOtherIterable()); + $this->acceptsSelfIterable($this->getOtherIterable()); + $this->acceptsSelfIterable('foo'); + + $this->doFoo(1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + } + + /** + * @param iterable $iterableWithIterableTypehint + * @param Bar[] $iterableWithConcreteTypehint + * @param iterable $arrayWithIterableTypehint + * @param Bar[]|Collection $unionIterableType + * @param Foo[]|Bar[]|Collection|array $mixedUnionIterableType + * @param Bar[]|Collection $unionIterableIterableType + * @param int[]|iterable $integers + * @param mixed[]|iterable $mixeds + */ + public function doFoo( + iterable $iterableWithoutTypehint, + iterable $iterableWithIterableTypehint, + iterable $iterableWithConcreteTypehint, + array $arrayWithIterableTypehint, + Collection $unionIterableType, + array $mixedUnionIterableType, + iterable $unionIterableIterableType, + $iterableSpecifiedLater, + iterable $integers, + iterable $mixeds + ) { + } } class Bar { - } interface Collection extends \Traversable { - } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods-strict.php b/tests/PHPStan/Rules/Methods/data/call-methods-strict.php index 2c3e53d703..1a060e2608 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods-strict.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods-strict.php @@ -1,8 +1,10 @@ -acceptsString($foo); + $foo = new ClassWithToString(); + $foo->acceptsString($foo); }; diff --git a/tests/PHPStan/Rules/Methods/data/call-methods.php b/tests/PHPStan/Rules/Methods/data/call-methods.php index 3d5669648c..2ec79ee57d 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods.php @@ -4,703 +4,625 @@ class Foo { + private function foo() + { + $this->protectedMethodFromChild(); + } - private function foo() - { - $this->protectedMethodFromChild(); - } + protected function bar() + { + } - protected function bar() - { - - } - - public function ipsum() - { - - } - - public function test($bar) - { - - } + public function ipsum() + { + } + public function test($bar) + { + } } class Bar extends Foo { - - private function foobar() - { - - } - - public function lorem() - { - $this->loremipsum(); // nonexistent - $this->foo(1); // private from an ancestor - $this->bar(); - $this->ipsum(); - $this->foobar(); - $this->lorem(); - $this->test(); // missing parameter - - $string = 'foo'; - $string->method(); - } - - public function dolor($foo, $bar, $baz) - { - // fixing PHP bug #71416 - $methodReflection = new \ReflectionMethod(Bar::class, 'dolor'); - $methodReflection->invoke(new self()); - $methodReflection->invoke(new self(), 'foo', 'bar', 'baz'); - } - - protected function protectedMethodFromChild() - { - $foo = new UnknownClass(); - $foo->doFoo(); - - $this->returnsVoid(); - $this->dolor($this->returnsVoid(), 'bar', 'baz'); - - foreach ($this->returnsVoid() as $void) { - $this->returnsVoid(); - if ($this->returnsVoid()) { - $this->returnsVoid(); - $this->returnsVoid() ? 'foo' : 'bar'; - doFoo() ? $this->returnsVoid() : 'bar'; - doFoo() ? 'foo' : $this->returnsVoid(); - $void = $this->returnsVoid(); - $void = $this->returnsVoid() ? 'foo' : 'bar'; - $void = doFoo() ? $this->returnsVoid() : 'bar'; - $void = doFoo() ? 'foo' : $this->returnsVoid(); - $this->returnsVoid() && 'foo'; - 'foo' && $this->returnsVoid(); - $this->returnsVoid() || 'foo'; - 'foo' || $this->returnsVoid(); - $void = $this->returnsVoid() && 'foo'; - $void = 'foo' && $this->returnsVoid(); - $void = $this->returnsVoid() || 'foo'; - $void = 'foo' || $this->returnsVoid(); - } - } - - switch ($this->returnsVoid()) { - case $this->returnsVoid(): - $this->returnsVoid(); - } - } - - /** - * @return void - */ - private function returnsVoid() - { - @$this->returnsVoid(); - } - + private function foobar() + { + } + + public function lorem() + { + $this->loremipsum(); // nonexistent + $this->foo(1); // private from an ancestor + $this->bar(); + $this->ipsum(); + $this->foobar(); + $this->lorem(); + $this->test(); // missing parameter + + $string = 'foo'; + $string->method(); + } + + public function dolor($foo, $bar, $baz) + { + // fixing PHP bug #71416 + $methodReflection = new \ReflectionMethod(Bar::class, 'dolor'); + $methodReflection->invoke(new self()); + $methodReflection->invoke(new self(), 'foo', 'bar', 'baz'); + } + + protected function protectedMethodFromChild() + { + $foo = new UnknownClass(); + $foo->doFoo(); + + $this->returnsVoid(); + $this->dolor($this->returnsVoid(), 'bar', 'baz'); + + foreach ($this->returnsVoid() as $void) { + $this->returnsVoid(); + if ($this->returnsVoid()) { + $this->returnsVoid(); + $this->returnsVoid() ? 'foo' : 'bar'; + doFoo() ? $this->returnsVoid() : 'bar'; + doFoo() ? 'foo' : $this->returnsVoid(); + $void = $this->returnsVoid(); + $void = $this->returnsVoid() ? 'foo' : 'bar'; + $void = doFoo() ? $this->returnsVoid() : 'bar'; + $void = doFoo() ? 'foo' : $this->returnsVoid(); + $this->returnsVoid() && 'foo'; + 'foo' && $this->returnsVoid(); + $this->returnsVoid() || 'foo'; + 'foo' || $this->returnsVoid(); + $void = $this->returnsVoid() && 'foo'; + $void = 'foo' && $this->returnsVoid(); + $void = $this->returnsVoid() || 'foo'; + $void = 'foo' || $this->returnsVoid(); + } + } + + switch ($this->returnsVoid()) { + case $this->returnsVoid(): + $this->returnsVoid(); + } + } + + /** + * @return void + */ + private function returnsVoid() + { + @$this->returnsVoid(); + } } $f = function () { - $arrayOfStdClass = new \ArrayObject([new \stdClass()]); - $arrayOfStdClass->doFoo(); + $arrayOfStdClass = new \ArrayObject([new \stdClass()]); + $arrayOfStdClass->doFoo(); }; $g = function () { - $pdo = new \PDO('dsn', 'username', 'password'); - $pdo->query(); - $pdo->query('statement'); + $pdo = new \PDO('dsn', 'username', 'password'); + $pdo->query(); + $pdo->query('statement'); }; class ClassWithToString { + public function __toString() + { + return 'foo'; + } - public function __toString() - { - return 'foo'; - } - - public function acceptsString(string $foo) - { - - } - + public function acceptsString(string $foo) + { + } } function () { - $foo = new ClassWithToString(); - $foo->acceptsString($foo); + $foo = new ClassWithToString(); + $foo->acceptsString($foo); - $closure = function () { + $closure = function () { + }; + $closure->__invoke(1, 2, 3); - }; - $closure->__invoke(1, 2, 3); - - $reflectionClass = new \ReflectionClass(Foo::class); - $reflectionClass->newInstance(); - $reflectionClass->newInstance(1, 2, 3); + $reflectionClass = new \ReflectionClass(Foo::class); + $reflectionClass->newInstance(); + $reflectionClass->newInstance(1, 2, 3); }; class ClassWithNullableProperty { + /** @var self|null */ + private $foo; - /** @var self|null */ - private $foo; - - public function doFoo() - { - if ($this->foo === null) { - $this->foo = new self(); - $this->foo->doFoo(); - } - } + public function doFoo() + { + if ($this->foo === null) { + $this->foo = new self(); + $this->foo->doFoo(); + } + } - public function doBar(&$bar) - { - $i = 0; - $this->doBar($i); // ok + public function doBar(&$bar) + { + $i = 0; + $this->doBar($i); // ok - $arr = [1, 2, 3]; - $this->doBar($arr[0]); // ok - $this->doBar(rand()); - $this->doBar(null); - } + $arr = [1, 2, 3]; + $this->doBar($arr[0]); // ok + $this->doBar(rand()); + $this->doBar(null); + } - public function doBaz() - { - /** @var Foo|null $foo */ - $foo = doFoo(); + public function doBaz() + { + /** @var Foo|null $foo */ + $foo = doFoo(); - /** @var Bar|null $bar */ - $bar = doBar(); + /** @var Bar|null $bar */ + $bar = doBar(); - if ($foo === null && $bar === null) { - throw new \Exception(); - } + if ($foo === null && $bar === null) { + throw new \Exception(); + } - $foo->ipsum(); - $bar->ipsum(); - } + $foo->ipsum(); + $bar->ipsum(); + } - public function doIpsum(): string - { - /** @var Foo|null $foo */ - $foo = doFoo(); + public function doIpsum(): string + { + /** @var Foo|null $foo */ + $foo = doFoo(); - /** @var Bar|null $bar */ - $bar = doBar(); + /** @var Bar|null $bar */ + $bar = doBar(); - if ($foo !== null && $bar === null) { - return ''; - } elseif ($bar !== null && $foo === null) { - return ''; - } + if ($foo !== null && $bar === null) { + return ''; + } elseif ($bar !== null && $foo === null) { + return ''; + } - $foo->ipsum(); - $bar->ipsum(); - - return ''; - } + $foo->ipsum(); + $bar->ipsum(); + return ''; + } } function () { - $dateTimeZone = new \DateTimeZone('Europe/Prague'); - $dateTimeZone->getTransitions(); - $dateTimeZone->getTransitions(1); - $dateTimeZone->getTransitions(1, 2); - $dateTimeZone->getTransitions(1, 2, 3); - - $domDocument = new \DOMDocument('1.0'); - $domDocument->saveHTML(); - $domDocument->saveHTML(new \DOMNode()); - $domDocument->saveHTML(null); + $dateTimeZone = new \DateTimeZone('Europe/Prague'); + $dateTimeZone->getTransitions(); + $dateTimeZone->getTransitions(1); + $dateTimeZone->getTransitions(1, 2); + $dateTimeZone->getTransitions(1, 2, 3); + + $domDocument = new \DOMDocument('1.0'); + $domDocument->saveHTML(); + $domDocument->saveHTML(new \DOMNode()); + $domDocument->saveHTML(null); }; class ReturningSomethingFromConstructor { - - public function __construct() - { - return new Foo(); - } - + public function __construct() + { + return new Foo(); + } } function () { - $obj = new ReturningSomethingFromConstructor(); - $foo = $obj->__construct(); + $obj = new ReturningSomethingFromConstructor(); + $foo = $obj->__construct(); }; class IssueWithEliminatingTypes { + public function doBar() + { + /** @var \DateTimeImmutable|null $date */ + $date = makeDate(); + if ($date !== null) { + return; + } - public function doBar() - { - /** @var \DateTimeImmutable|null $date */ - $date = makeDate(); - if ($date !== null) { - return; - } - - if (something()) { - $date = 'loremipsum'; - } else { - $date = 1; - } - - echo $date->foo(); // is surely string|int - } + if (something()) { + $date = 'loremipsum'; + } else { + $date = 1; + } + echo $date->foo(); // is surely string|int + } } interface FirstInterface { - - public function firstMethod(); - + public function firstMethod(); } interface SecondInterface { - - public function secondMethod(); - + public function secondMethod(); } class UnionInsteadOfIntersection { - - public function doFoo($object) - { - while ($object instanceof FirstInterface && $object instanceof SecondInterface) { - $object->firstMethod(); - $object->secondMethod(); - $object->firstMethod(1); - $object->secondMethod(1); - } - } - + public function doFoo($object) + { + while ($object instanceof FirstInterface && $object instanceof SecondInterface) { + $object->firstMethod(); + $object->secondMethod(); + $object->firstMethod(1); + $object->secondMethod(1); + } + } } class CallingOnNull { + public function doFoo() + { + $object = null; - public function doFoo() - { - $object = null; - - if ($object === null) { - // nothing - } - - $object->foo(); - } + if ($object === null) { + // nothing + } + $object->foo(); + } } class MethodsWithUnknownClasses { + /** @var FirstUnknownClass|SecondUnknownClass */ + private $foo; - /** @var FirstUnknownClass|SecondUnknownClass */ - private $foo; - - public function doFoo() - { - $this->foo->test(); - } - + public function doFoo() + { + $this->foo->test(); + } } class IgnoreNullableUnionProperty { + /** @var Foo|null */ + private $foo; - /** @var Foo|null */ - private $foo; - - public function doFoo() - { - $this->foo->ipsum(); - } - + public function doFoo() + { + $this->foo->ipsum(); + } } interface WithFooMethod { - - public function foo(): Foo; - + public function foo(): Foo; } interface WithFooAndBarMethod { + public function foo(); - public function foo(); - - public function bar(); - + public function bar(); } class MethodsOnUnionType { + /** @var WithFooMethod|WithFooAndBarMethod */ + private $object; - /** @var WithFooMethod|WithFooAndBarMethod */ - private $object; - - public function doFoo() - { - $this->object->foo(); // fine - $this->object->bar(); // WithFooMethod does not have bar() - } - + public function doFoo() + { + $this->object->foo(); // fine + $this->object->bar(); // WithFooMethod does not have bar() + } } interface SomeInterface { - } class MethodsOnIntersectionType { - - public function doFoo(WithFooMethod $foo) - { - if ($foo instanceof SomeInterface) { - $foo->foo(); - $foo->bar(); - $foo->foo()->test(); - } - } - + public function doFoo(WithFooMethod $foo) + { + if ($foo instanceof SomeInterface) { + $foo->foo(); + $foo->bar(); + $foo->foo()->test(); + } + } } class ObjectTypehint { + public function doFoo(object $object) + { + $this->doFoo($object); + $this->doBar($object); + } - public function doFoo(object $object) - { - $this->doFoo($object); - $this->doBar($object); - } - - public function doBar(Foo $foo) - { - $this->doFoo($foo); - $this->doBar($foo); - } - + public function doBar(Foo $foo) + { + $this->doFoo($foo); + $this->doBar($foo); + } } function () { - /** @var UnknownClass[] $arrayOfAnUnknownClass */ - $arrayOfAnUnknownClass = doFoo(); - $arrayOfAnUnknownClass->test(); + /** @var UnknownClass[] $arrayOfAnUnknownClass */ + $arrayOfAnUnknownClass = doFoo(); + $arrayOfAnUnknownClass->test(); }; function () { - $foo = false; - foreach ([] as $val) { - if ($foo === false) { - $foo = new Foo(); - } else { - $foo->ipsum(); - $foo->ipsum(1); - } - } + $foo = false; + foreach ([] as $val) { + if ($foo === false) { + $foo = new Foo(); + } else { + $foo->ipsum(); + $foo->ipsum(1); + } + } }; class NullableInPhpDoc { + /** + * @param string|null $test + */ + public function doFoo(string $test) + { + } - /** - * @param string|null $test - */ - public function doFoo(string $test) - { - - } - - public function doBar() - { - $this->doFoo(null); - } - + public function doBar() + { + $this->doFoo(null); + } } class ThreeTypesCall { + public function twoTypes(string $globalTitle) + { + if (($globalTitle = $this->threeTypes($globalTitle)) !== false) { + return ''; + } - public function twoTypes(string $globalTitle) - { - if (($globalTitle = $this->threeTypes($globalTitle)) !== false) { - return ''; - } + return false; + } - return false; - } + public function floatType(float $globalTitle) + { + if (($globalTitle = $this->threeTypes($globalTitle)) !== false) { + return ''; + } - public function floatType(float $globalTitle) - { - if (($globalTitle = $this->threeTypes($globalTitle)) !== false) { - return ''; - } - - return false; - } - - /** - * @param string $globalTitle - * - * @return int|bool|string - */ - private function threeTypes($globalTitle) { - return false; - } + return false; + } + /** + * @param string $globalTitle + * + * @return int|bool|string + */ + private function threeTypes($globalTitle) + { + return false; + } } class ScopeBelowInstanceofIsNoLongerChanged { + public function doBar() + { + $foo = doFoo(); + if ($foo instanceof Foo) { + } - public function doBar() - { - $foo = doFoo(); - if ($foo instanceof Foo) { - } - - $foo->nonexistentMethodOnFoo(); - } - + $foo->nonexistentMethodOnFoo(); + } } class CallVariadicMethodWithArrayInPhpDoc { - - /** - * @param array ...$args - */ - public function variadicMethod(...$args) - { - - } - - /** - * @param string[] ...$args - */ - public function variadicArrayMethod(array ...$args) - { - - } - - public function test() - { - $this->variadicMethod(1, 2, 3); - $this->variadicMethod([1, 2, 3]); - $this->variadicMethod(...[1, 2, 3]); - $this->variadicArrayMethod(['foo', 'bar'], ['foo', 'bar']); - $this->variadicArrayMethod(...[['foo', 'bar'], ['foo', 'bar']]); - } - + /** + * @param array ...$args + */ + public function variadicMethod(...$args) + { + } + + /** + * @param string[] ...$args + */ + public function variadicArrayMethod(array ...$args) + { + } + + public function test() + { + $this->variadicMethod(1, 2, 3); + $this->variadicMethod([1, 2, 3]); + $this->variadicMethod(...[1, 2, 3]); + $this->variadicArrayMethod(['foo', 'bar'], ['foo', 'bar']); + $this->variadicArrayMethod(...[['foo', 'bar'], ['foo', 'bar']]); + } } class NullCoalesce { + /** @var self|null */ + private $foo; - /** @var self|null */ - private $foo; - - public function doFoo() - { - $this->foo->find(1) ?? 'bar'; - - if ($this->foo->find(1) ?? 'bar') { - - } - - ($this->foo->find(1) ?? 'bar') ? 'foo' : 'bar'; + public function doFoo() + { + $this->foo->find(1) ?? 'bar'; - $this->foo->foo->find(1)->find(1); - } + if ($this->foo->find(1) ?? 'bar') { + } - /** - * @return self|null - */ - public function find() - { + ($this->foo->find(1) ?? 'bar') ? 'foo' : 'bar'; - } + $this->foo->foo->find(1)->find(1); + } + /** + * @return self|null + */ + public function find() + { + } } class IncompatiblePhpDocNullableTypeIssue { + /** + * @param int|null $param + */ + public function doFoo(string $param = null) + { + } - /** - * @param int|null $param - */ - public function doFoo(string $param = null) - { - - } - - public function doBar() - { - $this->doFoo('foo'); // OK - $this->doFoo(123); // error - } - + public function doBar() + { + $this->doFoo('foo'); // OK + $this->doFoo(123); // error + } } class TernaryEvaluation { + /** + * @param Foo|false $fooOrFalse + */ + public function doFoo($fooOrFalse) + { + $fooOrFalse ?: $this->doBar($fooOrFalse); + $fooOrFalse ? + $this->doBar($fooOrFalse) + : $this->doBar($fooOrFalse); + } - - /** - * @param Foo|false $fooOrFalse - */ - public function doFoo($fooOrFalse) - { - $fooOrFalse ?: $this->doBar($fooOrFalse); - $fooOrFalse ? - $this->doBar($fooOrFalse) - : $this->doBar($fooOrFalse); - } - - public function doBar(int $i) - { - - } - + public function doBar(int $i) + { + } } class ForeachSituation { + public function takesInt(int $s = null) + { + } - public function takesInt(int $s = null) - { - - } - - /** - * @param string[] $letters - */ - public function takesStringArray(array $letters) - { - $letter = null; - foreach ($letters as $letter) { - - } - $this->takesInt($letter); - } - + /** + * @param string[] $letters + */ + public function takesStringArray(array $letters) + { + $letter = null; + foreach ($letters as $letter) { + } + $this->takesInt($letter); + } } class LogicalAndSupport { + public function doFoo() + { + if (($object = $this->findObject()) and $object instanceof self) { + return $object->doFoo(); + } + } - public function doFoo() - { - if (($object = $this->findObject()) and $object instanceof self) { - return $object->doFoo(); - } - } - - /** - * @return object|null - */ - public function findObject() - { - - } - + /** + * @return object|null + */ + public function findObject() + { + } } class LiteralArrayTypeCheck { + public function test(string $str) + { + $data = [ + 'string' => 'foo', + 'int' => 12, + 'bool' => true, + ]; - public function test(string $str) - { - $data = [ - 'string' => 'foo', - 'int' => 12, - 'bool' => true, - ]; - - $this->test($data['string']); - $this->test($data['int']); - $this->test($data['bool']); - } - + $this->test($data['string']); + $this->test($data['int']); + $this->test($data['bool']); + } } class CallArrayKeyAfterAssigningToIt { + public function test() + { + $arr = [null, null]; + $arr[1] = new \DateTime(); + $arr[1]->add(new \DateInterval('P1D')); - public function test() - { - $arr = [null, null]; - $arr[1] = new \DateTime(); - $arr[1]->add(new \DateInterval('P1D')); - - $arr[0]->add(new \DateInterval('P1D')); - } - + $arr[0]->add(new \DateInterval('P1D')); + } } class CheckIsCallable { + public function test(callable $str) + { + $this->test('date'); + $this->test('nonexistentFunction'); + $this->test('Test\CheckIsCallable::test'); + $this->test('Test\CheckIsCallable::test2'); + } - public function test(callable $str) - { - $this->test('date'); - $this->test('nonexistentFunction'); - $this->test('Test\CheckIsCallable::test'); - $this->test('Test\CheckIsCallable::test2'); - } - - public function testClosure(\Closure $closure) - { - $this->testClosure(function () { - - }); - } - + public function testClosure(\Closure $closure) + { + $this->testClosure(function () { + }); + } } class ArrayKeysNull { - - public function doFoo() - { - $array = []; - - /** @var \DateTimeImmutable|null $nullableDateTime */ - $nullableDateTime = doFoo(); - $array['key'] = $nullableDateTime; - if ($array['key'] === null) { - $array['key'] = new \DateTimeImmutable(); - echo $array['key']->format('j. n. Y'); - } - } - - public function doBar(array $a) - { - if ($a['key'] === null) { - $a['key'] = new \DateTimeImmutable(); - echo $a['key']->format('j. n. Y'); - } - } - - public function doBaz(array $array) - { - if ($array['key'] === null) { - $array['key'] = new \DateTimeImmutable(); - echo $array['key']->format('j. n. Y'); - } - } - + public function doFoo() + { + $array = []; + + /** @var \DateTimeImmutable|null $nullableDateTime */ + $nullableDateTime = doFoo(); + $array['key'] = $nullableDateTime; + if ($array['key'] === null) { + $array['key'] = new \DateTimeImmutable(); + echo $array['key']->format('j. n. Y'); + } + } + + public function doBar(array $a) + { + if ($a['key'] === null) { + $a['key'] = new \DateTimeImmutable(); + echo $a['key']->format('j. n. Y'); + } + } + + public function doBaz(array $array) + { + if ($array['key'] === null) { + $array['key'] = new \DateTimeImmutable(); + echo $array['key']->format('j. n. Y'); + } + } } /** @@ -708,973 +630,862 @@ public function doBaz(array $array) */ class VariadicAnnotationMethod { - - public function doFoo() - { - $this->definedInPhpDoc(); - $this->definedInPhpDoc(42); - } - + public function doFoo() + { + $this->definedInPhpDoc(); + $this->definedInPhpDoc(42); + } } class PreIncString { - - public function doFoo(int $i, string $str) - { - $this->doFoo(1, ++$i); - } - + public function doFoo(int $i, string $str) + { + $this->doFoo(1, ++$i); + } } class AnonymousClass { + public function doFoo() + { + $class = new class() { + /** @var string */ + public $bar; - public function doFoo() - { - $class = new class() { - - /** @var string */ - public $bar; - - /** - * @return string - */ - public function doBar() - { - } - }; - $class->bar->bar(); - $class->doBar()->bar(); - } - + /** + * @return string + */ + public function doBar() + { + } + }; + $class->bar->bar(); + $class->doBar()->bar(); + } } class WeirdStaticIssueBase { - - /** - * @param static|int $value - */ - public static function get($value) - { - } - + /** + * @param static|int $value + */ + public static function get($value) + { + } } class WeirdStaticIssueImpl extends WeirdStaticIssueBase { - } function () { - /** @var WeirdStaticIssueImpl|int */ - $a = new WeirdStaticIssueImpl(); + /** @var WeirdStaticIssueImpl|int */ + $a = new WeirdStaticIssueImpl(); - WeirdStaticIssueImpl::get($a); + WeirdStaticIssueImpl::get($a); }; class CheckDefaultArrayKeys { - - /** - * @param string[] $array - */ - public function doFoo( - array $array - ) - { - foreach ($array as $key => $val) { - $this->doBar($key); - $this->doBaz($key); - $this->doLorem($key); - $this->doAmet($key); - - if (rand(0, 1) === 0) { - $key = new \stdClass(); - } - - $this->doBar($key); - $this->doBaz($key); - $this->doLorem($key); - $this->doIpsum($key); - $this->doDolor($key); - $this->doSit($key); - $this->doAmet($key); - } - } - - public function doBar(int $i) - { - - } - - public function doBaz(string $str) - { - - } - - /** - * @param int|string $intOrString - */ - public function doLorem($intOrString) - { - - } - - /** - * @param \stdClass|int $stdOrInt - */ - public function doIpsum($stdOrInt) - { - - } - - /** - * @param \stdClass|string $stdOrString - */ - public function doDolor($stdOrString) - { - - } - - /** - * @param \DateTimeImmutable|string $dateOrString - */ - public function doSit($dateOrString) - { - - } - - public function doAmet(\stdClass $std) - { - - } - - /** - * @param string[] $array - */ - public function doConsecteur(array $array) - { - foreach ($array as $key => $val) { - if (rand(0, 1) === 0) { - $key = 1; - } else { - $key = 'str'; - } - - $this->doBar($key); - $this->doBaz($key); - } - } - + /** + * @param string[] $array + */ + public function doFoo( + array $array + ) { + foreach ($array as $key => $val) { + $this->doBar($key); + $this->doBaz($key); + $this->doLorem($key); + $this->doAmet($key); + + if (rand(0, 1) === 0) { + $key = new \stdClass(); + } + + $this->doBar($key); + $this->doBaz($key); + $this->doLorem($key); + $this->doIpsum($key); + $this->doDolor($key); + $this->doSit($key); + $this->doAmet($key); + } + } + + public function doBar(int $i) + { + } + + public function doBaz(string $str) + { + } + + /** + * @param int|string $intOrString + */ + public function doLorem($intOrString) + { + } + + /** + * @param \stdClass|int $stdOrInt + */ + public function doIpsum($stdOrInt) + { + } + + /** + * @param \stdClass|string $stdOrString + */ + public function doDolor($stdOrString) + { + } + + /** + * @param \DateTimeImmutable|string $dateOrString + */ + public function doSit($dateOrString) + { + } + + public function doAmet(\stdClass $std) + { + } + + /** + * @param string[] $array + */ + public function doConsecteur(array $array) + { + foreach ($array as $key => $val) { + if (rand(0, 1) === 0) { + $key = 1; + } else { + $key = 'str'; + } + + $this->doBar($key); + $this->doBaz($key); + } + } } class CallAfterEmpty { + public function doFoo(?string $q, ?Foo $foo) + { + if (empty($q)) { + return; + } + if (empty($foo)) { + return; + } - public function doFoo(?string $q, ?Foo $foo) - { - if (empty($q)) { - return; - } - if (empty($foo)) { - return; - } - - $q->test(); - $foo->test(); - } - + $q->test(); + $foo->test(); + } } class ReflectionTypeGetString { - - public function doFoo(\ReflectionType $type) - { - echo $type->getName(); - echo $type->getName(123); - } - + public function doFoo(\ReflectionType $type) + { + echo $type->getName(); + echo $type->getName(123); + } } class MethodExists { + public function doFoo(Foo $foo, $mixed) + { + $foo->lorem(); + if (method_exists($foo, 'lorem')) { + $foo->lorem(); + } + $foo->lorem(); - public function doFoo(Foo $foo, $mixed) - { - $foo->lorem(); - if (method_exists($foo, 'lorem')) { - $foo->lorem(); - } - $foo->lorem(); - - if (method_exists($mixed, 'foo')) { - $mixed->foo(); - $this->doBar([$mixed, 'foo']); - $this->doBar([$mixed, 'bar']); - } - - if (is_object($mixed) && method_exists($mixed, 'foo')) { - $this->doBar([$mixed, 'foo']); - $this->doBar([$mixed, 'bar']); - } - } - - public function doBar(callable $callable) - { + if (method_exists($mixed, 'foo')) { + $mixed->foo(); + $this->doBar([$mixed, 'foo']); + $this->doBar([$mixed, 'bar']); + } - } + if (is_object($mixed) && method_exists($mixed, 'foo')) { + $this->doBar([$mixed, 'foo']); + $this->doBar([$mixed, 'bar']); + } + } + public function doBar(callable $callable) + { + } } class SimpleXMLElementPropertyTypehint { + public function doFoo(\SimpleXMLElement $xml) + { + if (!isset($xml->branches->branch)) { + return []; + } - public function doFoo(\SimpleXMLElement $xml) - { - if (!isset($xml->branches->branch)) { - return []; - } - - echo $xml->branches->children()->count(); - echo $xml->branches->children(123); - } - + echo $xml->branches->children()->count(); + echo $xml->branches->children(123); + } } class IssetCumulativeArray { - - public function doFoo() - { - $arr = [1, 1, 1, 1, 2, 5, 3, 2]; - $cumulative = []; - - foreach ($arr as $val) { - if (!isset($cumulative[$val])) { - $cumulative[$val] = 0; - } - - $cumulative[$val] = $cumulative[$val] + 1; - } - - foreach ($cumulative as $c) { - $this->doBar($c); - } - } - - public function doBar(string $s) - { - - } - - public function doBaz() - { - $arr = [1, 1, 1, 1, 2, 5, 3, 2]; - $cumulative = []; - - foreach ($arr as $val) { - if (isset($cumulative[$val])) { - $cumulative[$val] = $cumulative[$val] + 1; - } else { - $cumulative[$val] = 1; - } - } - - foreach ($cumulative as $c) { - $this->doBar($c); - } - } - - public function doLorem() - { - $arr = [1, 2, 3]; - $cumulative = []; - - foreach ($arr as $val) { - if (isset($cumulative[$val])) { - $cumulative[$val] = $cumulative[$val]; - } - - $cumulative[$val] = 1; - } - - foreach ($cumulative as $c) { - $this->doBar($c); - } - } - + public function doFoo() + { + $arr = [1, 1, 1, 1, 2, 5, 3, 2]; + $cumulative = []; + + foreach ($arr as $val) { + if (!isset($cumulative[$val])) { + $cumulative[$val] = 0; + } + + $cumulative[$val] = $cumulative[$val] + 1; + } + + foreach ($cumulative as $c) { + $this->doBar($c); + } + } + + public function doBar(string $s) + { + } + + public function doBaz() + { + $arr = [1, 1, 1, 1, 2, 5, 3, 2]; + $cumulative = []; + + foreach ($arr as $val) { + if (isset($cumulative[$val])) { + $cumulative[$val] = $cumulative[$val] + 1; + } else { + $cumulative[$val] = 1; + } + } + + foreach ($cumulative as $c) { + $this->doBar($c); + } + } + + public function doLorem() + { + $arr = [1, 2, 3]; + $cumulative = []; + + foreach ($arr as $val) { + if (isset($cumulative[$val])) { + $cumulative[$val] = $cumulative[$val]; + } + + $cumulative[$val] = 1; + } + + foreach ($cumulative as $c) { + $this->doBar($c); + } + } } class DoWhileNeverIssue { + public function doFoo(int $i): void + { + $ipv4Piece = null; - public function doFoo(int $i): void - { - $ipv4Piece = null; - - do { - if ($ipv4Piece === null) { - $ipv4Piece = $i; - } else { - $ipv4Piece = $ipv4Piece * 10 + $i; - } - - $this->requireInt($ipv4Piece); - } while (true); + do { + if ($ipv4Piece === null) { + $ipv4Piece = $i; + } else { + $ipv4Piece = $ipv4Piece * 10 + $i; + } - $this->requireInt($ipv4Piece); - } + $this->requireInt($ipv4Piece); + } while (true); - private function requireInt(int $i): void - { - - } + $this->requireInt($ipv4Piece); + } + private function requireInt(int $i): void + { + } } class ArrayOrNullCastToArray { + /** + * @return \ArrayObject|\self[]|null + */ + private function returnsArrayObjectOrNull() + { + } - /** - * @return \ArrayObject|\self[]|null - */ - private function returnsArrayObjectOrNull() - { - - } - - /** - * @param \self[] $array - * @return void - */ - private function operateOnArray(array $array): void { - } - - public function doFoo(): void - { - $this->operateOnArray((array) $this->returnsArrayObjectOrNull()); - $this->operateOnArray((array) null); - } + /** + * @param \self[] $array + * @return void + */ + private function operateOnArray(array $array): void + { + } + public function doFoo(): void + { + $this->operateOnArray((array) $this->returnsArrayObjectOrNull()); + $this->operateOnArray((array) null); + } } class CallAfterPropertyEmpty { + private $foo = 1; - private $foo = 1; - - public function doFoo() - { - if (!empty($this->foo)) { - $this->doBar(); - } - } - + public function doFoo() + { + if (!empty($this->foo)) { + $this->doBar(); + } + } } class ArraySliceWithNonEmptyArray { + /** + * @param array $a + */ + public function doFoo(array $a) + { + if (count($a) === 0) { + return; + } - /** - * @param array $a - */ - public function doFoo(array $a) - { - if (count($a) === 0) { - return; - } - - $a = array_slice($a, 0, 2); - - $a[0]->doesNotExist(); - } + $a = array_slice($a, 0, 2); + $a[0]->doesNotExist(); + } } class SwitchWithTypeEliminatingCase { + public function doFoo(?string $variable) + { + switch (true) { + case $variable === null: + throw new \Exception('gotcha'); + default: + $this->doBar($variable); + } + } - public function doFoo(?string $variable) - { - switch (true) { - case $variable === null: - throw new \Exception('gotcha'); - default: - $this->doBar($variable); - } - } - - public function doBar(string $str) - { - - } - + public function doBar(string $str) + { + } } class AssertInIf { + public function doFoo(bool $x) + { + if ($x) { + $o = new Foo(); + assert($o instanceof Bar); + } else { + $o = new Bar(); + } - public function doFoo(bool $x) - { - if ($x) { - $o = new Foo(); - assert($o instanceof Bar); - } else { - $o = new Bar(); - } - - $this->requireChild($o); - } - - public function requireChild(Bar $child) - { - - } + $this->requireChild($o); + } - public function doBar() - { - $array = [new Foo(), new Bar()]; + public function requireChild(Bar $child) + { + } - $arrayToPass = []; - foreach($array as $item) { - if(!$item instanceof Bar) { - continue; - } + public function doBar() + { + $array = [new Foo(), new Bar()]; - $arrayToPass[] = $item; - } + $arrayToPass = []; + foreach ($array as $item) { + if (!$item instanceof Bar) { + continue; + } - $this->requireChilds($arrayToPass); - } + $arrayToPass[] = $item; + } - /** - * @param Bar[] $bs - */ - public function requireChilds(array $bs) - { - - } + $this->requireChilds($arrayToPass); + } + /** + * @param Bar[] $bs + */ + public function requireChilds(array $bs) + { + } } class AssertInForeach { + /** + * @param (self|null)[] $a + */ + public function doFoo(iterable $as) + { + $bs = []; + foreach ($as as $a) { + assert($a !== null); + $bs[] = $a; + } - /** - * @param (self|null)[] $a - */ - public function doFoo(iterable $as) - { - $bs = []; - foreach ($as as $a) { - assert($a !== null); - $bs[] = $a; - } - - $this->doBar($bs); - } - - /** - * @param self[] $as - */ - public function doBar(array $as) - { - - } + $this->doBar($bs); + } + /** + * @param self[] $as + */ + public function doBar(array $as) + { + } } class AssertInFor { - - /** - * @param object[] $objects - */ - public function doFoo(array $objects) - { - $selfs = []; - for ($i = 1; $i <= 10; ++$i) { - $self = $objects[$i]; - assert($self instanceof self); - $selfs[] = $self; - } - - foreach ($selfs as $self) { - $self->doFoo([]); - $self->doBar([]); - } - } - + /** + * @param object[] $objects + */ + public function doFoo(array $objects) + { + $selfs = []; + for ($i = 1; $i <= 10; ++$i) { + $self = $objects[$i]; + assert($self instanceof self); + $selfs[] = $self; + } + + foreach ($selfs as $self) { + $self->doFoo([]); + $self->doBar([]); + } + } } class AssignmentInConditionEliminatingNull { + public function sayHello(): void + { + $edits = []; - public function sayHello(): void - { - $edits = []; + if ($foo = $this->createEdit()) { + $edits[] = $foo; + } - if ($foo = $this->createEdit()) { - $edits[] = $foo; - } + $this->applyEdits($edits); + } - $this->applyEdits($edits); - } - - /** - * @param self[] $edits - */ - private function applyEdits(array $edits) - { - } - - private function createEdit(): ?self - { - return rand(0,1) ? new self() : null; - } + /** + * @param self[] $edits + */ + private function applyEdits(array $edits) + { + } + private function createEdit(): ?self + { + return rand(0, 1) ? new self() : null; + } } class AssignmentInInstanceOf { + public function doFoo() + { + $x = ($foo = $this->doBar()) instanceof self + ? $foo->doFoo() + : []; + } - public function doFoo() - { - $x = ($foo = $this->doBar()) instanceof self - ? $foo->doFoo() - : []; - } - - /** - * @return self|bool - */ - public function doBar() - { - - } - - public function doBaz() - { - if (($foo = $this->doBar()) instanceof self && 'baz' === $foo->doFoo()) { - - } - } + /** + * @return self|bool + */ + public function doBar() + { + } + public function doBaz() + { + if (($foo = $this->doBar()) instanceof self && 'baz' === $foo->doFoo()) { + } + } } class SubtractedMixed { + public function doFoo($mixed) + { + if (is_int($mixed)) { + return; + } - public function doFoo($mixed) - { - if (is_int($mixed)) { - return; - } - - $this->requireInt($mixed); - $this->requireIntOrString($mixed); - - if (is_string($mixed)) { - return; - } - - $this->requireInt($mixed); - $this->requireIntOrString($mixed); - } - - public function requireInt(int $i) - { + $this->requireInt($mixed); + $this->requireIntOrString($mixed); - } + if (is_string($mixed)) { + return; + } - /** - * @param int|string $parameter - */ - public function requireIntOrString($parameter) - { + $this->requireInt($mixed); + $this->requireIntOrString($mixed); + } - } + public function requireInt(int $i) + { + } + /** + * @param int|string $parameter + */ + public function requireIntOrString($parameter) + { + } } class IsCallableResultsInMethodExists { - - /** - * @param object $value - */ - public function doFoo($value): void - { - if (is_callable([$value, 'toArray'])) { - $value->toArray(); - } - } - + /** + * @param object $value + */ + public function doFoo($value): void + { + if (is_callable([$value, 'toArray'])) { + $value->toArray(); + } + } } class NonEmptyArrayAcceptsBug { - - public function doFoo(array $elements) - { - /** @var \stdClass[] $links */ - $links = []; - - foreach ($elements as $link) { - $links[] = $link; - } - - if (!empty($links)) { - $this->doBar('a', ...$links); - $this->doBaz('a', $links); - $this->doLorem('a', $links); - } - } - - public function doBar(string $x, \stdClass ...$y) - { - - } - - /** - * @param string $x - * @param \stdClass[] $y - */ - public function doBaz(string $x, array $y) - { - - } - - /** - * @param string $x - * @param iterable<\stdClass> $y - */ - public function doLorem(string $x, iterable $y) - { - - } - + public function doFoo(array $elements) + { + /** @var \stdClass[] $links */ + $links = []; + + foreach ($elements as $link) { + $links[] = $link; + } + + if (!empty($links)) { + $this->doBar('a', ...$links); + $this->doBaz('a', $links); + $this->doLorem('a', $links); + } + } + + public function doBar(string $x, \stdClass ...$y) + { + } + + /** + * @param string $x + * @param \stdClass[] $y + */ + public function doBaz(string $x, array $y) + { + } + + /** + * @param string $x + * @param iterable<\stdClass> $y + */ + public function doLorem(string $x, iterable $y) + { + } } class ExpectsExceptionGenerics { + /** + * @template T of \Exception + * @param T $a + * @param T $b + * @return T + */ + public function expectsExceptionUpperBound($a, $b) + { + return $b; + } - /** - * @template T of \Exception - * @param T $a - * @param T $b - * @return T - */ - public function expectsExceptionUpperBound($a, $b) - { - return $b; - } - - public function doFoo(\Throwable $t) - { - $exception = $this->expectsExceptionUpperBound(new \Exception(), $t); - $this->requiresFoo($exception); - } - - public function requiresFoo(Foo $foo) - { - - } + public function doFoo(\Throwable $t) + { + $exception = $this->expectsExceptionUpperBound(new \Exception(), $t); + $this->requiresFoo($exception); + } + public function requiresFoo(Foo $foo) + { + } } class WithStringOrNull { + public function bar(ClassWithToString $bar = null): void + { + $this->baz($bar); + } - public function bar(ClassWithToString $bar = null): void - { - $this->baz($bar); - } - - public function baz(string $baz = null): void - { - - } - + public function baz(string $baz = null): void + { + } } class TestForEmptyArrayOrTrue { + public function buggyTest(): void + { + $emptyArrayOrTrue = rand(0, 1) > 0 ? [] : true; - public function buggyTest(): void - { - $emptyArrayOrTrue = rand(0, 1) > 0 ? [] : TRUE; - - if ($emptyArrayOrTrue) { - $this->requireBool($emptyArrayOrTrue); - } else { - $this->requireArray($emptyArrayOrTrue); - } - } - - public function okTest(): void - { - $nonEmptyArrayOrFalse = rand(0, 1) > 0 ? ['foo'] : FALSE; - - if ($nonEmptyArrayOrFalse) { - $this->requireArray($nonEmptyArrayOrFalse); - } else { - $this->requireBool($nonEmptyArrayOrFalse); - } - } + if ($emptyArrayOrTrue) { + $this->requireBool($emptyArrayOrTrue); + } else { + $this->requireArray($emptyArrayOrTrue); + } + } - public function requireArray(array $value): void - { + public function okTest(): void + { + $nonEmptyArrayOrFalse = rand(0, 1) > 0 ? ['foo'] : false; - } + if ($nonEmptyArrayOrFalse) { + $this->requireArray($nonEmptyArrayOrFalse); + } else { + $this->requireBool($nonEmptyArrayOrFalse); + } + } - public function requireBool(bool $value): void - { - - } + public function requireArray(array $value): void + { + } + public function requireBool(bool $value): void + { + } } class IterableUnpackCallMethods { - - /** - * @param int[] $integers - * @param int[]|null $integersOrNull - */ - public function doFoo( - array $integers, - ?array $integersOrNull, - string $str, - $mixed - ) - { - - $this->doBar( - ...[1, 2, 3], - ...$integers, - ...$integersOrNull, - ...2, - ...$str, - ...$mixed - ); - } - - public function doBar($arg1, $arg2, $arg3, $arg4, $arg5, $arg6) - { - - } - + /** + * @param int[] $integers + * @param int[]|null $integersOrNull + */ + public function doFoo( + array $integers, + ?array $integersOrNull, + string $str, + $mixed + ) { + $this->doBar( + ...[1, 2, 3], + ...$integers, + ...$integersOrNull, + ...2, + ...$str, + ...$mixed + ); + } + + public function doBar($arg1, $arg2, $arg3, $arg4, $arg5, $arg6) + { + } } class ClassStringWithUpperBounds { + /** + * @template T of \Exception + * @param class-string $s + * @param T $object + * @return T + */ + public function doFoo(string $s, $object) + { + } - /** - * @template T of \Exception - * @param class-string $s - * @param T $object - * @return T - */ - public function doFoo(string $s, $object) - { - } - - public function doBar(\Throwable $t) - { - $this->doFoo(\InvalidArgumentException::class, new \InvalidArgumentException()); - $this->doFoo(\Exception::class, new \Exception()); - $this->doFoo(\Throwable::class, $t); - } - + public function doBar(\Throwable $t) + { + $this->doFoo(\InvalidArgumentException::class, new \InvalidArgumentException()); + $this->doFoo(\Exception::class, new \Exception()); + $this->doFoo(\Throwable::class, $t); + } } abstract class CollectionWithStaticParam { - - /** - * @param static $other - */ - public function add(self $other): void - { - } - + /** + * @param static $other + */ + public function add(self $other): void + { + } } class AppleCollection extends CollectionWithStaticParam { - - public function doFoo() - { - $this->add(new AppleCollection()); - } - + public function doFoo() + { + $this->add(new AppleCollection()); + } } function () { - $foo = new AppleCollection(); - $foo->add(new AppleCollection()); + $foo = new AppleCollection(); + $foo->add(new AppleCollection()); }; class CallableWithMixedArray { - - public function doFoo() - { - $this->doBar(function (array $foo): array { - return ['foo']; - }); - $this->doBar(function (?array $foo): array { - return ['foo']; - }); - $this->doBar(function (array $foo): ?array { - return ['foo']; - }); - } - - /** - * @param callable(array): array $a - */ - public function doBar($a) - { - - } - + public function doFoo() + { + $this->doBar(function (array $foo): array { + return ['foo']; + }); + $this->doBar(function (?array $foo): array { + return ['foo']; + }); + $this->doBar(function (array $foo): ?array { + return ['foo']; + }); + } + + /** + * @param callable(array): array $a + */ + public function doBar($a) + { + } } class ClassString { + /** + * @param class-string $className + */ + public function doFoo(string $className) + { + $this->doBar($className); + } - /** - * @param class-string $className - */ - public function doFoo(string $className) - { - $this->doBar($className); - } - - /** - * @template T - * @param class-string $className - */ - public function doBar(string $className) - { - $this->doFoo($className); - } - + /** + * @template T + * @param class-string $className + */ + public function doBar(string $className) + { + $this->doFoo($className); + } } class TestVarAnnotationAboveMethodCall { - - public function doFoo(Foo $foo) - { - /** @var Bar $foo */ - $foo->lorem(); - } - + public function doFoo(Foo $foo) + { + /** @var Bar $foo */ + $foo->lorem(); + } } class ParameterTypeCheckVerbosity { + /** + * @param array{code: string}[] $members + */ + public function doFoo(array $members) + { + $this->doBar($members); + } - /** - * @param array{code: string}[] $members - */ - public function doFoo(array $members) - { - $this->doBar($members); - } - - /** - * @param array{id: string, code: string}[] $members - */ - public function doBar(array $members) - { - - } - + /** + * @param array{id: string, code: string}[] $members + */ + public function doBar(array $members) + { + } } class ConstantArrayAccepts { - - /** - * @param array{ - * name: string, - * color: string, - * year: int, - * } $param - */ - public function doFoo(array $param): void - { - $this->doBar($param); - } - - /** - * @param array{ - * name: string, - * color?: string, - * } $param - */ - public function doBar(array $param): void - { - - } - + /** + * @param array{ + * name: string, + * color: string, + * year: int, + * } $param + */ + public function doFoo(array $param): void + { + $this->doBar($param); + } + + /** + * @param array{ + * name: string, + * color?: string, + * } $param + */ + public function doBar(array $param): void + { + } } class ConstantArrayAcceptsOptionalKey { - - /** - * @param array{wrapperClass?: class-string} $params - */ - public function doFoo(array $params) - { - $this->doFoo(['wrapperClass' => \stdClass::class, 'undocumented' => 42]); - $this->doFoo([]); - } - + /** + * @param array{wrapperClass?: class-string} $params + */ + public function doFoo(array $params) + { + $this->doFoo(['wrapperClass' => \stdClass::class, 'undocumented' => 42]); + $this->doFoo([]); + } } class NumericStringParam { + /** + * @param numeric-string $test + */ + public function sayHello(string $test): void + { + } - /** - * @param numeric-string $test - */ - public function sayHello(string $test): void - { - - } - - public function doFoo() - { - $this->sayHello(123); - $this->sayHello('abc'); - $this->sayHello('123'); - } - + public function doFoo() + { + $this->sayHello(123); + $this->sayHello('abc'); + $this->sayHello('123'); + } } class XmlReaderOpen { - - public function doFoo(\XMLReader $xml): void - { - $xml->open('http://', null); - } - - public function openStatically(): void - { - $xml = \XMLReader::open('http://', null); - if ($xml !== false) { - $xml->read(); - } - } - + public function doFoo(\XMLReader $xml): void + { + $xml->open('http://', null); + } + + public function openStatically(): void + { + $xml = \XMLReader::open('http://', null); + if ($xml !== false) { + $xml->read(); + } + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-parent-abstract-method.php b/tests/PHPStan/Rules/Methods/data/call-parent-abstract-method.php index 10286a419b..dfc67cdd5d 100644 --- a/tests/PHPStan/Rules/Methods/data/call-parent-abstract-method.php +++ b/tests/PHPStan/Rules/Methods/data/call-parent-abstract-method.php @@ -4,9 +4,7 @@ interface Baz { - - public function uninstall(): void; - + public function uninstall(): void; } abstract class Foo implements Baz @@ -15,60 +13,48 @@ abstract class Foo implements Baz class Bar extends Foo { - - public function uninstall(): void - { - parent::uninstall(); - } - + public function uninstall(): void + { + parent::uninstall(); + } } abstract class Lorem { - - abstract public function doFoo(): void; - + abstract public function doFoo(): void; } class Ipsum extends Lorem { - - public function doFoo(): void - { - parent::doFoo(); - } - + public function doFoo(): void + { + parent::doFoo(); + } } abstract class Dolor extends Lorem { - - public function doBar(): void - { - parent::doFoo(); - } - + public function doBar(): void + { + parent::doFoo(); + } } abstract class SitAmet { - - abstract static function doFoo(): void; - + abstract public static function doFoo(): void; } function (): void { - SitAmet::doFoo(); + SitAmet::doFoo(); }; abstract class Consecteur { + public function doFoo() + { + static::doBar(); + } - public function doFoo() - { - static::doBar(); - } - - abstract public function doBar(): void; - + abstract public function doBar(): void; } diff --git a/tests/PHPStan/Rules/Methods/data/call-static-methods.php b/tests/PHPStan/Rules/Methods/data/call-static-methods.php index 96e370b430..9e9577c9d3 100644 --- a/tests/PHPStan/Rules/Methods/data/call-static-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-static-methods.php @@ -4,328 +4,290 @@ class Foo { - - public static function test() - { - Bar::protectedMethodFromChild(); - } - - protected static function baz() - { - - } - - public function loremIpsum() - { - - } - - private static function dolor() - { - - } - + public static function test() + { + Bar::protectedMethodFromChild(); + } + + protected static function baz() + { + } + + public function loremIpsum() + { + } + + private static function dolor() + { + } } class Bar extends Foo { - - public static function test() - { - Foo::test(); - Foo::baz(); - parent::test(); - parent::baz(); - Foo::bar(); // nonexistent - self::bar(); // nonexistent - parent::bar(); // nonexistent - Foo::loremIpsum(); // instance - Foo::dolor(); - } - - public function loremIpsum() - { - parent::loremIpsum(); - } - - protected static function protectedMethodFromChild() - { - - } - + public static function test() + { + Foo::test(); + Foo::baz(); + parent::test(); + parent::baz(); + Foo::bar(); // nonexistent + self::bar(); // nonexistent + parent::bar(); // nonexistent + Foo::loremIpsum(); // instance + Foo::dolor(); + } + + public function loremIpsum() + { + parent::loremIpsum(); + } + + protected static function protectedMethodFromChild() + { + } } class Ipsum { - - public static function ipsumTest() - { - parent::lorem(); // does not have a parent - Foo::test(); - Foo::test(1); - Foo::baz(); // protected and not from a parent - UnknownStaticMethodClass::loremIpsum(); - } - + public static function ipsumTest() + { + parent::lorem(); // does not have a parent + Foo::test(); + Foo::test(1); + Foo::baz(); // protected and not from a parent + UnknownStaticMethodClass::loremIpsum(); + } } class ClassWithConstructor { - - private function __construct($foo) - { - - } - + private function __construct($foo) + { + } } class CheckConstructor extends ClassWithConstructor { - - public function __construct() - { - parent::__construct(); - } - + public function __construct() + { + parent::__construct(); + } } function () { - self::someStaticMethod(); - static::someStaticMethod(); - parent::someStaticMethod(); - Foo::test(); - Foo::baz(); - Foo::bar(); - Foo::loremIpsum(); - Foo::dolor(); - - \Locale::getDisplayLanguage('cs_CZ'); // OK - \Locale::getDisplayLanguage('cs_CZ', 'en'); // OK - \Locale::getDisplayLanguage('cs_CZ', 'en', 'foo'); // should report 3 parameters given, 1-2 required + self::someStaticMethod(); + static::someStaticMethod(); + parent::someStaticMethod(); + Foo::test(); + Foo::baz(); + Foo::bar(); + Foo::loremIpsum(); + Foo::dolor(); + + \Locale::getDisplayLanguage('cs_CZ'); // OK + \Locale::getDisplayLanguage('cs_CZ', 'en'); // OK + \Locale::getDisplayLanguage('cs_CZ', 'en', 'foo'); // should report 3 parameters given, 1-2 required }; interface SomeInterface { - } function (Foo $foo) { - if ($foo instanceof SomeInterface) { - $foo::test(); - $foo::test(1, 2, 3); - } - - /** @var string|int $stringOrInt */ - $stringOrInt = doFoo(); - $stringOrInt::foo(); + if ($foo instanceof SomeInterface) { + $foo::test(); + $foo::test(1, 2, 3); + } + + /** @var string|int $stringOrInt */ + $stringOrInt = doFoo(); + $stringOrInt::foo(); }; -function (FOO $foo) -{ - $foo::test(); // do not report case mismatch - - FOO::unknownMethod(); - FOO::loremIpsum(); - FOO::dolor(); - FOO::test(1, 2, 3); - FOO::TEST(); - FOO::test(); +function (FOO $foo) { + $foo::test(); // do not report case mismatch + + FOO::unknownMethod(); + FOO::loremIpsum(); + FOO::dolor(); + FOO::test(1, 2, 3); + FOO::TEST(); + FOO::test(); }; function (string $className) { - $className::foo(); + $className::foo(); }; class CallingNonexistentParentConstructor extends Foo { - - public function __construct() - { - parent::__construct(); - } - + public function __construct() + { + parent::__construct(); + } } class Baz extends Foo { - - public function doFoo() - { - parent::nonexistent(); - } - - public static function doBaz() - { - parent::nonexistent(); - parent::loremIpsum(); - } - + public function doFoo() + { + parent::nonexistent(); + } + + public static function doBaz() + { + parent::nonexistent(); + parent::loremIpsum(); + } } class ClassOrString { - - public function doFoo() - { - /** @var self|string $class */ - $class = doFoo(); - $class::calledMethod(); - $class::calledMethod(1); - Self::calledMethod(); - } - - private static function calledMethod() - { - - } - - public function doBar() - { - if (rand(0, 1)) { - $class = 'Blabla'; - } else { - $class = 'Bleble'; - } - $class::calledMethod(); - } - + public function doFoo() + { + /** @var self|string $class */ + $class = doFoo(); + $class::calledMethod(); + $class::calledMethod(1); + self::calledMethod(); + } + + private static function calledMethod() + { + } + + public function doBar() + { + if (rand(0, 1)) { + $class = 'Blabla'; + } else { + $class = 'Bleble'; + } + $class::calledMethod(); + } } interface InterfaceWithStaticMethod { + public static function doFoo(); - public static function doFoo(); - - public function doInstanceFoo(); - + public function doInstanceFoo(); } class CallStaticMethodOnAnInterface { - - public function doFoo(InterfaceWithStaticMethod $foo) - { - InterfaceWithStaticMethod::doFoo(); - InterfaceWithStaticMethod::doBar(); - $foo::doFoo(); // fine - it's an object - - InterfaceWithStaticMethod::doInstanceFoo(); - $foo::doInstanceFoo(); - } - + public function doFoo(InterfaceWithStaticMethod $foo) + { + InterfaceWithStaticMethod::doFoo(); + InterfaceWithStaticMethod::doBar(); + $foo::doFoo(); // fine - it's an object + + InterfaceWithStaticMethod::doInstanceFoo(); + $foo::doInstanceFoo(); + } } class CallStaticMethodAfterAssignmentInBooleanAnd { - - public static function generateDeliverSmDlrForMessage() - { - if ( - ($messageState = self::getMessageStateByStatusId()) - && self::isMessageStateRequested($messageState) - ) { - } - } - - /** - * @return false|string - */ - public static function getMessageStateByStatusId() - { - } - - public static function isMessageStateRequested (string $messageState): bool - { - } - + public static function generateDeliverSmDlrForMessage() + { + if ( + ($messageState = self::getMessageStateByStatusId()) + && self::isMessageStateRequested($messageState) + ) { + } + } + + /** + * @return false|string + */ + public static function getMessageStateByStatusId() + { + } + + public static function isMessageStateRequested(string $messageState): bool + { + } } class PreserveArrayKeys { - private $p = []; - - /** - * @param array $map - */ - private function test(array $map) - { - foreach ($this->p['foo'] as $key => $_) { - if (!isset($map[$key])) { - throw self::e(array_keys($map)); - } - } - } - - /** - * @param array $map - */ - private function test2(array $map) - { - foreach ($this->p['foo'] as $key => $_) { - if (!array_key_exists($key, $map)) { - throw self::e(array_keys($map)); - } - } - } - - /** - * @param string[] $list - */ - private static function e($list): \Exception - { - return new \Exception(); - } + private $p = []; + + /** + * @param array $map + */ + private function test(array $map) + { + foreach ($this->p['foo'] as $key => $_) { + if (!isset($map[$key])) { + throw self::e(array_keys($map)); + } + } + } + + /** + * @param array $map + */ + private function test2(array $map) + { + foreach ($this->p['foo'] as $key => $_) { + if (!array_key_exists($key, $map)) { + throw self::e(array_keys($map)); + } + } + } + + /** + * @param string[] $list + */ + private static function e($list): \Exception + { + return new \Exception(); + } } class ClassStringChecks { - - /** - * @template T of Foo - * @param class-string $classString - * @param class-string $anotherClassString - * @param class-string $yetAnotherClassString - */ - public function doFoo( - string $classString, - string $anotherClassString, - string $yetAnotherClassString - ) - { - $classString::nonexistentMethod(); - - $anotherClassString::test(); - $anotherClassString::test(1, 2, 3); - $anotherClassString::nonexistentMethod(); - - $yetAnotherClassString::test(); - $yetAnotherClassString::test(1, 2, 3); - $yetAnotherClassString::nonexistentMethod(); - } - + /** + * @template T of Foo + * @param class-string $classString + * @param class-string $anotherClassString + * @param class-string $yetAnotherClassString + */ + public function doFoo( + string $classString, + string $anotherClassString, + string $yetAnotherClassString + ) { + $classString::nonexistentMethod(); + + $anotherClassString::test(); + $anotherClassString::test(1, 2, 3); + $anotherClassString::nonexistentMethod(); + + $yetAnotherClassString::test(); + $yetAnotherClassString::test(1, 2, 3); + $yetAnotherClassString::nonexistentMethod(); + } } trait TraitWithStaticMethod { - - public static function doFoo(): void - { - - } - + public static function doFoo(): void + { + } } class MethodCallingTraitWithStaticMethod { - - public function doFoo(): void - { - TraitWithStaticMethod::doFoo(); - } - - public function doBar(TraitWithStaticMethod $a): void - { - $a::doFoo(); - } - + public function doFoo(): void + { + TraitWithStaticMethod::doFoo(); + } + + public function doBar(TraitWithStaticMethod $a): void + { + $a::doFoo(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-trait-methods.php b/tests/PHPStan/Rules/Methods/data/call-trait-methods.php index 89624b3594..9f5cd3fe82 100644 --- a/tests/PHPStan/Rules/Methods/data/call-trait-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-trait-methods.php @@ -2,28 +2,23 @@ namespace CallTraitMethods; -trait Foo { - - public function fooMethod() - { - - } - +trait Foo +{ + public function fooMethod() + { + } } -class Bar { - - use Foo; - +class Bar +{ + use Foo; } class Baz extends Bar { - - public function bazMethod() - { - $this->fooMethod(); - $this->unexistentMethod(); - } - + public function bazMethod() + { + $this->fooMethod(); + $this->unexistentMethod(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-trait-overridden-methods.php b/tests/PHPStan/Rules/Methods/data/call-trait-overridden-methods.php index 65a39693ff..7168944417 100644 --- a/tests/PHPStan/Rules/Methods/data/call-trait-overridden-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-trait-overridden-methods.php @@ -2,36 +2,43 @@ namespace CallTraitOverriddenMethods; -trait TraitA { - function sameName() {} +trait TraitA +{ + public function sameName() + { + } } -trait TraitB { - use TraitA { - sameName as someOtherName; - } - function sameName() { - $this->someOtherName(); - } +trait TraitB +{ + use TraitA { + sameName as someOtherName; + } + public function sameName() + { + $this->someOtherName(); + } } -trait TraitC { - use TraitB { - sameName as YetAnotherName; - } - function sameName() - { - $this->YetAnotherName(); - } +trait TraitC +{ + use TraitB { + sameName as YetAnotherName; + } + public function sameName() + { + $this->YetAnotherName(); + } } -class SomeClass { - use TraitC { - sameName as wowSoManyNames; - } +class SomeClass +{ + use TraitC { + sameName as wowSoManyNames; + } - function sameName() - { - $this->wowSoManyNames(); - } + public function sameName() + { + $this->wowSoManyNames(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/call-variadic-methods.php b/tests/PHPStan/Rules/Methods/data/call-variadic-methods.php index b475ad8f22..e730f04b1b 100644 --- a/tests/PHPStan/Rules/Methods/data/call-variadic-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-variadic-methods.php @@ -4,89 +4,80 @@ class Foo { - - public function bar() - { - $this->baz(); - $this->lorem(); - $this->baz(1, 2, 3); - $this->lorem(1, 2, 3); - } - - public function baz($foo, ...$bar) - { - - } - - public function lorem($foo, $bar) - { - $foo = 'bar'; - if ($foo) { - func_get_args(); - } - } - - public function doFoo() - { - $this->doVariadicString(1, 'foo', 'bar'); - $this->doVariadicString(1, 2, 3); - $this->doVariadicString(1); - $this->doVariadicString('foo'); - $this->doVariadicWithFuncGetArgs('foo', 'bar'); - - $strings = ['foo', 'bar', 'baz']; - $this->doVariadicString(1, ...$strings); - $this->doVariadicString(1, 'foo', ...$strings); - - $integers = [1, 2, 3]; - $this->doVariadicString(1, 'foo', 1, ...$integers); - $this->doIntegerParameters(...$strings); - $this->doIntegerParameters(...$integers); - } - - public function doVariadicString(int $int, string ...$strings) - { - - } - - public function doVariadicWithFuncGetArgs() - { - func_get_args(); - } - - public function doIntegerParameters(int $foo, int $bar) - { - - } - + public function bar() + { + $this->baz(); + $this->lorem(); + $this->baz(1, 2, 3); + $this->lorem(1, 2, 3); + } + + public function baz($foo, ...$bar) + { + } + + public function lorem($foo, $bar) + { + $foo = 'bar'; + if ($foo) { + func_get_args(); + } + } + + public function doFoo() + { + $this->doVariadicString(1, 'foo', 'bar'); + $this->doVariadicString(1, 2, 3); + $this->doVariadicString(1); + $this->doVariadicString('foo'); + $this->doVariadicWithFuncGetArgs('foo', 'bar'); + + $strings = ['foo', 'bar', 'baz']; + $this->doVariadicString(1, ...$strings); + $this->doVariadicString(1, 'foo', ...$strings); + + $integers = [1, 2, 3]; + $this->doVariadicString(1, 'foo', 1, ...$integers); + $this->doIntegerParameters(...$strings); + $this->doIntegerParameters(...$integers); + } + + public function doVariadicString(int $int, string ...$strings) + { + } + + public function doVariadicWithFuncGetArgs() + { + func_get_args(); + } + + public function doIntegerParameters(int $foo, int $bar) + { + } } class Bar { - - /** - * @param string[] ...$strings - */ - function variadicStrings(string ...$strings) - { - - } - - /** - * @param string[] ...$strings - */ - function anotherVariadicStrings(...$strings) - { - - } - - public function doFoo() - { - $this->variadicStrings(1, 2); - $this->variadicStrings('foo', 'bar'); - - $this->anotherVariadicStrings(1, 2); - $this->anotherVariadicStrings('foo', 'bar'); - } - + /** + * @param string[] ...$strings + */ + public function variadicStrings(string ...$strings) + { + } + + /** + * @param string[] ...$strings + */ + public function anotherVariadicStrings(...$strings) + { + } + + public function doFoo() + { + $this->variadicStrings(1, 2); + $this->variadicStrings('foo', 'bar'); + + $this->anotherVariadicStrings(1, 2); + $this->anotherVariadicStrings('foo', 'bar'); + } } diff --git a/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc-without-curly-braces.php b/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc-without-curly-braces.php index 40fc39a70e..cfb0f77ed7 100644 --- a/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc-without-curly-braces.php +++ b/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc-without-curly-braces.php @@ -4,65 +4,53 @@ interface FooInterface { - - /** - * @param string $str - */ - public function doBar($str); - + /** + * @param string $str + */ + public function doBar($str); } class Foo implements FooInterface { - - /** - * @param int $i - */ - public function doFoo($i) - { - - } - - /** - * @inheritDoc - */ - public function doBar($str) - { - - } - + /** + * @param int $i + */ + public function doFoo($i) + { + } + + /** + * @inheritDoc + */ + public function doBar($str) + { + } } class Bar extends Foo { - - /** - * @inheritDoc - */ - public function doFoo($i) - { - - } - + /** + * @inheritDoc + */ + public function doFoo($i) + { + } } class Baz extends Bar { - - /** - * @inheritDoc - */ - public function doFoo($i) - { - - } - + /** + * @inheritDoc + */ + public function doFoo($i) + { + } } function () { - $baz = new Baz(); - $baz->doFoo(1); - $baz->doFoo('1'); - $baz->doBar('1'); - $baz->doBar(1); + $baz = new Baz(); + $baz->doFoo(1); + $baz->doFoo('1'); + $baz->doBar('1'); + $baz->doBar(1); }; diff --git a/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc.php b/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc.php index 97d3c4b99f..a9c4866461 100644 --- a/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc.php +++ b/tests/PHPStan/Rules/Methods/data/calling-method-with-inheritdoc.php @@ -4,65 +4,53 @@ interface FooInterface { - - /** - * @param string $str - */ - public function doBar($str); - + /** + * @param string $str + */ + public function doBar($str); } class Foo implements FooInterface { - - /** - * @param int $i - */ - public function doFoo($i) - { - - } - - /** - * {@inheritDoc} - */ - public function doBar($str) - { - - } - + /** + * @param int $i + */ + public function doFoo($i) + { + } + + /** + * {@inheritDoc} + */ + public function doBar($str) + { + } } class Bar extends Foo { - - /** - * {@inheritDoc} - */ - public function doFoo($i) - { - - } - + /** + * {@inheritDoc} + */ + public function doFoo($i) + { + } } class Baz extends Bar { - - /** - * {@inheritDoc} - */ - public function doFoo($i) - { - - } - + /** + * {@inheritDoc} + */ + public function doFoo($i) + { + } } function () { - $baz = new Baz(); - $baz->doFoo(1); - $baz->doFoo('1'); - $baz->doBar('1'); - $baz->doBar(1); + $baz = new Baz(); + $baz->doFoo(1); + $baz->doFoo('1'); + $baz->doBar('1'); + $baz->doBar(1); }; diff --git a/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php b/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php index 7bd1002b47..56d7fa4251 100644 --- a/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php +++ b/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php @@ -4,117 +4,94 @@ interface FooInterface { - - /** - * @param string $str - */ - public function doBar($str); - + /** + * @param string $str + */ + public function doBar($str); } class Foo implements FooInterface { - - /** - * @param int $i - */ - public function doFoo($i) - { - - } - - public function doBar($str) - { - - } - + /** + * @param int $i + */ + public function doFoo($i) + { + } + + public function doBar($str) + { + } } class Bar extends Foo { - - public function doFoo($i) - { - - } - + public function doFoo($i) + { + } } class Baz extends Bar { - - public function doFoo($i) - { - - } - + public function doFoo($i) + { + } } function () { - $baz = new Baz(); - $baz->doFoo(1); - $baz->doFoo('1'); - $baz->doBar('1'); - $baz->doBar(1); + $baz = new Baz(); + $baz->doFoo(1); + $baz->doFoo('1'); + $baz->doBar('1'); + $baz->doBar(1); }; class Lorem { - - /** - * @param B $b - * @param C $c - * @param A $a - * @param D $d - */ - public function doLorem($a, $b, $c, $d) - { - - } - + /** + * @param B $b + * @param C $c + * @param A $a + * @param D $d + */ + public function doLorem($a, $b, $c, $d) + { + } } class Ipsum extends Lorem { - - public function doLorem($x, $y, $z, $d) - { - - } - + public function doLorem($x, $y, $z, $d) + { + } } function (Ipsum $ipsum, A $a, B $b, C $c, D $d): void { - $ipsum->doLorem($a, $b, $c, $d); - $ipsum->doLorem(1, 1, 1, 1); + $ipsum->doLorem($a, $b, $c, $d); + $ipsum->doLorem(1, 1, 1, 1); }; class Dolor extends Ipsum { - - public function doLorem($g, $h, $i, $d) - { - - } - + public function doLorem($g, $h, $i, $d) + { + } } function (Dolor $ipsum, A $a, B $b, C $c, D $d): void { - $ipsum->doLorem($a, $b, $c, $d); - $ipsum->doLorem(1, 1, 1, 1); + $ipsum->doLorem($a, $b, $c, $d); + $ipsum->doLorem(1, 1, 1, 1); }; class TestArrayObject { - - /** - * @param \ArrayObject $arrayObject - */ - public function doFoo(\ArrayObject $arrayObject): void - { - $arrayObject->append(new \Exception()); - } - + /** + * @param \ArrayObject $arrayObject + */ + public function doFoo(\ArrayObject $arrayObject): void + { + $arrayObject->append(new \Exception()); + } } /** @@ -122,11 +99,10 @@ public function doFoo(\ArrayObject $arrayObject): void */ class TestArrayObject2 extends \ArrayObject { - } function (TestArrayObject2 $arrayObject2): void { - $arrayObject2->append(new \Exception()); + $arrayObject2->append(new \Exception()); }; /** @@ -134,14 +110,12 @@ function (TestArrayObject2 $arrayObject2): void { */ class TestArrayObject3 extends \ArrayObject { - - public function append($someValue) - { - return parent::append($someValue); - } - + public function append($someValue) + { + return parent::append($someValue); + } } function (TestArrayObject3 $arrayObject3): void { - $arrayObject3->append(new \Exception()); + $arrayObject3->append(new \Exception()); }; diff --git a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php index 7d50d87121..cadace171a 100644 --- a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php +++ b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php @@ -4,66 +4,58 @@ class Foo { - - /** - * @param mixed $explicit - */ - public function doFoo( - $implicit, - $explicit - ): void - { - $implicit->foo(); - $explicit->foo(); - } - - /** - * @template T - * @param T $t - */ - public function doBar($t): void - { - $t->foo(); - } - + /** + * @param mixed $explicit + */ + public function doFoo( + $implicit, + $explicit + ): void { + $implicit->foo(); + $explicit->foo(); + } + + /** + * @template T + * @param T $t + */ + public function doBar($t): void + { + $t->foo(); + } } class Bar { - - /** - * @param mixed $explicit - */ - public function doFoo( - $implicit, - $explicit - ): void - { - $this->doBar($implicit); - $this->doBar($explicit); - - $this->doBaz($implicit); - $this->doBaz($explicit); - } - - public function doBar(int $i): void - { - - } - - public function doBaz($mixed): void - { - - } - - /** - * @template T - * @param T $t - */ - public function doLorem($t): void - { - $this->doBar($t); - $this->doBaz($t); - } - + /** + * @param mixed $explicit + */ + public function doFoo( + $implicit, + $explicit + ): void { + $this->doBar($implicit); + $this->doBar($explicit); + + $this->doBaz($implicit); + $this->doBaz($explicit); + } + + public function doBar(int $i): void + { + } + + public function doBaz($mixed): void + { + } + + /** + * @template T + * @param T $t + */ + public function doLorem($t): void + { + $this->doBar($t); + $this->doBaz($t); + } } diff --git a/tests/PHPStan/Rules/Methods/data/check-nullables.php b/tests/PHPStan/Rules/Methods/data/check-nullables.php index ee5a3243e9..b1c0822f46 100644 --- a/tests/PHPStan/Rules/Methods/data/check-nullables.php +++ b/tests/PHPStan/Rules/Methods/data/check-nullables.php @@ -4,15 +4,13 @@ class Foo { + public function doFoo(string $foo) + { + $this->doFoo('foo'); + $this->doFoo(null); - public function doFoo(string $foo) - { - $this->doFoo('foo'); - $this->doFoo(null); - - /** @var string|null $stringOrNull */ - $stringOrNull = doFoo(); - $this->doFoo($stringOrNull); - } - + /** @var string|null $stringOrNull */ + $stringOrNull = doFoo(); + $this->doFoo($stringOrNull); + } } diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind-defined.php b/tests/PHPStan/Rules/Methods/data/closure-bind-defined.php index 764748ed54..a473636be7 100644 --- a/tests/PHPStan/Rules/Methods/data/closure-bind-defined.php +++ b/tests/PHPStan/Rules/Methods/data/closure-bind-defined.php @@ -4,15 +4,11 @@ class Foo { + private function privateMethod() + { + } - private function privateMethod() - { - - } - - public function publicMethod() - { - - } - + public function publicMethod() + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind.php b/tests/PHPStan/Rules/Methods/data/closure-bind.php index e4dec44d16..0e8c726d23 100644 --- a/tests/PHPStan/Rules/Methods/data/closure-bind.php +++ b/tests/PHPStan/Rules/Methods/data/closure-bind.php @@ -4,44 +4,42 @@ class Bar { - - public function fooMethod(): Foo - { - \Closure::bind(function (Foo $foo) { - $foo->privateMethod(); - $foo->nonexistentMethod(); - }, null, Foo::class); - - $this->fooMethod(); - $this->barMethod(); - $foo = new Foo(); - $foo->privateMethod(); - $foo->nonexistentMethod(); - - \Closure::bind(function () { - $this->fooMethod(); - $this->barMethod(); - }, $nonexistent, self::class); - - \Closure::bind(function (Foo $foo) { - $foo->privateMethod(); - $foo->nonexistentMethod(); - }, null, 'CallClosureBind\Foo'); - - \Closure::bind(function (Foo $foo) { - $foo->privateMethod(); - $foo->nonexistentMethod(); - }, null, new Foo()); - - \Closure::bind(function () { - // $this is Foo - $this->privateMethod(); - $this->nonexistentMethod(); - }, $this->fooMethod(), Foo::class); - - (function () { - $this->publicMethod(); - })->call(new Foo()); - } - + public function fooMethod(): Foo + { + \Closure::bind(function (Foo $foo) { + $foo->privateMethod(); + $foo->nonexistentMethod(); + }, null, Foo::class); + + $this->fooMethod(); + $this->barMethod(); + $foo = new Foo(); + $foo->privateMethod(); + $foo->nonexistentMethod(); + + \Closure::bind(function () { + $this->fooMethod(); + $this->barMethod(); + }, $nonexistent, self::class); + + \Closure::bind(function (Foo $foo) { + $foo->privateMethod(); + $foo->nonexistentMethod(); + }, null, 'CallClosureBind\Foo'); + + \Closure::bind(function (Foo $foo) { + $foo->privateMethod(); + $foo->nonexistentMethod(); + }, null, new Foo()); + + \Closure::bind(function () { + // $this is Foo + $this->privateMethod(); + $this->nonexistentMethod(); + }, $this->fooMethod(), Foo::class); + + (function () { + $this->publicMethod(); + })->call(new Foo()); + } } diff --git a/tests/PHPStan/Rules/Methods/data/closure-call.php b/tests/PHPStan/Rules/Methods/data/closure-call.php index 7b8f5862d8..078fe39345 100644 --- a/tests/PHPStan/Rules/Methods/data/closure-call.php +++ b/tests/PHPStan/Rules/Methods/data/closure-call.php @@ -2,10 +2,12 @@ namespace ClosureCall; -$newThis = new class {}; -$thing = new class {}; +$newThis = new class() {}; +$thing = new class() {}; -$predicate = function (object $thing): bool { return true; }; +$predicate = function (object $thing): bool { + return true; +}; $predicate->call(); $predicate->call($newThis); $predicate->call(42); @@ -14,5 +16,6 @@ $predicate->call($newThis, $thing, 42); $result = $predicate->call($newThis, $thing); -$operation = function (object $thing): void {}; +$operation = function (object $thing): void { +}; $voidResult = $operation->call($newThis, $thing); diff --git a/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php index 5b82190623..3e45ac8c2f 100644 --- a/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/constructor-statement-no-side-effects.php @@ -3,58 +3,49 @@ namespace ConstructorStatementNoSideEffects; function () { - new \Exception(); - throw new \Exception(); + new \Exception(); + throw new \Exception(); }; function () { - new \PDOStatement(); - new \stdClass(); + new \PDOStatement(); + new \stdClass(); }; class ConstructorWithPure { - - /** - * @phpstan-pure - */ - public function __construct() - { - - } - + /** + * @phpstan-pure + */ + public function __construct() + { + } } class ConstructorWithPureAndThrowsVoid { - - /** - * @phpstan-pure - * @throws void - */ - public function __construct() - { - - } - + /** + * @phpstan-pure + * @throws void + */ + public function __construct() + { + } } class ConstructorWithPureAndThrowsException { - - /** - * @phpstan-pure - * @throws \Exception - */ - public function __construct() - { - - } - + /** + * @phpstan-pure + * @throws \Exception + */ + public function __construct() + { + } } -function(): void { - new ConstructorWithPure(); - new ConstructorWithPureAndThrowsVoid(); - new ConstructorWithPureAndThrowsException(); +function (): void { + new ConstructorWithPure(); + new ConstructorWithPureAndThrowsVoid(); + new ConstructorWithPureAndThrowsException(); }; diff --git a/tests/PHPStan/Rules/Methods/data/disallow-named-arguments.php b/tests/PHPStan/Rules/Methods/data/disallow-named-arguments.php index daff44a02b..88aee29e8f 100644 --- a/tests/PHPStan/Rules/Methods/data/disallow-named-arguments.php +++ b/tests/PHPStan/Rules/Methods/data/disallow-named-arguments.php @@ -4,15 +4,12 @@ class Foo { - - public function doFoo(): void - { - $this->doBar(i: 1); - } - - public function doBar(int $i): void - { - - } - + public function doFoo(): void + { + $this->doBar(i: 1); + } + + public function doBar(int $i): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-methods.php b/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-methods.php index e99ee04f4b..fa5ee1dc11 100644 --- a/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-methods.php +++ b/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-methods.php @@ -4,83 +4,74 @@ class FooParent { - - /** - * @param int $int - * @param string $string - * @param ?float $float - * @param \stdClass|false $object - * @param bool $bool - * @param resource $resource - */ - public function bar( - $int, - $string, - $float, - $object, - $bool, - $resource - ): void { - } - + /** + * @param int $int + * @param string $string + * @param ?float $float + * @param \stdClass|false $object + * @param bool $bool + * @param resource $resource + */ + public function bar( + $int, + $string, + $float, + $object, + $bool, + $resource + ): void { + } } class Foo extends FooParent { - - /** - * @param int $int - * @param string $string - * @param ?float $float - * @param \stdClass|false $object - * @param bool $bool - * @param resource $resource - */ - public function baz( - $int = 10, - $string = 'string', - $float = null, - $object = false, - $bool = null, - $resource = false - ): void { - } - - public function bar( - $int = 10, - $string = 'string', - $float = null, - $object = false, - $bool = null, - $resource = false - ): void { - } - + /** + * @param int $int + * @param string $string + * @param ?float $float + * @param \stdClass|false $object + * @param bool $bool + * @param resource $resource + */ + public function baz( + $int = 10, + $string = 'string', + $float = null, + $object = false, + $bool = null, + $resource = false + ): void { + } + + public function bar( + $int = 10, + $string = 'string', + $float = null, + $object = false, + $bool = null, + $resource = false + ): void { + } } class Bar { - - /** - * @param array{name?:string} $settings - */ - public function doFoo(array $settings = []): void - { - - } - - public function doBar(float $test2 = 0): void - { - - } - - /** - * @template T - * @param T $a - */ - public function doBaz($a = 'str'): void - { - - } - + /** + * @param array{name?:string} $settings + */ + public function doFoo(array $settings = []): void + { + } + + public function doBar(float $test2 = 0): void + { + } + + /** + * @template T + * @param T $a + */ + public function doBaz($a = 'str'): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-trait-crash.php b/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-trait-crash.php index d20fbfc08f..70f1fbfac9 100644 --- a/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-trait-crash.php +++ b/tests/PHPStan/Rules/Methods/data/incompatible-default-parameter-type-trait-crash.php @@ -4,22 +4,21 @@ trait ConstructorWithoutArgumentsTrait { - public function __construct(\stdClass $foo = null) - { - } + public function __construct(\stdClass $foo = null) + { + } } class Foo { - use ConstructorWithoutArgumentsTrait; + use ConstructorWithoutArgumentsTrait; - /** - * @var \stdClass - */ - protected $foo; + /** + * @var \stdClass + */ + protected $foo; - public function __construct() - { - - } + public function __construct() + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/incorrect-method-case.php b/tests/PHPStan/Rules/Methods/data/incorrect-method-case.php index f6fad6ae69..d453952255 100644 --- a/tests/PHPStan/Rules/Methods/data/incorrect-method-case.php +++ b/tests/PHPStan/Rules/Methods/data/incorrect-method-case.php @@ -4,11 +4,9 @@ class Foo { - - public function fooBar() - { - $this->foobar(); - $this->fooBar(); - } - + public function fooBar() + { + $this->foobar(); + $this->fooBar(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/incorrect-static-method-case.php b/tests/PHPStan/Rules/Methods/data/incorrect-static-method-case.php index b139e58fe2..aa1b2d1832 100644 --- a/tests/PHPStan/Rules/Methods/data/incorrect-static-method-case.php +++ b/tests/PHPStan/Rules/Methods/data/incorrect-static-method-case.php @@ -4,11 +4,9 @@ class Foo { - - public static function fooBar() - { - self::foobar(); - self::fooBar(); - } - + public static function fooBar() + { + self::foobar(); + self::fooBar(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/infer-array-key.php b/tests/PHPStan/Rules/Methods/data/infer-array-key.php index d43e6ed0f9..4c580f63bd 100644 --- a/tests/PHPStan/Rules/Methods/data/infer-array-key.php +++ b/tests/PHPStan/Rules/Methods/data/infer-array-key.php @@ -9,16 +9,14 @@ */ class Foo implements \IteratorAggregate { + /** @var \stdClass[] */ + private $items; - /** @var \stdClass[] */ - private $items; - - public function getIterator() - { - $it = new \ArrayIterator($this->items); - assertType('(int|string)', $it->key()); - - return $it; - } + public function getIterator() + { + $it = new \ArrayIterator($this->items); + assertType('(int|string)', $it->key()); + return $it; + } } diff --git a/tests/PHPStan/Rules/Methods/data/invoke-magic-method.php b/tests/PHPStan/Rules/Methods/data/invoke-magic-method.php index 00c8f50e06..d03360e40c 100644 --- a/tests/PHPStan/Rules/Methods/data/invoke-magic-method.php +++ b/tests/PHPStan/Rules/Methods/data/invoke-magic-method.php @@ -4,25 +4,20 @@ class ClassForCallable { - - public function doFoo(callable $foo) - { - - } - + public function doFoo(callable $foo) + { + } } class ClassWithInvoke { - - public function __invoke() - { - } - + public function __invoke() + { + } } function () { - $foo = new ClassForCallable(); - $foo->doFoo(new ClassWithInvoke()); - $foo->doFoo($foo); + $foo = new ClassForCallable(); + $foo->doFoo(new ClassWithInvoke()); + $foo->doFoo($foo); }; diff --git a/tests/PHPStan/Rules/Methods/data/less-parameters-variadics.php b/tests/PHPStan/Rules/Methods/data/less-parameters-variadics.php index 47a9d75205..951a7b3b24 100644 --- a/tests/PHPStan/Rules/Methods/data/less-parameters-variadics.php +++ b/tests/PHPStan/Rules/Methods/data/less-parameters-variadics.php @@ -4,40 +4,28 @@ class Foo { - - public function doFoo(int $many, string $parameters, string $here) - { - - } - + public function doFoo(int $many, string $parameters, string $here) + { + } } class Bar extends Foo { - - public function doFoo(...$everything) - { - - } - + public function doFoo(...$everything) + { + } } class Baz extends Foo { - - public function doFoo(int ...$everything) - { - - } - + public function doFoo(int ...$everything) + { + } } class Lorem extends Foo { - - public function doFoo(int $many, string ...$everything) - { - - } - + public function doFoo(int $many, string ...$everything) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/match-expr-void-used.php b/tests/PHPStan/Rules/Methods/data/match-expr-void-used.php index fed26228b9..a26fc0e5a3 100644 --- a/tests/PHPStan/Rules/Methods/data/match-expr-void-used.php +++ b/tests/PHPStan/Rules/Methods/data/match-expr-void-used.php @@ -1,31 +1,28 @@ -= 8.0 += 8.0 namespace MatchExprVoidUsed; class Foo { - - public function doFoo($m): void - { - match ($this->doLorem()) { - $this->doBar() => $this->doBaz(), - default => $this->doBaz(), - }; - } - - public function doBar(): void - { - - } - - public function doBaz(): void - { - - } - - public function doLorem(): void - { - - } - + public function doFoo($m): void + { + match ($this->doLorem()) { + $this->doBar() => $this->doBaz(), + default => $this->doBaz(), + }; + } + + public function doBar(): void + { + } + + public function doBaz(): void + { + } + + public function doLorem(): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/merge-inherited-param.php b/tests/PHPStan/Rules/Methods/data/merge-inherited-param.php index 184754c62d..b3726db468 100644 --- a/tests/PHPStan/Rules/Methods/data/merge-inherited-param.php +++ b/tests/PHPStan/Rules/Methods/data/merge-inherited-param.php @@ -1,43 +1,52 @@ -method(new A(), new B()); // ok - $foo->method(new D(), new D()); // expects A, B + $foo->method(new A(), new B()); // ok + $foo->method(new D(), new D()); // expects A, B }; function (ChildClass $foo) { - $foo->method(new C(), new B()); // ok - $foo->method(new B(), new D()); // expects C, B + $foo->method(new C(), new B()); // ok + $foo->method(new B(), new D()); // expects C, B }; diff --git a/tests/PHPStan/Rules/Methods/data/merge-inherited-return.php b/tests/PHPStan/Rules/Methods/data/merge-inherited-return.php index fd34dfedde..9a210da4a0 100644 --- a/tests/PHPStan/Rules/Methods/data/merge-inherited-return.php +++ b/tests/PHPStan/Rules/Methods/data/merge-inherited-return.php @@ -2,53 +2,64 @@ namespace ReturnTypePhpDocMergeReturnInherited; -class A {} -class B extends A {} -class C extends B {} -class D extends A {} +class A +{ +} +class B extends A +{ +} +class C extends B +{ +} +class D extends A +{ +} class GrandparentClass { - /** @return B */ - public function method() { return new B(); } + /** @return B */ + public function method() + { + return new B(); + } } interface InterfaceC { - /** @return C */ - public function method(); + /** @return C */ + public function method(); } interface InterfaceA { - /** @return A */ - public function method(); + /** @return A */ + public function method(); } class ParentClass extends GrandparentClass implements InterfaceC, InterfaceA { - /** Some comment */ - public function method() - { - return new A(); - } + /** Some comment */ + public function method() + { + return new A(); + } } class ChildClass extends ParentClass { - public function method() - { - return new A(); - } + public function method() + { + return new A(); + } } class ChildClass2 extends ParentClass { - /** - * @return D - */ - public function method() - { - return new B(); - } + /** + * @return D + */ + public function method() + { + return new B(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/method-attributes.php b/tests/PHPStan/Rules/Methods/data/method-attributes.php index 314716934e..cd6b2c2145 100644 --- a/tests/PHPStan/Rules/Methods/data/method-attributes.php +++ b/tests/PHPStan/Rules/Methods/data/method-attributes.php @@ -5,50 +5,38 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_METHOD)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - #[Foo] - private function doFoo(): void - { - - } - + #[Foo] + private function doFoo(): void + { + } } class Ipsum { - - #[Bar] - private function doFoo(): void - { - - } - + #[Bar] + private function doFoo(): void + { + } } class Dolor { - - #[Baz] - private function doFoo(): void - { - - } - + #[Baz] + private function doFoo(): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects-phpdoc.php b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects-phpdoc.php index 1f6d4969b2..cd5d042b58 100644 --- a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects-phpdoc.php +++ b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects-phpdoc.php @@ -4,39 +4,39 @@ class Bzz { - function regular(string $a): string - { - return $a; - } + public function regular(string $a): string + { + return $a; + } - /** - * @phpstan-pure - */ - function pure1(string $a): string - { - return $a; - } + /** + * @phpstan-pure + */ + public function pure1(string $a): string + { + return $a; + } - /** - * @psalm-pure - */ - function pure2(string $a): string - { - return $a; - } + /** + * @psalm-pure + */ + public function pure2(string $a): string + { + return $a; + } - /** - * @psalm-pure - */ - function pure3(string $a): string - { - return $a; - } + /** + * @psalm-pure + */ + public function pure3(string $a): string + { + return $a; + } } -function(): void { - (new Bzz())->regular('test'); - (new Bzz())->pure1('test'); - (new Bzz())->pure2('test'); - (new Bzz())->pure3('test'); +function (): void { + (new Bzz())->regular('test'); + (new Bzz())->pure1('test'); + (new Bzz())->pure2('test'); + (new Bzz())->pure3('test'); }; diff --git a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php index b3f45e7684..bc7d846930 100644 --- a/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/method-call-statement-no-side-effects.php @@ -4,65 +4,57 @@ class Foo { - - public function doFoo(\DateTime $dt) - { - $dt->modify('+1 month'); - } - - public function doBar(\DateTimeImmutable $dti) - { - $dti->modify('+1 month'); - $dti->createFromFormat('Y-m-d', '2019-07-24'); - } - - public function doBaz(\Exception $e) - { - $e->getCode(); - } - + public function doFoo(\DateTime $dt) + { + $dt->modify('+1 month'); + } + + public function doBar(\DateTimeImmutable $dti) + { + $dti->modify('+1 month'); + $dti->createFromFormat('Y-m-d', '2019-07-24'); + } + + public function doBaz(\Exception $e) + { + $e->getCode(); + } } class Bar { - - public function doFoo() - { - - } - - /** - * @phpstan-pure - */ - public function doPure() - { - - } - - /** - * @phpstan-pure - * @throws void - */ - public function doPureWithThrowsVoid() - { - - } - - /** - * @phpstan-pure - * @throws \Exception - */ - public function doPureWithThrowsException() - { - - } - - public function doBar(): void - { - $this->doFoo(); - $this->doPure(); // report - $this->doPureWithThrowsVoid(); // report - $this->doPureWithThrowsException(); // do not report - } - + public function doFoo() + { + } + + /** + * @phpstan-pure + */ + public function doPure() + { + } + + /** + * @phpstan-pure + * @throws void + */ + public function doPureWithThrowsVoid() + { + } + + /** + * @phpstan-pure + * @throws \Exception + */ + public function doPureWithThrowsException() + { + } + + public function doBar(): void + { + $this->doFoo(); + $this->doPure(); // report + $this->doPureWithThrowsVoid(); // report + $this->doPureWithThrowsException(); // do not report + } } diff --git a/tests/PHPStan/Rules/Methods/data/method-signature-static.php b/tests/PHPStan/Rules/Methods/data/method-signature-static.php index 097ebd985c..43d5b122cb 100644 --- a/tests/PHPStan/Rules/Methods/data/method-signature-static.php +++ b/tests/PHPStan/Rules/Methods/data/method-signature-static.php @@ -4,26 +4,20 @@ class Foo { - - /** - * @param int $value - */ - public static function doFoo($value) - { - - } - + /** + * @param int $value + */ + public static function doFoo($value) + { + } } class Bar extends Foo { - - /** - * @param string $value - */ - public static function doFoo($value) - { - - } - + /** + * @param string $value + */ + public static function doFoo($value) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/method-signature-trait.php b/tests/PHPStan/Rules/Methods/data/method-signature-trait.php index 591aa96c76..8728044c5d 100644 --- a/tests/PHPStan/Rules/Methods/data/method-signature-trait.php +++ b/tests/PHPStan/Rules/Methods/data/method-signature-trait.php @@ -4,132 +4,128 @@ class SubClassUsingTrait extends BaseClass implements BaseInterface { - - use SubTrait; - + use SubTrait; } trait SubTrait { - - /** - * @param Dog $animal - */ - public function __construct($animal) - { - } - - public function parameterTypeTest1() - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest2($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest3($animal) - { - } - - /** - * @param Dog $animal - */ - public function parameterTypeTest4($animal) - { - } - - /** - * @param Dog $animal - */ - public function parameterTypeTest5($animal) - { - } - - /** - * @param Animal|null $animal - */ - public function parameterTypeTest6($animal) - { - } - - public function parameterTypeTest7($animal) - { - } - - /** - * @param mixed $animal - */ - public function parameterTypeTest8($animal) - { - } - - /** - * @return mixed - */ - public function returnTypeTest1() - { - } - - /** - * @return Animal - */ - public function returnTypeTest2() - { - } - - /** - * @return Dog - */ - public function returnTypeTest3() - { - } - - /** - * @return Animal - */ - public function returnTypeTest4() - { - } - - /** - * @return Cat - */ - public function returnTypeTest5() - { - } - - /** - * @return Animal - */ - public function returnTypeTest6() - { - } - - /** - * @return Animal - */ - public function returnTypeTest7() - { - } - - /** - * @return Animal - */ - public function returnTypeTest8() - { - } - - /** - * @return void - */ - public function returnTypeTest9() - { - } - + /** + * @param Dog $animal + */ + public function __construct($animal) + { + } + + public function parameterTypeTest1() + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest2($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest3($animal) + { + } + + /** + * @param Dog $animal + */ + public function parameterTypeTest4($animal) + { + } + + /** + * @param Dog $animal + */ + public function parameterTypeTest5($animal) + { + } + + /** + * @param Animal|null $animal + */ + public function parameterTypeTest6($animal) + { + } + + public function parameterTypeTest7($animal) + { + } + + /** + * @param mixed $animal + */ + public function parameterTypeTest8($animal) + { + } + + /** + * @return mixed + */ + public function returnTypeTest1() + { + } + + /** + * @return Animal + */ + public function returnTypeTest2() + { + } + + /** + * @return Dog + */ + public function returnTypeTest3() + { + } + + /** + * @return Animal + */ + public function returnTypeTest4() + { + } + + /** + * @return Cat + */ + public function returnTypeTest5() + { + } + + /** + * @return Animal + */ + public function returnTypeTest6() + { + } + + /** + * @return Animal + */ + public function returnTypeTest7() + { + } + + /** + * @return Animal + */ + public function returnTypeTest8() + { + } + + /** + * @return void + */ + public function returnTypeTest9() + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/method-signature.php b/tests/PHPStan/Rules/Methods/data/method-signature.php index c917073882..e2a792b5fb 100644 --- a/tests/PHPStan/Rules/Methods/data/method-signature.php +++ b/tests/PHPStan/Rules/Methods/data/method-signature.php @@ -16,420 +16,407 @@ class Cat extends Animal class BaseClass { - - /** - * @param Animal $animal - */ - public function __construct($animal) - { - } - - public function parameterTypeTest1() - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest2($animal) - { - } - - /** - * @param Dog $animal - */ - public function parameterTypeTest3($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest4($animal) - { - } - - /** - * @param Cat $animal - */ - public function parameterTypeTest5($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest6($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest7($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest8($animal) - { - } - - /** - * @return void - */ - public function returnTypeTest1() - { - } - - /** - * @return Animal - */ - public function returnTypeTest2() - { - } - - /** - * @return Animal - */ - public function returnTypeTest3() - { - } - - /** - * @return Dog - */ - public function returnTypeTest4() - { - } - - /** - * @return Dog - */ - public function returnTypeTest5() - { - } - - /** - * @return Animal|null - */ - public function returnTypeTest6() - { - } - - /** - * @return mixed - */ - public function returnTypeTest7() - { - } - - /** - * @return mixed - */ - public function returnTypeTest8() - { - } - - /** - * @return mixed - */ - public function returnTypeTest9() - { - } - + /** + * @param Animal $animal + */ + public function __construct($animal) + { + } + + public function parameterTypeTest1() + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest2($animal) + { + } + + /** + * @param Dog $animal + */ + public function parameterTypeTest3($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest4($animal) + { + } + + /** + * @param Cat $animal + */ + public function parameterTypeTest5($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest6($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest7($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest8($animal) + { + } + + /** + * @return void + */ + public function returnTypeTest1() + { + } + + /** + * @return Animal + */ + public function returnTypeTest2() + { + } + + /** + * @return Animal + */ + public function returnTypeTest3() + { + } + + /** + * @return Dog + */ + public function returnTypeTest4() + { + } + + /** + * @return Dog + */ + public function returnTypeTest5() + { + } + + /** + * @return Animal|null + */ + public function returnTypeTest6() + { + } + + /** + * @return mixed + */ + public function returnTypeTest7() + { + } + + /** + * @return mixed + */ + public function returnTypeTest8() + { + } + + /** + * @return mixed + */ + public function returnTypeTest9() + { + } } interface BaseInterface { - - /** - * @param Animal $animal - */ - public function __construct($animal); - - public function parameterTypeTest1(); - - /** - * @param Animal $animal - */ - public function parameterTypeTest2($animal); - - /** - * @param Dog $animal - */ - public function parameterTypeTest3($animal); - - /** - * @param Animal $animal - */ - public function parameterTypeTest4($animal); - - /** - * @param Cat $animal - */ - public function parameterTypeTest5($animal); - - /** - * @param Animal $animal - */ - public function parameterTypeTest6($animal); - - /** - * @param Animal $animal - */ - public function parameterTypeTest7($animal); - - /** - * @param Animal $animal - */ - public function parameterTypeTest8($animal); - - /** - * @return void - */ - public function returnTypeTest1(); - - /** - * @return Animal - */ - public function returnTypeTest2(); - - /** - * @return Animal - */ - public function returnTypeTest3(); - - /** - * @return Dog - */ - public function returnTypeTest4(); - - /** - * @return Dog - */ - public function returnTypeTest5(); - - /** - * @return Animal|null - */ - public function returnTypeTest6(); - - public function returnTypeTest7(); - - /** - * @return mixed - */ - public function returnTypeTest8(); - - public function returnTypeTest9(); - + /** + * @param Animal $animal + */ + public function __construct($animal); + + public function parameterTypeTest1(); + + /** + * @param Animal $animal + */ + public function parameterTypeTest2($animal); + + /** + * @param Dog $animal + */ + public function parameterTypeTest3($animal); + + /** + * @param Animal $animal + */ + public function parameterTypeTest4($animal); + + /** + * @param Cat $animal + */ + public function parameterTypeTest5($animal); + + /** + * @param Animal $animal + */ + public function parameterTypeTest6($animal); + + /** + * @param Animal $animal + */ + public function parameterTypeTest7($animal); + + /** + * @param Animal $animal + */ + public function parameterTypeTest8($animal); + + /** + * @return void + */ + public function returnTypeTest1(); + + /** + * @return Animal + */ + public function returnTypeTest2(); + + /** + * @return Animal + */ + public function returnTypeTest3(); + + /** + * @return Dog + */ + public function returnTypeTest4(); + + /** + * @return Dog + */ + public function returnTypeTest5(); + + /** + * @return Animal|null + */ + public function returnTypeTest6(); + + public function returnTypeTest7(); + + /** + * @return mixed + */ + public function returnTypeTest8(); + + public function returnTypeTest9(); } class BaseClassWithPrivateMethods { - - /** - * @param Animal $animal - */ - private function parameterTypeTest1($animal) - { - } - - /** - * @return Animal - */ - private function returnTypeTest1() - { - } - - /** - * @param Animal $animal - */ - private function parameterTypeTest2($animal) - { - } - - /** - * @return Animal - */ - private function returnTypeTest2() - { - } - + /** + * @param Animal $animal + */ + private function parameterTypeTest1($animal) + { + } + + /** + * @return Animal + */ + private function returnTypeTest1() + { + } + + /** + * @param Animal $animal + */ + private function parameterTypeTest2($animal) + { + } + + /** + * @return Animal + */ + private function returnTypeTest2() + { + } } - namespace MethodSignature; class SubClass extends BaseClass implements BaseInterface { - - /** - * @param Dog $animal - */ - public function __construct($animal) - { - } - - public function parameterTypeTest1() - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest2($animal) - { - } - - /** - * @param Animal $animal - */ - public function parameterTypeTest3($animal) - { - } - - /** - * @param Dog $animal - */ - public function parameterTypeTest4($animal) - { - } - - /** - * @param Dog $animal - */ - public function parameterTypeTest5($animal) - { - } - - /** - * @param Animal|null $animal - */ - public function parameterTypeTest6($animal) - { - } - - public function parameterTypeTest7($animal) - { - } - - /** - * @param mixed $animal - */ - public function parameterTypeTest8($animal) - { - } - - /** - * @return mixed - */ - public function returnTypeTest1() - { - } - - /** - * @return Animal - */ - public function returnTypeTest2() - { - } - - /** - * @return Dog - */ - public function returnTypeTest3() - { - } - - /** - * @return Animal - */ - public function returnTypeTest4() - { - } - - /** - * @return Cat - */ - public function returnTypeTest5() - { - } - - /** - * @return Animal - */ - public function returnTypeTest6() - { - } - - /** - * @return Animal - */ - public function returnTypeTest7() - { - } - - /** - * @return Animal - */ - public function returnTypeTest8() - { - } - - /** - * @return void - */ - public function returnTypeTest9() - { - } - + /** + * @param Dog $animal + */ + public function __construct($animal) + { + } + + public function parameterTypeTest1() + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest2($animal) + { + } + + /** + * @param Animal $animal + */ + public function parameterTypeTest3($animal) + { + } + + /** + * @param Dog $animal + */ + public function parameterTypeTest4($animal) + { + } + + /** + * @param Dog $animal + */ + public function parameterTypeTest5($animal) + { + } + + /** + * @param Animal|null $animal + */ + public function parameterTypeTest6($animal) + { + } + + public function parameterTypeTest7($animal) + { + } + + /** + * @param mixed $animal + */ + public function parameterTypeTest8($animal) + { + } + + /** + * @return mixed + */ + public function returnTypeTest1() + { + } + + /** + * @return Animal + */ + public function returnTypeTest2() + { + } + + /** + * @return Dog + */ + public function returnTypeTest3() + { + } + + /** + * @return Animal + */ + public function returnTypeTest4() + { + } + + /** + * @return Cat + */ + public function returnTypeTest5() + { + } + + /** + * @return Animal + */ + public function returnTypeTest6() + { + } + + /** + * @return Animal + */ + public function returnTypeTest7() + { + } + + /** + * @return Animal + */ + public function returnTypeTest8() + { + } + + /** + * @return void + */ + public function returnTypeTest9() + { + } } abstract class ReturnSomethingElseThenVoid implements BaseInterface { - - public function returnTypeTest1(): int - { - return 1; - } - + public function returnTypeTest1(): int + { + return 1; + } } class SubClassWithPrivateMethods extends BaseClassWithPrivateMethods { - - /** - * @param int $animal - */ - private function parameterTypeTest1($animal) - { - } - - /** - * @return string - */ - private function returnTypeTest1() - { - } - - /** - * @param int $animal - */ - public function parameterTypeTest2($animal) - { - } - - /** - * @return string - */ - public function returnTypeTest2() - { - } - + /** + * @param int $animal + */ + private function parameterTypeTest1($animal) + { + } + + /** + * @return string + */ + private function returnTypeTest1() + { + } + + /** + * @param int $animal + */ + public function parameterTypeTest2($animal) + { + } + + /** + * @return string + */ + public function returnTypeTest2() + { + } } /** @@ -437,47 +424,40 @@ public function returnTypeTest2() */ interface GenericRule { - - /** - * @param TNodeType $node - */ - public function processNode(\PhpParser\Node $node): void; - + /** + * @param TNodeType $node + */ + public function processNode(\PhpParser\Node $node): void; } class Rule implements GenericRule { - - /** - * @param \PhpParser\Node\Expr\StaticCall $node - */ - public function processNode(\PhpParser\Node $node): void - { - - } - + /** + * @param \PhpParser\Node\Expr\StaticCall $node + */ + public function processNode(\PhpParser\Node $node): void + { + } } interface ConstantArrayInterface { - - /** - * @return array{foo: string} - */ - public function foobar(): array; - + /** + * @return array{foo: string} + */ + public function foobar(): array; } class ConstantArrayClass implements ConstantArrayInterface { - /** - * @return array{foo: string, bar: string} - */ - public function foobar(): array - { - return [ - 'foo' => '', - 'bar' => '', - ]; - } + /** + * @return array{foo: string, bar: string} + */ + public function foobar(): array + { + return [ + 'foo' => '', + 'bar' => '', + ]; + } } diff --git a/tests/PHPStan/Rules/Methods/data/misleadingTypehints.php b/tests/PHPStan/Rules/Methods/data/misleadingTypehints.php index ef1c3b6c42..ff417640e3 100644 --- a/tests/PHPStan/Rules/Methods/data/misleadingTypehints.php +++ b/tests/PHPStan/Rules/Methods/data/misleadingTypehints.php @@ -2,50 +2,48 @@ class FooWithoutNamespace { - - public function misleadingBoolReturnType(): \boolean - { - if (rand(0, 1)) { - return true; - } - - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return new boolean(); - } - } - - public function misleadingIntReturnType(): \integer - { - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return true; - } - - if (rand(0, 1)) { - return new integer(); - } - } - - public function misleadingMixedReturnType(): mixed - { - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return true; - } - - if (rand(0, 1)) { - return new mixed(); - } - } - + public function misleadingBoolReturnType(): \boolean + { + if (rand(0, 1)) { + return true; + } + + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return new boolean(); + } + } + + public function misleadingIntReturnType(): \integer + { + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return true; + } + + if (rand(0, 1)) { + return new integer(); + } + } + + public function misleadingMixedReturnType(): mixed + { + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return true; + } + + if (rand(0, 1)) { + return new mixed(); + } + } } diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-impl.php b/tests/PHPStan/Rules/Methods/data/missing-method-impl.php index 3961db0367..e373246568 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-method-impl.php +++ b/tests/PHPStan/Rules/Methods/data/missing-method-impl.php @@ -4,40 +4,30 @@ interface Foo { - - public function doFoo(); - + public function doFoo(); } abstract class Bar implements Foo { + public function doBar() + { + } - public function doBar() - { - - } - - abstract public function doBaz(); - + abstract public function doBaz(); } class Baz implements Foo { + public function doBar() + { + } - public function doBar() - { - - } - - abstract public function doBaz(); - + abstract public function doBaz(); } interface Lorem extends Foo { - } new class() implements Foo { - }; diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php b/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php index 8346689258..9c32d7bfa8 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php +++ b/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php @@ -4,27 +4,20 @@ interface FooInterface { - public function getFoo($p1): void; - } class FooParent { - public function getBar($p2) { - } - } class Foo extends FooParent implements FooInterface { - public function getFoo($p1): void { - } /** @@ -32,7 +25,6 @@ public function getFoo($p1): void */ public function getBar($p2) { - } /** @@ -52,14 +44,12 @@ public function getFooBar($p5): bool return false; } - /** - * @param \stdClass|array|int|null $a - */ - public function unionTypeWithUnknownArrayValueTypehint($a) - { - - } - + /** + * @param \stdClass|array|int|null $a + */ + public function unionTypeWithUnknownArrayValueTypehint($a) + { + } } /** @@ -68,12 +58,10 @@ public function unionTypeWithUnknownArrayValueTypehint($a) */ interface GenericInterface { - } class NonGenericClass { - } /** @@ -82,118 +70,93 @@ class NonGenericClass */ class GenericClass { - } class Bar { + public function acceptsGenericInterface(GenericInterface $i) + { + } - public function acceptsGenericInterface(GenericInterface $i) - { - - } - - public function acceptsNonGenericClass(NonGenericClass $c) - { - - } - - public function acceptsGenericClass(GenericClass $c) - { - - } + public function acceptsNonGenericClass(NonGenericClass $c) + { + } + public function acceptsGenericClass(GenericClass $c) + { + } } class CollectionIterableAndGeneric { + public function acceptsCollection(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void + { + } - public function acceptsCollection(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void - { - - } - - /** - * @param \DoctrineIntersectionTypeIsSupertypeOf\Collection $collection - */ - public function acceptsCollection2(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void - { - - } - - /** - * @param \DoctrineIntersectionTypeIsSupertypeOf\Collection $collection - */ - public function acceptsCollection3(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void - { - - } + /** + * @param \DoctrineIntersectionTypeIsSupertypeOf\Collection $collection + */ + public function acceptsCollection2(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void + { + } + /** + * @param \DoctrineIntersectionTypeIsSupertypeOf\Collection $collection + */ + public function acceptsCollection3(\DoctrineIntersectionTypeIsSupertypeOf\Collection $collection): void + { + } } class TraversableInTemplateBound { - - /** - * @template T of \Iterator - * @param T $it - */ - public function doFoo($it) - { - - } - + /** + * @template T of \Iterator + * @param T $it + */ + public function doFoo($it) + { + } } class GenericClassInTemplateBound { - - /** - * @template T of GenericClass - * @param T $obj - */ - public function doFoo($obj) - { - - } - + /** + * @template T of GenericClass + * @param T $obj + */ + public function doFoo($obj) + { + } } class SerializableImpl implements \Serializable { + public function serialize(): string + { + return serialize([]); + } - public function serialize(): string - { - return serialize([]); - } - - public function unserialize($serialized): void - { - - } - + public function unserialize($serialized): void + { + } } class CallableSignature { - - public function doFoo(callable $cb): void - { - - } + public function doFoo(callable $cb): void + { + } } class SerializableImpl2 implements \Serializable { + public function serialize(): string + { + return serialize([]); + } - public function serialize(): string - { - return serialize([]); - } - - public function unserialize($data): void - { - - } - + public function unserialize($data): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php b/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php index 16e14ace5f..3af869babc 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php +++ b/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php @@ -4,27 +4,20 @@ interface FooInterface { - public function getFoo($p1); - } class FooParent { - public function getBar($p2) { - } - } class Foo extends FooParent implements FooInterface { - public function getFoo($p1) { - } /** @@ -32,7 +25,6 @@ public function getFoo($p1) */ public function getBar($p2) { - } public function getBaz(): bool @@ -40,14 +32,12 @@ public function getBaz(): bool return false; } - /** - * @return \stdClass|array|int|null - */ - public function unionTypeWithUnknownArrayValueTypehint() - { - - } - + /** + * @return \stdClass|array|int|null + */ + public function unionTypeWithUnknownArrayValueTypehint() + { + } } /** @@ -56,12 +46,10 @@ public function unionTypeWithUnknownArrayValueTypehint() */ interface GenericInterface { - } class NonGenericClass { - } /** @@ -70,35 +58,26 @@ class NonGenericClass */ class GenericClass { - } class Bar { + public function returnsGenericInterface(): GenericInterface + { + } - public function returnsGenericInterface(): GenericInterface - { - - } - - public function returnsNonGenericClass(): NonGenericClass - { - - } - - public function returnsGenericClass(): GenericClass - { - - } + public function returnsNonGenericClass(): NonGenericClass + { + } + public function returnsGenericClass(): GenericClass + { + } } class CallableSignature { - - public function doFoo(): callable - { - - } - + public function doFoo(): callable + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/missing-typehint-promoted-properties.php b/tests/PHPStan/Rules/Methods/data/missing-typehint-promoted-properties.php index 179da04b12..319a1c6d4a 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-typehint-promoted-properties.php +++ b/tests/PHPStan/Rules/Methods/data/missing-typehint-promoted-properties.php @@ -1,26 +1,27 @@ -= 8.0 += 8.0 namespace MissingTypehintPromotedProperties; class Foo { - - public function __construct( - private array $foo, - /** @var array */private array $bar - ) { } - + public function __construct( + private array $foo, + /** @var array */ + private array $bar + ) { + } } class Bar { - - /** - * @param array $bar - */ - public function __construct( - private array $foo, - private array $bar - ) { } - + /** + * @param array $bar + */ + public function __construct( + private array $foo, + private array $bar + ) { + } } diff --git a/tests/PHPStan/Rules/Methods/data/mixin.php b/tests/PHPStan/Rules/Methods/data/mixin.php index 1c8af21196..8414088a1a 100644 --- a/tests/PHPStan/Rules/Methods/data/mixin.php +++ b/tests/PHPStan/Rules/Methods/data/mixin.php @@ -4,12 +4,9 @@ class Foo { - - public function doFoo() - { - - } - + public function doFoo() + { + } } /** @@ -17,27 +14,23 @@ public function doFoo() */ class Bar { - - public function doBar() - { - - } - + public function doBar() + { + } } function (Bar $bar): void { - $bar->doFoo(); - $bar->doFoo(1); + $bar->doFoo(); + $bar->doFoo(1); }; class Baz extends Bar { - } function (Baz $baz): void { - $baz->doFoo(); - $baz->doFoo(1); + $baz->doFoo(); + $baz->doFoo(1); }; /** @@ -46,20 +39,17 @@ function (Baz $baz): void { */ class GenericFoo { - } class Test { - - /** - * @param GenericFoo<\Exception> $foo - */ - public function doFoo(GenericFoo $foo): void - { - echo $foo->getMessage(); - echo $foo->getMessage(1); - echo $foo->getMessagee(); - } - + /** + * @param GenericFoo<\Exception> $foo + */ + public function doFoo(GenericFoo $foo): void + { + echo $foo->getMessage(); + echo $foo->getMessage(1); + echo $foo->getMessagee(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/named-arguments.php b/tests/PHPStan/Rules/Methods/data/named-arguments.php index f5c779f171..4c2a6ec588 100644 --- a/tests/PHPStan/Rules/Methods/data/named-arguments.php +++ b/tests/PHPStan/Rules/Methods/data/named-arguments.php @@ -4,94 +4,88 @@ class Foo { - - public function doFoo( - int $i, - int $j, - int $k, ?int $l = null - ) - { - - } - - public function doBar(): void - { - $this->doFoo( - i: 1, - 2, - 3 - ); - $this->doFoo( - 1, - i: 1, - j: 2, - k: 3 - ); - $this->doFoo( - i: 1, - i: 2, - j: 3, - k: 4 - ); - - $this->doFoo( - 1, - j: 3 - ); - - $this->doFoo( - 1, - 2, - 3, - z: 4 - ); - - $this->doFoo( - 'foo', - j: 2, - k: 3 - ); - - $this->doFoo( - 1, - j: 'foo', - k: 3 - ); - - } - - public function doBaz(&$i): void - { - - } - - public function doLorem(?\stdClass $foo): void - { - $this->doBaz(i: 1); - $this->doBaz(i: $foo?->bar); - - $this->doFoo(i: 1, ...['j' => 2, 'k' => 3]); - - $this->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); - - $this->doFoo(...['k' => 3, 'i' => 1, 'str']); - } - - public function doIpsum(int $a, int $b, string ...$args): void - { - - } - - public function doDolor(): void - { - $this->doIpsum(...[1, 2, 3, 'foo' => 'foo']); - $this->doIpsum(...[1, 2, 'foo' => 'foo']); - $this->doIpsum(...['a' => 1, 'b' => 2, 'foo' => 'foo']); - $this->doIpsum(...['a' => 1, 'b' => 'foo', 'foo' => 'foo']); - $this->doIpsum(...['a' => 1, 'b' => 'foo', 'foo' => 1]); - $this->doIpsum(...['a' => 1, 'foo' => 'foo']); - $this->doIpsum(...['b' => 1, 'foo' => 'foo']); - $this->doIpsum(...[1, 2], 'foo'); - } - + public function doFoo( + int $i, + int $j, + int $k, + ?int $l = null + ) { + } + + public function doBar(): void + { + $this->doFoo( + i: 1, + 2, + 3 + ); + $this->doFoo( + 1, + i: 1, + j: 2, + k: 3 + ); + $this->doFoo( + i: 1, + i: 2, + j: 3, + k: 4 + ); + + $this->doFoo( + 1, + j: 3 + ); + + $this->doFoo( + 1, + 2, + 3, + z: 4 + ); + + $this->doFoo( + 'foo', + j: 2, + k: 3 + ); + + $this->doFoo( + 1, + j: 'foo', + k: 3 + ); + } + + public function doBaz(&$i): void + { + } + + public function doLorem(?\stdClass $foo): void + { + $this->doBaz(i: 1); + $this->doBaz(i: $foo?->bar); + + $this->doFoo(i: 1, ...['j' => 2, 'k' => 3]); + + $this->doFoo(...['k' => 3, 'i' => 1, 'j' => 'str']); + + $this->doFoo(...['k' => 3, 'i' => 1, 'str']); + } + + public function doIpsum(int $a, int $b, string ...$args): void + { + } + + public function doDolor(): void + { + $this->doIpsum(...[1, 2, 3, 'foo' => 'foo']); + $this->doIpsum(...[1, 2, 'foo' => 'foo']); + $this->doIpsum(...['a' => 1, 'b' => 2, 'foo' => 'foo']); + $this->doIpsum(...['a' => 1, 'b' => 'foo', 'foo' => 'foo']); + $this->doIpsum(...['a' => 1, 'b' => 'foo', 'foo' => 1]); + $this->doIpsum(...['a' => 1, 'foo' => 'foo']); + $this->doIpsum(...['b' => 1, 'foo' => 'foo']); + $this->doIpsum(...[1, 2], 'foo'); + } } diff --git a/tests/PHPStan/Rules/Methods/data/native-union-types.php b/tests/PHPStan/Rules/Methods/data/native-union-types.php index 2721ec711e..8cb70d22cc 100644 --- a/tests/PHPStan/Rules/Methods/data/native-union-types.php +++ b/tests/PHPStan/Rules/Methods/data/native-union-types.php @@ -1,18 +1,17 @@ -= 8.0 += 8.0 namespace NativeUnionTypesSupport; class Foo { + public function doFoo(int|bool $foo): int|bool + { + return 1; + } - public function doFoo(int|bool $foo): int|bool - { - return 1; - } - - public function doBar(): int|bool - { - - } - + public function doBar(): int|bool + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/negated-instanceof.php b/tests/PHPStan/Rules/Methods/data/negated-instanceof.php index 799499f5a9..95c4a4a7ea 100644 --- a/tests/PHPStan/Rules/Methods/data/negated-instanceof.php +++ b/tests/PHPStan/Rules/Methods/data/negated-instanceof.php @@ -4,13 +4,10 @@ class Foo { - - public function doFoo() - { - $foo = new \stdClass(); - if (!$foo instanceof self || $foo->doFoo()) { - - } - } - + public function doFoo() + { + $foo = new \stdClass(); + if (!$foo instanceof self || $foo->doFoo()) { + } + } } diff --git a/tests/PHPStan/Rules/Methods/data/nullable-parameters.defined.php b/tests/PHPStan/Rules/Methods/data/nullable-parameters.defined.php index 9c378b7bc2..ccc34d7803 100644 --- a/tests/PHPStan/Rules/Methods/data/nullable-parameters.defined.php +++ b/tests/PHPStan/Rules/Methods/data/nullable-parameters.defined.php @@ -4,10 +4,7 @@ class Foo { - - public function doFoo(int $integer, ?int $nullableInteger) - { - - } - + public function doFoo(int $integer, ?int $nullableInteger) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-rule.php b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-rule.php index 8484424f09..239a2949a1 100644 --- a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-rule.php +++ b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-rule.php @@ -1,20 +1,19 @@ -= 8.0 += 8.0 namespace NullsafeMethodCallRule; class Foo { - - public function doFoo( - $mixed, - ?\Exception $nullable, - \Exception $nonNullable - ): void - { - $mixed?->doFoo(); - $nullable?->doFoo(); - $nonNullable?->doFoo(); - (null)?->doFoo(); // reported by a different rule - } - + public function doFoo( + $mixed, + ?\Exception $nullable, + \Exception $nonNullable + ): void { + $mixed?->doFoo(); + $nullable?->doFoo(); + $nonNullable?->doFoo(); + (null)?->doFoo(); // reported by a different rule + } } diff --git a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-statement-no-side-effects.php index 9400ad283c..bc275a0b67 100644 --- a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call-statement-no-side-effects.php @@ -1,13 +1,13 @@ -= 8.0 += 8.0 namespace NullsafeMethodCallNoSideEffects; class Foo { - - public function doFoo(?\Exception $e): void - { - $e?->getMessage(); - } - + public function doFoo(?\Exception $e): void + { + $e?->getMessage(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php index dbe6ef395d..d2817e1d9d 100644 --- a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php +++ b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php @@ -1,30 +1,28 @@ -= 8.0 += 8.0 namespace NullsafeMethodCall; class Foo { - - public function doFoo(?self $selfOrNull): void - { - $selfOrNull?->doBar(); - $selfOrNull?->doBar(1); - } - - public function doBar(): void - { - - } - - public function doBaz(&$passedByRef): void - { - - } - - public function doLorem(?self $selfOrNull): void - { - $this->doBaz($selfOrNull?->test); - $this->doBaz($selfOrNull?->test->test); - } - + public function doFoo(?self $selfOrNull): void + { + $selfOrNull?->doBar(); + $selfOrNull?->doBar(1); + } + + public function doBar(): void + { + } + + public function doBaz(&$passedByRef): void + { + } + + public function doLorem(?self $selfOrNull): void + { + $this->doBaz($selfOrNull?->test); + $this->doBaz($selfOrNull?->test->test); + } } diff --git a/tests/PHPStan/Rules/Methods/data/only-relevant-unable-to-resolve-template-type.php b/tests/PHPStan/Rules/Methods/data/only-relevant-unable-to-resolve-template-type.php index 2b9b0dadfc..94f0386f98 100644 --- a/tests/PHPStan/Rules/Methods/data/only-relevant-unable-to-resolve-template-type.php +++ b/tests/PHPStan/Rules/Methods/data/only-relevant-unable-to-resolve-template-type.php @@ -4,56 +4,51 @@ class Foo { - - /** - * @template T - * @param T $a - * @return T[] - */ - public function doFoo($a) - { - - } - - /** - * @template T - * @return int[] - */ - public function doBar() - { - - } - - /** - * @template T - * @template U - * @param T[] $a - * @return T - */ - public function doBaz($a) - { - } - - public function doLorem() - { - $this->doFoo(1); - $this->doBar(); - $this->doBaz(1); - } - - /** - * @template T - * @param mixed $a - * @return T - */ - public function doIpsum($a) - { - - } - - public function doDolor() - { - $this->doIpsum(1); - } - + /** + * @template T + * @param T $a + * @return T[] + */ + public function doFoo($a) + { + } + + /** + * @template T + * @return int[] + */ + public function doBar() + { + } + + /** + * @template T + * @template U + * @param T[] $a + * @return T + */ + public function doBaz($a) + { + } + + public function doLorem() + { + $this->doFoo(1); + $this->doBar(); + $this->doBaz(1); + } + + /** + * @template T + * @param mixed $a + * @return T + */ + public function doIpsum($a) + { + } + + public function doDolor() + { + $this->doIpsum(1); + } } diff --git a/tests/PHPStan/Rules/Methods/data/order.php b/tests/PHPStan/Rules/Methods/data/order.php index a0de619026..379f4790ad 100644 --- a/tests/PHPStan/Rules/Methods/data/order.php +++ b/tests/PHPStan/Rules/Methods/data/order.php @@ -4,110 +4,97 @@ class Foo { - - /** - * @return int|null - */ - public function lorem() - { - - } - - /** - * @return int|null - */ - public function ipsum() - { - - } - + /** + * @return int|null + */ + public function lorem() + { + } + + /** + * @return int|null + */ + public function ipsum() + { + } } class Bar { - - /** - * @return int|null - */ - public function lorem() - { - - } - - /** - * @return int|null - */ - public function ipsum() - { - - } - + /** + * @return int|null + */ + public function lorem() + { + } + + /** + * @return int|null + */ + public function ipsum() + { + } } class Baz { - - /** - * @return Foo|null - */ - public function getFoo(): Foo - { - - } - - /** - * @return Bar|null - */ - public function getBar(): Bar - { - - } - - public function process(): string - { - if ($this->getFoo() === null && $this->getBar() === null) { - return ''; - } - if ($this->getFoo() !== null && $this->getBar() === null) { - return ''; - } elseif ($this->getBar() !== null && $this->getFoo() === null) { - return ''; - } - - $foo = $this->getFoo(); - $bar = $this->getBar(); - if ($bar->lorem() !== null && $foo->lorem() === null) { - return ''; - } elseif ($bar->lorem() === null && $foo->lorem() !== null) { - return ''; - } elseif ($bar->lorem() !== null && $foo->lorem() !== null) { - return $bar->lorem() > $foo->lorem() ? '' : ''; - } - if ($foo->ipsum() < $bar->ipsum()) { - return ''; - } - - return ''; - } - + /** + * @return Foo|null + */ + public function getFoo(): Foo + { + } + + /** + * @return Bar|null + */ + public function getBar(): Bar + { + } + + public function process(): string + { + if ($this->getFoo() === null && $this->getBar() === null) { + return ''; + } + if ($this->getFoo() !== null && $this->getBar() === null) { + return ''; + } elseif ($this->getBar() !== null && $this->getFoo() === null) { + return ''; + } + + $foo = $this->getFoo(); + $bar = $this->getBar(); + if ($bar->lorem() !== null && $foo->lorem() === null) { + return ''; + } elseif ($bar->lorem() === null && $foo->lorem() !== null) { + return ''; + } elseif ($bar->lorem() !== null && $foo->lorem() !== null) { + return $bar->lorem() > $foo->lorem() ? '' : ''; + } + if ($foo->ipsum() < $bar->ipsum()) { + return ''; + } + + return ''; + } } class Dolor { - public function getTime(): \DateTimeImmutable - { - return new \DateTimeImmutable(); - } - - public function process(): void - { - if (true && $this->getTime() === null) { - // nothing - - } elseif (false) { - // nothing - } - - $this->getTime()->getTimestamp(); - } + public function getTime(): \DateTimeImmutable + { + return new \DateTimeImmutable(); + } + + public function process(): void + { + if (true && $this->getTime() === null) { + // nothing + } elseif (false) { + // nothing + } + + $this->getTime()->getTimestamp(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/overriden-method-prototype.php b/tests/PHPStan/Rules/Methods/data/overriden-method-prototype.php index 90bc7e58d1..a869868e34 100644 --- a/tests/PHPStan/Rules/Methods/data/overriden-method-prototype.php +++ b/tests/PHPStan/Rules/Methods/data/overriden-method-prototype.php @@ -4,25 +4,19 @@ class Foo { - - protected function foo() - { - - } - + protected function foo() + { + } } class Bar extends Foo { - - public function foo() - { - - } - + public function foo() + { + } } function () { - $bar = new Bar(); - $bar->foo(); + $bar = new Bar(); + $bar->foo(); }; diff --git a/tests/PHPStan/Rules/Methods/data/overriding-method.php b/tests/PHPStan/Rules/Methods/data/overriding-method.php index cce1427a8a..09524f6dca 100644 --- a/tests/PHPStan/Rules/Methods/data/overriding-method.php +++ b/tests/PHPStan/Rules/Methods/data/overriding-method.php @@ -4,239 +4,176 @@ class Foo { + final public function doFoo() + { + } - final public function doFoo() - { + public function doBar() + { + } - } + public function doBaz() + { + } - public function doBar() - { + protected function doLorem() + { + } - } - - public function doBaz() - { - - } - - protected function doLorem() - { - - } - - public static function doIpsum() - { - - } - - public function doDolor() - { - - } + public static function doIpsum() + { + } + public function doDolor() + { + } } class Bar extends Foo { + public function doFoo() + { + } - public function doFoo() - { - - } - - private function doBar() - { - - } + private function doBar() + { + } - protected function doBaz() - { + protected function doBaz() + { + } - } + private function doLorem() + { + } - private function doLorem() - { - - } - - public function doIpsum() - { - - } - - public static function doDolor() - { - - } + public function doIpsum() + { + } + public static function doDolor() + { + } } class Baz { - - public function __construct(int $i) - { - - } - + public function __construct(int $i) + { + } } class Lorem extends Baz { - - public function __construct(string $s) - { - - } - + public function __construct(string $s) + { + } } abstract class Ipsum { + abstract public function __construct(int $i); - abstract public function __construct(int $i); - - public function doFoo(int $i) - { - - } - + public function doFoo(int $i) + { + } } class Dolor extends Ipsum { + public function __construct(string $s) + { + } - public function __construct(string $s) - { - - } - - public function doFoo() - { - - } - + public function doFoo() + { + } } class FixedArray extends \SplFixedArray { - - public function setSize(int $size): bool - { - - } - + public function setSize(int $size): bool + { + } } class Sit { + public function doFoo(int $i, int $j = null) + { + } - public function doFoo(int $i, int $j = null) - { - - } - - public function doBar(int ...$j) - { - - } - - public function doBaz(int $j) - { - - } + public function doBar(int ...$j) + { + } + public function doBaz(int $j) + { + } } class Amet extends Sit { + public function doFoo(int $i = null, int $j = null) + { + } - public function doFoo(int $i = null, int $j = null) - { - - } - - public function doBar(int $j) - { - - } - - public function doBaz(int ...$j) - { - - } + public function doBar(int $j) + { + } + public function doBaz(int ...$j) + { + } } class Consecteur extends Sit { - - public function doFoo(int $i, ?int $j) - { - - } - + public function doFoo(int $i, ?int $j) + { + } } class Etiam { - - public function doFoo(int &$i, int $j) - { - - } - + public function doFoo(int &$i, int $j) + { + } } class Lacus extends Etiam { - - public function doFoo(int $i, int &$j) - { - - } - + public function doFoo(int $i, int &$j) + { + } } class BazBaz extends Foo { - - public function doBar(int $i) - { - - } - + public function doBar(int $i) + { + } } class BazBazBaz extends Foo { - - public function doBar(int $i = null) - { - - } - + public function doBar(int $i = null) + { + } } class FooFoo extends Ipsum { - - public function doFoo(int $i, int $j) - { - - } - + public function doFoo(int $i, int $j) + { + } } class FooFooFoo extends Ipsum { - - public function doFoo(int $i, int $j = null) - { - - } - + public function doFoo(int $i, int $j = null) + { + } } /** @@ -244,75 +181,56 @@ public function doFoo(int $i, int $j = null) */ class SomeIterator implements \IteratorAggregate { - /** - * @return \Traversable - */ - public function getIterator() - { - yield new Foo; - } - + /** + * @return \Traversable + */ + public function getIterator() + { + yield new Foo(); + } } class SomeException extends \Exception { - - private function __construct() - { - - } - + private function __construct() + { + } } class OtherException extends \Exception { - - final public function __construct() - { - - } - + final public function __construct() + { + } } class SomeOtherException extends OtherException { - - public function __construct() - { - - } - + public function __construct() + { + } } class FinalWithAnnotation { - - /** - * @final - */ - public function doFoo() - { - - } - + /** + * @final + */ + public function doFoo() + { + } } class ExtendsFinalWithAnnotation extends FinalWithAnnotation { - - public function doFoo() - { - - } - + public function doFoo() + { + } } class FixedArrayOffsetExists extends \SplFixedArray { - - public function offsetExists(int $index) - { - - } - + public function offsetExists(int $index) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/overriding-variadics.php b/tests/PHPStan/Rules/Methods/data/overriding-variadics.php index b91bb38579..19962c17c1 100644 --- a/tests/PHPStan/Rules/Methods/data/overriding-variadics.php +++ b/tests/PHPStan/Rules/Methods/data/overriding-variadics.php @@ -4,66 +4,49 @@ interface ITranslator { - - /** - * Translates the given string. - * @param mixed $message - * @param string ...$parameters - */ - function translate($message, string ...$parameters): string; - + /** + * Translates the given string. + * @param mixed $message + * @param string ...$parameters + */ + public function translate($message, string ...$parameters): string; } class Translator implements ITranslator { - - /** - * @param string $message - * @param string ...$parameters - */ - public function translate($message, $lang = 'cs', string ...$parameters): string - { - - } - + /** + * @param string $message + * @param string ...$parameters + */ + public function translate($message, $lang = 'cs', string ...$parameters): string + { + } } class OtherTranslator implements ITranslator { - - public function translate($message, $lang, string ...$parameters): string - { - - } - + public function translate($message, $lang, string ...$parameters): string + { + } } class AnotherTranslator implements ITranslator { - - public function translate($message, $lang = 'cs', string $parameters): string - { - - } - + public function translate($message, $lang = 'cs', string $parameters): string + { + } } class YetAnotherTranslator implements ITranslator { - - public function translate($message, $lang = 'cs'): string - { - - } - + public function translate($message, $lang = 'cs'): string + { + } } class ReflectionClass extends \ReflectionClass { - - public function newInstance($arg = null, ...$args) - { - - } - + public function newInstance($arg = null, ...$args) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/parameter-contravariance-array.php b/tests/PHPStan/Rules/Methods/data/parameter-contravariance-array.php index 51a8be6f0c..ba784289a9 100644 --- a/tests/PHPStan/Rules/Methods/data/parameter-contravariance-array.php +++ b/tests/PHPStan/Rules/Methods/data/parameter-contravariance-array.php @@ -4,45 +4,33 @@ class Foo { + public function doFoo(array $a) + { + } - public function doFoo(array $a) - { - - } - - public function doBar(?array $a) - { - - } - + public function doBar(?array $a) + { + } } class Bar extends Foo { + public function doFoo(iterable $a) + { + } - public function doFoo(iterable $a) - { - - } - - public function doBar(?iterable $a) - { - - } - + public function doBar(?iterable $a) + { + } } class Baz extends Foo { + public function doFoo(?iterable $a) + { + } - public function doFoo(?iterable $a) - { - - } - - public function doBar(iterable $a) - { - - } - + public function doBar(iterable $a) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/parameter-contravariance-traversable.php b/tests/PHPStan/Rules/Methods/data/parameter-contravariance-traversable.php index 482a2cf651..d8e9c72b14 100644 --- a/tests/PHPStan/Rules/Methods/data/parameter-contravariance-traversable.php +++ b/tests/PHPStan/Rules/Methods/data/parameter-contravariance-traversable.php @@ -4,45 +4,33 @@ class Foo { + public function doFoo(\Traversable $a) + { + } - public function doFoo(\Traversable $a) - { - - } - - public function doBar(?\Traversable $a) - { - - } - + public function doBar(?\Traversable $a) + { + } } class Bar extends Foo { + public function doFoo(iterable $a) + { + } - public function doFoo(iterable $a) - { - - } - - public function doBar(?iterable $a) - { - - } - + public function doBar(?iterable $a) + { + } } class Baz extends Foo { + public function doFoo(?iterable $a) + { + } - public function doFoo(?iterable $a) - { - - } - - public function doBar(iterable $a) - { - - } - + public function doBar(iterable $a) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/parameter-contravariance.php b/tests/PHPStan/Rules/Methods/data/parameter-contravariance.php index 07bad11577..7fe7bbbaa1 100644 --- a/tests/PHPStan/Rules/Methods/data/parameter-contravariance.php +++ b/tests/PHPStan/Rules/Methods/data/parameter-contravariance.php @@ -4,60 +4,43 @@ class Foo { + public function doFoo(\Exception $e) + { + } - public function doFoo(\Exception $e) - { - - } - - public function doBar(\InvalidArgumentException $e) - { - - } - + public function doBar(\InvalidArgumentException $e) + { + } } class Bar extends Foo { + public function doFoo(?\Exception $e) + { + } - public function doFoo(?\Exception $e) - { - - } - - public function doBar(\Exception $e) - { - - } - + public function doBar(\Exception $e) + { + } } class Baz extends Foo { - - public function doBar(?\Exception $e) - { - - } - + public function doBar(?\Exception $e) + { + } } class Lorem extends Foo { - - public function doFoo(\InvalidArgumentException $e) - { - - } - + public function doFoo(\InvalidArgumentException $e) + { + } } class Ipsum extends Foo { - - public function doFoo(?\InvalidArgumentException $e) - { - - } - + public function doFoo(?\InvalidArgumentException $e) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/parameter-type-widening.php b/tests/PHPStan/Rules/Methods/data/parameter-type-widening.php index a3eb9f74bc..0cc8e7c657 100644 --- a/tests/PHPStan/Rules/Methods/data/parameter-type-widening.php +++ b/tests/PHPStan/Rules/Methods/data/parameter-type-widening.php @@ -4,20 +4,14 @@ class Foo { - - public function doFoo(string $foo): void - { - - } - + public function doFoo(string $foo): void + { + } } class Bar extends Foo { - - public function doFoo($foo): void - { - - } - + public function doFoo($foo): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/parle.php b/tests/PHPStan/Rules/Methods/data/parle.php index 238b36ec9b..4ecdd358c5 100644 --- a/tests/PHPStan/Rules/Methods/data/parle.php +++ b/tests/PHPStan/Rules/Methods/data/parle.php @@ -4,10 +4,7 @@ class Foo extends \Parle\RLexer { - - public function pushState(int $state): string - { - - } - + public function pushState(int $state): string + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/protected-method-call-from-parent.php b/tests/PHPStan/Rules/Methods/data/protected-method-call-from-parent.php index 458c000abb..fc3e261b23 100644 --- a/tests/PHPStan/Rules/Methods/data/protected-method-call-from-parent.php +++ b/tests/PHPStan/Rules/Methods/data/protected-method-call-from-parent.php @@ -4,18 +4,17 @@ class ParentClass { - public function test() - { - $a = new ChildClass(); - $a->onChild(); - } + public function test() + { + $a = new ChildClass(); + $a->onChild(); + } } class ChildClass extends ParentClass { - protected function onChild() - { - - } + protected function onChild() + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/recursive-iterator-iterator.php b/tests/PHPStan/Rules/Methods/data/recursive-iterator-iterator.php index 7d0555b0a7..f91b0ba5f1 100644 --- a/tests/PHPStan/Rules/Methods/data/recursive-iterator-iterator.php +++ b/tests/PHPStan/Rules/Methods/data/recursive-iterator-iterator.php @@ -4,15 +4,13 @@ class Foo { - - public function doFoo(): void - { - $it = new \RecursiveDirectoryIterator(__DIR__); - $it = new \RecursiveIteratorIterator($it); - foreach ($it as $_) { - echo $it->getSubPathname(); - echo $it->getSubPathname(1); - } - } - + public function doFoo(): void + { + $it = new \RecursiveDirectoryIterator(__DIR__); + $it = new \RecursiveIteratorIterator($it); + foreach ($it as $_) { + echo $it->getSubPathname(); + echo $it->getSubPathname(1); + } + } } diff --git a/tests/PHPStan/Rules/Methods/data/required-parameter-after-optional.php b/tests/PHPStan/Rules/Methods/data/required-parameter-after-optional.php index 647a05f3af..ed09a78396 100644 --- a/tests/PHPStan/Rules/Methods/data/required-parameter-after-optional.php +++ b/tests/PHPStan/Rules/Methods/data/required-parameter-after-optional.php @@ -4,22 +4,19 @@ class Foo { + public function doFoo($foo = null, $bar): void // not OK + { + } - public function doFoo($foo = null, $bar): void // not OK - { + public function doBar(int $foo = null, $bar): void // is OK + { + } - } - - public function doBar(int $foo = null, $bar): void // is OK - { - } - - public function doBaz(int $foo = 1, $bar): void // not OK - { - } - - public function doLorem(bool $foo = true, $bar): void // not OK - { - } + public function doBaz(int $foo = 1, $bar): void // not OK + { + } + public function doLorem(bool $foo = true, $bar): void // not OK + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/return-static-from-parent.php b/tests/PHPStan/Rules/Methods/data/return-static-from-parent.php index 387faf1374..5491076828 100644 --- a/tests/PHPStan/Rules/Methods/data/return-static-from-parent.php +++ b/tests/PHPStan/Rules/Methods/data/return-static-from-parent.php @@ -4,29 +4,23 @@ class Foo { - - /** - * @return static - */ - public function doFoo(): self - { - - } - + /** + * @return static + */ + public function doFoo(): self + { + } } class Bar extends Foo { - } class Baz extends Bar { - - public function doBaz(): self - { - $baz = $this->doFoo(); - return $baz; - } - + public function doBaz(): self + { + $baz = $this->doFoo(); + return $baz; + } } diff --git a/tests/PHPStan/Rules/Methods/data/return-static-static-method.php b/tests/PHPStan/Rules/Methods/data/return-static-static-method.php index b593641e26..49cbf249bd 100644 --- a/tests/PHPStan/Rules/Methods/data/return-static-static-method.php +++ b/tests/PHPStan/Rules/Methods/data/return-static-static-method.php @@ -4,24 +4,20 @@ class Foo { - - /** - * @return static - */ - public static function doFoo(): self - { - return new static(); - } - + /** + * @return static + */ + public static function doFoo(): self + { + return new static(); + } } class Bar extends Foo { - - public function doBar() - { - self::doFoo()::doFoo()::doBar(); - self::doFoo()::doFoo()::doBaz(); - } - + public function doBar() + { + self::doFoo()::doFoo()::doBar(); + self::doFoo()::doFoo()::doBaz(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/return-static.php b/tests/PHPStan/Rules/Methods/data/return-static.php index fa7d6b6b90..912db7140d 100644 --- a/tests/PHPStan/Rules/Methods/data/return-static.php +++ b/tests/PHPStan/Rules/Methods/data/return-static.php @@ -4,27 +4,26 @@ class A { - /** @return static */ - public function returnStatic() - { - - } + /** @return static */ + public function returnStatic() + { + } } class B extends A { - /** @return static */ - public function test() - { - return $this->returnStatic(); - } + /** @return static */ + public function test() + { + return $this->returnStatic(); + } } final class B2 extends A { - /** @return static */ - public function test() - { - return $this->returnStatic(); - } + /** @return static */ + public function test() + { + return $this->returnStatic(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/return-type-covariance.php b/tests/PHPStan/Rules/Methods/data/return-type-covariance.php index 5493ce83b8..30d3edcfa7 100644 --- a/tests/PHPStan/Rules/Methods/data/return-type-covariance.php +++ b/tests/PHPStan/Rules/Methods/data/return-type-covariance.php @@ -4,68 +4,52 @@ class Foo { + public function doFoo(): iterable + { + } - public function doFoo(): iterable - { + public function doBar(): array + { + } - } - - public function doBar(): array - { - - } - - public function doBaz(): \Exception - { - - } - - public function doLorem(): \InvalidArgumentException - { - - } + public function doBaz(): \Exception + { + } + public function doLorem(): \InvalidArgumentException + { + } } class Bar extends Foo { + public function doFoo(): array + { + } - public function doFoo(): array - { - - } - - public function doBar(): iterable - { + public function doBar(): iterable + { + } - } - - public function doBaz(): \InvalidArgumentException - { - - } - - public function doLorem(): \Exception - { - - } + public function doBaz(): \InvalidArgumentException + { + } + public function doLorem(): \Exception + { + } } class A { - - public function foo(string $s): ?\stdClass - { - - } + public function foo(string $s): ?\stdClass + { + } } class B extends A { - - public function foo($s) - { - return rand(0, 1) ? new stdClass : null; - } - + public function foo($s) + { + return rand(0, 1) ? new stdClass() : null; + } } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes-7.0.php b/tests/PHPStan/Rules/Methods/data/returnTypes-7.0.php index 9bbd34b745..5a45c803d2 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes-7.0.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes-7.0.php @@ -4,10 +4,8 @@ class FooPhp70 extends FooParent implements FooInterface { - - public function returnInteger(): int - { - return; - } - + public function returnInteger(): int + { + return; + } } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes-defined.php b/tests/PHPStan/Rules/Methods/data/returnTypes-defined.php index be2a3a944c..95cd7238b4 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes-defined.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes-defined.php @@ -4,39 +4,34 @@ class FooParent { - - /** - * @return static - */ - public function returnStatic(): self - { - return $this; - } - - /** - * @return int - */ - public function returnIntFromParent() - { - return 1; - } - - /** - * @return void - */ - public function returnsVoid() - { - - } - + /** + * @return static + */ + public function returnStatic(): self + { + return $this; + } + + /** + * @return int + */ + public function returnIntFromParent() + { + return 1; + } + + /** + * @return void + */ + public function returnsVoid() + { + } } interface FooInterface { - } class OtherInterfaceImpl implements FooInterface { - } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes-iterable.php b/tests/PHPStan/Rules/Methods/data/returnTypes-iterable.php index c7f07f534d..f5b0293080 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes-iterable.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes-iterable.php @@ -4,37 +4,35 @@ class Foo { + public function nativeTypehint(iterable $parameter): iterable + { + return $parameter; + } - public function nativeTypehint(iterable $parameter): iterable - { - return $parameter; - } + /** + * @param iterable $parameter + * @return iterable + */ + public function noNativeTypehint(iterable $parameter) + { + return $parameter; + } - /** - * @param iterable $parameter - * @return iterable - */ - public function noNativeTypehint(iterable $parameter) - { - return $parameter; - } - - /** - * @return string[] - */ - public function stringIterable(): iterable - { - return [1]; - return ['1']; - } - - /** - * @return string[]|iterable - */ - public function stringIterablePipe(): iterable - { - return [1]; - return ['1']; - } + /** + * @return string[] + */ + public function stringIterable(): iterable + { + return [1]; + return ['1']; + } + /** + * @return string[]|iterable + */ + public function stringIterablePipe(): iterable + { + return [1]; + return ['1']; + } } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes-overridenTypeInIfCondition.php b/tests/PHPStan/Rules/Methods/data/returnTypes-overridenTypeInIfCondition.php index bb4f24aa0f..3b85c35688 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes-overridenTypeInIfCondition.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes-overridenTypeInIfCondition.php @@ -4,15 +4,12 @@ class OverridenTypeInIfCondition { - - public function getAnotherAnotherStock(): Stock - { - $stock = new Stock(); - if ($stock->findStock() === null) { - - } - - return $stock->findStock(); - } - + public function getAnotherAnotherStock(): Stock + { + $stock = new Stock(); + if ($stock->findStock() === null) { + } + + return $stock->findStock(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index 99babb0253..eaa4bf568d 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -4,1183 +4,1136 @@ class Foo extends FooParent implements FooInterface { - - public function returnNothing() - { - return; - } - - public function returnInteger(): int - { - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return 'foo'; - } - $foo = function () { - return 'bar'; - }; - } - - public function returnObject(): Bar - { - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return new self(); - } - - if (rand(0, 1)) { - return new Bar(); - } - } - - public function returnChild(): self - { - if (rand(0, 1)) { - return new self(); - } - - if (rand(0, 1)) { - return new FooChild(); - } - - if (rand(0, 1)) { - return new OtherInterfaceImpl(); - } - } - - /** - * @return string|null - */ - public function returnNullable() - { - if (rand(0, 1)) { - return 'foo'; - } - - if (rand(0, 1)) { - return null; - } - } - - public function returnInterface(): FooInterface - { - return new self(); - } - - /** - * @return void - */ - public function returnVoid() - { - if (rand(0, 1)) { - return; - } - - if (rand(0, 1)) { - return null; - } - - if (rand(0, 1)) { - return 1; - } - } - - /** - * @return static - */ - public function returnStatic(): FooParent - { - if (rand(0, 1)) { - return parent::returnStatic(); - } - - $parent = new FooParent(); - - if (rand(0, 1)) { - return $parent->returnStatic(); // the only case with wrong static base class - } - - if (rand(0, 1)) { - return $this->returnStatic(); - } - } - - public function returnAlias(): Foo - { - return new FooAlias(); - } - - public function returnAnotherAlias(): FooAlias - { - return new Foo(); - } - - /** - * @param self[]|Collection $collection - * @return self[]|Collection|array - */ - public function returnUnionIterableType($collection) - { - if (rand(0, 1)) { - return $collection; - } - - if (rand(0, 1)) { - return new Collection(); - } - - if (rand(0, 1)) { - return new self(); - } - - if (rand(0, 1)) { - return [new self()]; - } - - if (rand(0, 1)) { - return new Bar(); - } - - if (rand(0, 1)) { - return [new Bar()]; - } - - if (rand(0, 1)) { - return 1; - } - - if (rand(0, 1)) { - return; - } - - /** @var Bar[]|Collection $barListOrCollection */ - $barListOrCollection = doFoo(); - - if (rand(0, 1)) { - return $barListOrCollection; - } - - /** @var self[]|AnotherCollection $selfListOrAnotherCollection */ - $selfListOrAnotherCollection = doFoo(); - - if (rand(0, 1)) { - return $selfListOrAnotherCollection; - } - - /** @var self[]|Collection|AnotherCollection $selfListOrCollectionorAnotherCollection */ - $selfListOrCollectionorAnotherCollection = doFoo(); - - if (rand(0, 1)) { - return $selfListOrCollectionorAnotherCollection; - } - - /** @var Bar[]|AnotherCollection $completelyDiffernetUnionIterable */ - $completelyDiffernetUnionIterable = doFoo(); - - if (rand(0, 1)) { - return $completelyDiffernetUnionIterable; - } - - if (rand(0, 1)) { - return null; - } - } - - /** - * @param self[]|Collection $collection - * @return self[]|Collection|AnotherCollection|null - */ - public function returnUnionIterableLooserReturnType($collection) - { - if (rand(0, 1)) { - return $collection; - } - - if (rand(0, 1)) { - return null; - } - } - - /** - * @return $this - */ - public function returnThis(): self - { - if (rand(0, 1)) { - return $this; - } - if (rand(0, 1)) { - return new self(); - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return new static(); - } - - if (rand(0, 1)) { - return null; - } - - if (rand(0, 1)) { - $that = $this; - return $that; - } - } - - /** - * @return $this|null - */ - public function returnThisOrNull() - { - if (rand(0, 1)) { - return $this; - } - if (rand(0, 1)) { - return new self(); - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return null; - } - if (rand(0, 1)) { - return $this->returnThis(); - } - if (rand(0, 1)) { - return $this->returnStaticThatReturnsNewStatic(); - } - } - - /** - * @return static - */ - public function returnStaticThatReturnsNewStatic(): self - { - if (rand(0, 1)) { - return new static(); - } - if (rand(0, 1)) { - return $this; - } - } - - public function returnsParent(): parent - { - if (rand(0, 1)) { - return new FooParent(); - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return null; - } - } - - /** - * @return parent - */ - public function returnsPhpDocParent() - { - if (rand(0, 1)) { - return new FooParent(); - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return null; - } - } - - /** - * @return scalar - */ - public function returnScalar() - { - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return 10.1; - } - if (rand(0, 1)) { - return 'a'; - } - if (rand(0, 1)) { - return false; - } - if (rand(0, 1)) { - return new \stdClass(); - } - } - - /** - * @return int - */ - public function containsYield() - { - yield 1; - return; - } - - public function returnsNullInTernary(): int - { - /** @var int|null $intOrNull */ - $intOrNull = doFoo(); - - if (rand(0, 1)) { - return $intOrNull; - } - if (rand(0, 1)) { - return $intOrNull !== null ? $intOrNull : 5; - } - if (rand(0, 1)) { - return $intOrNull !== null ? $intOrNull : null; - } - } - - public function misleadingBoolReturnType(): \ReturnTypes\boolean - { - if (rand(0, 1)) { - return true; - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return new boolean(); - } - } - - public function misleadingIntReturnType(): \ReturnTypes\integer - { - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return true; - } - if (rand(0, 1)) { - return new integer(); - } - } - - public function misleadingMixedReturnType(): mixed - { - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return true; - } - if (rand(0, 1)) { - return new mixed(); - } - } + public function returnNothing() + { + return; + } + + public function returnInteger(): int + { + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return 'foo'; + } + $foo = function () { + return 'bar'; + }; + } + + public function returnObject(): Bar + { + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return new self(); + } + + if (rand(0, 1)) { + return new Bar(); + } + } + + public function returnChild(): self + { + if (rand(0, 1)) { + return new self(); + } + + if (rand(0, 1)) { + return new FooChild(); + } + + if (rand(0, 1)) { + return new OtherInterfaceImpl(); + } + } + + /** + * @return string|null + */ + public function returnNullable() + { + if (rand(0, 1)) { + return 'foo'; + } + + if (rand(0, 1)) { + return null; + } + } + + public function returnInterface(): FooInterface + { + return new self(); + } + + /** + * @return void + */ + public function returnVoid() + { + if (rand(0, 1)) { + return; + } + + if (rand(0, 1)) { + return null; + } + + if (rand(0, 1)) { + return 1; + } + } + + /** + * @return static + */ + public function returnStatic(): FooParent + { + if (rand(0, 1)) { + return parent::returnStatic(); + } + + $parent = new FooParent(); + + if (rand(0, 1)) { + return $parent->returnStatic(); // the only case with wrong static base class + } + + if (rand(0, 1)) { + return $this->returnStatic(); + } + } + + public function returnAlias(): Foo + { + return new FooAlias(); + } + + public function returnAnotherAlias(): FooAlias + { + return new Foo(); + } + + /** + * @param self[]|Collection $collection + * @return self[]|Collection|array + */ + public function returnUnionIterableType($collection) + { + if (rand(0, 1)) { + return $collection; + } + + if (rand(0, 1)) { + return new Collection(); + } + + if (rand(0, 1)) { + return new self(); + } + + if (rand(0, 1)) { + return [new self()]; + } + + if (rand(0, 1)) { + return new Bar(); + } + + if (rand(0, 1)) { + return [new Bar()]; + } + + if (rand(0, 1)) { + return 1; + } + + if (rand(0, 1)) { + return; + } + + /** @var Bar[]|Collection $barListOrCollection */ + $barListOrCollection = doFoo(); + + if (rand(0, 1)) { + return $barListOrCollection; + } + + /** @var self[]|AnotherCollection $selfListOrAnotherCollection */ + $selfListOrAnotherCollection = doFoo(); + + if (rand(0, 1)) { + return $selfListOrAnotherCollection; + } + + /** @var self[]|Collection|AnotherCollection $selfListOrCollectionorAnotherCollection */ + $selfListOrCollectionorAnotherCollection = doFoo(); + + if (rand(0, 1)) { + return $selfListOrCollectionorAnotherCollection; + } + + /** @var Bar[]|AnotherCollection $completelyDiffernetUnionIterable */ + $completelyDiffernetUnionIterable = doFoo(); + + if (rand(0, 1)) { + return $completelyDiffernetUnionIterable; + } + + if (rand(0, 1)) { + return null; + } + } + + /** + * @param self[]|Collection $collection + * @return self[]|Collection|AnotherCollection|null + */ + public function returnUnionIterableLooserReturnType($collection) + { + if (rand(0, 1)) { + return $collection; + } + + if (rand(0, 1)) { + return null; + } + } + + /** + * @return $this + */ + public function returnThis(): self + { + if (rand(0, 1)) { + return $this; + } + if (rand(0, 1)) { + return new self(); + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return new static(); + } + + if (rand(0, 1)) { + return null; + } + + if (rand(0, 1)) { + $that = $this; + return $that; + } + } + + /** + * @return $this|null + */ + public function returnThisOrNull() + { + if (rand(0, 1)) { + return $this; + } + if (rand(0, 1)) { + return new self(); + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return null; + } + if (rand(0, 1)) { + return $this->returnThis(); + } + if (rand(0, 1)) { + return $this->returnStaticThatReturnsNewStatic(); + } + } + + /** + * @return static + */ + public function returnStaticThatReturnsNewStatic(): self + { + if (rand(0, 1)) { + return new static(); + } + if (rand(0, 1)) { + return $this; + } + } + + public function returnsParent(): parent + { + if (rand(0, 1)) { + return new FooParent(); + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return null; + } + } + + /** + * @return parent + */ + public function returnsPhpDocParent() + { + if (rand(0, 1)) { + return new FooParent(); + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return null; + } + } + + /** + * @return scalar + */ + public function returnScalar() + { + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return 10.1; + } + if (rand(0, 1)) { + return 'a'; + } + if (rand(0, 1)) { + return false; + } + if (rand(0, 1)) { + return new \stdClass(); + } + } + + /** + * @return int + */ + public function containsYield() + { + yield 1; + return; + } + + public function returnsNullInTernary(): int + { + /** @var int|null $intOrNull */ + $intOrNull = doFoo(); + + if (rand(0, 1)) { + return $intOrNull; + } + if (rand(0, 1)) { + return $intOrNull !== null ? $intOrNull : 5; + } + if (rand(0, 1)) { + return $intOrNull !== null ? $intOrNull : null; + } + } + + public function misleadingBoolReturnType(): \ReturnTypes\boolean + { + if (rand(0, 1)) { + return true; + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return new boolean(); + } + } + + public function misleadingIntReturnType(): \ReturnTypes\integer + { + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return true; + } + if (rand(0, 1)) { + return new integer(); + } + } + + public function misleadingMixedReturnType(): mixed + { + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return true; + } + if (rand(0, 1)) { + return new mixed(); + } + } } class FooChild extends Foo { - } class Stock { - - /** @var self */ - private $stock; - - /** @var self|null */ - private $nullableStock; - - public function getActualStock(): self - { - if (is_null($this->stock)) - { - $this->stock = $this->findStock(); - if (is_null($this->stock)) { - throw new \Exception(); - } - return $this->stock; - } - return $this->stock; - } - - /** - * @return self|null - */ - public function findStock() - { - return new self(); - } - - public function getAnotherStock(): self - { - return $this->findStock(); - } - - public function returnSelf(): self - { - $stock = $this->findStock(); - if ($stock === null) { - $stock = new self(); - } - - return $stock; - } - - public function returnSelfAgain(): self - { - $stock = $this->findStock(); - if ($stock === null) { - $stock = new self(); - } elseif (test()) { - doFoo(); - } - - return $stock; - } - - public function returnSelfYetAgain(): self - { - $stock = $this->findStock(); - if ($stock === null) { - $stock = new self(); - } elseif (test()) { - doFoo(); - } else { - doBar(); - } - - return $stock; - } - - public function returnSelfYetYetAgain(): self - { - if ($this->nullableStock === null) { - $this->nullableStock = new self(); - } - - return $this->nullableStock; - } - - public function returnSelfAgainError(): self - { - $stock = $this->findStock(); - if (doFoo()) { - $stock = new self(); - } - - return $stock; // still possible null - } - - public function returnsSelfAgainAgain(): self - { - while (true) { - try { - if ($this->getActualStock() === null) { - continue; - } - } catch (\Exception $ex) { - continue; - } - return $this->getActualStock(); - } - } - - public function returnYetSelfAgainError(): self - { - $stock = $this->findStock(); - if ($stock === false) { - $stock = new self(); - } - - return $stock; // still possible null - } - + /** @var self */ + private $stock; + + /** @var self|null */ + private $nullableStock; + + public function getActualStock(): self + { + if (is_null($this->stock)) { + $this->stock = $this->findStock(); + if (is_null($this->stock)) { + throw new \Exception(); + } + return $this->stock; + } + return $this->stock; + } + + /** + * @return self|null + */ + public function findStock() + { + return new self(); + } + + public function getAnotherStock(): self + { + return $this->findStock(); + } + + public function returnSelf(): self + { + $stock = $this->findStock(); + if ($stock === null) { + $stock = new self(); + } + + return $stock; + } + + public function returnSelfAgain(): self + { + $stock = $this->findStock(); + if ($stock === null) { + $stock = new self(); + } elseif (test()) { + doFoo(); + } + + return $stock; + } + + public function returnSelfYetAgain(): self + { + $stock = $this->findStock(); + if ($stock === null) { + $stock = new self(); + } elseif (test()) { + doFoo(); + } else { + doBar(); + } + + return $stock; + } + + public function returnSelfYetYetAgain(): self + { + if ($this->nullableStock === null) { + $this->nullableStock = new self(); + } + + return $this->nullableStock; + } + + public function returnSelfAgainError(): self + { + $stock = $this->findStock(); + if (doFoo()) { + $stock = new self(); + } + + return $stock; // still possible null + } + + public function returnsSelfAgainAgain(): self + { + while (true) { + try { + if ($this->getActualStock() === null) { + continue; + } + } catch (\Exception $ex) { + continue; + } + return $this->getActualStock(); + } + } + + public function returnYetSelfAgainError(): self + { + $stock = $this->findStock(); + if ($stock === false) { + $stock = new self(); + } + + return $stock; // still possible null + } } class Issue105 { - /** - * @param string $type - * - * @return array|float|int|null|string - */ - public function manyTypes(string $type) - { - - } - - /** - * @return array - */ - public function returnArray(): array - { - $result = $this->manyTypes('array'); - $result = is_array($result) ? $result : []; - - return $result; - } - - public function returnAnotherArray(): array - { - $result = $this->manyTypes('array'); - if (!is_array($result)) { - $result = []; - } - - return $result; - } + /** + * @param string $type + * + * @return array|float|int|null|string + */ + public function manyTypes(string $type) + { + } + + /** + * @return array + */ + public function returnArray(): array + { + $result = $this->manyTypes('array'); + $result = is_array($result) ? $result : []; + + return $result; + } + + public function returnAnotherArray(): array + { + $result = $this->manyTypes('array'); + if (!is_array($result)) { + $result = []; + } + + return $result; + } } class ReturningSomethingFromConstructor { - - public function __construct() - { - return new Foo(); - } - + public function __construct() + { + return new Foo(); + } } class WeirdReturnFormat { - - /** - * @return \PHPStan\Foo\Bar | - * \PHPStan\Foo\Baz - */ - public function test() - { - return 1; - } - + /** + * @return \PHPStan\Foo\Bar | + * \PHPStan\Foo\Baz + */ + public function test() + { + return 1; + } } class Collection implements \IteratorAggregate { - - public function getIterator() - { - return new \ArrayIterator([]); - } - + public function getIterator() + { + return new \ArrayIterator([]); + } } class AnotherCollection implements \IteratorAggregate { - - public function getIterator() - { - return new \ArrayIterator([]); - } - + public function getIterator() + { + return new \ArrayIterator([]); + } } class GeneratorMethod { - - public function doFoo(): \Generator - { - return false; - yield "foo"; - } - + public function doFoo(): \Generator + { + return false; + yield "foo"; + } } class ReturnTernary { - - /** - * @param Foo|false $fooOrFalse - * @return Foo - */ - public function returnTernary($fooOrFalse): Foo - { - if (rand(0, 1)) { - return $fooOrFalse ?: new Foo(); - } - if (rand(0, 1)) { - return $fooOrFalse !== false ? $fooOrFalse : new Foo(); - } - - if (rand(0, 1)) { - $fooOrFalse ? ($fooResult = $fooOrFalse) : new Foo(); - return $fooResult; - } - - if (rand(0, 1)) { - $fooOrFalse ? false : ($falseResult = $fooOrFalse); - return $falseResult; - } - } - - /** - * @return static|null - */ - public function returnStatic() - { - $out = doFoo(); - - return is_a($out, static::class, false) ? $out : null; - } - + /** + * @param Foo|false $fooOrFalse + * @return Foo + */ + public function returnTernary($fooOrFalse): Foo + { + if (rand(0, 1)) { + return $fooOrFalse ?: new Foo(); + } + if (rand(0, 1)) { + return $fooOrFalse !== false ? $fooOrFalse : new Foo(); + } + + if (rand(0, 1)) { + $fooOrFalse ? ($fooResult = $fooOrFalse) : new Foo(); + return $fooResult; + } + + if (rand(0, 1)) { + $fooOrFalse ? false : ($falseResult = $fooOrFalse); + return $falseResult; + } + } + + /** + * @return static|null + */ + public function returnStatic() + { + $out = doFoo(); + + return is_a($out, static::class, false) ? $out : null; + } } class TrickyVoid { - - /** - * @return int|void - */ - public function returnVoidOrInt() - { - if (rand(0, 1)) { - return; - } - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return 'str'; - } - } - + /** + * @return int|void + */ + public function returnVoidOrInt() + { + if (rand(0, 1)) { + return; + } + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return 'str'; + } + } } class TernaryWithJsonEncode { - - public function toJsonOrNull(array $arr, string $s): ?string - { - if (rand(0, 1)) { - return json_encode($arr) ?: null; - } - if (rand(0, 1)) { - return json_encode($arr) ? json_encode($arr): null; - } - if (rand(0, 1)) { - return (rand(0, 1) ? $s : false) ?: null; - } - } - - public function toJson(array $arr): string - { - if (rand(0, 1)) { - return json_encode($arr) ?: ''; - } - if (rand(0, 1)) { - return json_encode($arr) ? json_encode($arr) : ''; - } - if (rand(0, 1)) { - return json_encode($arr) ?: json_encode($arr); - } - } - + public function toJsonOrNull(array $arr, string $s): ?string + { + if (rand(0, 1)) { + return json_encode($arr) ?: null; + } + if (rand(0, 1)) { + return json_encode($arr) ? json_encode($arr) : null; + } + if (rand(0, 1)) { + return (rand(0, 1) ? $s : false) ?: null; + } + } + + public function toJson(array $arr): string + { + if (rand(0, 1)) { + return json_encode($arr) ?: ''; + } + if (rand(0, 1)) { + return json_encode($arr) ? json_encode($arr) : ''; + } + if (rand(0, 1)) { + return json_encode($arr) ?: json_encode($arr); + } + } } class AppendedArrayReturnType { - - /** @return int[] */ - public function foo() : array { - $arr = []; - $arr[] = new \stdClass(); - return $arr; - } - - /** - * @param int[] $arr - * @return int[] - */ - public function bar(array $arr): array - { - $arr[] = new \stdClass(); - return $arr; - } - + /** @return int[] */ + public function foo(): array + { + $arr = []; + $arr[] = new \stdClass(); + return $arr; + } + + /** + * @param int[] $arr + * @return int[] + */ + public function bar(array $arr): array + { + $arr[] = new \stdClass(); + return $arr; + } } class WrongMagicMethods { - - public function __toString() - { - return true; - } - - public function __isset($name) - { - return 42; - } - - public function __destruct() - { - return 1; - } - - public function __unset($name) - { - return 1; - } - - public function __sleep() - { - return [ - new \stdClass(), - ]; - } - - public function __wakeup() - { - return 1; - } - - public static function __set_state(array $properties) - { - return ['foo' => 'bar']; - } - - public function __clone() - { - return 1; - } - + public function __toString() + { + return true; + } + + public function __isset($name) + { + return 42; + } + + public function __destruct() + { + return 1; + } + + public function __unset($name) + { + return 1; + } + + public function __sleep() + { + return [ + new \stdClass(), + ]; + } + + public function __wakeup() + { + return 1; + } + + public static function __set_state(array $properties) + { + return ['foo' => 'bar']; + } + + public function __clone() + { + return 1; + } } class ReturnSpecifiedMethodCall { - - /** - * @return string|false - */ - public function stringOrFalse() - { - - } - - public function doFoo(): string - { - if ($this->stringOrFalse()) { - return $this->stringOrFalse(); - } - - if (is_string($this->stringOrFalse())) { - return $this->stringOrFalse(); - } - - return ''; - } - + /** + * @return string|false + */ + public function stringOrFalse() + { + } + + public function doFoo(): string + { + if ($this->stringOrFalse()) { + return $this->stringOrFalse(); + } + + if (is_string($this->stringOrFalse())) { + return $this->stringOrFalse(); + } + + return ''; + } } class ArrayFillKeysIssue { - /** - * @param string[] $stringIds - * - * @return array - */ - public function getIPs(array $stringIds) - { - $paired = array_fill_keys($stringIds, []); - foreach ($stringIds as $id) { - $paired[$id][] = new Foo(); - } - return $paired; - } - - /** - * @param string[] $stringIds - * - * @return array - */ - public function getIPs2(array $stringIds) - { - $paired = array_fill_keys($stringIds, []); - foreach ($stringIds as $id) { - $paired[$id][] = new Bar(); - } - return $paired; - } + /** + * @param string[] $stringIds + * + * @return array + */ + public function getIPs(array $stringIds) + { + $paired = array_fill_keys($stringIds, []); + foreach ($stringIds as $id) { + $paired[$id][] = new Foo(); + } + return $paired; + } + + /** + * @param string[] $stringIds + * + * @return array + */ + public function getIPs2(array $stringIds) + { + $paired = array_fill_keys($stringIds, []); + foreach ($stringIds as $id) { + $paired[$id][] = new Bar(); + } + return $paired; + } } class AssertThisInstanceOf { - - /** - * @return $this - */ - public function doFoo() - { - assert($this instanceof FooInterface); - return $this; - } - - /** - * @return $this - */ - public function doBar() - { - $otherInstance = new self(); - assert($otherInstance instanceof FooInterface); - return $otherInstance; - } - + /** + * @return $this + */ + public function doFoo() + { + assert($this instanceof FooInterface); + return $this; + } + + /** + * @return $this + */ + public function doBar() + { + $otherInstance = new self(); + assert($otherInstance instanceof FooInterface); + return $otherInstance; + } } class NestedArrayCheck { - - /** - * @param mixed[] $rows - * @return array - */ - public function doFoo(array $rows) - { - $entities = []; - - foreach ($rows as $row) { - $entities['string'][] = 'string'; - } - - return $entities; - } - - /** - * @param mixed[] $rows - * @return array - */ - public function doBar(array $rows) - { - $entities = []; - - foreach ($rows as $row) { - $entities['string']['foo'] = 'string'; - } - - return $entities; - } - + /** + * @param mixed[] $rows + * @return array + */ + public function doFoo(array $rows) + { + $entities = []; + + foreach ($rows as $row) { + $entities['string'][] = 'string'; + } + + return $entities; + } + + /** + * @param mixed[] $rows + * @return array + */ + public function doBar(array $rows) + { + $entities = []; + + foreach ($rows as $row) { + $entities['string']['foo'] = 'string'; + } + + return $entities; + } } class CheckNullWithConstantType { + public const SOME_NULL_CONST = null; - const SOME_NULL_CONST = null; - - public function doFoo(?array $nullableArray): array - { - if ($nullableArray === self::SOME_NULL_CONST) { - $nullableArray = []; - } - - return $nullableArray; - } + public function doFoo(?array $nullableArray): array + { + if ($nullableArray === self::SOME_NULL_CONST) { + $nullableArray = []; + } + return $nullableArray; + } } class NullConditionInDoWhile { - - public function doFoo(): string - { - do { - $string = $this->doBar(); - } while ($string === null); - - return $string; - } - - public function doBar(): ?string - { - - } - + public function doFoo(): string + { + do { + $string = $this->doBar(); + } while ($string === null); + + return $string; + } + + public function doBar(): ?string + { + } } class RecursiveStaticResolving { - /** - * @return $this - */ - public function f2(): self - { - return $this; - } - - /** - * @return $this - */ - public function f3(): self - { - return $this; - } - - /** - * @return $this - */ - public function f1(): self - { - return $this->f2()->f3(); - } + /** + * @return $this + */ + public function f2(): self + { + return $this; + } + + /** + * @return $this + */ + public function f3(): self + { + return $this; + } + + /** + * @return $this + */ + public function f1(): self + { + return $this->f2()->f3(); + } } class Foo2 extends FooParent implements FooInterface { - public function returnIntFromParent() - { - if (rand(0, 1)) { - return 1; - } - if (rand(0, 1)) { - return '1'; - } - if (rand(0, 1)) { - return new integer(); - } - } - - public function returnsVoid(): self - { - return $this; - } + public function returnIntFromParent() + { + if (rand(0, 1)) { + return 1; + } + if (rand(0, 1)) { + return '1'; + } + if (rand(0, 1)) { + return new integer(); + } + } + + public function returnsVoid(): self + { + return $this; + } } class HelloWorld { - /** - * @param string $column Columna - * @return string - */ - public function columnToField(string $column) : string - { - $idx = strrpos($column, '.'); - $field = str_replace('.', '_', $column); - $field[$idx] = '.'; - - return $field; - } + /** + * @param string $column Columna + * @return string + */ + public function columnToField(string $column): string + { + $idx = strrpos($column, '.'); + $field = str_replace('.', '_', $column); + $field[$idx] = '.'; + + return $field; + } } class AssertInIf { + /** @var string|null */ + private $foo; - /** @var string|null */ - private $foo; - - public function doFoo(): string - { - if ($this->foo === null) { - $foo = getenv('FOO'); - assert($foo !== false); - assert(is_string($foo)); + public function doFoo(): string + { + if ($this->foo === null) { + $foo = getenv('FOO'); + assert($foo !== false); + assert(is_string($foo)); - $this->foo = $foo; - } - - return $this->foo; - } + $this->foo = $foo; + } + return $this->foo; + } } class VariableOverwrittenInForeach { - - public function doFoo(): int - { - $x = 0; - $y = 1; - foreach ([0, 1, 2] as $i) { - $x = $y; - $y = "hello"; - } - return $x; - } - - /** - * @param int[] $arrayOfIntegers - * @return int - */ - public function doBar(array $arrayOfIntegers): int - { - $x = 0; - $y = 1; - foreach ($arrayOfIntegers as $i) { - $x = $y; - $y = "hello"; - } - return $x; - } - + public function doFoo(): int + { + $x = 0; + $y = 1; + foreach ([0, 1, 2] as $i) { + $x = $y; + $y = "hello"; + } + return $x; + } + + /** + * @param int[] $arrayOfIntegers + * @return int + */ + public function doBar(array $arrayOfIntegers): int + { + $x = 0; + $y = 1; + foreach ($arrayOfIntegers as $i) { + $x = $y; + $y = "hello"; + } + return $x; + } } class ReturnStaticGeneric { - - /** - * @template T of self - * @param T $foo - * @return T - */ - public function doFoo($foo) - { - return $foo::returnsStatic(); - } - - /** - * @template T of self - * @param T $foo - * @return T - */ - public function doBar($foo) - { - return $foo->instanceReturnsStatic(); - } - - /** @return static */ - public static function returnsStatic() - { - return new static(); - } - - /** @return static */ - public function instanceReturnsStatic() { - if (rand(0, 1)) { - return new static(); - } - if (rand(0, 1)) { - return new self(); - } - if (rand(0, 1)) { - return $this; - } - } + /** + * @template T of self + * @param T $foo + * @return T + */ + public function doFoo($foo) + { + return $foo::returnsStatic(); + } + + /** + * @template T of self + * @param T $foo + * @return T + */ + public function doBar($foo) + { + return $foo->instanceReturnsStatic(); + } + + /** @return static */ + public static function returnsStatic() + { + return new static(); + } + + /** @return static */ + public function instanceReturnsStatic() + { + if (rand(0, 1)) { + return new static(); + } + if (rand(0, 1)) { + return new self(); + } + if (rand(0, 1)) { + return $this; + } + } } interface InterfaceThatWillBeDocInherited { - - /** - * @return $this - */ - public function setTableSchema(): self; - - /** - * @return $this - */ - public function setTableSchema2(): self; - - /** - * @return $this - */ - public function setTableSchema3(): self; - - /** - * @return static - */ - public function setTableSchema4(): self; - - /** - * @return static - */ - public function setTableSchema5(): self; - + /** + * @return $this + */ + public function setTableSchema(): self; + + /** + * @return $this + */ + public function setTableSchema2(): self; + + /** + * @return $this + */ + public function setTableSchema3(): self; + + /** + * @return static + */ + public function setTableSchema4(): self; + + /** + * @return static + */ + public function setTableSchema5(): self; } class ClassThatImplementsInterfaceAndInheritDocsIt implements InterfaceThatWillBeDocInherited { - - public function setTableSchema(): InterfaceThatWillBeDocInherited - { - return $this; - } - - /** - * {@inheritDoc} - */ - public function setTableSchema2(): InterfaceThatWillBeDocInherited - { - return $this; - } - - /** - * @inheritDoc - */ - public function setTableSchema3(): InterfaceThatWillBeDocInherited - { - return $this; - } - - /** - * @inheritDoc - */ - public function setTableSchema4(): InterfaceThatWillBeDocInherited - { - return $this; - } - - /** - * @inheritDoc - */ - public function setTableSchema5(): InterfaceThatWillBeDocInherited - { - return $this; - } - + public function setTableSchema(): InterfaceThatWillBeDocInherited + { + return $this; + } + + /** + * {@inheritDoc} + */ + public function setTableSchema2(): InterfaceThatWillBeDocInherited + { + return $this; + } + + /** + * @inheritDoc + */ + public function setTableSchema3(): InterfaceThatWillBeDocInherited + { + return $this; + } + + /** + * @inheritDoc + */ + public function setTableSchema4(): InterfaceThatWillBeDocInherited + { + return $this; + } + + /** + * @inheritDoc + */ + public function setTableSchema5(): InterfaceThatWillBeDocInherited + { + return $this; + } } class WithIntegerConst { - public const INTEGER_CONST = 1; - - public function returnIntegerBySumWithStaticConst(): int - { - return static::INTEGER_CONST + 1; - } - - public function returnIntegerBySumWithStaticConst1(): int - { - return static::INTEGER_CONST + 1 + 1; - } - - public function returnIntegerBySumWithStaticConst2(): int - { - return 1 + static::INTEGER_CONST + 1; - } - - public function returnIntegerBySumWithStaticConst3(): int - { - return 1 + 1 + static::INTEGER_CONST; - } + public const INTEGER_CONST = 1; + + public function returnIntegerBySumWithStaticConst(): int + { + return static::INTEGER_CONST + 1; + } + + public function returnIntegerBySumWithStaticConst1(): int + { + return static::INTEGER_CONST + 1 + 1; + } + + public function returnIntegerBySumWithStaticConst2(): int + { + return 1 + static::INTEGER_CONST + 1; + } + + public function returnIntegerBySumWithStaticConst3(): int + { + return 1 + 1 + static::INTEGER_CONST; + } } class VarAnnotationAboveStmtReturn { - - public function doFoo(\DateTimeInterface $date): \DateTimeImmutable - { - /** @var \DateTimeImmutable $date */ - return $date; - } - - public function doBar(\DateTimeInterface $date): \DateTimeImmutable - { - /** @var \DateTimeImmutable */ - return $date; - } - + public function doFoo(\DateTimeInterface $date): \DateTimeImmutable + { + /** @var \DateTimeImmutable $date */ + return $date; + } + + public function doBar(\DateTimeInterface $date): \DateTimeImmutable + { + /** @var \DateTimeImmutable */ + return $date; + } } /** @@ -1190,52 +1143,46 @@ public function doBar(\DateTimeInterface $date): \DateTimeImmutable */ abstract class CollectionWithArrayKey implements \Iterator { - - /** @var array */ - private $data = []; - - /** - * @param CollectionValue $value - * @param CollectionKey - */ - public function add($value, $key = null): void - { - $this->data[$key] = $value; - } - - /** - * @return CollectionKey|null - */ - public function key() - { - return key($this->data); - } - + /** @var array */ + private $data = []; + + /** + * @param CollectionValue $value + * @param CollectionKey + */ + public function add($value, $key = null): void + { + $this->data[$key] = $value; + } + + /** + * @return CollectionKey|null + */ + public function key() + { + return key($this->data); + } } class Bug3072 { - - /** - * @template T - * @return iterable - */ - public function getIterable(): iterable - { - return []; - } - + /** + * @template T + * @return iterable + */ + public function getIterable(): iterable + { + return []; + } } class NeverReturn { - - /** - * @return never - */ - public function doFoo(): void - { - return; - } - + /** + * @return never + */ + public function doFoo(): void + { + return; + } } diff --git a/tests/PHPStan/Rules/Methods/data/shadowed-trait-method.php b/tests/PHPStan/Rules/Methods/data/shadowed-trait-method.php index ddf9bc3fa6..ef06e7d2de 100644 --- a/tests/PHPStan/Rules/Methods/data/shadowed-trait-method.php +++ b/tests/PHPStan/Rules/Methods/data/shadowed-trait-method.php @@ -4,22 +4,17 @@ trait FooTrait { - - public function doFoo() - { - $this->doBar(); - } - + public function doFoo() + { + $this->doBar(); + } } class Foo { + use FooTrait; - use FooTrait; - - public function doFoo() - { - - } - + public function doFoo() + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/sibling-method-prototype.php b/tests/PHPStan/Rules/Methods/data/sibling-method-prototype.php index 97668d7430..3ec28e2e2f 100644 --- a/tests/PHPStan/Rules/Methods/data/sibling-method-prototype.php +++ b/tests/PHPStan/Rules/Methods/data/sibling-method-prototype.php @@ -4,44 +4,33 @@ class Base { - - protected function foo() - { - - } - + protected function foo() + { + } } class Other extends Base { - - protected function foo() - { - - } - + protected function foo() + { + } } -class Child extends Base { - - public function bar() - { - $other = new Other(); - $other->foo(); - } - +class Child extends Base +{ + public function bar() + { + $other = new Other(); + $other->foo(); + } } function () { - - new class extends Base { - - public function bar() - { - $other = new Other(); - $other->foo(); - } - - }; - + new class() extends Base { + public function bar() + { + $other = new Other(); + $other->foo(); + } + }; }; diff --git a/tests/PHPStan/Rules/Methods/data/static-call-on-expression.php b/tests/PHPStan/Rules/Methods/data/static-call-on-expression.php index d1e3fb9e14..5043c0ed21 100644 --- a/tests/PHPStan/Rules/Methods/data/static-call-on-expression.php +++ b/tests/PHPStan/Rules/Methods/data/static-call-on-expression.php @@ -4,14 +4,12 @@ class Foo { - - public static function doFoo(): self - { - return new static(); - } - + public static function doFoo(): self + { + return new static(); + } } function () { - Foo::doFoo()::doBar(); + Foo::doFoo()::doBar(); }; diff --git a/tests/PHPStan/Rules/Methods/data/static-calls-to-instance-methods.php b/tests/PHPStan/Rules/Methods/data/static-calls-to-instance-methods.php index f547de5105..ea983a4a5a 100644 --- a/tests/PHPStan/Rules/Methods/data/static-calls-to-instance-methods.php +++ b/tests/PHPStan/Rules/Methods/data/static-calls-to-instance-methods.php @@ -4,48 +4,42 @@ class Foo { - - public static function doStaticFoo() - { - Foo::doFoo(); // cannot call from static context - } - - public function doFoo() - { - Foo::doFoo(); - Bar::doBar(); // not guaranteed, works only in instance of Bar - } - - protected function doProtectedFoo() - { - - } - - private function doPrivateFoo() - { - - } - + public static function doStaticFoo() + { + Foo::doFoo(); // cannot call from static context + } + + public function doFoo() + { + Foo::doFoo(); + Bar::doBar(); // not guaranteed, works only in instance of Bar + } + + protected function doProtectedFoo() + { + } + + private function doPrivateFoo() + { + } } class Bar extends Foo { - - public static function doStaticBar() - { - Foo::doFoo(); // cannot call from static context - } - - public function doBar() - { - Foo::doFoo(); - Foo::dofoo(); - Foo::doFoo(1); - Foo::doProtectedFoo(); - Foo::doPrivateFoo(); - Bar::doBar(); - static::doFoo(); - static::doFoo(1); - } - + public static function doStaticBar() + { + Foo::doFoo(); // cannot call from static context + } + + public function doBar() + { + Foo::doFoo(); + Foo::dofoo(); + Foo::doFoo(1); + Foo::doProtectedFoo(); + Foo::doPrivateFoo(); + Bar::doBar(); + static::doFoo(); + static::doFoo(1); + } } diff --git a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects-phpdoc.php b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects-phpdoc.php index 9eaa60284e..43466c1377 100644 --- a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects-phpdoc.php +++ b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects-phpdoc.php @@ -4,68 +4,64 @@ class BzzStatic { - static function regular(string $a): string - { - return $a; - } + public static function regular(string $a): string + { + return $a; + } - /** - * @phpstan-pure - */ - static function pure1(string $a): string - { - return $a; - } + /** + * @phpstan-pure + */ + public static function pure1(string $a): string + { + return $a; + } - /** - * @psalm-pure - */ - static function pure2(string $a): string - { - return $a; - } + /** + * @psalm-pure + */ + public static function pure2(string $a): string + { + return $a; + } - /** - * @pure - */ - static function pure3(string $a): string - { - return $a; - } + /** + * @pure + */ + public static function pure3(string $a): string + { + return $a; + } } -function(): void { - BzzStatic::regular('test'); - BzzStatic::pure1('test'); - BzzStatic::pure2('test'); - BzzStatic::pure3('test'); +function (): void { + BzzStatic::regular('test'); + BzzStatic::pure1('test'); + BzzStatic::pure2('test'); + BzzStatic::pure3('test'); }; class PureThrows { + /** + * @phpstan-pure + * @throws void + */ + public static function pureAndThrowsVoid() + { + } - /** - * @phpstan-pure - * @throws void - */ - public static function pureAndThrowsVoid() - { - - } - - /** - * @phpstan-pure - * @throws \Exception - */ - public static function pureAndThrowsException() - { - - } - - public function doFoo(): void - { - self::pureAndThrowsVoid(); - self::pureAndThrowsException(); - } + /** + * @phpstan-pure + * @throws \Exception + */ + public static function pureAndThrowsException() + { + } + public function doFoo(): void + { + self::pureAndThrowsVoid(); + self::pureAndThrowsException(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php index 9c4e984e42..cd4c999691 100644 --- a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php @@ -6,32 +6,26 @@ class Foo { - - public function doFoo(\DateTimeImmutable $dt) - { - DateTimeImmutable::createFromFormat('Y-m-d', '2019-07-24'); - $dt::createFromFormat('Y-m-d', '2019-07-24'); - } - + public function doFoo(\DateTimeImmutable $dt) + { + DateTimeImmutable::createFromFormat('Y-m-d', '2019-07-24'); + $dt::createFromFormat('Y-m-d', '2019-07-24'); + } } class MyOwnDateTime extends \DateTime { - - public function doFoo() - { - parent::format('j. n. Y'); - parent::modify('-1 day'); - } - + public function doFoo() + { + parent::format('j. n. Y'); + parent::modify('-1 day'); + } } class FooException extends \Exception { - - public function __construct() - { - parent::__construct(); - } - + public function __construct() + { + parent::__construct(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/static-method-named-arguments.php b/tests/PHPStan/Rules/Methods/data/static-method-named-arguments.php index 4046b4b1c0..e560322d9f 100644 --- a/tests/PHPStan/Rules/Methods/data/static-method-named-arguments.php +++ b/tests/PHPStan/Rules/Methods/data/static-method-named-arguments.php @@ -4,16 +4,13 @@ class Foo { + public static function doFoo(int $i, int $j): void + { + } - public static function doFoo(int $i, int $j): void - { - - } - - public function doBar(): void - { - self::doFoo(i: 1); - self::doFoo(i:1, j: 2, z: 3); - } - + public function doBar(): void + { + self::doFoo(i: 1); + self::doFoo(i:1, j: 2, z: 3); + } } diff --git a/tests/PHPStan/Rules/Methods/data/static-methods-class-exists.php b/tests/PHPStan/Rules/Methods/data/static-methods-class-exists.php index 6b107dc5f3..1d7dbc09dc 100644 --- a/tests/PHPStan/Rules/Methods/data/static-methods-class-exists.php +++ b/tests/PHPStan/Rules/Methods/data/static-methods-class-exists.php @@ -6,14 +6,12 @@ class Foo { + public function doFoo(): void + { + if (!class_exists(Bar::class)) { + return; + } - public function doFoo(): void - { - if (!class_exists(Bar::class)) { - return; - } - - Bar::doBar(); - } - + Bar::doBar(); + } } diff --git a/tests/PHPStan/Rules/Methods/data/stringable-strict.php b/tests/PHPStan/Rules/Methods/data/stringable-strict.php index 9698483db4..892bd1ae04 100644 --- a/tests/PHPStan/Rules/Methods/data/stringable-strict.php +++ b/tests/PHPStan/Rules/Methods/data/stringable-strict.php @@ -1,18 +1,17 @@ -doFoo(new Bar()); - } - + public function doBar(): void + { + $this->doFoo(new Bar()); + } } diff --git a/tests/PHPStan/Rules/Methods/data/stringable.php b/tests/PHPStan/Rules/Methods/data/stringable.php index cc689ea4ae..22962e955f 100644 --- a/tests/PHPStan/Rules/Methods/data/stringable.php +++ b/tests/PHPStan/Rules/Methods/data/stringable.php @@ -6,48 +6,39 @@ class Foo { - - public function __toString(): string - { - return 'foo'; - } - + public function __toString(): string + { + return 'foo'; + } } class Bar implements Stringable { - - public function __toString(): string - { - return 'foo'; - } - + public function __toString(): string + { + return 'foo'; + } } interface Lorem extends Stringable { - } class Baz { - - public function doFoo(Stringable $s): void - { - - } - - public function doBar(Lorem $l): void - { - $this->doFoo(new Foo()); - $this->doFoo(new Bar()); - $this->doFoo($l); - $this->doBaz($l); - } - - public function doBaz(string $s): void - { - - } - + public function doFoo(Stringable $s): void + { + } + + public function doBar(Lorem $l): void + { + $this->doFoo(new Foo()); + $this->doFoo(new Bar()); + $this->doFoo($l); + $this->doBaz($l); + } + + public function doBaz(string $s): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/template-string-bound.php b/tests/PHPStan/Rules/Methods/data/template-string-bound.php index c55ae9c885..a6b80ba9ab 100644 --- a/tests/PHPStan/Rules/Methods/data/template-string-bound.php +++ b/tests/PHPStan/Rules/Methods/data/template-string-bound.php @@ -5,49 +5,45 @@ /** @template T of string */ class Foo { - - /** @var T */ - private $value; - - /** - * @param T $value - */ - public function __construct($value) - { - $this->value = $value; - } - - /** - * @return T - */ - public function getValue(): string - { - return $this->value; - } - + /** @var T */ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return T + */ + public function getValue(): string + { + return $this->value; + } } /** @template T of int */ class Bar { - - /** @var T */ - private $value; - - /** - * @param T $value - */ - public function __construct($value) - { - $this->value = $value; - } - - /** - * @return T - */ - public function getValue(): int - { - return $this->value; - } - + /** @var T */ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return T + */ + public function getValue(): int + { + return $this->value; + } } diff --git a/tests/PHPStan/Rules/Methods/data/trait-method-problem.php b/tests/PHPStan/Rules/Methods/data/trait-method-problem.php index aaf1a25e6f..10e074f0ba 100644 --- a/tests/PHPStan/Rules/Methods/data/trait-method-problem.php +++ b/tests/PHPStan/Rules/Methods/data/trait-method-problem.php @@ -4,16 +4,14 @@ trait X { - - abstract public static function a(self $b): void; - + abstract public static function a(self $b): void; } class Y { + use X; - use X; - - public static function a(self $b): void {} - + public static function a(self $b): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/typehints-iterable.php b/tests/PHPStan/Rules/Methods/data/typehints-iterable.php index 06f92f92cc..062c13361f 100644 --- a/tests/PHPStan/Rules/Methods/data/typehints-iterable.php +++ b/tests/PHPStan/Rules/Methods/data/typehints-iterable.php @@ -4,13 +4,10 @@ class IterableTypehints { - - /** - * @param iterable $iterable - */ - public function doFoo(iterable $iterable) - { - - } - + /** + * @param iterable $iterable + */ + public function doFoo(iterable $iterable) + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/typehints.php b/tests/PHPStan/Rules/Methods/data/typehints.php index f967e44906..cc83bafc38 100644 --- a/tests/PHPStan/Rules/Methods/data/typehints.php +++ b/tests/PHPStan/Rules/Methods/data/typehints.php @@ -4,117 +4,99 @@ class FooMethodTypehints { - - function foo(FooMethodTypehints $foo, $bar, array $lorem): NonexistentClass - { - - } - - function bar(BarMethodTypehints $bar): array - { - - } - - function baz(...$bar): FooMethodTypehints - { - - } - - /** - * @param FooMethodTypehints[] $foos - * @param BarMethodTypehints[] $bars - * @return BazMethodTypehints[] - */ - function lorem($foos, $bars) - { - - } - - /** - * @param FooMethodTypehints[] $foos - * @param BarMethodTypehints[] $bars - * @return BazMethodTypehints[] - */ - function ipsum(array $foos, array $bars): array - { - - } - - /** - * @param FooMethodTypehints[] $foos - * @param FooMethodTypehints|BarMethodTypehints[] $bars - * @return self|BazMethodTypehints[] - */ - function dolor(array $foos, array $bars): array - { - - } - - function parentWithoutParent(parent $parent): parent - { - - } - - /** - * @param parent $parent - * @return parent - */ - function phpDocParentWithoutParent($parent) - { - - } - - function badCaseTypehints(fOOMethodTypehints $foo): fOOMethodTypehintS - { - - } - - /** - * @param fOOMethodTypehints|\STDClass $foo - * @return fOOMethodTypehintS|\stdclass - */ - function unionTypeBadCaseTypehints($foo) - { - - } - - /** - * @param FOOMethodTypehints $foo - * @return FOOMethodTypehints - */ - function badCaseInNativeAndPhpDoc(FooMethodTypehints $foo): FooMethodTypehints - { - - } - - /** - * @param FooMethodTypehints $foo - * @return FooMethodTypehints - */ - function anotherBadCaseInNativeAndPhpDoc(FOOMethodTypehints $foo): FOOMethodTypehints - { - - } - - /** - * @param array $array - */ - function unknownTypesInArrays(array $array) - { - - } - + public function foo(FooMethodTypehints $foo, $bar, array $lorem): NonexistentClass + { + } + + public function bar(BarMethodTypehints $bar): array + { + } + + public function baz(...$bar): FooMethodTypehints + { + } + + /** + * @param FooMethodTypehints[] $foos + * @param BarMethodTypehints[] $bars + * @return BazMethodTypehints[] + */ + public function lorem($foos, $bars) + { + } + + /** + * @param FooMethodTypehints[] $foos + * @param BarMethodTypehints[] $bars + * @return BazMethodTypehints[] + */ + public function ipsum(array $foos, array $bars): array + { + } + + /** + * @param FooMethodTypehints[] $foos + * @param FooMethodTypehints|BarMethodTypehints[] $bars + * @return self|BazMethodTypehints[] + */ + public function dolor(array $foos, array $bars): array + { + } + + public function parentWithoutParent(parent $parent): parent + { + } + + /** + * @param parent $parent + * @return parent + */ + public function phpDocParentWithoutParent($parent) + { + } + + public function badCaseTypehints(fOOMethodTypehints $foo): fOOMethodTypehintS + { + } + + /** + * @param fOOMethodTypehints|\STDClass $foo + * @return fOOMethodTypehintS|\stdclass + */ + public function unionTypeBadCaseTypehints($foo) + { + } + + /** + * @param FOOMethodTypehints $foo + * @return FOOMethodTypehints + */ + public function badCaseInNativeAndPhpDoc(FooMethodTypehints $foo): FooMethodTypehints + { + } + + /** + * @param FooMethodTypehints $foo + * @return FooMethodTypehints + */ + public function anotherBadCaseInNativeAndPhpDoc(FOOMethodTypehints $foo): FOOMethodTypehints + { + } + + /** + * @param array $array + */ + public function unknownTypesInArrays(array $array) + { + } } class CallableTypehints { - - /** @param callable(Bla): Ble $cb */ - public function doFoo(callable $cb): void - { - - } - + /** @param callable(Bla): Ble $cb */ + public function doFoo(callable $cb): void + { + } } /** @@ -122,23 +104,19 @@ public function doFoo(callable $cb): void */ class TemplateTypeMissingInParameter { - - /** - * @template U of object - * @param class-string $class - */ - public function doFoo(string $class): void - { - - } - - /** - * @template U of object - * @param class-string $class - */ - public function doBar(string $class): void - { - - } - + /** + * @template U of object + * @param class-string $class + */ + public function doFoo(string $class): void + { + } + + /** + * @template U of object + * @param class-string $class + */ + public function doBar(string $class): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/variadic-always-optional.php b/tests/PHPStan/Rules/Methods/data/variadic-always-optional.php index 24f26ced08..a4e7d9bc91 100644 --- a/tests/PHPStan/Rules/Methods/data/variadic-always-optional.php +++ b/tests/PHPStan/Rules/Methods/data/variadic-always-optional.php @@ -4,30 +4,22 @@ class Foo { + public function doFoo(string ...$test): void + { + } - public function doFoo(string ...$test): void - { - - } - - public function doBar(): void - { - - } - + public function doBar(): void + { + } } class Bar extends Foo { + public function doFoo(string ...$test): void + { + } - public function doFoo(string ...$test): void - { - - } - - public function doBar(...$test): void - { - - } - + public function doBar(...$test): void + { + } } diff --git a/tests/PHPStan/Rules/Methods/data/void-parameter-typehint.php b/tests/PHPStan/Rules/Methods/data/void-parameter-typehint.php index a291092853..1cf5232efe 100644 --- a/tests/PHPStan/Rules/Methods/data/void-parameter-typehint.php +++ b/tests/PHPStan/Rules/Methods/data/void-parameter-typehint.php @@ -4,10 +4,8 @@ class Foo { - - public function doFoo(void $param): int - { - return 1; - } - + public function doFoo(void $param): int + { + return 1; + } } diff --git a/tests/PHPStan/Rules/Methods/data/without-union-types.php b/tests/PHPStan/Rules/Methods/data/without-union-types.php index 29fd87931e..22a31e5605 100644 --- a/tests/PHPStan/Rules/Methods/data/without-union-types.php +++ b/tests/PHPStan/Rules/Methods/data/without-union-types.php @@ -4,14 +4,12 @@ class Foo { - - /** @var self|false */ - private $selfOrFalse; - - public function doFoo() - { - $this->selfOrFalse->doFoo(); - $this->selfOrFalse->doFoo(1, 2, 3); - } - + /** @var self|false */ + private $selfOrFalse; + + public function doFoo() + { + $this->selfOrFalse->doFoo(); + $this->selfOrFalse->doFoo(1, 2, 3); + } } diff --git a/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php index ebb411bfac..fa402b887b 100644 --- a/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/missing-closure-native-return-typehint.php'], [ - [ - 'Anonymous function should have native return typehint "void".', - 10, - ], - [ - 'Anonymous function should have native return typehint "void".', - 13, - ], - [ - 'Anonymous function should have native return typehint "Generator".', - 16, - ], - [ - 'Mixing returning values with empty return statements - return null should be used here.', - 25, - ], - [ - 'Anonymous function should have native return typehint "?int".', - 23, - ], - [ - 'Anonymous function should have native return typehint "?int".', - 33, - ], - [ - 'Anonymous function sometimes return something but return statement at the end is missing.', - 40, - ], - [ - 'Anonymous function should have native return typehint "array".', - 46, - ], - ]); - } - - public function testBug2682(): void - { - $this->analyse([__DIR__ . '/data/bug-2682.php'], [ - [ - 'Anonymous function should have native return typehint "void".', - 9, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-closure-native-return-typehint.php'], [ + [ + 'Anonymous function should have native return typehint "void".', + 10, + ], + [ + 'Anonymous function should have native return typehint "void".', + 13, + ], + [ + 'Anonymous function should have native return typehint "Generator".', + 16, + ], + [ + 'Mixing returning values with empty return statements - return null should be used here.', + 25, + ], + [ + 'Anonymous function should have native return typehint "?int".', + 23, + ], + [ + 'Anonymous function should have native return typehint "?int".', + 33, + ], + [ + 'Anonymous function sometimes return something but return statement at the end is missing.', + 40, + ], + [ + 'Anonymous function should have native return typehint "array".', + 46, + ], + ]); + } + public function testBug2682(): void + { + $this->analyse([__DIR__ . '/data/bug-2682.php'], [ + [ + 'Anonymous function should have native return typehint "void".', + 9, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index 9485d228ca..c5fab0e99d 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -1,4 +1,6 @@ -checkExplicitMixedMissingReturn, true); - } - - public function testRule(): void - { - $this->checkExplicitMixedMissingReturn = true; + protected function getRule(): Rule + { + return new MissingReturnRule($this->checkExplicitMixedMissingReturn, true); + } - $this->analyse([__DIR__ . '/data/missing-return.php'], [ - [ - 'Method MissingReturn\Foo::doFoo() should return int but return statement is missing.', - 8, - ], - [ - 'Method MissingReturn\Foo::doBar() should return int but return statement is missing.', - 15, - ], - [ - 'Method MissingReturn\Foo::doBaz() should return int but return statement is missing.', - 21, - ], - [ - 'Method MissingReturn\Foo::doLorem() should return int but return statement is missing.', - 36, - ], - [ - 'Anonymous function should return int but return statement is missing.', - 105, - ], - [ - 'Function MissingReturn\doFoo() should return int but return statement is missing.', - 112, - ], - [ - 'Method MissingReturn\SwitchBranches::doBar() should return int but return statement is missing.', - 146, - ], - [ - 'Method MissingReturn\SwitchBranches::doLorem() should return int but return statement is missing.', - 172, - ], - [ - 'Method MissingReturn\SwitchBranches::doIpsum() should return int but return statement is missing.', - 182, - ], - [ - 'Method MissingReturn\SwitchBranches::doDolor() should return int but return statement is missing.', - 193, - ], - [ - 'Method MissingReturn\TryCatchFinally::doBaz() should return int but return statement is missing.', - 234, - ], - [ - 'Method MissingReturn\TryCatchFinally::doDolor() should return int but return statement is missing.', - 263, - ], - [ - 'Method MissingReturn\ReturnInPhpDoc::doFoo() should return int but return statement is missing.', - 290, - ], - [ - 'Method MissingReturn\FooTemplateMixedType::doFoo() should return T but return statement is missing.', - 321, - ], - [ - 'Method MissingReturn\MissingReturnGenerators::emptyBodyUnspecifiedTReturn() should return Generator but return statement is missing.', - 331, - ], - [ - 'Method MissingReturn\MissingReturnGenerators::emptyBodyUnspecifiedTReturn2() should return Generator but return statement is missing.', - 344, - ], - [ - 'Method MissingReturn\MissingReturnGenerators::emptyBodySpecifiedTReturn() should return Generator but return statement is missing.', - 360, - ], - [ - 'Method MissingReturn\MissingReturnGenerators::bodySpecifiedTReturn() should return string but return statement is missing.', - 370, - ], - [ - 'Method MissingReturn\NeverReturn::doBaz() should always throw an exception or terminate script execution but doesn\'t do that.', - 473, - ], - ]); - } + public function testRule(): void + { + $this->checkExplicitMixedMissingReturn = true; - public function testCheckMissingReturnWithTemplateMixedType(): void - { - $this->checkExplicitMixedMissingReturn = false; - $this->analyse([__DIR__ . '/data/missing-return-template-mixed-type.php'], [ - [ - 'Method MissingReturnTemplateMixedType\Foo::doFoo() should return T but return statement is missing.', - 13, - ], - ]); - } + $this->analyse([__DIR__ . '/data/missing-return.php'], [ + [ + 'Method MissingReturn\Foo::doFoo() should return int but return statement is missing.', + 8, + ], + [ + 'Method MissingReturn\Foo::doBar() should return int but return statement is missing.', + 15, + ], + [ + 'Method MissingReturn\Foo::doBaz() should return int but return statement is missing.', + 21, + ], + [ + 'Method MissingReturn\Foo::doLorem() should return int but return statement is missing.', + 36, + ], + [ + 'Anonymous function should return int but return statement is missing.', + 105, + ], + [ + 'Function MissingReturn\doFoo() should return int but return statement is missing.', + 112, + ], + [ + 'Method MissingReturn\SwitchBranches::doBar() should return int but return statement is missing.', + 146, + ], + [ + 'Method MissingReturn\SwitchBranches::doLorem() should return int but return statement is missing.', + 172, + ], + [ + 'Method MissingReturn\SwitchBranches::doIpsum() should return int but return statement is missing.', + 182, + ], + [ + 'Method MissingReturn\SwitchBranches::doDolor() should return int but return statement is missing.', + 193, + ], + [ + 'Method MissingReturn\TryCatchFinally::doBaz() should return int but return statement is missing.', + 234, + ], + [ + 'Method MissingReturn\TryCatchFinally::doDolor() should return int but return statement is missing.', + 263, + ], + [ + 'Method MissingReturn\ReturnInPhpDoc::doFoo() should return int but return statement is missing.', + 290, + ], + [ + 'Method MissingReturn\FooTemplateMixedType::doFoo() should return T but return statement is missing.', + 321, + ], + [ + 'Method MissingReturn\MissingReturnGenerators::emptyBodyUnspecifiedTReturn() should return Generator but return statement is missing.', + 331, + ], + [ + 'Method MissingReturn\MissingReturnGenerators::emptyBodyUnspecifiedTReturn2() should return Generator but return statement is missing.', + 344, + ], + [ + 'Method MissingReturn\MissingReturnGenerators::emptyBodySpecifiedTReturn() should return Generator but return statement is missing.', + 360, + ], + [ + 'Method MissingReturn\MissingReturnGenerators::bodySpecifiedTReturn() should return string but return statement is missing.', + 370, + ], + [ + 'Method MissingReturn\NeverReturn::doBaz() should always throw an exception or terminate script execution but doesn\'t do that.', + 473, + ], + ]); + } - public function testBug2875(): void - { - $this->checkExplicitMixedMissingReturn = true; - $this->analyse([__DIR__ . '/data/bug-2875.php'], []); - } + public function testCheckMissingReturnWithTemplateMixedType(): void + { + $this->checkExplicitMixedMissingReturn = false; + $this->analyse([__DIR__ . '/data/missing-return-template-mixed-type.php'], [ + [ + 'Method MissingReturnTemplateMixedType\Foo::doFoo() should return T but return statement is missing.', + 13, + ], + ]); + } - public function testMissingMixedReturnInEmptyBody(): void - { - $this->checkExplicitMixedMissingReturn = true; - $this->analyse([__DIR__ . '/data/missing-mixed-return-empty-body.php'], [ - [ - 'Method MissingMixedReturnEmptyBody\HelloWorld::doFoo() should return mixed but return statement is missing.', - 11, - ], - ]); - } + public function testBug2875(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-2875.php'], []); + } - public function testBug3669(): void - { - $this->checkExplicitMixedMissingReturn = true; + public function testMissingMixedReturnInEmptyBody(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/missing-mixed-return-empty-body.php'], [ + [ + 'Method MissingMixedReturnEmptyBody\HelloWorld::doFoo() should return mixed but return statement is missing.', + 11, + ], + ]); + } - require_once __DIR__ . '/data/bug-3669.php'; - $this->analyse([__DIR__ . '/data/bug-3669.php'], []); - } + public function testBug3669(): void + { + $this->checkExplicitMixedMissingReturn = true; + require_once __DIR__ . '/data/bug-3669.php'; + $this->analyse([__DIR__ . '/data/bug-3669.php'], []); + } } diff --git a/tests/PHPStan/Rules/Missing/data/bug-2682.php b/tests/PHPStan/Rules/Missing/data/bug-2682.php index 61e97a5292..606544d706 100644 --- a/tests/PHPStan/Rules/Missing/data/bug-2682.php +++ b/tests/PHPStan/Rules/Missing/data/bug-2682.php @@ -4,12 +4,12 @@ class HelloWorld { - public function sayHello(): void - { - function(array $array) { - function(): string { - return 'abc'; - }; - }; - } + public function sayHello(): void + { + function (array $array) { + function (): string { + return 'abc'; + }; + }; + } } diff --git a/tests/PHPStan/Rules/Missing/data/bug-2875.php b/tests/PHPStan/Rules/Missing/data/bug-2875.php index f621dde1ed..cf5798baef 100644 --- a/tests/PHPStan/Rules/Missing/data/bug-2875.php +++ b/tests/PHPStan/Rules/Missing/data/bug-2875.php @@ -2,17 +2,24 @@ namespace Bug2875MissingReturn; -class A {} -class B {} +class A +{ +} +class B +{ +} class HelloWorld { - /** @param A|B|null $obj */ - function one($obj): int - { - if ($obj === null) return 1; - else if ($obj instanceof A) return 2; - else if ($obj instanceof B) return 3; - } - + /** @param A|B|null $obj */ + public function one($obj): int + { + if ($obj === null) { + return 1; + } elseif ($obj instanceof A) { + return 2; + } elseif ($obj instanceof B) { + return 3; + } + } } diff --git a/tests/PHPStan/Rules/Missing/data/bug-3669.php b/tests/PHPStan/Rules/Missing/data/bug-3669.php index b9c6e8ee9e..910d23ff44 100644 --- a/tests/PHPStan/Rules/Missing/data/bug-3669.php +++ b/tests/PHPStan/Rules/Missing/data/bug-3669.php @@ -4,12 +4,12 @@ function foo(): \Generator { - while ($bar = yield 'foo') { - } + while ($bar = yield 'foo') { + } } function bar($m): \Generator { - while ($bar = yield $m) { - } + while ($bar = yield $m) { + } } diff --git a/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php b/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php index 1eb7d37d14..ebb382b798 100644 --- a/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php +++ b/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php @@ -4,52 +4,49 @@ class Foo { + public function doFoo() + { + function () { + }; + function () { + return; + }; + function (bool $bool) { + if ($bool) { + return; + } else { + yield 1; + } + }; + function (bool $bool) { + if ($bool) { + return; + } else { + return 1; + } + }; + function (): int { + return 1; + }; + function (bool $bool) { + if ($bool) { + return null; + } else { + return 1; + } + }; + function (bool $bool) { + if ($bool) { + return 1; + } + }; - public function doFoo() - { - function () { - - }; - function () { - return; - }; - function (bool $bool) { - if ($bool) { - return; - } else { - yield 1; - } - }; - function (bool $bool) { - if ($bool) { - return; - } else { - return 1; - } - }; - function (): int { - return 1; - }; - function (bool $bool) { - if ($bool) { - return null; - } else { - return 1; - } - }; - function (bool $bool) { - if ($bool) { - return 1; - } - }; - - function () { - $array = [ - 'foo' => 'bar', - ]; - - return $array; - }; - } + function () { + $array = [ + 'foo' => 'bar', + ]; + return $array; + }; + } } diff --git a/tests/PHPStan/Rules/Missing/data/missing-mixed-return-empty-body.php b/tests/PHPStan/Rules/Missing/data/missing-mixed-return-empty-body.php index f10dde1fbe..ebf8e16546 100644 --- a/tests/PHPStan/Rules/Missing/data/missing-mixed-return-empty-body.php +++ b/tests/PHPStan/Rules/Missing/data/missing-mixed-return-empty-body.php @@ -4,13 +4,10 @@ class HelloWorld { - - /** - * @return mixed - */ - public function doFoo() - { - - } - + /** + * @return mixed + */ + public function doFoo() + { + } } diff --git a/tests/PHPStan/Rules/Missing/data/missing-return-template-mixed-type.php b/tests/PHPStan/Rules/Missing/data/missing-return-template-mixed-type.php index 004b276c3f..0ec7dd33ea 100644 --- a/tests/PHPStan/Rules/Missing/data/missing-return-template-mixed-type.php +++ b/tests/PHPStan/Rules/Missing/data/missing-return-template-mixed-type.php @@ -4,15 +4,12 @@ class Foo { - - /** - * @template T - * @param T $a - * @return T - */ - public function doFoo($a) - { - - } - + /** + * @template T + * @param T $a + * @return T + */ + public function doFoo($a) + { + } } diff --git a/tests/PHPStan/Rules/Missing/data/missing-return.php b/tests/PHPStan/Rules/Missing/data/missing-return.php index d6558bdb87..a634583434 100644 --- a/tests/PHPStan/Rules/Missing/data/missing-return.php +++ b/tests/PHPStan/Rules/Missing/data/missing-return.php @@ -4,495 +4,440 @@ class Foo { - - public function doFoo(): int - { - - } - - public function doBar(): int - { - // noop comment - } - - public function doBaz(): int - { - doFoo(); - doBar(); - } - - public function doLorem(): int - { - if (doFoo()) { - - } - - try { - - } catch (\Exception $e) { - - } - - if (doFoo()) { - doFoo(); - doBar(); - if (doFoo()) { - - } elseif (blabla()) { - - } else { - - } - } else { - try { - - } catch (\Exception $e) { - - } - } - } - + public function doFoo(): int + { + } + + public function doBar(): int + { + // noop comment + } + + public function doBaz(): int + { + doFoo(); + doBar(); + } + + public function doLorem(): int + { + if (doFoo()) { + } + + try { + } catch (\Exception $e) { + } + + if (doFoo()) { + doFoo(); + doBar(); + if (doFoo()) { + } elseif (blabla()) { + } else { + } + } else { + try { + } catch (\Exception $e) { + } + } + } } class Bar { - - public function doFoo(): int - { - return 1; - } - - public function doBar(): int - { - doFoo(); - - return 1; - } - - public function doBaz(): void - { - - } - - public function doLorem(): void - { - doFoo(); - } - - public function doIpsum() - { - - } - - public function doDolor() - { - doFoo(); - } - - public function doSit(): iterable - { - doBar(); - doFoo(yield 1); - } - + public function doFoo(): int + { + return 1; + } + + public function doBar(): int + { + doFoo(); + + return 1; + } + + public function doBaz(): void + { + } + + public function doLorem(): void + { + doFoo(); + } + + public function doIpsum() + { + } + + public function doDolor() + { + doFoo(); + } + + public function doSit(): iterable + { + doBar(); + doFoo(yield 1); + } } class Baz { - - public function doFoo() - { - function (): int { - - }; - } - + public function doFoo() + { + function (): int { + }; + } } function doFoo(): int { - } class Yielding { - - public function doFoo(bool $bool): iterable - { - while ($bool) { - yield 1; - } - } - + public function doFoo(bool $bool): iterable + { + while ($bool) { + yield 1; + } + } } class SwitchBranches { - - public function doFoo(int $i): int - { - switch ($i) { - case 0: - case 1: - case 2: - return 1; - default: - return 2; - } - } - - public function doBar(int $i): int - { - switch ($i) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 2; - } - } - - public function doBaz(int $i): int - { - switch ($i) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 2; - default: - return 3; - } - } - - public function doLorem(int $i): int - { - switch ($i) { - case 0: - case 1: - case 2: - return 1; - } - } - - public function doIpsum(int $i): int - { - switch ($i) { - case 0: - return 1; - case 1: - case 2: - default: - } - } - - public function doDolor(int $i): int - { - switch ($i) { - case 0: - return 1; - case 1: - case 2: - } - } - + public function doFoo(int $i): int + { + switch ($i) { + case 0: + case 1: + case 2: + return 1; + default: + return 2; + } + } + + public function doBar(int $i): int + { + switch ($i) { + case 0: + return 0; + case 1: + return 1; + case 2: + return 2; + } + } + + public function doBaz(int $i): int + { + switch ($i) { + case 0: + return 0; + case 1: + return 1; + case 2: + return 2; + default: + return 3; + } + } + + public function doLorem(int $i): int + { + switch ($i) { + case 0: + case 1: + case 2: + return 1; + } + } + + public function doIpsum(int $i): int + { + switch ($i) { + case 0: + return 1; + case 1: + case 2: + default: + } + } + + public function doDolor(int $i): int + { + switch ($i) { + case 0: + return 1; + case 1: + case 2: + } + } } class TryCatchFinally { - - public function doFoo(): int - { - try { - - } catch (\Exception $e) { - - } catch (\Throwable $e) { - - } finally { - return 1; - } - } - - public function doBar(): int - { - try { - return 1; - } catch (\Exception $e) { - return 1; - } catch (\Throwable $e) { - return 1; - } finally { - - } - } - - public function doBaz(): int - { - try { - maybeThrow(); return 1; - } catch (\Exception $e) { - return 1; - } catch (\Throwable $e) { - - } - } - - public function doLorem(): int - { - try { - return 1; - } finally { - - } - } - - public function doIpsum(): int - { - try { - - } finally { - return 1; - } - } - - public function doDolor(): int - { - try { - - } finally { - - } - } - + public function doFoo(): int + { + try { + } catch (\Exception $e) { + } catch (\Throwable $e) { + } finally { + return 1; + } + } + + public function doBar(): int + { + try { + return 1; + } catch (\Exception $e) { + return 1; + } catch (\Throwable $e) { + return 1; + } finally { + } + } + + public function doBaz(): int + { + try { + maybeThrow(); + return 1; + } catch (\Exception $e) { + return 1; + } catch (\Throwable $e) { + } + } + + public function doLorem(): int + { + try { + return 1; + } finally { + } + } + + public function doIpsum(): int + { + try { + } finally { + return 1; + } + } + + public function doDolor(): int + { + try { + } finally { + } + } } class MoreYielding { - - public function doFoo(bool $foo): iterable - { - if ($foo) { - yield 1; - } - } - + public function doFoo(bool $foo): iterable + { + if ($foo) { + yield 1; + } + } } class ReturnInPhpDoc { - - /** - * @return int - */ - public function doFoo() - { - - } - + /** + * @return int + */ + public function doFoo() + { + } } class YieldInAssign { + public function doFoo(\Generator $items): \Generator + { + while ($items->valid()) { + $item = $items->current(); - public function doFoo(\Generator $items): \Generator - { - while ($items->valid()) { - $item = $items->current(); - - $state = yield $item; - - $items->send($state); - } - } + $state = yield $item; + $items->send($state); + } + } } class FooTemplateMixedType { - - /** - * @template T - * @param T $a - * @return T - */ - public function doFoo($a) - { - - } - + /** + * @template T + * @param T $a + * @return T + */ + public function doFoo($a) + { + } } class MissingReturnGenerators { - - public function emptyBodyUnspecifiedTReturn(): \Generator - { - - } - - public function bodyUnspecifiedTReturn(): \Generator - { - yield 1; - } - - /** - * @return \Generator - */ - public function emptyBodyUnspecifiedTReturn2(): \Generator - { - - } - - /** - * @return \Generator - */ - public function bodyUnspecifiedTReturn2(): \Generator - { - yield 1; - } - - /** - * @return \Generator - */ - public function emptyBodySpecifiedTReturn(): \Generator - { - - } - - /** - * @return \Generator - */ - public function bodySpecifiedTReturn(): \Generator - { - yield 1; - } - - /** - * @return \Generator - */ - public function bodySpecifiedVoidTReturn(): \Generator - { - yield 1; - } - - /** - * @return \Generator - */ - public function bodySpecifiedVoidTReturn2(): \Generator - { - yield 1; - return; - } - - /** - * @return \Generator - */ - public function bodySpecifiedVoidTReturn3(): \Generator - { - yield 1; - return 2; - } - - public function yieldInWhileCondition(): \Generator - { - while($foo = yield 'foo') { - } - } - - public function yieldInForCondition(): \Generator - { - for($foo = 0; $foo > 0; $foo = yield 1) { - } - } - - public function yieldInDoCondition(): \Generator - { - do { - } while(yield 1); - } - - public function yieldInForeach(): \Generator - { - foreach (yield 1 as $bar) { - } - } - - public function yieldInIf(): \Generator - { - if (yield 1) { - } - } - - public function yieldInSwitch(): \Generator - { - switch (yield 1) { - default: - } - } - + public function emptyBodyUnspecifiedTReturn(): \Generator + { + } + + public function bodyUnspecifiedTReturn(): \Generator + { + yield 1; + } + + /** + * @return \Generator + */ + public function emptyBodyUnspecifiedTReturn2(): \Generator + { + } + + /** + * @return \Generator + */ + public function bodyUnspecifiedTReturn2(): \Generator + { + yield 1; + } + + /** + * @return \Generator + */ + public function emptyBodySpecifiedTReturn(): \Generator + { + } + + /** + * @return \Generator + */ + public function bodySpecifiedTReturn(): \Generator + { + yield 1; + } + + /** + * @return \Generator + */ + public function bodySpecifiedVoidTReturn(): \Generator + { + yield 1; + } + + /** + * @return \Generator + */ + public function bodySpecifiedVoidTReturn2(): \Generator + { + yield 1; + return; + } + + /** + * @return \Generator + */ + public function bodySpecifiedVoidTReturn3(): \Generator + { + yield 1; + return 2; + } + + public function yieldInWhileCondition(): \Generator + { + while ($foo = yield 'foo') { + } + } + + public function yieldInForCondition(): \Generator + { + for ($foo = 0; $foo > 0; $foo = yield 1) { + } + } + + public function yieldInDoCondition(): \Generator + { + do { + } while (yield 1); + } + + public function yieldInForeach(): \Generator + { + foreach (yield 1 as $bar) { + } + } + + public function yieldInIf(): \Generator + { + if (yield 1) { + } + } + + public function yieldInSwitch(): \Generator + { + switch (yield 1) { + default: + } + } } class VoidUnion { - - /** - * @return int|void - */ - public function doFoo() - { - echo 'test'; - } - + /** + * @return int|void + */ + public function doFoo() + { + echo 'test'; + } } class NeverReturn { - - /** - * @return never - */ - public function doFoo() - { - throw new \Exception(); - } - - /** - * @return never - */ - public function doBar() - { - die; - } - - /** - * @return never - */ - public function doBaz() - { - - } - + /** + * @return never + */ + public function doFoo() + { + throw new \Exception(); + } + + /** + * @return never + */ + public function doBar() + { + die; + } + + /** + * @return never + */ + public function doBaz() + { + } } class ClosureWithMissingReturnWithoutTypehint { - - public function doFoo(): void - { - function () { - if (rand(0, 1)) { - return; - } - }; - - function () { - if (rand(0, 1)) { - return null; - } - }; - } - + public function doFoo(): void + { + function () { + if (rand(0, 1)) { + return; + } + }; + + function () { + if (rand(0, 1)) { + return null; + } + }; + } } diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php index 442d127776..d77738c899 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingNamesInGroupUseRule($broker, new ClassCaseSensitivityCheck($broker), true); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingNamesInGroupUseRule($broker, new ClassCaseSensitivityCheck($broker), true); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/uses-defined.php'; - $this->analyse([__DIR__ . '/data/group-uses.php'], [ - [ - 'Function Uses\foo used with incorrect case: Uses\Foo.', - 6, - ], - [ - 'Used function Uses\baz not found.', - 7, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Interface Uses\Lorem referenced with incorrect case: Uses\LOREM.', - 11, - ], - [ - 'Function Uses\foo used with incorrect case: Uses\Foo.', - 13, - ], - [ - 'Used constant Uses\OTHER_CONSTANT not found.', - 15, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/uses-defined.php'; + $this->analyse([__DIR__ . '/data/group-uses.php'], [ + [ + 'Function Uses\foo used with incorrect case: Uses\Foo.', + 6, + ], + [ + 'Used function Uses\baz not found.', + 7, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Interface Uses\Lorem referenced with incorrect case: Uses\LOREM.', + 11, + ], + [ + 'Function Uses\foo used with incorrect case: Uses\Foo.', + 13, + ], + [ + 'Used constant Uses\OTHER_CONSTANT not found.', + 15, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php index dc0750e851..e9b6002f56 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingNamesInUseRule($broker, new ClassCaseSensitivityCheck($broker, true), true); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingNamesInUseRule($broker, new ClassCaseSensitivityCheck($broker, true), true); - } - - public function testRule(): void - { - require_once __DIR__ . '/data/uses-defined.php'; - $this->analyse([__DIR__ . '/data/uses.php'], [ - [ - 'Used function Uses\bar not found.', - 7, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Used constant Uses\OTHER_CONSTANT not found.', - 8, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Function Uses\foo used with incorrect case: Uses\Foo.', - 9, - ], - [ - 'Interface Uses\Lorem referenced with incorrect case: Uses\LOREM.', - 10, - ], - [ - 'Class DateTime referenced with incorrect case: DATETIME.', - 11, - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/uses-defined.php'; + $this->analyse([__DIR__ . '/data/uses.php'], [ + [ + 'Used function Uses\bar not found.', + 7, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Used constant Uses\OTHER_CONSTANT not found.', + 8, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function Uses\foo used with incorrect case: Uses\Foo.', + 9, + ], + [ + 'Interface Uses\Lorem referenced with incorrect case: Uses\LOREM.', + 10, + ], + [ + 'Class DateTime referenced with incorrect case: DATETIME.', + 11, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Namespaces/data/group-uses.php b/tests/PHPStan/Rules/Namespaces/data/group-uses.php index 85515d349f..5be4086c5b 100644 --- a/tests/PHPStan/Rules/Namespaces/data/group-uses.php +++ b/tests/PHPStan/Rules/Namespaces/data/group-uses.php @@ -2,18 +2,14 @@ namespace SomeOtherUseNamespace; -use function Uses\{ - Foo, - baz -}; -use Uses\{ - Bar, - LOREM, - Nonexistent, // could be namespace - function Foo as fooFunctionAgain, - const MY_CONSTANT, - const OTHER_CONSTANT -}; +use Uses\Bar; +use Uses\LOREM; +use Uses\Nonexistent; +use function Uses\Foo; +use function Uses\baz; +use function Uses\functionFoo as fooFunctionAgain; +use const Uses\MY_CONSTANT; +use const Uses\OTHER_CONSTANT; use const Uses\{ - MY_CONSTANT as MY_CONSTANT_AGAIN + MY_CONSTANT as MY_CONSTANT_AGAIN }; diff --git a/tests/PHPStan/Rules/Namespaces/data/uses-defined.php b/tests/PHPStan/Rules/Namespaces/data/uses-defined.php index 141cf76a9d..ec56667b72 100644 --- a/tests/PHPStan/Rules/Namespaces/data/uses-defined.php +++ b/tests/PHPStan/Rules/Namespaces/data/uses-defined.php @@ -2,18 +2,16 @@ namespace Uses; -define ('Uses\MY_CONSTANT', 1); - -function foo() { +define('Uses\MY_CONSTANT', 1); +function foo() +{ } class Bar { - } interface Lorem { - } diff --git a/tests/PHPStan/Rules/Namespaces/data/uses.php b/tests/PHPStan/Rules/Namespaces/data/uses.php index 192a46e6af..0480524527 100644 --- a/tests/PHPStan/Rules/Namespaces/data/uses.php +++ b/tests/PHPStan/Rules/Namespaces/data/uses.php @@ -4,8 +4,10 @@ use Uses\Bar; use Uses\Nonexistent; // could be namespace -use function Uses\foo as fooFunction, Uses\bar; -use const Uses\MY_CONSTANT, Uses\OTHER_CONSTANT; -use function Uses\Foo; +use Uses\bar; +use Uses\OTHER_CONSTANT; use Uses\LOREM; use DATETIME; +use function Uses\foo as fooFunction; +use function Uses\Foo; +use const Uses\MY_CONSTANT; diff --git a/tests/PHPStan/Rules/NodeConnectingRule.php b/tests/PHPStan/Rules/NodeConnectingRule.php index 9629e48986..6b63816c13 100644 --- a/tests/PHPStan/Rules/NodeConnectingRule.php +++ b/tests/PHPStan/Rules/NodeConnectingRule.php @@ -1,4 +1,6 @@ -getAttribute('parent')), - get_class($node->getAttribute('previous')), - get_class($node->getAttribute('next')) - ), - ]; - } - + public function processNode(Node $node, Scope $scope): array + { + return [ + sprintf( + 'Parent: %s, previous: %s, next: %s', + get_class($node->getAttribute('parent')), + get_class($node->getAttribute('previous')), + get_class($node->getAttribute('next')) + ), + ]; + } } diff --git a/tests/PHPStan/Rules/NodeConnectingRuleTest.php b/tests/PHPStan/Rules/NodeConnectingRuleTest.php index 410b58b36a..fb881d9f12 100644 --- a/tests/PHPStan/Rules/NodeConnectingRuleTest.php +++ b/tests/PHPStan/Rules/NodeConnectingRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/node-connecting.php'], [ - [ - 'Parent: PhpParser\Node\Stmt\If_, previous: PhpParser\Node\Stmt\Switch_, next: PhpParser\Node\Stmt\Foreach_', - 11, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/node-connecting.php'], [ + [ + 'Parent: PhpParser\Node\Stmt\If_, previous: PhpParser\Node\Stmt\Switch_, next: PhpParser\Node\Stmt\Foreach_', + 11, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Operators/InvalidAssignVarRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidAssignVarRuleTest.php index 3bda92c21b..45204684a8 100644 --- a/tests/PHPStan/Rules/Operators/InvalidAssignVarRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidAssignVarRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/invalid-assign-var.php'], [ - [ - 'Nullsafe operator cannot be on left side of assignment.', - 12, - ], - [ - 'Nullsafe operator cannot be on left side of assignment.', - 13, - ], - [ - 'Nullsafe operator cannot be on left side of assignment.', - 14, - ], - [ - 'Nullsafe operator cannot be on left side of assignment.', - 16, - ], - [ - 'Nullsafe operator cannot be on left side of assignment.', - 17, - ], - [ - 'Expression on left side of assignment is not assignable.', - 31, - ], - [ - 'Expression on left side of assignment is not assignable.', - 33, - ], - [ - 'Nullsafe operator cannot be on right side of assignment by reference.', - 39, - ], - [ - 'Nullsafe operator cannot be on right side of assignment by reference.', - 40, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/invalid-assign-var.php'], [ + [ + 'Nullsafe operator cannot be on left side of assignment.', + 12, + ], + [ + 'Nullsafe operator cannot be on left side of assignment.', + 13, + ], + [ + 'Nullsafe operator cannot be on left side of assignment.', + 14, + ], + [ + 'Nullsafe operator cannot be on left side of assignment.', + 16, + ], + [ + 'Nullsafe operator cannot be on left side of assignment.', + 17, + ], + [ + 'Expression on left side of assignment is not assignable.', + 31, + ], + [ + 'Expression on left side of assignment is not assignable.', + 33, + ], + [ + 'Nullsafe operator cannot be on right side of assignment by reference.', + 39, + ], + [ + 'Nullsafe operator cannot be on right side of assignment by reference.', + 40, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index e72e1f3364..7fb252645c 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidBinaryOperationRule( - new \PhpParser\PrettyPrinter\Standard(), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-binary.php'], [ - [ - 'Binary operation "-" between array and array results in an error.', - 12, - ], - [ - 'Binary operation "/" between 5 and 0 results in an error.', - 15, - ], - [ - 'Binary operation "%" between 5 and 0 results in an error.', - 16, - ], - [ - 'Binary operation "/" between int and 0.0 results in an error.', - 17, - ], - [ - 'Binary operation "+" between 1 and string results in an error.', - 20, - ], - [ - 'Binary operation "+" between 1 and \'blabla\' results in an error.', - 21, - ], - [ - 'Binary operation "+=" between array and \'foo\' results in an error.', - 28, - ], - [ - 'Binary operation "-=" between array and array results in an error.', - 34, - ], - [ - 'Binary operation "<<" between string and string results in an error.', - 47, - ], - [ - 'Binary operation ">>" between string and string results in an error.', - 48, - ], - [ - 'Binary operation ">>=" between string and string results in an error.', - 49, - ], - [ - 'Binary operation "<<=" between string and string results in an error.', - 59, - ], - [ - 'Binary operation "&" between string and 5 results in an error.', - 69, - ], - [ - 'Binary operation "|" between string and 5 results in an error.', - 73, - ], - [ - 'Binary operation "^" between string and 5 results in an error.', - 77, - ], - [ - 'Binary operation "." between string and stdClass results in an error.', - 87, - ], - [ - 'Binary operation ".=" between string and stdClass results in an error.', - 91, - ], - [ - 'Binary operation "/" between 5 and 0|1 results in an error.', - 122, - ], - [ - 'Binary operation "." between array and \'xyz\' results in an error.', - 127, - ], - [ - 'Binary operation "." between array|string and \'xyz\' results in an error.', - 134, - ], - [ - 'Binary operation "+" between (array|string) and 1 results in an error.', - 136, - ], - [ - 'Binary operation "+" between stdClass and int results in an error.', - 157, - ], - ]); - } - - public function testBug2964(): void - { - $this->analyse([__DIR__ . '/data/bug2964.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-binary.php'], [ + [ + 'Binary operation "-" between array and array results in an error.', + 12, + ], + [ + 'Binary operation "/" between 5 and 0 results in an error.', + 15, + ], + [ + 'Binary operation "%" between 5 and 0 results in an error.', + 16, + ], + [ + 'Binary operation "/" between int and 0.0 results in an error.', + 17, + ], + [ + 'Binary operation "+" between 1 and string results in an error.', + 20, + ], + [ + 'Binary operation "+" between 1 and \'blabla\' results in an error.', + 21, + ], + [ + 'Binary operation "+=" between array and \'foo\' results in an error.', + 28, + ], + [ + 'Binary operation "-=" between array and array results in an error.', + 34, + ], + [ + 'Binary operation "<<" between string and string results in an error.', + 47, + ], + [ + 'Binary operation ">>" between string and string results in an error.', + 48, + ], + [ + 'Binary operation ">>=" between string and string results in an error.', + 49, + ], + [ + 'Binary operation "<<=" between string and string results in an error.', + 59, + ], + [ + 'Binary operation "&" between string and 5 results in an error.', + 69, + ], + [ + 'Binary operation "|" between string and 5 results in an error.', + 73, + ], + [ + 'Binary operation "^" between string and 5 results in an error.', + 77, + ], + [ + 'Binary operation "." between string and stdClass results in an error.', + 87, + ], + [ + 'Binary operation ".=" between string and stdClass results in an error.', + 91, + ], + [ + 'Binary operation "/" between 5 and 0|1 results in an error.', + 122, + ], + [ + 'Binary operation "." between array and \'xyz\' results in an error.', + 127, + ], + [ + 'Binary operation "." between array|string and \'xyz\' results in an error.', + 134, + ], + [ + 'Binary operation "+" between (array|string) and 1 results in an error.', + 136, + ], + [ + 'Binary operation "+" between stdClass and int results in an error.', + 157, + ], + ]); + } - public function testBug3515(): void - { - $this->analyse([__DIR__ . '/data/bug-3515.php'], []); - } + public function testBug2964(): void + { + $this->analyse([__DIR__ . '/data/bug2964.php'], []); + } + public function testBug3515(): void + { + $this->analyse([__DIR__ . '/data/bug-3515.php'], []); + } } diff --git a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php index ef493ca348..1898186de9 100644 --- a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidComparisonOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-comparison.php'], [ - [ - 'Comparison operation "==" between stdClass and int results in an error.', - 15, - ], - [ - 'Comparison operation "!=" between stdClass and int results in an error.', - 16, - ], - [ - 'Comparison operation "<" between stdClass and int results in an error.', - 17, - ], - [ - 'Comparison operation ">" between stdClass and int results in an error.', - 18, - ], - [ - 'Comparison operation "<=" between stdClass and int results in an error.', - 19, - ], - [ - 'Comparison operation ">=" between stdClass and int results in an error.', - 20, - ], - [ - 'Comparison operation "<=>" between stdClass and int results in an error.', - 21, - ], - [ - 'Comparison operation "==" between stdClass and float|null results in an error.', - 25, - ], - [ - 'Comparison operation "<" between stdClass and float|null results in an error.', - 26, - ], - [ - 'Comparison operation "==" between stdClass and float|int|null results in an error.', - 43, - ], - [ - 'Comparison operation "<" between stdClass and float|int|null results in an error.', - 44, - ], - [ - 'Comparison operation "==" between stdClass and 1 results in an error.', - 48, - ], - [ - 'Comparison operation "<" between stdClass and 1 results in an error.', - 49, - ], - [ - 'Comparison operation "==" between stdClass and int|stdClass results in an error.', - 56, - ], - [ - 'Comparison operation "<" between stdClass and int|stdClass results in an error.', - 57, - ], - [ - 'Comparison operation "==" between array and int results in an error.', - 61, - ], - [ - 'Comparison operation "!=" between array and int results in an error.', - 62, - ], - [ - 'Comparison operation "<" between array and int results in an error.', - 63, - ], - [ - 'Comparison operation ">" between array and int results in an error.', - 64, - ], - [ - 'Comparison operation "<=" between array and int results in an error.', - 65, - ], - [ - 'Comparison operation ">=" between array and int results in an error.', - 66, - ], - [ - 'Comparison operation "<=>" between array and int results in an error.', - 67, - ], - [ - 'Comparison operation "==" between array and float|null results in an error.', - 71, - ], - [ - 'Comparison operation "<" between array and float|null results in an error.', - 72, - ], - [ - 'Comparison operation "==" between array and float|int|null results in an error.', - 84, - ], - [ - 'Comparison operation "<" between array and float|int|null results in an error.', - 85, - ], - [ - 'Comparison operation "==" between array and 1 results in an error.', - 89, - ], - [ - 'Comparison operation "<" between array and 1 results in an error.', - 90, - ], - [ - 'Comparison operation "==" between array and array|int results in an error.', - 97, - ], - [ - 'Comparison operation "<" between array and array|int results in an error.', - 98, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-comparison.php'], [ + [ + 'Comparison operation "==" between stdClass and int results in an error.', + 15, + ], + [ + 'Comparison operation "!=" between stdClass and int results in an error.', + 16, + ], + [ + 'Comparison operation "<" between stdClass and int results in an error.', + 17, + ], + [ + 'Comparison operation ">" between stdClass and int results in an error.', + 18, + ], + [ + 'Comparison operation "<=" between stdClass and int results in an error.', + 19, + ], + [ + 'Comparison operation ">=" between stdClass and int results in an error.', + 20, + ], + [ + 'Comparison operation "<=>" between stdClass and int results in an error.', + 21, + ], + [ + 'Comparison operation "==" between stdClass and float|null results in an error.', + 25, + ], + [ + 'Comparison operation "<" between stdClass and float|null results in an error.', + 26, + ], + [ + 'Comparison operation "==" between stdClass and float|int|null results in an error.', + 43, + ], + [ + 'Comparison operation "<" between stdClass and float|int|null results in an error.', + 44, + ], + [ + 'Comparison operation "==" between stdClass and 1 results in an error.', + 48, + ], + [ + 'Comparison operation "<" between stdClass and 1 results in an error.', + 49, + ], + [ + 'Comparison operation "==" between stdClass and int|stdClass results in an error.', + 56, + ], + [ + 'Comparison operation "<" between stdClass and int|stdClass results in an error.', + 57, + ], + [ + 'Comparison operation "==" between array and int results in an error.', + 61, + ], + [ + 'Comparison operation "!=" between array and int results in an error.', + 62, + ], + [ + 'Comparison operation "<" between array and int results in an error.', + 63, + ], + [ + 'Comparison operation ">" between array and int results in an error.', + 64, + ], + [ + 'Comparison operation "<=" between array and int results in an error.', + 65, + ], + [ + 'Comparison operation ">=" between array and int results in an error.', + 66, + ], + [ + 'Comparison operation "<=>" between array and int results in an error.', + 67, + ], + [ + 'Comparison operation "==" between array and float|null results in an error.', + 71, + ], + [ + 'Comparison operation "<" between array and float|null results in an error.', + 72, + ], + [ + 'Comparison operation "==" between array and float|int|null results in an error.', + 84, + ], + [ + 'Comparison operation "<" between array and float|int|null results in an error.', + 85, + ], + [ + 'Comparison operation "==" between array and 1 results in an error.', + 89, + ], + [ + 'Comparison operation "<" between array and 1 results in an error.', + 90, + ], + [ + 'Comparison operation "==" between array and array|int results in an error.', + 97, + ], + [ + 'Comparison operation "<" between array and array|int results in an error.', + 98, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php index a5564251cd..91f29dc4a4 100644 --- a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/invalid-inc-dec.php'], [ - [ - 'Cannot use ++ on a non-variable.', - 11, - ], - [ - 'Cannot use -- on a non-variable.', - 12, - ], - [ - 'Cannot use ++ on stdClass.', - 17, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-inc-dec.php'], [ + [ + 'Cannot use ++ on a non-variable.', + 11, + ], + [ + 'Cannot use -- on a non-variable.', + 12, + ], + [ + 'Cannot use ++ on stdClass.', + 17, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index 77bb4fe9e3..296e34b418 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/invalid-unary.php'], [ - [ - 'Unary operation "+" on string results in an error.', - 11, - ], - [ - 'Unary operation "-" on string results in an error.', - 12, - ], - [ - 'Unary operation "+" on \'bla\' results in an error.', - 19, - ], - [ - 'Unary operation "-" on \'bla\' results in an error.', - 20, - ], - [ - 'Unary operation "~" on array() results in an error.', - 24, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-unary.php'], [ + [ + 'Unary operation "+" on string results in an error.', + 11, + ], + [ + 'Unary operation "-" on string results in an error.', + 12, + ], + [ + 'Unary operation "+" on \'bla\' results in an error.', + 19, + ], + [ + 'Unary operation "-" on \'bla\' results in an error.', + 20, + ], + [ + 'Unary operation "~" on array() results in an error.', + 24, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Operators/data/bug-3515.php b/tests/PHPStan/Rules/Operators/data/bug-3515.php index 277d601a4c..3695575f84 100644 --- a/tests/PHPStan/Rules/Operators/data/bug-3515.php +++ b/tests/PHPStan/Rules/Operators/data/bug-3515.php @@ -6,8 +6,8 @@ $bar = $foo + 1; function (): void { - $foo = 'foo'; + $foo = 'foo'; - /** @var int $foo */ - $bar = $foo + 1; + /** @var int $foo */ + $bar = $foo + 1; }; diff --git a/tests/PHPStan/Rules/Operators/data/bug2964.php b/tests/PHPStan/Rules/Operators/data/bug2964.php index 6b15c5bdc6..78f1526cc6 100644 --- a/tests/PHPStan/Rules/Operators/data/bug2964.php +++ b/tests/PHPStan/Rules/Operators/data/bug2964.php @@ -4,12 +4,10 @@ class Foo { - - public function doFoo(string $value) - { - if (is_numeric($value)) { - return $value * 1024; - } - } - + public function doFoo(string $value) + { + if (is_numeric($value)) { + return $value * 1024; + } + } } diff --git a/tests/PHPStan/Rules/Operators/data/invalid-assign-var.php b/tests/PHPStan/Rules/Operators/data/invalid-assign-var.php index d3b8cc435c..12af82760e 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-assign-var.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-assign-var.php @@ -4,42 +4,37 @@ class Foo { - - public function doFoo( - ?\stdClass $a - ): void - { - $a?->foo = 'bar'; - $a?->foo->bar = 'bar'; - $a?->foo->bar['foo'] = 'bar'; - - [$a?->foo->bar] = 'test'; - [$a?->foo->bar => $b, $f?->foo->bar => $c] = 'test'; - - $c = 'foo'; - } - - public function doBar( - \stdClass $s - ) - { - $s->foo = 'bar'; - $d = 'foo'; - $s['test'] = 'baz'; - \stdClass::$foo = 'bar'; - - $s->foo() = 'test'; - - [$s->foo()] = ['test']; - [$s] = ['test']; - } - - public function doBaz(?\stdClass $a, \stdClass $b) - { - $x = &$a?->bar->foo; - $y = &$a?->bar; - $z = $b?->bar; - } - - + public function doFoo( + ?\stdClass $a + ): void { + $a?->foo = 'bar'; + $a?->foo->bar = 'bar'; + $a?->foo->bar['foo'] = 'bar'; + + [$a?->foo->bar] = 'test'; + [$a?->foo->bar => $b, $f?->foo->bar => $c] = 'test'; + + $c = 'foo'; + } + + public function doBar( + \stdClass $s + ) { + $s->foo = 'bar'; + $d = 'foo'; + $s['test'] = 'baz'; + \stdClass::$foo = 'bar'; + + $s->foo() = 'test'; + + [$s->foo()] = ['test']; + [$s] = ['test']; + } + + public function doBaz(?\stdClass $a, \stdClass $b) + { + $x = &$a?->bar->foo; + $y = &$a?->bar; + $z = $b?->bar; + } } diff --git a/tests/PHPStan/Rules/Operators/data/invalid-binary.php b/tests/PHPStan/Rules/Operators/data/invalid-binary.php index 9ea75a0589..ebab8de90e 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-binary.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-binary.php @@ -1,155 +1,154 @@ > $int2; - $int1 >>= $int2; + $int1 << $int2; + $int1 >> $int2; + $int1 >>= $int2; - $str1 << $str2; - $str1 >> $str2; - $str1 >>= $str2; + $str1 << $str2; + $str1 >> $str2; + $str1 >>= $str2; }; function ( - int $int1, - int $int2, - string $str1, - string $str2 + int $int1, + int $int2, + string $str1, + string $str2 ) { - $int1 <<= $int2; - $str1 <<= $str2; + $int1 <<= $int2; + $str1 <<= $str2; }; function ( - int $int, - string $string + int $int, + string $string ) { - $int & 5; - $int & "5"; - $string & "x"; - $string & 5; - $int | 5; - $int | "5"; - $string | "x"; - $string | 5; - $int ^ 5; - $int ^ "5"; - $string ^ "x"; - $string ^ 5; + $int & 5; + $int & "5"; + $string & "x"; + $string & 5; + $int | 5; + $int | "5"; + $string | "x"; + $string | 5; + $int ^ 5; + $int ^ "5"; + $string ^ "x"; + $string ^ 5; }; function ( - string $string1, - string $string2, - stdClass $std, - \Test\ClassWithToString $classWithToString + string $string1, + string $string2, + stdClass $std, + \Test\ClassWithToString $classWithToString ) { - $string1 . $string2; - $string1 . $std; - $string1 . $classWithToString; + $string1 . $string2; + $string1 . $std; + $string1 . $classWithToString; - $string1 .= $string2; - $string1 .= $std; - $string2 .= $classWithToString; + $string1 .= $string2; + $string1 .= $std; + $string2 .= $classWithToString; }; -function () -{ - $result = [ - 'id' => 'blabla', // string - 'allowedRoomCounter' => 0, - 'roomCounter' => 0, - ]; +function () { + $result = [ + 'id' => 'blabla', // string + 'allowedRoomCounter' => 0, + 'roomCounter' => 0, + ]; - foreach ([1, 2] as $x) { - $result['allowedRoomCounter'] += $x; - } + foreach ([1, 2] as $x) { + $result['allowedRoomCounter'] += $x; + } }; function () { - $o = new stdClass; - $o->user ?? ''; - $o->user->name ?? ''; + $o = new stdClass(); + $o->user ?? ''; + $o->user->name ?? ''; - nonexistentFunction() ?? ''; + nonexistentFunction() ?? ''; }; function () { - $possibleZero = 0; - if (doFoo()) { - $possibleZero = 1; - } + $possibleZero = 0; + if (doFoo()) { + $possibleZero = 1; + } - 5 / $possibleZero; + 5 / $possibleZero; }; function ($a, array $b, string $c) { - echo str_replace('abc', 'def', $a) . 'xyz'; - echo str_replace('abc', 'def', $b) . 'xyz'; - echo str_replace('abc', 'def', $c) . 'xyz'; + echo str_replace('abc', 'def', $a) . 'xyz'; + echo str_replace('abc', 'def', $b) . 'xyz'; + echo str_replace('abc', 'def', $c) . 'xyz'; - $strOrArray = 'str'; - if (rand(0, 1) === 0) { - $strOrArray = []; - } - echo str_replace('abc', 'def', $strOrArray) . 'xyz'; + $strOrArray = 'str'; + if (rand(0, 1) === 0) { + $strOrArray = []; + } + echo str_replace('abc', 'def', $strOrArray) . 'xyz'; - echo str_replace('abc', 'def', $a) + 1; + echo str_replace('abc', 'def', $a) + 1; }; function (array $a) { - $b = []; - if (rand(0, 1)) { - $b['foo'] = 'bar'; - } - $b += $a; + $b = []; + if (rand(0, 1)) { + $b['foo'] = 'bar'; + } + $b += $a; }; function (array $a) { - $b = []; - if (rand(0, 1)) { - $b['foo'] = 'bar'; - } - $a + $b; + $b = []; + if (rand(0, 1)) { + $b['foo'] = 'bar'; + } + $a + $b; }; function (stdClass $ob, int $n) { @@ -158,18 +157,18 @@ function (stdClass $ob, int $n) { }; function (array $args) { - if (isset($args['class'])) { - } + if (isset($args['class'])) { + } - [] + $args; + [] + $args; }; function (array $args) { - if (isset($args['class'])) { - [] + $args; - } + if (isset($args['class'])) { + [] + $args; + } }; function (array $args) { - isset($args['y']) ? $args + [] : $args; + isset($args['y']) ? $args + [] : $args; }; diff --git a/tests/PHPStan/Rules/Operators/data/invalid-comparison.php b/tests/PHPStan/Rules/Operators/data/invalid-comparison.php index 5c2ac5b346..4060f4f4c8 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-comparison.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-comparison.php @@ -1,7 +1,6 @@ $n; - $ob <= $n; - $ob >= $n; - $ob <=> $n; + $ob == $n; + $ob != $n; + $ob < $n; + $ob > $n; + $ob <= $n; + $ob >= $n; + $ob <=> $n; }; function (array $ob, ?float $n) { - $ob == $n; - $ob < $n; + $ob == $n; + $ob < $n; }; function (array $ob, string $str) { - $ob == $str; - $ob < $str; + $ob == $str; + $ob < $str; }; function (array $ob, callable $fn) { - /** @var int|float|null $n */ - $n = $fn(); + /** @var int|float|null $n */ + $n = $fn(); - $ob == $n; - $ob < $n; + $ob == $n; + $ob < $n; }; function (array $ob) { - $ob == 1; - $ob < 1; + $ob == 1; + $ob < 1; }; function (array $ob, callable $fn) { - /** @var int|array $a */ - $a = $fn(); + /** @var int|array $a */ + $a = $fn(); - $ob == $a; - $ob < $a; + $ob == $a; + $ob < $a; }; /** @@ -103,6 +102,6 @@ function (array $ob, callable $fn) { * @param string[] $b */ function (array $a, array $b) { - $a == $b; - $a < $b; + $a == $b; + $a < $b; }; diff --git a/tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php b/tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php index 9e551e875f..67251dd3fa 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php @@ -3,16 +3,16 @@ namespace InvalidIncDec; function ($a, int $i, ?float $j, string $str, \stdClass $std) { - $a++; + $a++; - $b = [1]; - $b[0]++; + $b = [1]; + $b[0]++; - date('j. n. Y')++; - date('j. n. Y')--; + date('j. n. Y')++; + date('j. n. Y')--; - $i++; - $j++; - $str++; - $std++; + $i++; + $j++; + $str++; + $std++; }; diff --git a/tests/PHPStan/Rules/Operators/data/invalid-unary.php b/tests/PHPStan/Rules/Operators/data/invalid-unary.php index dea25521ce..87a2bbcd2b 100644 --- a/tests/PHPStan/Rules/Operators/data/invalid-unary.php +++ b/tests/PHPStan/Rules/Operators/data/invalid-unary.php @@ -1,26 +1,26 @@ getByType(FileTypeMapper::class), + new GenericObjectTypeCheck() + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new IncompatiblePhpDocTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new GenericObjectTypeCheck() - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/incompatible-types.php'], [ - [ - 'PHPDoc tag @param references unknown parameter: $unknown', - 12, - ], - [ - 'PHPDoc tag @param for parameter $b with type array is incompatible with native type string.', - 12, - ], - [ - 'PHPDoc tag @param for parameter $d with type float|int is not subtype of native type int.', - 12, - ], - [ - 'PHPDoc tag @return with type string is incompatible with native type int.', - 66, - ], - [ - 'PHPDoc tag @return with type int|string is not subtype of native type int.', - 75, - ], - [ - 'PHPDoc tag @param for parameter $strings with type array is incompatible with native type string.', - 91, - ], - [ - 'PHPDoc tag @param for parameter $numbers with type string is incompatible with native type int.', - 99, - ], - [ - 'PHPDoc tag @param for parameter $arr contains unresolvable type.', - 117, - ], - [ - 'PHPDoc tag @param references unknown parameter: $arrX', - 117, - ], - [ - 'PHPDoc tag @param for parameter $foo contains unresolvable type.', - 126, - ], - [ - 'PHPDoc tag @return contains unresolvable type.', - 126, - ], - [ - 'PHPDoc tag @param for parameter $a with type T is not subtype of native type int.', - 154, - ], - [ - 'PHPDoc tag @param for parameter $b with type U of DateTimeInterface is not subtype of native type DateTime.', - 154, - ], - [ - 'PHPDoc tag @return with type DateTimeInterface is not subtype of native type DateTime.', - 154, - ], - [ - 'PHPDoc tag @param for parameter $foo contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', - 185, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $baz does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 185, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $lorem specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', - 185, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $ipsum is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 185, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $dolor is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 185, - ], - [ - 'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', - 185, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 201, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', - 209, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 217, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 225, - ], - [ - 'Type mixed in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $t is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 242, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $v is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 242, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $x is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 242, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 250, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 266, - ], - [ - 'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', - 274, - ], - ]); - } - - public function testBug4643(): void - { - $this->analyse([__DIR__ . '/data/bug-4643.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/incompatible-types.php'], [ + [ + 'PHPDoc tag @param references unknown parameter: $unknown', + 12, + ], + [ + 'PHPDoc tag @param for parameter $b with type array is incompatible with native type string.', + 12, + ], + [ + 'PHPDoc tag @param for parameter $d with type float|int is not subtype of native type int.', + 12, + ], + [ + 'PHPDoc tag @return with type string is incompatible with native type int.', + 66, + ], + [ + 'PHPDoc tag @return with type int|string is not subtype of native type int.', + 75, + ], + [ + 'PHPDoc tag @param for parameter $strings with type array is incompatible with native type string.', + 91, + ], + [ + 'PHPDoc tag @param for parameter $numbers with type string is incompatible with native type int.', + 99, + ], + [ + 'PHPDoc tag @param for parameter $arr contains unresolvable type.', + 117, + ], + [ + 'PHPDoc tag @param references unknown parameter: $arrX', + 117, + ], + [ + 'PHPDoc tag @param for parameter $foo contains unresolvable type.', + 126, + ], + [ + 'PHPDoc tag @return contains unresolvable type.', + 126, + ], + [ + 'PHPDoc tag @param for parameter $a with type T is not subtype of native type int.', + 154, + ], + [ + 'PHPDoc tag @param for parameter $b with type U of DateTimeInterface is not subtype of native type DateTime.', + 154, + ], + [ + 'PHPDoc tag @return with type DateTimeInterface is not subtype of native type DateTime.', + 154, + ], + [ + 'PHPDoc tag @param for parameter $foo contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', + 185, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $baz does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 185, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $lorem specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', + 185, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $ipsum is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 185, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $dolor is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 185, + ], + [ + 'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', + 185, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 201, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', + 209, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 217, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 225, + ], + [ + 'Type mixed in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $t is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 242, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $v is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 242, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @param for parameter $x is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 242, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 250, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @return does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 266, + ], + [ + 'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', + 274, + ], + ]); + } + public function testBug4643(): void + { + $this->analyse([__DIR__ . '/data/bug-4643.php'], []); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php index 9341cfb205..c966887d9f 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/incompatible-property-phpdoc.php'], [ - [ - 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$bar contains unresolvable type.', - 12, - ], - [ - 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$classStringInt contains unresolvable type.', - 18, - ], - [ - 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$fooGeneric contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', - 24, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$notEnoughTypesGenericfoo does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 30, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$tooManyTypesGenericfoo specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', - 33, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$invalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 36, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$anotherInvalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 39, - ], - [ - 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$unknownClassConstant contains unresolvable type.', - 42, - ], - [ - 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$unknownClassConstant2 contains unresolvable type.', - 45, - ], - ]); - } - - public function testNativeTypes(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/incompatible-property-native-types.php'], [ - [ - 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$selfTwo with type object is not subtype of native type IncompatiblePhpDocPropertyNativeType\Foo.', - 12, - ], - [ - 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$foo with type IncompatiblePhpDocPropertyNativeType\Bar is incompatible with native type IncompatiblePhpDocPropertyNativeType\Foo.', - 15, - ], - [ - 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$stringOrInt with type int|string is not subtype of native type string.', - 21, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/incompatible-property-phpdoc.php'], [ + [ + 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$bar contains unresolvable type.', + 12, + ], + [ + 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$classStringInt contains unresolvable type.', + 18, + ], + [ + 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$fooGeneric contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', + 24, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$notEnoughTypesGenericfoo does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 30, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$tooManyTypesGenericfoo specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', + 33, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$invalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 36, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$anotherInvalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 39, + ], + [ + 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$unknownClassConstant contains unresolvable type.', + 42, + ], + [ + 'PHPDoc tag @var for property InvalidPhpDoc\FooWithProperty::$unknownClassConstant2 contains unresolvable type.', + 45, + ], + ]); + } - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testNativeTypes(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/incompatible-property-native-types.php'], [ + [ + 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$selfTwo with type object is not subtype of native type IncompatiblePhpDocPropertyNativeType\Foo.', + 12, + ], + [ + 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$foo with type IncompatiblePhpDocPropertyNativeType\Bar is incompatible with native type IncompatiblePhpDocPropertyNativeType\Foo.', + 15, + ], + [ + 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$stringOrInt with type int|string is not subtype of native type string.', + 21, + ], + ]); + } - $this->analyse([__DIR__ . '/data/incompatible-property-promoted.php'], [ - [ - 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$bar contains unresolvable type.', - 16, - ], - [ - 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$classStringInt contains unresolvable type.', - 22, - ], - [ - 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$fooGeneric contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', - 28, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$notEnoughTypesGenericfoo does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 34, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$tooManyTypesGenericfoo specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', - 37, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$invalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 40, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$anotherInvalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 43, - ], - [ - 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$unknownClassConstant contains unresolvable type.', - 46, - ], - [ - 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$unknownClassConstant2 contains unresolvable type.', - 49, - ], - ]); - } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function testBug4227(): void - { - if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + $this->analyse([__DIR__ . '/data/incompatible-property-promoted.php'], [ + [ + 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$bar contains unresolvable type.', + 16, + ], + [ + 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$classStringInt contains unresolvable type.', + 22, + ], + [ + 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$fooGeneric contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', + 28, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$notEnoughTypesGenericfoo does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 34, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$tooManyTypesGenericfoo specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', + 37, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$invalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 40, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$anotherInvalidTypeGenericfoo is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 43, + ], + [ + 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$unknownClassConstant contains unresolvable type.', + 46, + ], + [ + 'PHPDoc type for property InvalidPhpDocPromotedProperties\FooWithProperty::$unknownClassConstant2 contains unresolvable type.', + 49, + ], + ]); + } - $this->analyse([__DIR__ . '/data/bug-4227.php'], []); - } + public function testBug4227(): void + { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/bug-4227.php'], []); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php index 0f3ee70c93..2cf4af6b31 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php @@ -1,4 +1,6 @@ -getByType(Lexer::class), + self::getContainer()->getByType(PhpDocParser::class) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidPHPStanDocTagRule( - self::getContainer()->getByType(Lexer::class), - self::getContainer()->getByType(PhpDocParser::class) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], [ - [ - 'Unknown PHPDoc tag: @phpstan-extens', - 7, - ], - [ - 'Unknown PHPDoc tag: @phpstan-pararm', - 14, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], [ + [ + 'Unknown PHPDoc tag: @phpstan-extens', + 7, + ], + [ + 'Unknown PHPDoc tag: @phpstan-pararm', + 14, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index 11c4ac4650..6b9daa1385 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -1,4 +1,6 @@ -getByType(Lexer::class), + self::getContainer()->getByType(PhpDocParser::class) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidPhpDocTagValueRule( - self::getContainer()->getByType(Lexer::class), - self::getContainer()->getByType(PhpDocParser::class) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], [ - [ - 'PHPDoc tag @param has invalid value (): Unexpected token "\n * ", expected type at offset 13', - 25, - ], - [ - 'PHPDoc tag @param has invalid value ($invalid): Unexpected token "$invalid", expected type at offset 24', - 25, - ], - [ - 'PHPDoc tag @param has invalid value ($invalid Foo): Unexpected token "$invalid", expected type at offset 43', - 25, - ], - [ - 'PHPDoc tag @param has invalid value (A & B | C $paramNameA): Unexpected token "|", expected variable at offset 72', - 25, - ], - [ - 'PHPDoc tag @param has invalid value ((A & B $paramNameB): Unexpected token "$paramNameB", expected \')\' at offset 105', - 25, - ], - [ - 'PHPDoc tag @param has invalid value (~A & B $paramNameC): Unexpected token "~A", expected type at offset 127', - 25, - ], - [ - 'PHPDoc tag @var has invalid value (): Unexpected token "\n * ", expected type at offset 156', - 25, - ], - [ - 'PHPDoc tag @var has invalid value ($invalid): Unexpected token "$invalid", expected type at offset 165', - 25, - ], - [ - 'PHPDoc tag @var has invalid value ($invalid Foo): Unexpected token "$invalid", expected type at offset 182', - 25, - ], - [ - 'PHPDoc tag @return has invalid value (): Unexpected token "\n * ", expected type at offset 208', - 25, - ], - [ - 'PHPDoc tag @return has invalid value ([int, string]): Unexpected token "[", expected type at offset 220', - 25, - ], - [ - 'PHPDoc tag @return has invalid value (A & B | C): Unexpected token "|", expected TOKEN_OTHER at offset 251', - 25, - ], - [ - 'PHPDoc tag @var has invalid value (\\\Foo|\Bar $test): Unexpected token "\\\\\\\Foo|\\\Bar", expected type at offset 9', - 29, - ], - /*[ - 'PHPDoc tag @var has invalid value ...', - 59, - ],*/ - [ - 'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18', - 62, - ], - [ - 'PHPDoc tag @throws has invalid value ((\Exception): Unexpected token "*/", expected \')\' at offset 24', - 72, - ], - ]); - } - - public function testBug4731(): void - { - $this->analyse([__DIR__ . '/data/bug-4731.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], [ + [ + 'PHPDoc tag @param has invalid value (): Unexpected token "\n * ", expected type at offset 13', + 25, + ], + [ + 'PHPDoc tag @param has invalid value ($invalid): Unexpected token "$invalid", expected type at offset 24', + 25, + ], + [ + 'PHPDoc tag @param has invalid value ($invalid Foo): Unexpected token "$invalid", expected type at offset 43', + 25, + ], + [ + 'PHPDoc tag @param has invalid value (A & B | C $paramNameA): Unexpected token "|", expected variable at offset 72', + 25, + ], + [ + 'PHPDoc tag @param has invalid value ((A & B $paramNameB): Unexpected token "$paramNameB", expected \')\' at offset 105', + 25, + ], + [ + 'PHPDoc tag @param has invalid value (~A & B $paramNameC): Unexpected token "~A", expected type at offset 127', + 25, + ], + [ + 'PHPDoc tag @var has invalid value (): Unexpected token "\n * ", expected type at offset 156', + 25, + ], + [ + 'PHPDoc tag @var has invalid value ($invalid): Unexpected token "$invalid", expected type at offset 165', + 25, + ], + [ + 'PHPDoc tag @var has invalid value ($invalid Foo): Unexpected token "$invalid", expected type at offset 182', + 25, + ], + [ + 'PHPDoc tag @return has invalid value (): Unexpected token "\n * ", expected type at offset 208', + 25, + ], + [ + 'PHPDoc tag @return has invalid value ([int, string]): Unexpected token "[", expected type at offset 220', + 25, + ], + [ + 'PHPDoc tag @return has invalid value (A & B | C): Unexpected token "|", expected TOKEN_OTHER at offset 251', + 25, + ], + [ + 'PHPDoc tag @var has invalid value (\\\Foo|\Bar $test): Unexpected token "\\\\\\\Foo|\\\Bar", expected type at offset 9', + 29, + ], + /*[ + 'PHPDoc tag @var has invalid value ...', + 59, + ],*/ + [ + 'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18', + 62, + ], + [ + 'PHPDoc tag @throws has invalid value ((\Exception): Unexpected token "*/", expected \')\' at offset 24', + 72, + ], + ]); + } - public function testBug4731WithoutFirstTag(): void - { - $this->analyse([__DIR__ . '/data/bug-4731-no-first-tag.php'], []); - } + public function testBug4731(): void + { + $this->analyse([__DIR__ . '/data/bug-4731.php'], []); + } + public function testBug4731WithoutFirstTag(): void + { + $this->analyse([__DIR__ . '/data/bug-4731-no-first-tag.php'], []); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index c47a45a87e..d9b57213ad 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new InvalidPhpDocVarTagTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + $broker, + new ClassCaseSensitivityCheck($broker), + new GenericObjectTypeCheck(), + new MissingTypehintCheck($broker, true, true, true), + true, + true + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new InvalidPhpDocVarTagTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - $broker, - new ClassCaseSensitivityCheck($broker), - new GenericObjectTypeCheck(), - new MissingTypehintCheck($broker, true, true, true), - true, - true - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/invalid-var-tag-type.php'], [ - [ - 'PHPDoc tag @var for variable $test contains unresolvable type.', - 13, - ], - [ - 'PHPDoc tag @var contains unresolvable type.', - 16, - ], - [ - 'PHPDoc tag @var for variable $test contains unknown class InvalidVarTagType\aray.', - 20, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @var for variable $value contains unresolvable type.', - 22, - ], - [ - 'PHPDoc tag @var for variable $staticVar contains unresolvable type.', - 27, - ], - [ - 'Class InvalidVarTagType\Foo referenced with incorrect case: InvalidVarTagType\foo.', - 31, - ], - [ - 'PHPDoc tag @var for variable $test has invalid type InvalidVarTagType\FooTrait.', - 34, - ], - [ - 'PHPDoc tag @var for variable $test contains generic type InvalidPhpDoc\Foo but class InvalidPhpDoc\Foo is not generic.', - 40, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', - 46, - ], - [ - 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', - 49, - ], - [ - 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 52, - ], - [ - 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', - 55, - ], - [ - 'PHPDoc tag @var for variable $test has no value type specified in iterable type array.', - 58, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'PHPDoc tag @var for variable $test contains generic class InvalidPhpDocDefinitions\FooGeneric but does not specify its types: T, U', - 61, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'PHPDoc tag @var for variable $foo contains unknown class InvalidVarTagType\Blabla.', - 67, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } - - public function testBug4486(): void - { - $this->analyse([__DIR__ . '/data/bug-4486.php'], [ - [ - 'PHPDoc tag @var for variable $one contains unknown class Bug4486\ClassName1.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @var for variable $two contains unknown class Bug4486\ClassName2.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @var for variable $three contains unknown class Some\Namespaced\ClassName1.', - 15, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-var-tag-type.php'], [ + [ + 'PHPDoc tag @var for variable $test contains unresolvable type.', + 13, + ], + [ + 'PHPDoc tag @var contains unresolvable type.', + 16, + ], + [ + 'PHPDoc tag @var for variable $test contains unknown class InvalidVarTagType\aray.', + 20, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @var for variable $value contains unresolvable type.', + 22, + ], + [ + 'PHPDoc tag @var for variable $staticVar contains unresolvable type.', + 27, + ], + [ + 'Class InvalidVarTagType\Foo referenced with incorrect case: InvalidVarTagType\foo.', + 31, + ], + [ + 'PHPDoc tag @var for variable $test has invalid type InvalidVarTagType\FooTrait.', + 34, + ], + [ + 'PHPDoc tag @var for variable $test contains generic type InvalidPhpDoc\Foo but class InvalidPhpDoc\Foo is not generic.', + 40, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test does not specify all template types of class InvalidPhpDocDefinitions\FooGeneric: T, U', + 46, + ], + [ + 'Generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test specifies 3 template types, but class InvalidPhpDocDefinitions\FooGeneric supports only 2: T, U', + 49, + ], + [ + 'Type Throwable in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 52, + ], + [ + 'Type stdClass in generic type InvalidPhpDocDefinitions\FooGeneric in PHPDoc tag @var for variable $test is not subtype of template type U of Exception of class InvalidPhpDocDefinitions\FooGeneric.', + 55, + ], + [ + 'PHPDoc tag @var for variable $test has no value type specified in iterable type array.', + 58, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'PHPDoc tag @var for variable $test contains generic class InvalidPhpDocDefinitions\FooGeneric but does not specify its types: T, U', + 61, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'PHPDoc tag @var for variable $foo contains unknown class InvalidVarTagType\Blabla.', + 67, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - public function testBug4486Namespace(): void - { - $this->analyse([__DIR__ . '/data/bug-4486-ns.php'], [ - [ - 'PHPDoc tag @var for variable $one contains unknown class ClassName1.', - 6, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @var for variable $two contains unknown class Bug4486Namespace\ClassName1.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testBug4486(): void + { + $this->analyse([__DIR__ . '/data/bug-4486.php'], [ + [ + 'PHPDoc tag @var for variable $one contains unknown class Bug4486\ClassName1.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @var for variable $two contains unknown class Bug4486\ClassName2.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @var for variable $three contains unknown class Some\Namespaced\ClassName1.', + 15, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + public function testBug4486Namespace(): void + { + $this->analyse([__DIR__ . '/data/bug-4486-ns.php'], [ + [ + 'PHPDoc tag @var for variable $one contains unknown class ClassName1.', + 6, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @var for variable $two contains unknown class Bug4486Namespace\ClassName1.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php index 5d3b9b1e46..5767c300ef 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new InvalidThrowsPhpDocValueRule(self::getContainer()->getByType(FileTypeMapper::class)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/incompatible-throws.php'], [ - [ - 'PHPDoc tag @throws with type Undefined is not subtype of Throwable', - 54, - ], - [ - 'PHPDoc tag @throws with type bool is not subtype of Throwable', - 61, - ], - [ - 'PHPDoc tag @throws with type DateTimeImmutable is not subtype of Throwable', - 68, - ], - [ - 'PHPDoc tag @throws with type DateTimeImmutable|Throwable is not subtype of Throwable', - 75, - ], - [ - 'PHPDoc tag @throws with type DateTimeImmutable&IteratorAggregate is not subtype of Throwable', - 82, - ], - [ - 'PHPDoc tag @throws with type Throwable|void is not subtype of Throwable', - 96, - ], - [ - 'PHPDoc tag @throws with type stdClass|void is not subtype of Throwable', - 103, - ], - [ - 'PHPDoc tag @throws with type stdClass is not subtype of Throwable', - 118, - ], - ]); - } - - public function testInheritedPhpDocs(): void - { - $this->analyse([__DIR__ . '/data/merge-inherited-throws.php'], [ - [ - 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\A is not subtype of Throwable', - 13, - ], - [ - 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\B is not subtype of Throwable', - 19, - ], - [ - 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D is not subtype of Throwable', - 28, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/incompatible-throws.php'], [ + [ + 'PHPDoc tag @throws with type Undefined is not subtype of Throwable', + 54, + ], + [ + 'PHPDoc tag @throws with type bool is not subtype of Throwable', + 61, + ], + [ + 'PHPDoc tag @throws with type DateTimeImmutable is not subtype of Throwable', + 68, + ], + [ + 'PHPDoc tag @throws with type DateTimeImmutable|Throwable is not subtype of Throwable', + 75, + ], + [ + 'PHPDoc tag @throws with type DateTimeImmutable&IteratorAggregate is not subtype of Throwable', + 82, + ], + [ + 'PHPDoc tag @throws with type Throwable|void is not subtype of Throwable', + 96, + ], + [ + 'PHPDoc tag @throws with type stdClass|void is not subtype of Throwable', + 103, + ], + [ + 'PHPDoc tag @throws with type stdClass is not subtype of Throwable', + 118, + ], + ]); + } - public function dataMergeInheritedPhpDocs(): array - { - return [ - [ - \InvalidThrowsPhpDocMergeInherited\Two::class, - 'method', - 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', - ], - [ - \InvalidThrowsPhpDocMergeInherited\Three::class, - 'method', - 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', - ], - [ - \InvalidThrowsPhpDocMergeInherited\Four::class, - 'method', - 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', - ], - ]; - } + public function testInheritedPhpDocs(): void + { + $this->analyse([__DIR__ . '/data/merge-inherited-throws.php'], [ + [ + 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\A is not subtype of Throwable', + 13, + ], + [ + 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\B is not subtype of Throwable', + 19, + ], + [ + 'PHPDoc tag @throws with type InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D is not subtype of Throwable', + 28, + ], + ]); + } - /** - * @dataProvider dataMergeInheritedPhpDocs - * @param string $className - * @param string $method - * @param string $expectedType - */ - public function testMergeInheritedPhpDocs( - string $className, - string $method, - string $expectedType - ): void - { - $reflectionProvider = $this->createBroker(); - $reflection = $reflectionProvider->getClass($className); - $method = $reflection->getNativeMethod($method); - $throwsType = $method->getThrowType(); - $this->assertNotNull($throwsType); - $this->assertSame($expectedType, $throwsType->describe(VerbosityLevel::precise())); - } + public function dataMergeInheritedPhpDocs(): array + { + return [ + [ + \InvalidThrowsPhpDocMergeInherited\Two::class, + 'method', + 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', + ], + [ + \InvalidThrowsPhpDocMergeInherited\Three::class, + 'method', + 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', + ], + [ + \InvalidThrowsPhpDocMergeInherited\Four::class, + 'method', + 'InvalidThrowsPhpDocMergeInherited\C|InvalidThrowsPhpDocMergeInherited\D', + ], + ]; + } + /** + * @dataProvider dataMergeInheritedPhpDocs + * @param string $className + * @param string $method + * @param string $expectedType + */ + public function testMergeInheritedPhpDocs( + string $className, + string $method, + string $expectedType + ): void { + $reflectionProvider = $this->createBroker(); + $reflection = $reflectionProvider->getClass($className); + $method = $reflection->getNativeMethod($method); + $throwsType = $method->getThrowType(); + $this->assertNotNull($throwsType); + $this->assertSame($expectedType, $throwsType->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 05d47dbc03..b610efa045 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -1,4 +1,6 @@ -getByType(FileTypeMapper::class), + true + ); + } - protected function getRule(): Rule - { - return new WrongVariableNameInVarTagRule( - self::getContainer()->getByType(FileTypeMapper::class), - true - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/wrong-variable-name-var.php'], [ - [ - 'Variable $foo in PHPDoc tag @var does not match assigned variable $test.', - 17, - ], - [ - 'Multiple PHPDoc @var tags above single variable assignment are not supported.', - 23, - ], - [ - 'Variable $foo in PHPDoc tag @var does not match any variable in the foreach loop: $list, $key, $val', - 66, - ], - [ - 'PHPDoc tag @var above foreach loop does not specify variable name.', - 71, - ], - [ - 'PHPDoc tag @var above multiple static variables does not specify variable name.', - 85, - ], - [ - 'PHPDoc tag @var above multiple static variables does not specify variable name.', - 91, - ], - [ - 'PHPDoc tag @var above multiple static variables does not specify variable name.', - 91, - ], - [ - 'Variable $foo in PHPDoc tag @var does not match any static variable: $test', - 94, - ], - [ - 'PHPDoc tag @var does not specify variable name.', - 103, - ], - [ - 'Variable $foo in PHPDoc tag @var does not exist.', - 109, - ], - [ - 'Multiple PHPDoc @var tags above single variable assignment are not supported.', - 125, - ], - [ - 'Variable $b in PHPDoc tag @var does not exist.', - 134, - ], - [ - 'PHPDoc tag @var does not specify variable name.', - 155, - ], - [ - 'PHPDoc tag @var does not specify variable name.', - 176, - ], - [ - 'Variable $foo in PHPDoc tag @var does not exist.', - 210, - ], - [ - 'PHPDoc tag @var above foreach loop does not specify variable name.', - 234, - ], - [ - 'Variable $foo in PHPDoc tag @var does not exist.', - 248, - ], - [ - 'Variable $bar in PHPDoc tag @var does not exist.', - 248, - ], - [ - 'Variable $slots in PHPDoc tag @var does not exist.', - 262, - ], - [ - 'Variable $slots in PHPDoc tag @var does not exist.', - 268, - ], - [ - 'PHPDoc tag @var above assignment does not specify variable name.', - 274, - ], - [ - 'Variable $slots in PHPDoc tag @var does not match assigned variable $itemSlots.', - 280, - ], - [ - 'PHPDoc tag @var above a class has no effect.', - 300, - ], - [ - 'PHPDoc tag @var above a method has no effect.', - 304, - ], - [ - 'PHPDoc tag @var above a function has no effect.', - 312, - ], - ]); - } - - public function testEmptyFileWithVarThis(): void - { - $this->analyse([__DIR__ . '/data/wrong-variable-name-var-empty-this.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/wrong-variable-name-var.php'], [ + [ + 'Variable $foo in PHPDoc tag @var does not match assigned variable $test.', + 17, + ], + [ + 'Multiple PHPDoc @var tags above single variable assignment are not supported.', + 23, + ], + [ + 'Variable $foo in PHPDoc tag @var does not match any variable in the foreach loop: $list, $key, $val', + 66, + ], + [ + 'PHPDoc tag @var above foreach loop does not specify variable name.', + 71, + ], + [ + 'PHPDoc tag @var above multiple static variables does not specify variable name.', + 85, + ], + [ + 'PHPDoc tag @var above multiple static variables does not specify variable name.', + 91, + ], + [ + 'PHPDoc tag @var above multiple static variables does not specify variable name.', + 91, + ], + [ + 'Variable $foo in PHPDoc tag @var does not match any static variable: $test', + 94, + ], + [ + 'PHPDoc tag @var does not specify variable name.', + 103, + ], + [ + 'Variable $foo in PHPDoc tag @var does not exist.', + 109, + ], + [ + 'Multiple PHPDoc @var tags above single variable assignment are not supported.', + 125, + ], + [ + 'Variable $b in PHPDoc tag @var does not exist.', + 134, + ], + [ + 'PHPDoc tag @var does not specify variable name.', + 155, + ], + [ + 'PHPDoc tag @var does not specify variable name.', + 176, + ], + [ + 'Variable $foo in PHPDoc tag @var does not exist.', + 210, + ], + [ + 'PHPDoc tag @var above foreach loop does not specify variable name.', + 234, + ], + [ + 'Variable $foo in PHPDoc tag @var does not exist.', + 248, + ], + [ + 'Variable $bar in PHPDoc tag @var does not exist.', + 248, + ], + [ + 'Variable $slots in PHPDoc tag @var does not exist.', + 262, + ], + [ + 'Variable $slots in PHPDoc tag @var does not exist.', + 268, + ], + [ + 'PHPDoc tag @var above assignment does not specify variable name.', + 274, + ], + [ + 'Variable $slots in PHPDoc tag @var does not match assigned variable $itemSlots.', + 280, + ], + [ + 'PHPDoc tag @var above a class has no effect.', + 300, + ], + [ + 'PHPDoc tag @var above a method has no effect.', + 304, + ], + [ + 'PHPDoc tag @var above a function has no effect.', + 312, + ], + ]); + } - public function testAboveUse(): void - { - $this->analyse([__DIR__ . '/data/var-above-use.php'], []); - } + public function testEmptyFileWithVarThis(): void + { + $this->analyse([__DIR__ . '/data/wrong-variable-name-var-empty-this.php'], []); + } - public function testAboveDeclare(): void - { - $this->analyse([__DIR__ . '/data/var-above-declare.php'], []); - } + public function testAboveUse(): void + { + $this->analyse([__DIR__ . '/data/var-above-use.php'], []); + } - public function testBug3515(): void - { - $this->analyse([__DIR__ . '/data/bug-3515.php'], []); - } + public function testAboveDeclare(): void + { + $this->analyse([__DIR__ . '/data/var-above-declare.php'], []); + } - public function testBug4500(): void - { - $this->analyse([__DIR__ . '/data/bug-4500.php'], [ - [ - 'PHPDoc tag @var above multiple global variables does not specify variable name.', - 23, - ], - [ - 'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem', - 43, - ], - [ - 'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem', - 49, - ], - ]); - } + public function testBug3515(): void + { + $this->analyse([__DIR__ . '/data/bug-3515.php'], []); + } - public function testBug4504(): void - { - $this->analyse([__DIR__ . '/data/bug-4504.php'], []); - } + public function testBug4500(): void + { + $this->analyse([__DIR__ . '/data/bug-4500.php'], [ + [ + 'PHPDoc tag @var above multiple global variables does not specify variable name.', + 23, + ], + [ + 'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem', + 43, + ], + [ + 'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem', + 49, + ], + ]); + } - public function testBug4505(): void - { - $this->analyse([__DIR__ . '/data/bug-4505.php'], []); - } + public function testBug4504(): void + { + $this->analyse([__DIR__ . '/data/bug-4504.php'], []); + } + public function testBug4505(): void + { + $this->analyse([__DIR__ . '/data/bug-4505.php'], []); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4227.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4227.php index 440577cae6..ba25ad0757 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4227.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4227.php @@ -1,20 +1,22 @@ -= 7.4 += 7.4 namespace Bug4227; class Foo { - private bool $property; - private int $count = 0; - private ?string $string = null; + private bool $property; + private int $count = 0; + private ?string $string = null; - public function __construct(bool $property) - { - $this->property = $property; - } + public function __construct(bool $property) + { + $this->property = $property; + } - public function count(): int - { - return $this->count; - } + public function count(): int + { + return $this->count; + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4486-ns.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4486-ns.php index e914313cb2..0eff329654 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4486-ns.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4486-ns.php @@ -3,6 +3,7 @@ /** * @var ClassName1 $one */ + namespace Bug4486Namespace; /** diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4486.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4486.php index 2e5d815acc..7c4f159a04 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4486.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4486.php @@ -7,8 +7,8 @@ * @var ClassName2 $two */ -use \Some\Namespaced\ClassName1; -use \Some\Namespaced\ClassName2; +use Some\Namespaced\ClassName1; +use Some\Namespaced\ClassName2; /** * @var ClassName1 $three diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4500.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4500.php index bdf832d565..dd11c4cc7d 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4500.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4500.php @@ -4,49 +4,47 @@ class Foo { - - public function doFoo(): void - { - /** @var int */ - global $foo; - } - - public function doBar(): void - { - /** @var int $foo */ - global $foo; - } - - public function doBaz(): void - { - /** @var int */ - global $foo, $bar; - } - - public function doLorem(): void - { - /** @var int $foo */ - global $foo, $bar; - } - - public function doIpsum(): void - { - /** - * @var int $foo - * @var string $bar - */ - global $foo, $bar; - - $baz = 'foo'; - - /** @var int $baz */ - global $lorem; - } - - public function doDolor(): void - { - /** @var int $baz */ - global $lorem; - } - + public function doFoo(): void + { + /** @var int */ + global $foo; + } + + public function doBar(): void + { + /** @var int $foo */ + global $foo; + } + + public function doBaz(): void + { + /** @var int */ + global $foo, $bar; + } + + public function doLorem(): void + { + /** @var int $foo */ + global $foo, $bar; + } + + public function doIpsum(): void + { + /** + * @var int $foo + * @var string $bar + */ + global $foo, $bar; + + $baz = 'foo'; + + /** @var int $baz */ + global $lorem; + } + + public function doDolor(): void + { + /** @var int $baz */ + global $lorem; + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4504.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4504.php index dc6a7b2745..6ac7e52c3d 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4504.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4504.php @@ -4,13 +4,10 @@ class Foo { - - public function sayHello($models): void - { - /** @var \Iterator $models */ - foreach ($models as $k => $v) { - - } - } - + public function sayHello($models): void + { + /** @var \Iterator $models */ + foreach ($models as $k => $v) { + } + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4505.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4505.php index 3093732b2d..0e1665e477 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4505.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4505.php @@ -4,14 +4,13 @@ class Foo { + public function doFoo(): void + { + if (true) { + return; + } - public function doFoo(): void { - if (true) { - return; - } - - /** @var int $foobar */ - $foobar = 1; - } - + /** @var int $foobar */ + $foobar = 1; + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4643.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4643.php index fafbcf4e11..c811de45df 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4643.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4643.php @@ -6,55 +6,47 @@ interface Entity { - } class User implements Entity { - } class Admin extends User { - } class Article implements Entity { - } /** @template E of Entity */ class Repository { - - /** - * @template F of E - * @param F $entity - * @return F - */ - function store(Entity $entity): Entity - { - assertType('F of E of Bug4643\Entity (class Bug4643\Repository, argument) (method Bug4643\Repository::store(), argument)', $entity); - return $entity; - } - + /** + * @template F of E + * @param F $entity + * @return F + */ + public function store(Entity $entity): Entity + { + assertType('F of E of Bug4643\Entity (class Bug4643\Repository, argument) (method Bug4643\Repository::store(), argument)', $entity); + return $entity; + } } /** @extends Repository */ class UserRepository extends Repository { - - function store(Entity $entity): Entity - { - assertType('F of Bug4643\User (method Bug4643\Repository::store(), argument)', $entity); - return $entity; - } - + public function store(Entity $entity): Entity + { + assertType('F of Bug4643\User (method Bug4643\Repository::store(), argument)', $entity); + return $entity; + } } function (UserRepository $r): void { - assertType(User::class, $r->store(new User())); - assertType(Admin::class, $r->store(new Admin())); - assertType('F of Bug4643\User (method Bug4643\Repository::store(), parameter)', $r->store(new Article())); // should be User::class, but inheriting template tags is now broken like that + assertType(User::class, $r->store(new User())); + assertType(Admin::class, $r->store(new Admin())); + assertType('F of Bug4643\User (method Bug4643\Repository::store(), parameter)', $r->store(new Article())); // should be User::class, but inheriting template tags is now broken like that }; diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4731-no-first-tag.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4731-no-first-tag.php index 692897caee..82ad978670 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4731-no-first-tag.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4731-no-first-tag.php @@ -1,4 +1,6 @@ -format('j. n. Y'); - } + public function sayHello(\DateTimeImmutable $date): void + { + echo 'Hello, ' . $date->format('j. n. Y'); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-4731.php b/tests/PHPStan/Rules/PhpDoc/data/bug-4731.php index 5f58cb740e..d45571085e 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-4731.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-4731.php @@ -1,4 +1,6 @@ -format('j. n. Y'); - } + public function sayHello(\DateTimeImmutable $date): void + { + echo 'Hello, ' . $date->format('j. n. Y'); + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php index 93a4a38a91..aa247414b3 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php @@ -1,38 +1,35 @@ -= 7.4 += 7.4 namespace IncompatiblePhpDocPropertyNativeType; class Foo { + /** @var self */ + private object $selfOne; - /** @var self */ - private object $selfOne; - - /** @var object */ - private self $selfTwo; - - /** @var Bar */ - private Foo $foo; + /** @var object */ + private self $selfTwo; - /** @var string */ - private string $string; + /** @var Bar */ + private Foo $foo; - /** @var string|int */ - private string $stringOrInt; + /** @var string */ + private string $string; - /** @var string */ - private ?string $stringOrNull; + /** @var string|int */ + private string $stringOrInt; + /** @var string */ + private ?string $stringOrNull; } class Bar { - } class Baz { - - private string $stringProp; - + private string $stringProp; } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-phpdoc.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-phpdoc.php index 2aa3ffc894..5c934fe261 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-phpdoc.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-phpdoc.php @@ -4,44 +4,42 @@ class FooWithProperty { + /** @var aray */ + private $foo; - /** @var aray */ - private $foo; + /** @var Foo&Bar */ + private $bar; - /** @var Foo&Bar */ - private $bar; + /** @var never */ + private $baz; - /** @var never */ - private $baz; + /** @var class-string */ + private $classStringInt; - /** @var class-string */ - private $classStringInt; + /** @var class-string */ + private $classStringValid; - /** @var class-string */ - private $classStringValid; + /** @var array{\InvalidPhpDocDefinitions\Foo<\stdClass>} */ + private $fooGeneric; - /** @var array{\InvalidPhpDocDefinitions\Foo<\stdClass>} */ - private $fooGeneric; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $validGenericFoo; - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $validGenericFoo; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $notEnoughTypesGenericfoo; - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $notEnoughTypesGenericfoo; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $tooManyTypesGenericfoo; - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $tooManyTypesGenericfoo; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $invalidTypeGenericfoo; - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $invalidTypeGenericfoo; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $anotherInvalidTypeGenericfoo; - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $anotherInvalidTypeGenericfoo; - - /** @var UnknownClass::BLABLA */ - private $unknownClassConstant; - - /** @var self::BLABLA */ - private $unknownClassConstant2; + /** @var UnknownClass::BLABLA */ + private $unknownClassConstant; + /** @var self::BLABLA */ + private $unknownClassConstant2; } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-promoted.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-promoted.php index f42989946a..b678d13f80 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-promoted.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-promoted.php @@ -1,4 +1,6 @@ -= 8.0 += 8.0 namespace InvalidPhpDocPromotedProperties; @@ -7,46 +9,45 @@ class FooWithProperty { + public function __construct( + /** @var aray */ + private $foo, - public function __construct( - /** @var aray */ - private $foo, - - /** @var Foo&Bar */ - private $bar, - - /** @var never */ - private $baz, + /** @var Foo&Bar */ + private $bar, - /** @var class-string */ - private $classStringInt, + /** @var never */ + private $baz, - /** @var class-string */ - private $classStringValid, + /** @var class-string */ + private $classStringInt, - /** @var array{\InvalidPhpDocDefinitions\Foo<\stdClass>} */ - private $fooGeneric, + /** @var class-string */ + private $classStringValid, - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $validGenericFoo, + /** @var array{\InvalidPhpDocDefinitions\Foo<\stdClass>} */ + private $fooGeneric, - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $notEnoughTypesGenericfoo, + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $validGenericFoo, - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $tooManyTypesGenericfoo, + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $notEnoughTypesGenericfoo, - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $invalidTypeGenericfoo, + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $tooManyTypesGenericfoo, - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $anotherInvalidTypeGenericfoo, + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $invalidTypeGenericfoo, - /** @var UnknownClass::BLABLA */ - private $unknownClassConstant, + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $anotherInvalidTypeGenericfoo, - /** @var self::BLABLA */ - private $unknownClassConstant2 - ) { } + /** @var UnknownClass::BLABLA */ + private $unknownClassConstant, + /** @var self::BLABLA */ + private $unknownClassConstant2 + ) { + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-throws.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-throws.php index 7ce3088fc3..6e3776e57b 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-throws.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-throws.php @@ -2,7 +2,7 @@ namespace InvalidPhpDoc; -function noDoc() : void +function noDoc(): void { } @@ -114,6 +114,6 @@ function exceptionTemplateThrows() function inlineThrows() { - /** @throws \stdClass */ - $i = 1; + /** @throws \stdClass */ + $i = 1; } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php index 517a3e0ac3..43ccce4c3a 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php @@ -11,7 +11,6 @@ */ function paramTest(int $a, string $b, iterable $c, int $d) { - } @@ -20,7 +19,6 @@ function paramTest(int $a, string $b, iterable $c, int $d) */ function variadicNumbers(int ...$numbers) { - } @@ -29,7 +27,6 @@ function variadicNumbers(int ...$numbers) */ function variadicStrings(string ...$strings) { - } @@ -38,7 +35,6 @@ function variadicStrings(string ...$strings) */ function testReturnIntOk(): int { - } @@ -47,7 +43,6 @@ function testReturnIntOk(): int */ function testReturnBoolOk(): bool { - } @@ -56,7 +51,6 @@ function testReturnBoolOk(): bool */ function testReturnTrueOk(): bool { - } @@ -65,7 +59,6 @@ function testReturnTrueOk(): bool */ function testReturnIntInvalid(): int { - } @@ -74,7 +67,6 @@ function testReturnIntInvalid(): int */ function testReturnIntNotSubType(): int { - } /** @@ -82,7 +74,6 @@ function testReturnIntNotSubType(): int */ function anotherVariadicStrings(string ...$strings) { - } /** @@ -90,7 +81,6 @@ function anotherVariadicStrings(string ...$strings) */ function incompatibleVariadicStrings(string ...$strings) { - } /** @@ -98,7 +88,6 @@ function incompatibleVariadicStrings(string ...$strings) */ function incompatibleVariadicNumbers(int ...$numbers) { - } /** @@ -106,7 +95,6 @@ function incompatibleVariadicNumbers(int ...$numbers) */ function variadicStringArrays(array ...$strings) { - } /** @@ -116,7 +104,6 @@ function variadicStringArrays(array ...$strings) */ function unresolvableTypes(array $arr): bool { - } /** @@ -125,7 +112,6 @@ function unresolvableTypes(array $arr): bool */ function neverTypes($foo) { - } /** @@ -170,7 +156,7 @@ function genericWithTypeHintsSupertype(\DateTimeInterface $a): \DateTimeInterfac */ function explicitNever($foo) { - throw new \Exception(); + throw new \Exception(); } /** @@ -184,7 +170,6 @@ function explicitNever($foo) */ function generics($foo, $bar, $baz, $lorem, $ipsum, $dolor) { - } /** @@ -192,7 +177,6 @@ function generics($foo, $bar, $baz, $lorem, $ipsum, $dolor) */ function genericsBar() { - } /** @@ -200,7 +184,6 @@ function genericsBar() */ function genericsBaz() { - } /** @@ -208,7 +191,6 @@ function genericsBaz() */ function genericsLorem() { - } /** @@ -216,7 +198,6 @@ function genericsLorem() */ function genericsIpsum() { - } /** @@ -224,7 +205,6 @@ function genericsIpsum() */ function genericsDolor() { - } /** @@ -241,7 +221,6 @@ function genericsDolor() */ function genericGenerics($t, $u, $v, $w, $x) { - } /** @@ -249,7 +228,6 @@ function genericGenerics($t, $u, $v, $w, $x) */ function genericNestedWrongTemplateArgs() { - } /** @@ -257,7 +235,6 @@ function genericNestedWrongTemplateArgs() */ function genericNestedOkTemplateArgs() { - } /** @@ -265,7 +242,6 @@ function genericNestedOkTemplateArgs() */ function genericNestedWrongArgCount() { - } /** @@ -273,5 +249,4 @@ function genericNestedWrongArgCount() */ function genericNestedNonTemplateArgs() { - } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php index 7eff223ae0..a147383ece 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php @@ -4,7 +4,6 @@ class Foo { - } /** @@ -13,5 +12,4 @@ class Foo */ class FooGeneric { - } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php index 7d9b462f0d..38503309d7 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php @@ -25,9 +25,8 @@ function foo() { - /** @var \\Foo|\Bar $test */ - $test = doFoo(); - + /** @var \\Foo|\Bar $test */ + $test = doFoo(); } /** @@ -36,40 +35,32 @@ function foo() */ class Foo { - } class Bar { - - /** - * @psalm-param list() $a - */ - public function doFoo($a) - { - - } - + /** + * @psalm-param list() $a + */ + public function doFoo($a) + { + } } class Baz { + /** @var callable(int) */ + private $fooProperty; - /** @var callable(int) */ - private $fooProperty; - - /** @var (Foo|Bar */ - private $barProperty; - + /** @var (Foo|Bar */ + private $barProperty; } class InlineThrows { - - public function doFoo() - { - /** @throws (\Exception */ - $i = 1; - } - + public function doFoo() + { + /** @throws (\Exception */ + $i = 1; + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-doc.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-doc.php index fa2e510986..b2d96137ca 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-doc.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-doc.php @@ -2,37 +2,39 @@ namespace InvalidPHPStanDoc; -class Baz{} +class Baz +{ +} /** @phpstan-extens Baz */ class Boo extends Baz { - /** - * @phpstan-template T - * @phpstan-pararm class-string $a - * @phpstan-return T - */ - function foo(string $a){} - - /** - * @phpstan-ignore-next-line - * @phpstan-pararm - */ - function bar() - { - - } + /** + * @phpstan-template T + * @phpstan-pararm class-string $a + * @phpstan-return T + */ + public function foo(string $a) + { + } - function baz() - { - /** @phpstan-va */$a = $b; /** @phpstan-ignore-line */ - $c = 'foo'; - } + /** + * @phpstan-ignore-next-line + * @phpstan-pararm + */ + public function bar() + { + } - /** - * @phpstan-throws void - */ - function any() - { + public function baz() + { + /** @phpstan-va */$a = $b; /** @phpstan-ignore-line */ + $c = 'foo'; + } - } + /** + * @phpstan-throws void + */ + public function any() + { + } } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php index 1c1991710a..ff7a145448 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php @@ -4,82 +4,75 @@ class Foo { + public function doFoo() + { + /** @var self $test */ + $test = new self(); - public function doFoo() - { - /** @var self $test */ - $test = new self(); + /** @var self&\stdClass $test */ + $test = new self(); - /** @var self&\stdClass $test */ - $test = new self(); + /** @var self&\stdClass */ + $test = new self(); - /** @var self&\stdClass */ - $test = new self(); + /** @var aray $test */ + $test = new self(); - /** @var aray $test */ - $test = new self(); + /** @var int&string $value */ + foreach ([1, 2, 3] as $value) { + } - /** @var int&string $value */ - foreach ([1, 2, 3] as $value) { + /** @var self&\stdClass $staticVar */ + static $staticVar = 1; - } + /** @var foo $test */ + $test = new self(); - /** @var self&\stdClass $staticVar */ - static $staticVar = 1; + /** @var FooTrait $test */ + $test = new self(); - /** @var foo $test */ - $test = new self(); + /** @var never $test */ + $test = doFoo(); - /** @var FooTrait $test */ - $test = new self(); + /** @var \InvalidPhpDoc\Foo<\stdClass> $test */ + $test = doFoo(); - /** @var never $test */ - $test = doFoo(); + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); - /** @var \InvalidPhpDoc\Foo<\stdClass> $test */ - $test = doFoo(); + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); + /** @var array $test */ + $test = doFoo(); - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); - - /** @var array $test */ - $test = doFoo(); - - /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ - $test = doFoo(); - } - - public function doBar($foo) - { - /** @var Blabla $foo */ - if (true) { - - } - } + /** @var \InvalidPhpDocDefinitions\FooGeneric $test */ + $test = doFoo(); + } + public function doBar($foo) + { + /** @var Blabla $foo */ + if (true) { + } + } } trait FooTrait { - } class Bar { - - /** @var Blabla */ - private $foo; - + /** @var Blabla */ + private $foo; } diff --git a/tests/PHPStan/Rules/PhpDoc/data/merge-inherited-throws.php b/tests/PHPStan/Rules/PhpDoc/data/merge-inherited-throws.php index d8e0362f26..dce39ff347 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/merge-inherited-throws.php +++ b/tests/PHPStan/Rules/PhpDoc/data/merge-inherited-throws.php @@ -1,40 +1,58 @@ - $var) { - - } - - /** @var int $key */ - foreach ($list as $key => $var) { - - } - - /** @var int $var */ - foreach ($list as $key => $var) { - - } - - /** - * @var int $foo - * @var int $bar - * @var int $baz - * @var int $lorem - */ - foreach ($list as $key => [$foo, $bar, [$baz, $lorem]]) { - - } - - /** - * @var int $foo - * @var int $bar - * @var int $baz - * @var int $lorem - */ - foreach ($list as $key => list($foo, $bar, list($baz, $lorem))) { - - } - - /** - * @var int $foo - */ - foreach ($list as $key => $val) { - - } - - /** @var int */ - foreach ($list as $key => $val) { - - } - } - - public function doBaz() - { - /** @var int $var */ - static $var; - - /** @var int */ - static $var; - - /** @var int */ - static $var, $bar; - - /** - * @var int - * @var string - */ - static $var, $bar; - - /** @var int $foo */ - static $test; - } - - public function doLorem($test) - { - /** @var int $test */ - $test2 = doFoo(); - - /** @var int */ - $test->foo(); - - /** @var int $test */ - $test->foo(); - - /** @var int $foo */ - $test->foo(); - } - - public function multiplePrefixedTagsAreFine() - { - /** - * @var int - * @phpstan-var int - * @psalm-var int - */ - $test = doFoo(); // OK - - /** - * @var int - * @var string - */ - $test = doFoo(); // error - } - - public function testEcho($a) - { - /** @var string $a */ - echo $a; - - /** @var string $b */ - echo $a; - } - - public function throwVar($a) - { - /** @var \Exception $a */ - throw $a; - } - - public function throwVar2($a) - { - /** @var \Exception */ - throw $a; - } - - public function throwVar3($a) - { - /** - * @var \Exception - * @var \InvalidArgumentException - */ - throw $a; - } - - public function returnVar($a) - { - /** @var \stdClass $a */ - return $a; - } - - public function returnVar2($a) - { - /** @var \stdClass */ - return $a; - } - - public function returnVar3($a) - { - /** - * @var \stdClass - * @var \DateTime - */ - return $a; - } - - public function thisInVar1() - { - /** @var Repository $this */ - $this->demo(); - } - - public function thisInVar2() - { - /** @var Repository $this */ - $demo = $this->demo(); - } - - public function overrideDifferentVariableAboveAssign() - { - $foo = 'foo'; - - /** @var int $foo */ - $bar = $foo + 1; - } - - public function testIf($foo) - { - /** @var int $foo */ - do { - - } while (true); - } - - public function testIf2() - { - /** @var int $foo */ - do { - - } while (true); - } - + public function doFoo() + { + /** @var int $test */ + $test = doFoo(); + + /** @var int */ + $test = doFoo(); + + /** @var int $foo */ + $test = doFoo(); + + /** + * @var int + * @var string + */ + $test = doFoo(); + } + + public function doBar(array $list) + { + /** @var int[] $list */ + foreach ($list as $key => $var) { + } + + /** @var int $key */ + foreach ($list as $key => $var) { + } + + /** @var int $var */ + foreach ($list as $key => $var) { + } + + /** + * @var int $foo + * @var int $bar + * @var int $baz + * @var int $lorem + */ + foreach ($list as $key => [$foo, $bar, [$baz, $lorem]]) { + } + + /** + * @var int $foo + * @var int $bar + * @var int $baz + * @var int $lorem + */ + foreach ($list as $key => list($foo, $bar, list($baz, $lorem))) { + } + + /** + * @var int $foo + */ + foreach ($list as $key => $val) { + } + + /** @var int */ + foreach ($list as $key => $val) { + } + } + + public function doBaz() + { + /** @var int $var */ + static $var; + + /** @var int */ + static $var; + + /** @var int */ + static $var, $bar; + + /** + * @var int + * @var string + */ + static $var, $bar; + + /** @var int $foo */ + static $test; + } + + public function doLorem($test) + { + /** @var int $test */ + $test2 = doFoo(); + + /** @var int */ + $test->foo(); + + /** @var int $test */ + $test->foo(); + + /** @var int $foo */ + $test->foo(); + } + + public function multiplePrefixedTagsAreFine() + { + /** + * @var int + * @phpstan-var int + * @psalm-var int + */ + $test = doFoo(); // OK + + /** + * @var int + * @var string + */ + $test = doFoo(); // error + } + + public function testEcho($a) + { + /** @var string $a */ + echo $a; + + /** @var string $b */ + echo $a; + } + + public function throwVar($a) + { + /** @var \Exception $a */ + throw $a; + } + + public function throwVar2($a) + { + /** @var \Exception */ + throw $a; + } + + public function throwVar3($a) + { + /** + * @var \Exception + * @var \InvalidArgumentException + */ + throw $a; + } + + public function returnVar($a) + { + /** @var \stdClass $a */ + return $a; + } + + public function returnVar2($a) + { + /** @var \stdClass */ + return $a; + } + + public function returnVar3($a) + { + /** + * @var \stdClass + * @var \DateTime + */ + return $a; + } + + public function thisInVar1() + { + /** @var Repository $this */ + $this->demo(); + } + + public function thisInVar2() + { + /** @var Repository $this */ + $demo = $this->demo(); + } + + public function overrideDifferentVariableAboveAssign() + { + $foo = 'foo'; + + /** @var int $foo */ + $bar = $foo + 1; + } + + public function testIf($foo) + { + /** @var int $foo */ + do { + } while (true); + } + + public function testIf2() + { + /** @var int $foo */ + do { + } while (true); + } } class Bar { + /** @var int */ + private $test; - /** @var int */ - private $test; - - /** @var string */ - const TEST = 'str'; - + /** @var string */ + public const TEST = 'str'; } class ForeachJustValueVar { - - public function doBar(array $list) - { - /** @var int */ - foreach ($list as $val) { - - } - } - + public function doBar(array $list) + { + /** @var int */ + foreach ($list as $val) { + } + } } class MultipleDocComments { - - public function doFoo(): void - { - /** @var int $foo */ - /** @var string $bar */ - echo 'foo'; - } - - public function doBar(array $slots): void - { - /** @var \stdClass[] $itemSlots */ - /** @var \stdClass[] $slots */ - $itemSlots = []; - } - - public function doBaz(): void - { - /** @var \stdClass[] $itemSlots */ - /** @var \stdClass[] $slots */ - $itemSlots = []; - } - - public function doLorem(): void - { - /** @var \stdClass[] $slots */ - $itemSlots['foo'] = 'bar'; - } - - public function doIpsum(): void - { - /** @var \stdClass[] */ - $itemSlots['foo'] = 'bar'; - } - - public function doDolor(): void - { - /** @var \stdClass[] $slots */ - $itemSlots = []; - - /** @var int $test */ - [[$test]] = doFoo(); - } - - public function doSit(): void - { - /** - * @var int $foo - * @var int $bar - */ - [$foo, $bar] = doFoo(); - } - + public function doFoo(): void + { + /** @var int $foo */ + /** @var string $bar */ + echo 'foo'; + } + + public function doBar(array $slots): void + { + /** @var \stdClass[] $itemSlots */ + /** @var \stdClass[] $slots */ + $itemSlots = []; + } + + public function doBaz(): void + { + /** @var \stdClass[] $itemSlots */ + /** @var \stdClass[] $slots */ + $itemSlots = []; + } + + public function doLorem(): void + { + /** @var \stdClass[] $slots */ + $itemSlots['foo'] = 'bar'; + } + + public function doIpsum(): void + { + /** @var \stdClass[] */ + $itemSlots['foo'] = 'bar'; + } + + public function doDolor(): void + { + /** @var \stdClass[] $slots */ + $itemSlots = []; + + /** @var int $test */ + [[$test]] = doFoo(); + } + + public function doSit(): void + { + /** + * @var int $foo + * @var int $bar + */ + [$foo, $bar] = doFoo(); + } } /** @@ -299,17 +281,13 @@ public function doSit(): void */ class VarInWrongPlaces { - - /** @var int $a */ - public function doFoo($a) - { - - } - + /** @var int $a */ + public function doFoo($a) + { + } } /** @var int */ function doFoo(): void { - } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index 6554f967ed..352eeb8c9a 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new AccessPropertiesInAssignRule( + new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), true) + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new AccessPropertiesInAssignRule( - new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), true) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/access-properties-assign.php'], [ - [ - 'Access to an undefined property TestAccessPropertiesAssign\AccessPropertyWithDimFetch::$foo.', - 15, - ], - ]); - } - - public function testRuleExpressionNames(): void - { - $this->analyse([__DIR__ . '/data/properties-from-variable-into-object.php'], [ - [ - 'Access to an undefined property PropertiesFromVariableIntoObject\Foo::$noop.', - 26, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/access-properties-assign.php'], [ + [ + 'Access to an undefined property TestAccessPropertiesAssign\AccessPropertyWithDimFetch::$foo.', + 15, + ], + ]); + } - public function testRuleExpressionNames2(): void - { - $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ - [ - 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', - 42, - ], - [ - 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', - 54, - ], - [ - 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', - 69, - ], - [ - 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', - 110, - ], - ]); - } + public function testRuleExpressionNames(): void + { + $this->analyse([__DIR__ . '/data/properties-from-variable-into-object.php'], [ + [ + 'Access to an undefined property PropertiesFromVariableIntoObject\Foo::$noop.', + 26, + ], + ]); + } + public function testRuleExpressionNames2(): void + { + $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ + [ + 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', + 42, + ], + [ + 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', + 54, + ], + [ + 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', + 69, + ], + [ + 'Access to an undefined property PropertiesFromArrayIntoObject\Foo::$noop.', + 110, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index cb843caafc..d637f7b521 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); - return new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, $this->checkThisOnly, $this->checkUnionTypes, false), true); - } + /** @var bool */ + private $checkUnionTypes; - public function testAccessProperties(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse( - [__DIR__ . '/data/access-properties.php'], - [ - [ - 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', - 23, - ], - [ - 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', - 24, - ], - [ - 'Cannot access property $propertyOnString on string.', - 31, - ], - [ - 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', - 42, - ], - [ - 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', - 43, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$baz.', - 49, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$nonexistent.', - 52, - ], - [ - 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', - 58, - ], - [ - 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', - 59, - ], - [ - 'Access to property $foo on an unknown class TestAccessProperties\UnknownClass.', - 63, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyBaz.', - 68, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyNonexistent.', - 70, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', - 76, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', - 77, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', - 80, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', - 83, - ], - [ - 'Access to property $test on an unknown class TestAccessProperties\FirstUnknownClass.', - 146, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to property $test on an unknown class TestAccessProperties\SecondUnknownClass.', - 146, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined property TestAccessProperties\WithFooAndBarProperty|TestAccessProperties\WithFooProperty::$bar.', - 176, - ], - [ - 'Access to an undefined property TestAccessProperties\SomeInterface&TestAccessProperties\WithFooProperty::$bar.', - 193, - ], - [ - 'Cannot access property $ipsum on TestAccessProperties\FooAccessProperties|null.', - 207, - ], - [ - 'Cannot access property $foo on null.', - 220, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$lorem.', - 247, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$dolor.', - 250, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 264, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 266, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 270, - ], - [ - 'Cannot access property $bar on TestAccessProperties\NullCoalesce|null.', - 272, - ], - [ - 'Cannot access property $foo on TestAccessProperties\NullCoalesce|null.', - 272, - ], - [ - 'Cannot access property $foo on TestAccessProperties\NullCoalesce|null.', - 272, - ], - [ - 'Access to an undefined property class@anonymous/tests/PHPStan/Rules/Properties/data/access-properties.php:294::$barProperty.', - 299, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', - 364, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', - 386, - ], - [ - 'Cannot access property $selfOrNull on TestAccessProperties\RevertNonNullabilityForIsset|null.', - 402, - ], - [ - 'Cannot access property $array on stdClass|null.', - 412, - ], - ] - ); - } + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, $this->checkThisOnly, $this->checkUnionTypes, false), true); + } - public function testAccessPropertiesWithoutUnionTypes(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = false; - $this->analyse( - [__DIR__ . '/data/access-properties.php'], - [ - [ - 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', - 23, - ], - [ - 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', - 24, - ], - [ - 'Cannot access property $propertyOnString on string.', - 31, - ], - [ - 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', - 42, - ], - [ - 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', - 43, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$baz.', - 49, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$nonexistent.', - 52, - ], - [ - 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', - 58, - ], - [ - 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', - 59, - ], - [ - 'Access to property $foo on an unknown class TestAccessProperties\UnknownClass.', - 63, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyBaz.', - 68, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyNonexistent.', - 70, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', - 76, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', - 77, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', - 80, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', - 83, - ], - [ - 'Access to property $test on an unknown class TestAccessProperties\FirstUnknownClass.', - 146, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to property $test on an unknown class TestAccessProperties\SecondUnknownClass.', - 146, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined property TestAccessProperties\SomeInterface&TestAccessProperties\WithFooProperty::$bar.', - 193, - ], - [ - 'Cannot access property $foo on null.', - 220, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$lorem.', - 247, - ], - [ - 'Access to an undefined property TestAccessProperties\FooAccessProperties::$dolor.', - 250, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 264, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 266, - ], - [ - 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', - 270, - ], - [ - 'Cannot access property $bar on TestAccessProperties\NullCoalesce|null.', - 272, - ], - [ - 'Access to an undefined property class@anonymous/tests/PHPStan/Rules/Properties/data/access-properties.php:294::$barProperty.', - 299, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', - 364, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', - 386, - ], - ] - ); - } + public function testAccessProperties(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse( + [__DIR__ . '/data/access-properties.php'], + [ + [ + 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', + 23, + ], + [ + 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', + 24, + ], + [ + 'Cannot access property $propertyOnString on string.', + 31, + ], + [ + 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', + 42, + ], + [ + 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', + 43, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$baz.', + 49, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$nonexistent.', + 52, + ], + [ + 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', + 58, + ], + [ + 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', + 59, + ], + [ + 'Access to property $foo on an unknown class TestAccessProperties\UnknownClass.', + 63, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyBaz.', + 68, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyNonexistent.', + 70, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', + 76, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', + 77, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', + 80, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', + 83, + ], + [ + 'Access to property $test on an unknown class TestAccessProperties\FirstUnknownClass.', + 146, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to property $test on an unknown class TestAccessProperties\SecondUnknownClass.', + 146, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined property TestAccessProperties\WithFooAndBarProperty|TestAccessProperties\WithFooProperty::$bar.', + 176, + ], + [ + 'Access to an undefined property TestAccessProperties\SomeInterface&TestAccessProperties\WithFooProperty::$bar.', + 193, + ], + [ + 'Cannot access property $ipsum on TestAccessProperties\FooAccessProperties|null.', + 207, + ], + [ + 'Cannot access property $foo on null.', + 220, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$lorem.', + 247, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$dolor.', + 250, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 264, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 266, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 270, + ], + [ + 'Cannot access property $bar on TestAccessProperties\NullCoalesce|null.', + 272, + ], + [ + 'Cannot access property $foo on TestAccessProperties\NullCoalesce|null.', + 272, + ], + [ + 'Cannot access property $foo on TestAccessProperties\NullCoalesce|null.', + 272, + ], + [ + 'Access to an undefined property class@anonymous/tests/PHPStan/Rules/Properties/data/access-properties.php:294::$barProperty.', + 299, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', + 364, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', + 386, + ], + [ + 'Cannot access property $selfOrNull on TestAccessProperties\RevertNonNullabilityForIsset|null.', + 402, + ], + [ + 'Cannot access property $array on stdClass|null.', + 412, + ], + ] + ); + } - public function testAccessPropertiesOnThisOnly(): void - { - $this->checkThisOnly = true; - $this->checkUnionTypes = true; - $this->analyse( - [__DIR__ . '/data/access-properties.php'], - [ - [ - 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', - 23, - ], - [ - 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', - 24, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', - 364, - ], - [ - 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', - 386, - ], - ] - ); - } + public function testAccessPropertiesWithoutUnionTypes(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = false; + $this->analyse( + [__DIR__ . '/data/access-properties.php'], + [ + [ + 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', + 23, + ], + [ + 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', + 24, + ], + [ + 'Cannot access property $propertyOnString on string.', + 31, + ], + [ + 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', + 42, + ], + [ + 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', + 43, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$baz.', + 49, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$nonexistent.', + 52, + ], + [ + 'Access to private property TestAccessProperties\FooAccessProperties::$foo.', + 58, + ], + [ + 'Access to protected property TestAccessProperties\FooAccessProperties::$bar.', + 59, + ], + [ + 'Access to property $foo on an unknown class TestAccessProperties\UnknownClass.', + 63, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyBaz.', + 68, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$emptyNonexistent.', + 70, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', + 76, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherNonexistent.', + 77, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', + 80, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$anotherEmptyNonexistent.', + 83, + ], + [ + 'Access to property $test on an unknown class TestAccessProperties\FirstUnknownClass.', + 146, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to property $test on an unknown class TestAccessProperties\SecondUnknownClass.', + 146, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined property TestAccessProperties\SomeInterface&TestAccessProperties\WithFooProperty::$bar.', + 193, + ], + [ + 'Cannot access property $foo on null.', + 220, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$lorem.', + 247, + ], + [ + 'Access to an undefined property TestAccessProperties\FooAccessProperties::$dolor.', + 250, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 264, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 266, + ], + [ + 'Access to an undefined property TestAccessProperties\NullCoalesce::$bar.', + 270, + ], + [ + 'Cannot access property $bar on TestAccessProperties\NullCoalesce|null.', + 272, + ], + [ + 'Access to an undefined property class@anonymous/tests/PHPStan/Rules/Properties/data/access-properties.php:294::$barProperty.', + 299, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', + 364, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', + 386, + ], + ] + ); + } - public function testAccessPropertiesAfterIsNullInBooleanOr(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/access-properties-after-isnull.php'], [ - [ - 'Cannot access property $fooProperty on null.', - 16, - ], - [ - 'Cannot access property $fooProperty on null.', - 25, - ], - [ - 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', - 28, - ], - [ - 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', - 31, - ], - [ - 'Cannot access property $fooProperty on null.', - 35, - ], - [ - 'Cannot access property $fooProperty on null.', - 44, - ], - [ - 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', - 47, - ], - [ - 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', - 50, - ], - ]); - } + public function testAccessPropertiesOnThisOnly(): void + { + $this->checkThisOnly = true; + $this->checkUnionTypes = true; + $this->analyse( + [__DIR__ . '/data/access-properties.php'], + [ + [ + 'Access to an undefined property TestAccessProperties\BarAccessProperties::$loremipsum.', + 23, + ], + [ + 'Access to private property $foo of parent class TestAccessProperties\FooAccessProperties.', + 24, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessPropertyWithDimFetch::$foo.', + 364, + ], + [ + 'Access to an undefined property TestAccessProperties\AccessInIsset::$foo.', + 386, + ], + ] + ); + } - public function testDateIntervalChildProperties(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/date-interval-child-properties.php'], [ - [ - 'Access to an undefined property AccessPropertiesDateIntervalChild\DateIntervalChild::$nonexistent.', - 14, - ], - ]); - } + public function testAccessPropertiesAfterIsNullInBooleanOr(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/access-properties-after-isnull.php'], [ + [ + 'Cannot access property $fooProperty on null.', + 16, + ], + [ + 'Cannot access property $fooProperty on null.', + 25, + ], + [ + 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', + 28, + ], + [ + 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', + 31, + ], + [ + 'Cannot access property $fooProperty on null.', + 35, + ], + [ + 'Cannot access property $fooProperty on null.', + 44, + ], + [ + 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', + 47, + ], + [ + 'Access to an undefined property AccessPropertiesAfterIsNull\Foo::$barProperty.', + 50, + ], + ]); + } - public function testClassExists(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; + public function testDateIntervalChildProperties(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/date-interval-child-properties.php'], [ + [ + 'Access to an undefined property AccessPropertiesDateIntervalChild\DateIntervalChild::$nonexistent.', + 14, + ], + ]); + } - $this->analyse([__DIR__ . '/data/access-properties-class-exists.php'], [ - [ - 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Bar.', - 15, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Baz.', - 15, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Baz.', - 18, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Bar.', - 22, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testClassExists(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; - public function testMixin(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/mixin.php'], [ - [ - 'Access to an undefined property MixinProperties\GenericFoo::$namee.', - 51, - ], - ]); - } + $this->analyse([__DIR__ . '/data/access-properties-class-exists.php'], [ + [ + 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Bar.', + 15, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Baz.', + 15, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Baz.', + 18, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to property $lorem on an unknown class AccessPropertiesClassExists\Bar.', + 22, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - public function testBug3947(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3947.php'], []); - } + public function testMixin(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/mixin.php'], [ + [ + 'Access to an undefined property MixinProperties\GenericFoo::$namee.', + 51, + ], + ]); + } - public function testNullSafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testBug3947(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3947.php'], []); + } - $this->checkThisOnly = false; - $this->checkUnionTypes = true; + public function testNullSafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - $this->analyse([__DIR__ . '/data/nullsafe-property-fetch.php'], [ - [ - 'Access to an undefined property NullsafePropertyFetch\Foo::$baz.', - 13, - ], - [ - 'Cannot access property $bar on string.', - 18, - ], - [ - 'Cannot access property $bar on string.', - 19, - ], - [ - 'Cannot access property $bar on string.', - 21, - ], - [ - 'Cannot access property $bar on string.', - 22, - ], - ]); - } + $this->checkThisOnly = false; + $this->checkUnionTypes = true; - public function testBug3371(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-3371.php'], []); - } + $this->analyse([__DIR__ . '/data/nullsafe-property-fetch.php'], [ + [ + 'Access to an undefined property NullsafePropertyFetch\Foo::$baz.', + 13, + ], + [ + 'Cannot access property $bar on string.', + 18, + ], + [ + 'Cannot access property $bar on string.', + 19, + ], + [ + 'Cannot access property $bar on string.', + 21, + ], + [ + 'Cannot access property $bar on string.', + 22, + ], + ]); + } - public function testBug4527(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testBug3371(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3371.php'], []); + } - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-4527.php'], []); - } + public function testBug4527(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } - public function testBug4808(): void - { - $this->checkThisOnly = false; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-4808.php'], []); - } + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4527.php'], []); + } + public function testBug4808(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4808.php'], []); + } } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index 214fa443d7..9d1f9abb64 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new AccessStaticPropertiesInAssignRule( + new AccessStaticPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker)) + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new AccessStaticPropertiesInAssignRule( - new AccessStaticPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker)) - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/access-static-properties-assign.php'], [ - [ - 'Access to an undefined static property TestAccessStaticPropertiesAssign\AccessStaticPropertyWithDimFetch::$foo.', - 15, - ], - ]); - } - - public function testRuleExpressionNames(): void - { - $this->analyse([__DIR__ . '/data/properties-from-array-into-static-object.php'], [ - [ - 'Access to an undefined static property PropertiesFromArrayIntoStaticObject\Foo::$noop.', - 29, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/access-static-properties-assign.php'], [ + [ + 'Access to an undefined static property TestAccessStaticPropertiesAssign\AccessStaticPropertyWithDimFetch::$foo.', + 15, + ], + ]); + } + public function testRuleExpressionNames(): void + { + $this->analyse([__DIR__ . '/data/properties-from-array-into-static-object.php'], [ + [ + 'Access to an undefined static property PropertiesFromArrayIntoStaticObject\Foo::$noop.', + 29, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 8d0736c71d..29d1cac1ed 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new AccessStaticPropertiesRule( + $broker, + new RuleLevelHelper($broker, true, false, true, false), + new ClassCaseSensitivityCheck($broker) + ); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new AccessStaticPropertiesRule( - $broker, - new RuleLevelHelper($broker, true, false, true, false), - new ClassCaseSensitivityCheck($broker) - ); - } - - public function testAccessStaticProperties(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); - } - $this->analyse([__DIR__ . '/data/access-static-properties.php'], [ - [ - 'Access to an undefined static property FooAccessStaticProperties::$bar.', - 23, - ], - [ - 'Access to an undefined static property BarAccessStaticProperties::$bar.', - 24, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$bar.', - 25, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 26, - ], - [ - 'IpsumAccessStaticProperties::ipsum() accesses parent::$lorem but IpsumAccessStaticProperties does not extend any class.', - 42, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 44, - ], - [ - 'Access to static property $test on an unknown class UnknownStaticProperties.', - 47, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$baz.', - 53, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$nonexistent.', - 55, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyBaz.', - 63, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyNonexistent.', - 65, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', - 71, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', - 72, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', - 75, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', - 78, - ], - [ - 'Accessing self::$staticFooProperty outside of class scope.', - 84, - ], - [ - 'Accessing static::$staticFooProperty outside of class scope.', - 85, - ], - [ - 'Accessing parent::$staticFooProperty outside of class scope.', - 86, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 89, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 90, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$nonexistent.', - 94, - ], - [ - 'Access to static property $test on an unknown class NonexistentClass.', - 97, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined static property FooAccessStaticProperties&SomeInterface::$nonexistent.', - 108, - ], - [ - 'Cannot access static property $foo on int|string.', - 113, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 119, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$unknownProperties.', - 119, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 120, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 120, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 121, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 121, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 122, - ], - [ - 'Access to an undefined static property ClassOrString|string::$unknownProperty.', - 141, - ], - [ - 'Static access to instance property ClassOrString::$instanceProperty.', - 152, - ], - [ - 'Access to an undefined static property AccessPropertyWithDimFetch::$foo.', - 163, - ], - [ - 'Access to an undefined static property AccessInIsset::$foo.', - 185, - ], - [ - 'Access to static property $foo on trait TraitWithStaticProperty.', - 204, - ], - [ - 'Access to static property $foo on an unknown class TraitWithStaticProperty.', - 209, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } - - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/static-properties-class-exists.php'], []); - } + public function testAccessStaticProperties(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('Test does not run on PHP 7.4 because of referencing parent:: without parent class.'); + } + $this->analyse([__DIR__ . '/data/access-static-properties.php'], [ + [ + 'Access to an undefined static property FooAccessStaticProperties::$bar.', + 23, + ], + [ + 'Access to an undefined static property BarAccessStaticProperties::$bar.', + 24, + ], + [ + 'Access to an undefined static property FooAccessStaticProperties::$bar.', + 25, + ], + [ + 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', + 26, + ], + [ + 'IpsumAccessStaticProperties::ipsum() accesses parent::$lorem but IpsumAccessStaticProperties does not extend any class.', + 42, + ], + [ + 'Access to protected property $foo of class FooAccessStaticProperties.', + 44, + ], + [ + 'Access to static property $test on an unknown class UnknownStaticProperties.', + 47, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$baz.', + 53, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$nonexistent.', + 55, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyBaz.', + 63, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyNonexistent.', + 65, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', + 71, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', + 72, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', + 75, + ], + [ + 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', + 78, + ], + [ + 'Accessing self::$staticFooProperty outside of class scope.', + 84, + ], + [ + 'Accessing static::$staticFooProperty outside of class scope.', + 85, + ], + [ + 'Accessing parent::$staticFooProperty outside of class scope.', + 86, + ], + [ + 'Access to protected property $foo of class FooAccessStaticProperties.', + 89, + ], + [ + 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', + 90, + ], + [ + 'Access to an undefined static property FooAccessStaticProperties::$nonexistent.', + 94, + ], + [ + 'Access to static property $test on an unknown class NonexistentClass.', + 97, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Access to an undefined static property FooAccessStaticProperties&SomeInterface::$nonexistent.', + 108, + ], + [ + 'Cannot access static property $foo on int|string.', + 113, + ], + [ + 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', + 119, + ], + [ + 'Access to an undefined static property FooAccessStaticProperties::$unknownProperties.', + 119, + ], + [ + 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', + 120, + ], + [ + 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', + 120, + ], + [ + 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', + 121, + ], + [ + 'Access to protected property $foo of class FooAccessStaticProperties.', + 121, + ], + [ + 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', + 122, + ], + [ + 'Access to an undefined static property ClassOrString|string::$unknownProperty.', + 141, + ], + [ + 'Static access to instance property ClassOrString::$instanceProperty.', + 152, + ], + [ + 'Access to an undefined static property AccessPropertyWithDimFetch::$foo.', + 163, + ], + [ + 'Access to an undefined static property AccessInIsset::$foo.', + 185, + ], + [ + 'Access to static property $foo on trait TraitWithStaticProperty.', + 204, + ], + [ + 'Access to static property $foo on an unknown class TraitWithStaticProperty.', + 209, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + public function testClassExists(): void + { + $this->analyse([__DIR__ . '/data/static-properties-class-exists.php'], []); + } } diff --git a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php index 42064cd3e8..6d806b06dd 100644 --- a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): Rule - { - return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testDefaultValueTypesAssignedToProperties(): void - { - $this->analyse([__DIR__ . '/data/properties-assigned-default-value-types.php'], [ - [ - 'Property PropertiesAssignedDefaultValuesTypes\Foo::$stringPropertyWithWrongDefaultValue (string) does not accept default value of type int.', - 15, - ], - [ - 'Static property PropertiesAssignedDefaultValuesTypes\Foo::$staticStringPropertyWithWrongDefaultValue (string) does not accept default value of type int.', - 18, - ], - [ - 'Static property PropertiesAssignedDefaultValuesTypes\Foo::$windowsNtVersions (array) does not accept default value of type array.', - 24, - ], - ]); - } - - public function testDefaultValueForNativePropertyType(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - $this->analyse([__DIR__ . '/data/default-value-for-native-property-type.php'], [ - [ - 'Property DefaultValueForNativePropertyType\Foo::$foo (DateTime) does not accept default value of type null.', - 8, - ], - ]); - } + public function testDefaultValueTypesAssignedToProperties(): void + { + $this->analyse([__DIR__ . '/data/properties-assigned-default-value-types.php'], [ + [ + 'Property PropertiesAssignedDefaultValuesTypes\Foo::$stringPropertyWithWrongDefaultValue (string) does not accept default value of type int.', + 15, + ], + [ + 'Static property PropertiesAssignedDefaultValuesTypes\Foo::$staticStringPropertyWithWrongDefaultValue (string) does not accept default value of type int.', + 18, + ], + [ + 'Static property PropertiesAssignedDefaultValuesTypes\Foo::$windowsNtVersions (array) does not accept default value of type array.', + 24, + ], + ]); + } - public function testDefaultValueForPromotedProperty(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } + public function testDefaultValueForNativePropertyType(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/default-value-for-native-property-type.php'], [ + [ + 'Property DefaultValueForNativePropertyType\Foo::$foo (DateTime) does not accept default value of type null.', + 8, + ], + ]); + } - $this->analyse([__DIR__ . '/data/default-value-for-promoted-property.php'], [ - [ - 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', - 9, - ], - [ - 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', - 10, - ], - ]); - } + public function testDefaultValueForPromotedProperty(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/default-value-for-promoted-property.php'], [ + [ + 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', + 9, + ], + [ + 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', + 10, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php b/tests/PHPStan/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php index bcfe73709f..9c5d254dde 100644 --- a/tests/PHPStan/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php +++ b/tests/PHPStan/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php @@ -1,27 +1,27 @@ -extensions = $extensions; - } - - /** - * @return ReadWritePropertiesExtension[] - */ - public function getExtensions(): array - { - return $this->extensions; - } + /** + * @param ReadWritePropertiesExtension[] $extensions + */ + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + /** + * @return ReadWritePropertiesExtension[] + */ + public function getExtensions(): array + { + return $this->extensions; + } } diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php index 99dea64d96..5af96ed27a 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new ExistingClassesInPropertiesRule( + $broker, + new ClassCaseSensitivityCheck($broker), + true, + false + ); + } - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new ExistingClassesInPropertiesRule( - $broker, - new ClassCaseSensitivityCheck($broker), - true, - false - ); - } - - public function testNonexistentClass(): void - { - $this->analyse( - [ - __DIR__ . '/data/properties-types.php', - ], - [ - [ - 'Property PropertiesTypes\Foo::$bar has unknown class PropertiesTypes\Bar as its type.', - 12, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$bars has unknown class PropertiesTypes\Bar as its type.', - 18, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$dolors has unknown class PropertiesTypes\Dolor as its type.', - 21, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$dolors has unknown class PropertiesTypes\Ipsum as its type.', - 21, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$fooWithWrongCase has unknown class PropertiesTypes\BAR as its type.', - 24, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$fooWithWrongCase has unknown class PropertiesTypes\Fooo as its type.', - 24, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Class PropertiesTypes\Foo referenced with incorrect case: PropertiesTypes\FOO.', - 24, - ], - [ - 'Property PropertiesTypes\Foo::$withTrait has invalid type PropertiesTypes\SomeTrait.', - 27, - ], - [ - 'Property PropertiesTypes\Foo::$nonexistentClassInGenericObjectType has unknown class PropertiesTypes\Foooo as its type.', - 33, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesTypes\Foo::$nonexistentClassInGenericObjectType has unknown class PropertiesTypes\Barrrr as its type.', - 33, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ] - ); - } - - public function testNativeTypes(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + public function testNonexistentClass(): void + { + $this->analyse( + [ + __DIR__ . '/data/properties-types.php', + ], + [ + [ + 'Property PropertiesTypes\Foo::$bar has unknown class PropertiesTypes\Bar as its type.', + 12, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$bars has unknown class PropertiesTypes\Bar as its type.', + 18, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$dolors has unknown class PropertiesTypes\Dolor as its type.', + 21, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$dolors has unknown class PropertiesTypes\Ipsum as its type.', + 21, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$fooWithWrongCase has unknown class PropertiesTypes\BAR as its type.', + 24, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$fooWithWrongCase has unknown class PropertiesTypes\Fooo as its type.', + 24, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class PropertiesTypes\Foo referenced with incorrect case: PropertiesTypes\FOO.', + 24, + ], + [ + 'Property PropertiesTypes\Foo::$withTrait has invalid type PropertiesTypes\SomeTrait.', + 27, + ], + [ + 'Property PropertiesTypes\Foo::$nonexistentClassInGenericObjectType has unknown class PropertiesTypes\Foooo as its type.', + 33, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesTypes\Foo::$nonexistentClassInGenericObjectType has unknown class PropertiesTypes\Barrrr as its type.', + 33, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ] + ); + } - $this->analyse([__DIR__ . '/data/properties-native-types.php'], [ - [ - 'Property PropertiesNativeTypes\Foo::$bar has unknown class PropertiesNativeTypes\Bar as its type.', - 10, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesNativeTypes\Foo::$baz has unknown class PropertiesNativeTypes\Baz as its type.', - 13, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PropertiesNativeTypes\Foo::$baz has unknown class PropertiesNativeTypes\Baz as its type.', - 13, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testNativeTypes(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + $this->analyse([__DIR__ . '/data/properties-native-types.php'], [ + [ + 'Property PropertiesNativeTypes\Foo::$bar has unknown class PropertiesNativeTypes\Bar as its type.', + 10, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesNativeTypes\Foo::$baz has unknown class PropertiesNativeTypes\Baz as its type.', + 13, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PropertiesNativeTypes\Foo::$baz has unknown class PropertiesNativeTypes\Baz as its type.', + 13, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } - $this->analyse([__DIR__ . '/data/properties-promoted-types.php'], [ - [ - 'Property PromotedPropertiesExistingClasses\Foo::$baz has invalid type PromotedPropertiesExistingClasses\SomeTrait.', - 11, - ], - [ - 'Property PromotedPropertiesExistingClasses\Foo::$lorem has invalid type PromotedPropertiesExistingClasses\SomeTrait.', - 12, - ], - [ - 'Property PromotedPropertiesExistingClasses\Foo::$ipsum has unknown class PromotedPropertiesExistingClasses\Bar as its type.', - 13, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Property PromotedPropertiesExistingClasses\Foo::$dolor has unknown class PromotedPropertiesExistingClasses\Bar as its type.', - 14, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/properties-promoted-types.php'], [ + [ + 'Property PromotedPropertiesExistingClasses\Foo::$baz has invalid type PromotedPropertiesExistingClasses\SomeTrait.', + 11, + ], + [ + 'Property PromotedPropertiesExistingClasses\Foo::$lorem has invalid type PromotedPropertiesExistingClasses\SomeTrait.', + 12, + ], + [ + 'Property PromotedPropertiesExistingClasses\Foo::$ipsum has unknown class PromotedPropertiesExistingClasses\Bar as its type.', + 13, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Property PromotedPropertiesExistingClasses\Foo::$dolor has unknown class PromotedPropertiesExistingClasses\Bar as its type.', + 14, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index 622295adab..f149ccd734 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new MissingPropertyTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - $broker = $this->createReflectionProvider(); - return new MissingPropertyTypehintRule(new MissingTypehintCheck($broker, true, true, true)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/missing-property-typehint.php'], [ - [ - 'Property MissingPropertyTypehint\MyClass::$prop1 has no typehint specified.', - 7, - ], - [ - 'Property MissingPropertyTypehint\MyClass::$prop2 has no typehint specified.', - 9, - ], - [ - 'Property MissingPropertyTypehint\MyClass::$prop3 has no typehint specified.', - 14, - ], - [ - 'Property MissingPropertyTypehint\ChildClass::$unionProp type has no value type specified in iterable type array.', - 32, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - [ - 'Property MissingPropertyTypehint\Bar::$foo with generic interface MissingPropertyTypehint\GenericInterface does not specify its types: T, U', - 74, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Property MissingPropertyTypehint\Bar::$baz with generic class MissingPropertyTypehint\GenericClass does not specify its types: A, B', - 80, - 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', - ], - [ - 'Property MissingPropertyTypehint\CallableSignature::$cb type has no signature specified for callable.', - 93, - ], - ]); - } - - public function testBug3402(): void - { - $this->analyse([__DIR__ . '/data/bug-3402.php'], []); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-property-typehint.php'], [ + [ + 'Property MissingPropertyTypehint\MyClass::$prop1 has no typehint specified.', + 7, + ], + [ + 'Property MissingPropertyTypehint\MyClass::$prop2 has no typehint specified.', + 9, + ], + [ + 'Property MissingPropertyTypehint\MyClass::$prop3 has no typehint specified.', + 14, + ], + [ + 'Property MissingPropertyTypehint\ChildClass::$unionProp type has no value type specified in iterable type array.', + 32, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Property MissingPropertyTypehint\Bar::$foo with generic interface MissingPropertyTypehint\GenericInterface does not specify its types: T, U', + 74, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Property MissingPropertyTypehint\Bar::$baz with generic class MissingPropertyTypehint\GenericClass does not specify its types: A, B', + 80, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Property MissingPropertyTypehint\CallableSignature::$cb type has no signature specified for callable.', + 93, + ], + ]); + } - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - $this->analyse([__DIR__ . '/data/promoted-properties-missing-typehint.php'], [ - [ - 'Property PromotedPropertiesMissingTypehint\Foo::$lorem has no typehint specified.', - 15, - ], - [ - 'Property PromotedPropertiesMissingTypehint\Foo::$ipsum type has no value type specified in iterable type array.', - 16, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - ]); - } + public function testBug3402(): void + { + $this->analyse([__DIR__ . '/data/bug-3402.php'], []); + } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/promoted-properties-missing-typehint.php'], [ + [ + 'Property PromotedPropertiesMissingTypehint\Foo::$lorem has no typehint specified.', + 15, + ], + [ + 'Property PromotedPropertiesMissingTypehint\Foo::$ipsum type has no value type specified in iterable type array.', + 16, + MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php index 5ed217dd66..e00e2a009b 100644 --- a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php +++ b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/nullsafe-property-fetch-rule.php'], [ - [ - 'Using nullsafe property access on non-nullable type Exception. Use -> instead.', - 16, - ], - ]); - } + public function testRule(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/nullsafe-property-fetch-rule.php'], [ + [ + 'Using nullsafe property access on non-nullable type Exception. Use -> instead.', + 16, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index 006cd4433f..767a834538 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(); + return new PropertyAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true), + new NullsafeCheck(), + new PhpVersion(80000), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - return new PropertyAttributesRule( - new AttributesCheck( - $reflectionProvider, - new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), - new NullsafeCheck(), - new PhpVersion(80000), - true, - true, - true, - true - ), - new ClassCaseSensitivityCheck($reflectionProvider, false) - ) - ); - } - - public function testRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/property-attributes.php'], [ - [ - 'Attribute class PropertyAttributes\Foo does not have the property target.', - 26, - ], - ]); - } + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/property-attributes.php'], [ + [ + 'Attribute class PropertyAttributes\Foo does not have the property target.', + 26, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php index f496d70757..9c1b9cc6c3 100644 --- a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, $this->checkThisOnly, true, false), $this->checkThisOnly); - } - - public function testPropertyMustBeReadableInAssignOp(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ - [ - 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 22, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 32, - ], - ]); - } + protected function getRule(): \PHPStan\Rules\Rule + { + return new ReadingWriteOnlyPropertiesRule(new PropertyDescriptor(), new PropertyReflectionFinder(), new RuleLevelHelper($this->createReflectionProvider(), true, $this->checkThisOnly, true, false), $this->checkThisOnly); + } - public function testPropertyMustBeReadableInAssignOpCheckThisOnly(): void - { - $this->checkThisOnly = true; - $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ - [ - 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 22, - ], - ]); - } + public function testPropertyMustBeReadableInAssignOp(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ + [ + 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 22, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 32, + ], + ]); + } - public function testReadingWriteOnlyProperties(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [ - [ - 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 17, - ], - [ - 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 22, - ], - ]); - } + public function testPropertyMustBeReadableInAssignOpCheckThisOnly(): void + { + $this->checkThisOnly = true; + $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ + [ + 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 22, + ], + ]); + } - public function testReadingWriteOnlyPropertiesCheckThisOnly(): void - { - $this->checkThisOnly = true; - $this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [ - [ - 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 17, - ], - ]); - } + public function testReadingWriteOnlyProperties(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [ + [ + 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 17, + ], + [ + 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 22, + ], + ]); + } - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testReadingWriteOnlyPropertiesCheckThisOnly(): void + { + $this->checkThisOnly = true; + $this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [ + [ + 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 17, + ], + ]); + } - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/reading-write-only-properties-nullsafe.php'], [ - [ - 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', - 9, - ], - ]); - } + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/reading-write-only-properties-nullsafe.php'], [ + [ + 'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.', + 9, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index c50283c221..2e7d81329e 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder()); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder()); - } - - public function testTypesAssignedToProperties(): void - { - $this->analyse([__DIR__ . '/data/properties-assigned-types.php'], [ - [ - 'Property PropertiesAssignedTypes\Foo::$stringProperty (string) does not accept int.', - 29, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$intProperty (int) does not accept string.', - 31, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$fooProperty (PropertiesAssignedTypes\Foo) does not accept PropertiesAssignedTypes\Bar.', - 33, - ], - [ - 'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.', - 35, - ], - [ - 'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.', - 37, - ], - [ - 'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.', - 39, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Foo.', - 44, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept array.', - 45, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Bar.', - 46, - ], - [ - 'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.', - 48, - ], - [ - 'Static property PropertiesAssignedTypes\Ipsum::$parentStaticStringProperty (string) does not accept int.', - 50, - ], - [ - 'Property PropertiesAssignedTypes\Foo::$intProperty (int) does not accept string.', - 60, - ], - [ - 'Property PropertiesAssignedTypes\Ipsum::$foo (PropertiesAssignedTypes\Ipsum) does not accept PropertiesAssignedTypes\Bar.', - 143, - ], - [ - 'Static property PropertiesAssignedTypes\Ipsum::$fooStatic (PropertiesAssignedTypes\Ipsum) does not accept PropertiesAssignedTypes\Bar.', - 144, - ], - [ - 'Property PropertiesAssignedTypes\AssignRefFoo::$stringProperty (string) does not accept int.', - 312, - ], - ]); - } - - public function testBug1216(): void - { - $this->analyse([__DIR__ . '/data/bug-1216.php'], [ - [ - 'Property Bug1216PropertyTest\Baz::$untypedBar (string) does not accept int.', - 35, - ], - [ - 'Property Bug1216PropertyTest\Dummy::$foo (Exception) does not accept stdClass.', - 59, - ], - ]); - } + public function testTypesAssignedToProperties(): void + { + $this->analyse([__DIR__ . '/data/properties-assigned-types.php'], [ + [ + 'Property PropertiesAssignedTypes\Foo::$stringProperty (string) does not accept int.', + 29, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$intProperty (int) does not accept string.', + 31, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$fooProperty (PropertiesAssignedTypes\Foo) does not accept PropertiesAssignedTypes\Bar.', + 33, + ], + [ + 'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.', + 35, + ], + [ + 'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.', + 37, + ], + [ + 'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.', + 39, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Foo.', + 44, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept array.', + 45, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Bar.', + 46, + ], + [ + 'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.', + 48, + ], + [ + 'Static property PropertiesAssignedTypes\Ipsum::$parentStaticStringProperty (string) does not accept int.', + 50, + ], + [ + 'Property PropertiesAssignedTypes\Foo::$intProperty (int) does not accept string.', + 60, + ], + [ + 'Property PropertiesAssignedTypes\Ipsum::$foo (PropertiesAssignedTypes\Ipsum) does not accept PropertiesAssignedTypes\Bar.', + 143, + ], + [ + 'Static property PropertiesAssignedTypes\Ipsum::$fooStatic (PropertiesAssignedTypes\Ipsum) does not accept PropertiesAssignedTypes\Bar.', + 144, + ], + [ + 'Property PropertiesAssignedTypes\AssignRefFoo::$stringProperty (string) does not accept int.', + 312, + ], + ]); + } - public function testTypesAssignedToPropertiesExpressionNames(): void - { - $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ - [ - 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', - 42, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', - 54, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept stdClass.', - 66, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.', - 69, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.', - 69, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.', - 69, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept (float|int).', - 73, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float.', - 83, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.', - 97, - ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', - 110, - ], - [ - 'Property PropertiesFromArrayIntoObject\FooBar::$foo (string) does not accept float.', - 147, - ], - ]); - } + public function testBug1216(): void + { + $this->analyse([__DIR__ . '/data/bug-1216.php'], [ + [ + 'Property Bug1216PropertyTest\Baz::$untypedBar (string) does not accept int.', + 35, + ], + [ + 'Property Bug1216PropertyTest\Dummy::$foo (Exception) does not accept stdClass.', + 59, + ], + ]); + } - public function testTypesAssignedToStaticPropertiesExpressionNames(): void - { - $this->analyse([__DIR__ . '/data/properties-from-array-into-static-object.php'], [ - [ - 'Static property PropertiesFromArrayIntoStaticObject\Foo::$lall (stdClass|null) does not accept string.', - 29, - ], - [ - 'Static property PropertiesFromArrayIntoStaticObject\Foo::$foo (string) does not accept float.', - 36, - ], - [ - 'Static property PropertiesFromArrayIntoStaticObject\FooBar::$foo (string) does not accept float.', - 72, - ], - ]); - } + public function testTypesAssignedToPropertiesExpressionNames(): void + { + $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ + [ + 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', + 42, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', + 54, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept stdClass.', + 66, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.', + 69, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.', + 69, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.', + 69, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept (float|int).', + 73, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float.', + 83, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.', + 97, + ], + [ + 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', + 110, + ], + [ + 'Property PropertiesFromArrayIntoObject\FooBar::$foo (string) does not accept float.', + 147, + ], + ]); + } - public function testBug3777(): void - { - $this->analyse([__DIR__ . '/data/bug-3777.php'], [ - [ - 'Property Bug3777\Bar::$foo (Bug3777\Foo) does not accept Bug3777\Fooo.', - 58, - ], - [ - 'Property Bug3777\Ipsum::$ipsum (Bug3777\Lorem) does not accept Bug3777\Lorem.', - 95, - ], - [ - 'Property Bug3777\Ipsum2::$lorem2 (Bug3777\Lorem2) does not accept Bug3777\Lorem2.', - 129, - ], - [ - 'Property Bug3777\Ipsum2::$ipsum2 (Bug3777\Lorem2) does not accept Bug3777\Lorem2.', - 131, - ], - [ - 'Property Bug3777\Ipsum3::$ipsum3 (Bug3777\Lorem3) does not accept Bug3777\Lorem3.', - 168, - ], - ]); - } + public function testTypesAssignedToStaticPropertiesExpressionNames(): void + { + $this->analyse([__DIR__ . '/data/properties-from-array-into-static-object.php'], [ + [ + 'Static property PropertiesFromArrayIntoStaticObject\Foo::$lall (stdClass|null) does not accept string.', + 29, + ], + [ + 'Static property PropertiesFromArrayIntoStaticObject\Foo::$foo (string) does not accept float.', + 36, + ], + [ + 'Static property PropertiesFromArrayIntoStaticObject\FooBar::$foo (string) does not accept float.', + 72, + ], + ]); + } + public function testBug3777(): void + { + $this->analyse([__DIR__ . '/data/bug-3777.php'], [ + [ + 'Property Bug3777\Bar::$foo (Bug3777\Foo) does not accept Bug3777\Fooo.', + 58, + ], + [ + 'Property Bug3777\Ipsum::$ipsum (Bug3777\Lorem) does not accept Bug3777\Lorem.', + 95, + ], + [ + 'Property Bug3777\Ipsum2::$lorem2 (Bug3777\Lorem2) does not accept Bug3777\Lorem2.', + 129, + ], + [ + 'Property Bug3777\Ipsum2::$ipsum2 (Bug3777\Lorem2) does not accept Bug3777\Lorem2.', + 131, + ], + [ + 'Property Bug3777\Ipsum3::$ipsum3 (Bug3777\Lorem3) does not accept Bug3777\Lorem3.', + 168, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index b4b5937bfa..8a00a18065 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -1,4 +1,6 @@ -getDeclaringClass()->getName() === 'UninitializedProperty\\TestExtension' && $propertyName === 'inited'; - } - - }, - ]), - [ - 'UninitializedProperty\\TestCase::setUp', - ] - ); - } + public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool + { + return false; + } - public function testRule(): void - { - if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + public function isInitialized(PropertyReflection $property, string $propertyName): bool + { + return $property->getDeclaringClass()->getName() === 'UninitializedProperty\\TestExtension' && $propertyName === 'inited'; + } + }, + ]), + [ + 'UninitializedProperty\\TestCase::setUp', + ] + ); + } - $this->analyse([__DIR__ . '/data/uninitialized-property.php'], [ - [ - 'Class UninitializedProperty\Foo has an uninitialized property $bar. Give it default value or assign it in the constructor.', - 10, - ], - [ - 'Class UninitializedProperty\Foo has an uninitialized property $baz. Give it default value or assign it in the constructor.', - 12, - ], - [ - 'Access to an uninitialized property UninitializedProperty\Bar::$foo.', - 33, - ], - [ - 'Class UninitializedProperty\Lorem has an uninitialized property $baz. Give it default value or assign it in the constructor.', - 59, - ], - [ - 'Class UninitializedProperty\TestExtension has an uninitialized property $uninited. Give it default value or assign it in the constructor.', - 122, - ], - ]); - } + public function testRule(): void + { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } - public function testPromotedProperties(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0'); - } + $this->analyse([__DIR__ . '/data/uninitialized-property.php'], [ + [ + 'Class UninitializedProperty\Foo has an uninitialized property $bar. Give it default value or assign it in the constructor.', + 10, + ], + [ + 'Class UninitializedProperty\Foo has an uninitialized property $baz. Give it default value or assign it in the constructor.', + 12, + ], + [ + 'Access to an uninitialized property UninitializedProperty\Bar::$foo.', + 33, + ], + [ + 'Class UninitializedProperty\Lorem has an uninitialized property $baz. Give it default value or assign it in the constructor.', + 59, + ], + [ + 'Class UninitializedProperty\TestExtension has an uninitialized property $uninited. Give it default value or assign it in the constructor.', + 122, + ], + ]); + } - $this->analyse([__DIR__ . '/data/uninitialized-property-promoted.php'], []); - } + public function testPromotedProperties(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/uninitialized-property-promoted.php'], []); + } } diff --git a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php index c5415f6c57..12b7d6c68e 100644 --- a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); - } - - public function testCheckThisOnlyProperties(): void - { - $this->checkThisOnly = true; - $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 15, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 16, - ], - ]); - } - - public function testCheckAllProperties(): void - { - $this->checkThisOnly = false; - $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 15, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 16, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 25, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 26, - ], - [ - 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', - 35, - ], - ]); - } - + /** @var bool */ + private $checkThisOnly; + + protected function getRule(): \PHPStan\Rules\Rule + { + return new WritingToReadOnlyPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); + } + + public function testCheckThisOnlyProperties(): void + { + $this->checkThisOnly = true; + $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 15, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 16, + ], + ]); + } + + public function testCheckAllProperties(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [ + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 15, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 16, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 25, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 26, + ], + [ + 'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.', + 35, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php b/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php index 434d25be5d..12458d742d 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php @@ -4,52 +4,38 @@ class Foo { - - /** @var self|null */ - private $fooProperty; - - /** - * @param self|null $foo - */ - public function doFoo($foo) - { - if (is_null($foo) && $foo->fooProperty) { - - } - if (is_null($foo) || $foo->fooProperty) { - - } - if (!is_null($foo) && $foo->fooProperty) { - - } - if (!is_null($foo) || $foo->fooProperty) { - - } - if (is_null($foo) || $foo->barProperty) { - - } - if (!is_null($foo) && $foo->barProperty) { - - } - - while (is_null($foo) && $foo->fooProperty) { - - } - while (is_null($foo) || $foo->fooProperty) { - - } - while (!is_null($foo) && $foo->fooProperty) { - - } - while (!is_null($foo) || $foo->fooProperty) { - - } - while (is_null($foo) || $foo->barProperty) { - - } - while (!is_null($foo) && $foo->barProperty) { - - } - } - + /** @var self|null */ + private $fooProperty; + + /** + * @param self|null $foo + */ + public function doFoo($foo) + { + if (is_null($foo) && $foo->fooProperty) { + } + if (is_null($foo) || $foo->fooProperty) { + } + if (!is_null($foo) && $foo->fooProperty) { + } + if (!is_null($foo) || $foo->fooProperty) { + } + if (is_null($foo) || $foo->barProperty) { + } + if (!is_null($foo) && $foo->barProperty) { + } + + while (is_null($foo) && $foo->fooProperty) { + } + while (is_null($foo) || $foo->fooProperty) { + } + while (!is_null($foo) && $foo->fooProperty) { + } + while (!is_null($foo) || $foo->fooProperty) { + } + while (is_null($foo) || $foo->barProperty) { + } + while (!is_null($foo) && $foo->barProperty) { + } + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties-assign.php b/tests/PHPStan/Rules/Properties/data/access-properties-assign.php index 36c26da329..8e0178dc1d 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties-assign.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties-assign.php @@ -4,15 +4,13 @@ class AccessPropertyWithDimFetch { + public function doFoo() + { + $this->foo['foo'] = 'test'; // already reported by a separate rule + } - public function doFoo() - { - $this->foo['foo'] = 'test'; // already reported by a separate rule - } - - public function doBar() - { - $this->foo = 'test'; - } - + public function doBar() + { + $this->foo = 'test'; + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties-class-exists.php b/tests/PHPStan/Rules/Properties/data/access-properties-class-exists.php index 0cc1391a43..10c707dab2 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties-class-exists.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties-class-exists.php @@ -6,34 +6,32 @@ class Foo { - - /** @var Bar|Baz */ - private $union; - - public function doFoo(): void - { - echo $this->union->lorem; - - if (class_exists(Bar::class)) { - echo $this->union->lorem; - } - - if (class_exists(Baz::class)) { - echo $this->union->lorem; - } - - if (class_exists(Bar::class) && class_exists(Baz::class)) { - echo $this->union->lorem; - } - } - - public function doBar($arg): void - { - if (class_exists(Bar::class) && class_exists(Baz::class)) { - if (is_int($arg->foo)) { - echo $this->union->lorem; - } - } - } - + /** @var Bar|Baz */ + private $union; + + public function doFoo(): void + { + echo $this->union->lorem; + + if (class_exists(Bar::class)) { + echo $this->union->lorem; + } + + if (class_exists(Baz::class)) { + echo $this->union->lorem; + } + + if (class_exists(Bar::class) && class_exists(Baz::class)) { + echo $this->union->lorem; + } + } + + public function doBar($arg): void + { + if (class_exists(Bar::class) && class_exists(Baz::class)) { + if (is_int($arg->foo)) { + echo $this->union->lorem; + } + } + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties.php b/tests/PHPStan/Rules/Properties/data/access-properties.php index cade6d5480..dc1b80716d 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties.php @@ -4,417 +4,362 @@ class FooAccessProperties { + private $foo; - private $foo; - - protected $bar; - - public $ipsum; + protected $bar; + public $ipsum; } class BarAccessProperties extends FooAccessProperties { - - private $foobar; - - public function foo() - { - $this->loremipsum; // nonexistent - $this->foo; // private from an ancestor - $this->bar; - $this->ipsum; - $this->foobar; - Foo::class; - - $string = 'foo'; - $string->propertyOnString; - } - + private $foobar; + + public function foo() + { + $this->loremipsum; // nonexistent + $this->foo; // private from an ancestor + $this->bar; + $this->ipsum; + $this->foobar; + Foo::class; + + $string = 'foo'; + $string->propertyOnString; + } } class BazAccessProperties { - - public function foo(\stdClass $stdClass) - { - $foo = new FooAccessProperties(); - $foo->foo; - $foo->bar; - $foo->ipsum; - if (isset($foo->baz)) { - $foo->baz; - } - isset($foo->baz); - $foo->baz; - $stdClass->foo; - if (!isset($foo->nonexistent)) { - $foo->nonexistent; - return; - } - $foo->nonexistent; - - $fooAlias = new FooAccessPropertiesAlias(); - $fooAlias->foo; - $fooAlias->bar; - $fooAlias->ipsum; - - $bar = new UnknownClass(); - $bar->foo; - - if (!empty($foo->emptyBaz)) { - $foo->emptyBaz; - } - $foo->emptyBaz; - if (empty($foo->emptyNonexistent)) { - $foo->emptyNonexistent; - return; - } - $foo->emptyNonexistent; - - isset($foo->anotherNonexistent) ? $foo->anotherNonexistent : null; - isset($foo->anotherNonexistent) ? null : $foo->anotherNonexistent; - !isset($foo->anotherNonexistent) ? $foo->anotherNonexistent : null; - !isset($foo->anotherNonexistent) ? null : $foo->anotherNonexistent; - - empty($foo->anotherEmptyNonexistent) ? $foo->anotherEmptyNonexistent : null; - empty($foo->anotherEmptyNonexistent) ? null : $foo->anotherEmptyNonexistent; - !empty($foo->anotherEmptyNonexistent) ? $foo->anotherEmptyNonexistent : null; - !empty($foo->anotherEmptyNonexistent) ? null : $foo->anotherEmptyNonexistent; - - $doc = new \DOMDocument(); - $doc->firstChild; - $doc->childNodes[0]; - - /** @var \DOMElement $el */ - $el = doFoo(); - $el->textContent; - } - + public function foo(\stdClass $stdClass) + { + $foo = new FooAccessProperties(); + $foo->foo; + $foo->bar; + $foo->ipsum; + if (isset($foo->baz)) { + $foo->baz; + } + isset($foo->baz); + $foo->baz; + $stdClass->foo; + if (!isset($foo->nonexistent)) { + $foo->nonexistent; + return; + } + $foo->nonexistent; + + $fooAlias = new FooAccessPropertiesAlias(); + $fooAlias->foo; + $fooAlias->bar; + $fooAlias->ipsum; + + $bar = new UnknownClass(); + $bar->foo; + + if (!empty($foo->emptyBaz)) { + $foo->emptyBaz; + } + $foo->emptyBaz; + if (empty($foo->emptyNonexistent)) { + $foo->emptyNonexistent; + return; + } + $foo->emptyNonexistent; + + isset($foo->anotherNonexistent) ? $foo->anotherNonexistent : null; + isset($foo->anotherNonexistent) ? null : $foo->anotherNonexistent; + !isset($foo->anotherNonexistent) ? $foo->anotherNonexistent : null; + !isset($foo->anotherNonexistent) ? null : $foo->anotherNonexistent; + + empty($foo->anotherEmptyNonexistent) ? $foo->anotherEmptyNonexistent : null; + empty($foo->anotherEmptyNonexistent) ? null : $foo->anotherEmptyNonexistent; + !empty($foo->anotherEmptyNonexistent) ? $foo->anotherEmptyNonexistent : null; + !empty($foo->anotherEmptyNonexistent) ? null : $foo->anotherEmptyNonexistent; + + $doc = new \DOMDocument(); + $doc->firstChild; + $doc->childNodes[0]; + + /** @var \DOMElement $el */ + $el = doFoo(); + $el->textContent; + } } class NullPropertyIssue { - - /** @var FooAccessProperties|null */ - private $fooOrNull; - - public function doFoo() - { - if ($this->fooOrNull !== null) { - return $this->fooOrNull; - } - - if (doSomething()) { - $this->fooOrNull = new FooAccessProperties(); - } else { - $this->fooOrNull = new FooAccessProperties(); - } - - $this->fooOrNull->ipsum; - } - + /** @var FooAccessProperties|null */ + private $fooOrNull; + + public function doFoo() + { + if ($this->fooOrNull !== null) { + return $this->fooOrNull; + } + + if (doSomething()) { + $this->fooOrNull = new FooAccessProperties(); + } else { + $this->fooOrNull = new FooAccessProperties(); + } + + $this->fooOrNull->ipsum; + } } class IssetIssue { - - public function doFoo($data) - { - $data = $this->returnMixed(); - - isset($data['action']['test']) ? 'foo' : 'bar'; - isset($data->element[0]['foo']) ? (string) $data->element[0]['bar'] : ''; - isset($data->anotherElement[0]['code']) ? (string) $data->anotherElement[0]['code'] : ''; - } - - public function returnMixed() - { - - } - + public function doFoo($data) + { + $data = $this->returnMixed(); + + isset($data['action']['test']) ? 'foo' : 'bar'; + isset($data->element[0]['foo']) ? (string) $data->element[0]['bar'] : ''; + isset($data->anotherElement[0]['code']) ? (string) $data->anotherElement[0]['code'] : ''; + } + + public function returnMixed() + { + } } class PropertiesWithUnknownClasses { + /** @var FirstUnknownClass|SecondUnknownClass */ + private $foo; - /** @var FirstUnknownClass|SecondUnknownClass */ - private $foo; - - public function doFoo() - { - $this->foo->test; - } - + public function doFoo() + { + $this->foo->test; + } } class WithFooProperty { - - public $foo; - + public $foo; } class WithFooAndBarProperty { + public $foo; - public $foo; - - public $bar; - + public $bar; } class PropertiesOnUnionType { - - /** @var WithFooProperty|WithFooAndBarProperty */ - private $object; - - public function doFoo() - { - $this->object->foo; // fine - $this->object->bar; // WithFooProperty does not have $bar - } - + /** @var WithFooProperty|WithFooAndBarProperty */ + private $object; + + public function doFoo() + { + $this->object->foo; // fine + $this->object->bar; // WithFooProperty does not have $bar + } } interface SomeInterface { - } class PropertiesOnIntersectionType { - - public function doFoo(WithFooProperty $foo) - { - if ($foo instanceof SomeInterface) { - $foo->foo; - $foo->bar; - } - } - + public function doFoo(WithFooProperty $foo) + { + if ($foo instanceof SomeInterface) { + $foo->foo; + $foo->bar; + } + } } class IgnoreNullableUnionProperty { + /** @var FooAccessProperties|null */ + private $foo; - /** @var FooAccessProperties|null */ - private $foo; - - public function doFoo() - { - $this->foo->ipsum; - } - + public function doFoo() + { + $this->foo->ipsum; + } } class AccessNullProperty { + /** @var null */ + private $test; - /** @var null */ - private $test; - - public function doFoo() - { - $this->test->foo; - } - + public function doFoo() + { + $this->test->foo; + } } class CheckingPropertyNotNullInIfCondition { - - public function doFoo() - { - $foo = null; - $bar = null; - if (null !== $foo ? $foo->ipsum : false) { - - } elseif ($bar !== null ? $bar->ipsum : false) { - - } - } - + public function doFoo() + { + $foo = null; + $bar = null; + if (null !== $foo ? $foo->ipsum : false) { + } elseif ($bar !== null ? $bar->ipsum : false) { + } + } } class PropertyExists { - - public function doFoo() - { - $foo = new FooAccessProperties(); - $foo->lorem; - if (property_exists($foo, 'lorem')) { - $foo->lorem; - $foo->dolor; - } - } - + public function doFoo() + { + $foo = new FooAccessProperties(); + $foo->lorem; + if (property_exists($foo, 'lorem')) { + $foo->lorem; + $foo->dolor; + } + } } class NullCoalesce { + /** @var self|null */ + private $foo; - /** @var self|null */ - private $foo; + public function doFoo() + { + $this->foo->bar ?? 'bar'; - public function doFoo() - { - $this->foo->bar ?? 'bar'; + if ($this->foo->bar ?? 'bar') { + } - if ($this->foo->bar ?? 'bar') { - - } - - ($this->foo->bar ?? 'bar') ? 'foo' : 'bar'; - - $this->foo->foo->foo->bar; - } + ($this->foo->bar ?? 'bar') ? 'foo' : 'bar'; + $this->foo->foo->foo->bar; + } } class IssetPropertyInWhile { - - public function doFoo() - { - while (isset($this->foo)) { - echo $this->foo; - } - } - + public function doFoo() + { + while (isset($this->foo)) { + echo $this->foo; + } + } } class AnonyousClass { - - public function doFoo() - { - $foo = new class () { - public $fooProperty; - }; - - $foo->fooProperty; - $foo->barProperty; - } - + public function doFoo() + { + $foo = new class() { + public $fooProperty; + }; + + $foo->fooProperty; + $foo->barProperty; + } } class PropertyIssetOnPossibleFalse { - - /** @var int */ - private $bar; - - /** - * @param self|false $selfOrFalse - */ - public function doFoo($selfOrFalse) - { - if (isset($selfOrFalse->foo)) { - echo $selfOrFalse->foo; - echo $selfOrFalse->bar; - } - } - + /** @var int */ + private $bar; + + /** + * @param self|false $selfOrFalse + */ + public function doFoo($selfOrFalse) + { + if (isset($selfOrFalse->foo)) { + echo $selfOrFalse->foo; + echo $selfOrFalse->bar; + } + } } class WeirdErrorWithCall { + private $var; - private $var; - - /** @var \Closure */ - private $closure; + /** @var \Closure */ + private $closure; - /** @var object */ - private $context; + /** @var object */ + private $context; - public function doFoo() - { - $this->var->call(1); - } - - public function doBar() - { - $this->closure->call($this->context); - } + public function doFoo() + { + $this->var->call(1); + } + public function doBar() + { + $this->closure->call($this->context); + } } class ClosureCallSupport { - - public function doFoo() - { - $foo = new FooAccessProperties(); - (function () { - $this->foo = 'test'; - })->call($foo); - } - + public function doFoo() + { + $foo = new FooAccessProperties(); + (function () { + $this->foo = 'test'; + })->call($foo); + } } class AccessPropertyWithDimFetch { - - public function doFoo() - { - $this->foo['foo'] = 'test'; - } - - public function doBar() - { - $this->foo = 'test'; // reported by a separate rule - } - + public function doFoo() + { + $this->foo['foo'] = 'test'; + } + + public function doBar() + { + $this->foo = 'test'; // reported by a separate rule + } } class AccessInIsset { - - public function doFoo() - { - if (isset($this->foo)) { - - } - } - - public function doBar() - { - if (isset($this->foo['foo'])) { - - } - } - + public function doFoo() + { + if (isset($this->foo)) { + } + } + + public function doBar() + { + if (isset($this->foo['foo'])) { + } + } } class RevertNonNullabilityForIsset { - - /** @var self|null */ - private $selfOrNull; - - public function doFoo() - { - isset($this->selfOrNull->selfOrNull); - echo $this->selfOrNull->selfOrNull; - } - + /** @var self|null */ + private $selfOrNull; + + public function doFoo() + { + isset($this->selfOrNull->selfOrNull); + echo $this->selfOrNull->selfOrNull; + } } class Bug1884 { - - function mustReport(?\stdClass $nullable): bool - { - return isset($nullable->array['key']); - } - - function mustNotReport(?\stdClass $nullable): bool - { - return isset($nullable, $nullable->array['key']); - } - + public function mustReport(?\stdClass $nullable): bool + { + return isset($nullable->array['key']); + } + + public function mustNotReport(?\stdClass $nullable): bool + { + return isset($nullable, $nullable->array['key']); + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-static-properties-assign.php b/tests/PHPStan/Rules/Properties/data/access-static-properties-assign.php index a3a8f1b652..0fde510024 100644 --- a/tests/PHPStan/Rules/Properties/data/access-static-properties-assign.php +++ b/tests/PHPStan/Rules/Properties/data/access-static-properties-assign.php @@ -4,15 +4,13 @@ class AccessStaticPropertyWithDimFetch { + public function doFoo() + { + self::$foo['foo'] = 'test'; // already reported by a separate rule + } - public function doFoo() - { - self::$foo['foo'] = 'test'; // already reported by a separate rule - } - - public function doBar() - { - self::$foo = 'test'; - } - + public function doBar() + { + self::$foo = 'test'; + } } diff --git a/tests/PHPStan/Rules/Properties/data/access-static-properties.php b/tests/PHPStan/Rules/Properties/data/access-static-properties.php index 84abb1bceb..17e7824b9a 100644 --- a/tests/PHPStan/Rules/Properties/data/access-static-properties.php +++ b/tests/PHPStan/Rules/Properties/data/access-static-properties.php @@ -1,212 +1,195 @@ -foo = 'foo'; // OK - $this->bar = 'bar'; // OK - $this->untypedBar = 123; // error - } - + public function __construct() + { + $this->foo = 'foo'; // OK + $this->bar = 'bar'; // OK + $this->untypedBar = 123; // error + } } trait DecoratorTrait { - - /** @var \stdClass */ - public $foo; - + /** @var \stdClass */ + public $foo; } /** @@ -50,15 +46,13 @@ trait DecoratorTrait */ class Dummy { - - use DecoratorTrait; - + use DecoratorTrait; } function (Dummy $dummy): void { - $dummy->foo = new \stdClass(); + $dummy->foo = new \stdClass(); }; function (Dummy $dummy): void { - $dummy->foo = new \Exception(); + $dummy->foo = new \Exception(); }; diff --git a/tests/PHPStan/Rules/Properties/data/bug-3371.php b/tests/PHPStan/Rules/Properties/data/bug-3371.php index 80f89e9869..6e854c66f7 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-3371.php +++ b/tests/PHPStan/Rules/Properties/data/bug-3371.php @@ -4,39 +4,38 @@ class HelloWorld { - - /** - * returns last error message or false if no errors occurred - * - * @param \mysqli|null|false $mysqli mysql link - * - * @return string|bool error or false - */ - public function getError($mysqli) - { - $error_number = 0; - $mysqliValid = $mysqli !== null && $mysqli !== false; - - if ($mysqliValid) { - $error_number = $mysqli->errno; - $error_message = $mysqli->error; - } - if ($mysqliValid && $error_number === 0) { - $error_number = (int) $mysqli->connect_errno; - $error_message = $mysqli->connect_error; - } - - - if ($mysqli !== null && $mysqli !== false) { - $error_number = $mysqli->errno; - $error_message = $mysqli->error; - } - if ($mysqli !== null && $mysqli !== false && $error_number === 0) { - $error_number = (int) $mysqli->connect_errno; - $error_message = $mysqli->connect_error; - } - - - return 'string'; - } + /** + * returns last error message or false if no errors occurred + * + * @param \mysqli|null|false $mysqli mysql link + * + * @return string|bool error or false + */ + public function getError($mysqli) + { + $error_number = 0; + $mysqliValid = $mysqli !== null && $mysqli !== false; + + if ($mysqliValid) { + $error_number = $mysqli->errno; + $error_message = $mysqli->error; + } + if ($mysqliValid && $error_number === 0) { + $error_number = (int) $mysqli->connect_errno; + $error_message = $mysqli->connect_error; + } + + + if ($mysqli !== null && $mysqli !== false) { + $error_number = $mysqli->errno; + $error_message = $mysqli->error; + } + if ($mysqli !== null && $mysqli !== false && $error_number === 0) { + $error_number = (int) $mysqli->connect_errno; + $error_message = $mysqli->connect_error; + } + + + return 'string'; + } } diff --git a/tests/PHPStan/Rules/Properties/data/bug-3402.php b/tests/PHPStan/Rules/Properties/data/bug-3402.php index 0a71ae1de3..bc8468742e 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-3402.php +++ b/tests/PHPStan/Rules/Properties/data/bug-3402.php @@ -4,9 +4,7 @@ class Foo { - - /** Some comment */ - /** @var self */ - private $foo; - + /** Some comment */ + /** @var self */ + private $foo; } diff --git a/tests/PHPStan/Rules/Properties/data/bug-3777.php b/tests/PHPStan/Rules/Properties/data/bug-3777.php index 6ac99af08b..bfad2257fc 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-3777.php +++ b/tests/PHPStan/Rules/Properties/data/bug-3777.php @@ -6,59 +6,53 @@ class HelloWorld { - /** - * @var \SplObjectStorage<\DateTimeImmutable, null> - */ - public $dates; - - public function __construct() - { - $this->dates = new \SplObjectStorage(); - assertType('SplObjectStorage', $this->dates); - } + /** + * @var \SplObjectStorage<\DateTimeImmutable, null> + */ + public $dates; + + public function __construct() + { + $this->dates = new \SplObjectStorage(); + assertType('SplObjectStorage', $this->dates); + } } /** @template T of object */ class Foo { - - public function __construct() - { - - } - + public function __construct() + { + } } /** @template T of object */ class Fooo { - } class Bar { - - /** @var Foo<\stdClass> */ - private $foo; - - /** @var Fooo<\stdClass> */ - private $fooo; - - public function __construct() - { - $this->foo = new Foo(); - assertType('Bug3777\Foo', $this->foo); - - $this->fooo = new Fooo(); - assertType('Bug3777\Fooo', $this->fooo); - } - - public function doBar() - { - $this->foo = new Fooo(); - assertType('Bug3777\Fooo', $this->foo); - } - + /** @var Foo<\stdClass> */ + private $foo; + + /** @var Fooo<\stdClass> */ + private $fooo; + + public function __construct() + { + $this->foo = new Foo(); + assertType('Bug3777\Foo', $this->foo); + + $this->fooo = new Fooo(); + assertType('Bug3777\Fooo', $this->fooo); + } + + public function doBar() + { + $this->foo = new Fooo(); + assertType('Bug3777\Fooo', $this->foo); + } } /** @@ -67,35 +61,30 @@ public function doBar() */ class Lorem { - - /** - * @param T $t - * @param U $u - */ - public function __construct($t, $u) - { - - } - + /** + * @param T $t + * @param U $u + */ + public function __construct($t, $u) + { + } } class Ipsum { - - /** @var Lorem<\stdClass, \Exception> */ - private $lorem; - - /** @var Lorem<\stdClass, \Exception> */ - private $ipsum; - - public function __construct() - { - $this->lorem = new Lorem(new \stdClass, new \Exception()); - assertType('Bug3777\Lorem', $this->lorem); - $this->ipsum = new Lorem(new \Exception(), new \stdClass); - assertType('Bug3777\Lorem', $this->ipsum); - } - + /** @var Lorem<\stdClass, \Exception> */ + private $lorem; + + /** @var Lorem<\stdClass, \Exception> */ + private $ipsum; + + public function __construct() + { + $this->lorem = new Lorem(new \stdClass(), new \Exception()); + assertType('Bug3777\Lorem', $this->lorem); + $this->ipsum = new Lorem(new \Exception(), new \stdClass()); + assertType('Bug3777\Lorem', $this->ipsum); + } } /** @@ -104,34 +93,29 @@ public function __construct() */ class Lorem2 { - - /** - * @param T $t - */ - public function __construct($t) - { - - } - + /** + * @param T $t + */ + public function __construct($t) + { + } } class Ipsum2 { - - /** @var Lorem2<\stdClass, \Exception> */ - private $lorem2; - - /** @var Lorem2<\stdClass, \Exception> */ - private $ipsum2; - - public function __construct() - { - $this->lorem2 = new Lorem2(new \stdClass); - assertType('Bug3777\Lorem2', $this->lorem2); - $this->ipsum2 = new Lorem2(new \Exception()); - assertType('Bug3777\Lorem2', $this->ipsum2); - } - + /** @var Lorem2<\stdClass, \Exception> */ + private $lorem2; + + /** @var Lorem2<\stdClass, \Exception> */ + private $ipsum2; + + public function __construct() + { + $this->lorem2 = new Lorem2(new \stdClass()); + assertType('Bug3777\Lorem2', $this->lorem2); + $this->ipsum2 = new Lorem2(new \Exception()); + assertType('Bug3777\Lorem2', $this->ipsum2); + } } /** @@ -140,33 +124,28 @@ public function __construct() */ class Lorem3 { - - /** - * @param T $t - * @param U $u - */ - public function __construct($t, $u) - { - - } - + /** + * @param T $t + * @param U $u + */ + public function __construct($t, $u) + { + } } class Ipsum3 { - - /** @var Lorem3<\stdClass, \Exception> */ - private $lorem3; - - /** @var Lorem3<\stdClass, \Exception> */ - private $ipsum3; - - public function __construct() - { - $this->lorem3 = new Lorem3(new \stdClass, new \Exception()); - assertType('Bug3777\Lorem3', $this->lorem3); - $this->ipsum3 = new Lorem3(new \Exception(), new \stdClass()); - assertType('Bug3777\Lorem3', $this->ipsum3); - } - + /** @var Lorem3<\stdClass, \Exception> */ + private $lorem3; + + /** @var Lorem3<\stdClass, \Exception> */ + private $ipsum3; + + public function __construct() + { + $this->lorem3 = new Lorem3(new \stdClass(), new \Exception()); + assertType('Bug3777\Lorem3', $this->lorem3); + $this->ipsum3 = new Lorem3(new \Exception(), new \stdClass()); + assertType('Bug3777\Lorem3', $this->ipsum3); + } } diff --git a/tests/PHPStan/Rules/Properties/data/bug-3947.php b/tests/PHPStan/Rules/Properties/data/bug-3947.php index a028ef02e1..0752133346 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-3947.php +++ b/tests/PHPStan/Rules/Properties/data/bug-3947.php @@ -4,11 +4,11 @@ class HelloWorld { - public function sayHello(\SimpleXMLElement $item): void - { - foreach ($item->items->children() as $groupItem) { - switch ((string)$groupItem->orderType) { - } - } - } + public function sayHello(\SimpleXMLElement $item): void + { + foreach ($item->items->children() as $groupItem) { + switch ((string)$groupItem->orderType) { + } + } + } } diff --git a/tests/PHPStan/Rules/Properties/data/bug-4527.php b/tests/PHPStan/Rules/Properties/data/bug-4527.php index bb2d57a792..c9f4cfc96b 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-4527.php +++ b/tests/PHPStan/Rules/Properties/data/bug-4527.php @@ -1,19 +1,21 @@ -= 8.0 += 8.0 namespace Bug4527; class Foo { - - /** - * @param Bar[] $bars - */ - public function foo(array $bars): void - { - ($bars['randomKey'] ?? null)?->bar; - } + /** + * @param Bar[] $bars + */ + public function foo(array $bars): void + { + ($bars['randomKey'] ?? null)?->bar; + } } -class Bar { - public $bar; +class Bar +{ + public $bar; } diff --git a/tests/PHPStan/Rules/Properties/data/bug-4808.php b/tests/PHPStan/Rules/Properties/data/bug-4808.php index 938a1772b8..7a515cc3d8 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-4808.php +++ b/tests/PHPStan/Rules/Properties/data/bug-4808.php @@ -4,21 +4,21 @@ class A { - /** @var bool */ - private $x = true; + /** @var bool */ + private $x = true; - public function preventUnusedVariable(): bool - { - return $this->x; - } + public function preventUnusedVariable(): bool + { + return $this->x; + } } class B extends A { - public function getPrivateProp(): bool - { - return \Closure::bind(function () { - return $this->x; - }, $this, A::class)(); - } + public function getPrivateProp(): bool + { + return \Closure::bind(function () { + return $this->x; + }, $this, A::class)(); + } } diff --git a/tests/PHPStan/Rules/Properties/data/date-interval-child-properties.php b/tests/PHPStan/Rules/Properties/data/date-interval-child-properties.php index eec242a4a5..5ec0a17d46 100644 --- a/tests/PHPStan/Rules/Properties/data/date-interval-child-properties.php +++ b/tests/PHPStan/Rules/Properties/data/date-interval-child-properties.php @@ -4,14 +4,12 @@ class DateIntervalChild extends \DateInterval { - - public function doFoo() - { - echo $this->invert; - echo $this->d; - echo $this->m; - echo $this->y; - echo $this->nonexistent; - } - + public function doFoo() + { + echo $this->invert; + echo $this->d; + echo $this->m; + echo $this->y; + echo $this->nonexistent; + } } diff --git a/tests/PHPStan/Rules/Properties/data/default-value-for-native-property-type.php b/tests/PHPStan/Rules/Properties/data/default-value-for-native-property-type.php index 2fb9af10f5..455bfbc722 100644 --- a/tests/PHPStan/Rules/Properties/data/default-value-for-native-property-type.php +++ b/tests/PHPStan/Rules/Properties/data/default-value-for-native-property-type.php @@ -4,7 +4,5 @@ class Foo { - - private \DateTime $foo = null; - + private \DateTime $foo = null; } diff --git a/tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php b/tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php index 649b7b2e56..e87398359a 100644 --- a/tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php +++ b/tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php @@ -1,14 +1,16 @@ -= 8.0 += 8.0 namespace DefaultValueForPromotedProperty; class Foo { - - public function __construct( - private int $foo = 'foo', - /** @var int */ private $foo = '', - private int $baz = 1 - ) {} - + public function __construct( + private int $foo = 'foo', + /** @var int */ + private $foo = '', + private int $baz = 1 + ) { + } } diff --git a/tests/PHPStan/Rules/Properties/data/isset-specify.php b/tests/PHPStan/Rules/Properties/data/isset-specify.php index 7b2bcd1d80..ab20dfddf7 100644 --- a/tests/PHPStan/Rules/Properties/data/isset-specify.php +++ b/tests/PHPStan/Rules/Properties/data/isset-specify.php @@ -4,18 +4,18 @@ class Example { - function foo(?ObjectWithArrayProp $nullableObject): bool - { - return isset( - $nullableObject, - $nullableObject->arrayProperty['key'], - $nullableObject->fooProperty['foo'] - ); - } + public function foo(?ObjectWithArrayProp $nullableObject): bool + { + return isset( + $nullableObject, + $nullableObject->arrayProperty['key'], + $nullableObject->fooProperty['foo'] + ); + } } class ObjectWithArrayProp { - /** @var mixed[] */ - public $arrayProperty; + /** @var mixed[] */ + public $arrayProperty; } diff --git a/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php b/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php index 79cba0ab31..5d3f5039fd 100644 --- a/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php +++ b/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php @@ -4,44 +4,41 @@ class MyClass { - private $prop1; + private $prop1; - protected $prop2 = null; + protected $prop2 = null; - /** - * @var - */ - public $prop3; + /** + * @var + */ + public $prop3; } class ChildClass extends MyClass { - /** - * @var int - */ - protected $prop1; - - /** - * @var null - */ - protected $prop2; - - /** - * @var \stdClass|array|int|null - */ - private $unionProp; - + /** + * @var int + */ + protected $prop1; + + /** + * @var null + */ + protected $prop2; + + /** + * @var \stdClass|array|int|null + */ + private $unionProp; } class PrefixedTags { + /** @phpstan-var int */ + private $fooPhpstan; - /** @phpstan-var int */ - private $fooPhpstan; - - /** @psalm-var int */ - private $fooPsalm; - + /** @psalm-var int */ + private $fooPsalm; } /** @@ -50,12 +47,10 @@ class PrefixedTags */ interface GenericInterface { - } class NonGenericClass { - } /** @@ -64,32 +59,27 @@ class NonGenericClass */ class GenericClass { - } class Bar { + /** @var \MissingPropertyTypehint\GenericInterface */ + private $foo; - /** @var \MissingPropertyTypehint\GenericInterface */ - private $foo; - - /** @var \MissingPropertyTypehint\NonGenericClass */ - private $bar; - - /** @var \MissingPropertyTypehint\GenericClass */ - private $baz; + /** @var \MissingPropertyTypehint\NonGenericClass */ + private $bar; + /** @var \MissingPropertyTypehint\GenericClass */ + private $baz; } $foo = new class() { - /** @var float */ - private $dateTime; + /** @var float */ + private $dateTime; }; class CallableSignature { - - /** @var callable */ - private $cb; - + /** @var callable */ + private $cb; } diff --git a/tests/PHPStan/Rules/Properties/data/mixin.php b/tests/PHPStan/Rules/Properties/data/mixin.php index e6ba546647..fb652870c2 100644 --- a/tests/PHPStan/Rules/Properties/data/mixin.php +++ b/tests/PHPStan/Rules/Properties/data/mixin.php @@ -4,9 +4,7 @@ class Foo { - - public $fooProp; - + public $fooProp; } /** @@ -14,20 +12,18 @@ class Foo */ class Bar { - } function (Bar $bar): void { - $bar->fooProp; + $bar->fooProp; }; class Baz extends Bar { - } function (Baz $baz): void { - $baz->fooProp; + $baz->fooProp; }; /** @@ -36,19 +32,16 @@ function (Baz $baz): void { */ class GenericFoo { - } class Test { - - /** - * @param GenericFoo<\ReflectionClass> $foo - */ - public function doFoo(GenericFoo $foo): void - { - echo $foo->name; - echo $foo->namee; - } - + /** + * @param GenericFoo<\ReflectionClass> $foo + */ + public function doFoo(GenericFoo $foo): void + { + echo $foo->name; + echo $foo->namee; + } } diff --git a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch-rule.php b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch-rule.php index 9ce3a97f4b..347e1db31f 100644 --- a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch-rule.php +++ b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch-rule.php @@ -1,20 +1,19 @@ -= 8.0 += 8.0 namespace NullsafePropertyFetchRule; class Foo { - - public function doFoo( - $mixed, - ?\Exception $nullable, - \Exception $nonNullable - ): void - { - $mixed?->foo; - $nullable?->foo; - $nonNullable?->foo; - (null)?->foo; // reported by a different rule - } - + public function doFoo( + $mixed, + ?\Exception $nullable, + \Exception $nonNullable + ): void { + $mixed?->foo; + $nullable?->foo; + $nonNullable?->foo; + (null)?->foo; // reported by a different rule + } } diff --git a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php index 9c6bbb66c4..19ea22206b 100644 --- a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php +++ b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php @@ -1,25 +1,25 @@ -= 8.0 += 8.0 namespace NullsafePropertyFetch; class Foo { + private $bar; - private $bar; - - public function doFoo(?self $selfOrNull): void - { - $selfOrNull?->bar; - $selfOrNull?->baz; - } - - public function doBar(string $string, ?string $nullableString): void - { - echo $string->bar ?? 4; - echo $nullableString->bar ?? 4; + public function doFoo(?self $selfOrNull): void + { + $selfOrNull?->bar; + $selfOrNull?->baz; + } - echo $string?->bar ?? 4; - echo $nullableString?->bar ?? 4; - } + public function doBar(string $string, ?string $nullableString): void + { + echo $string->bar ?? 4; + echo $nullableString->bar ?? 4; + echo $string?->bar ?? 4; + echo $nullableString?->bar ?? 4; + } } diff --git a/tests/PHPStan/Rules/Properties/data/promoted-properties-missing-typehint.php b/tests/PHPStan/Rules/Properties/data/promoted-properties-missing-typehint.php index 533f61edc4..e37d210fdf 100644 --- a/tests/PHPStan/Rules/Properties/data/promoted-properties-missing-typehint.php +++ b/tests/PHPStan/Rules/Properties/data/promoted-properties-missing-typehint.php @@ -1,19 +1,21 @@ -= 8.0 += 8.0 namespace PromotedPropertiesMissingTypehint; class Foo { - - /** - * @param int $baz - */ - public function __construct( - private int $foo, - /** @var int */ private $bar, - private $baz, - private $lorem, - private array $ipsum - ) { } - + /** + * @param int $baz + */ + public function __construct( + private int $foo, + /** @var int */ + private $bar, + private $baz, + private $lorem, + private array $ipsum + ) { + } } diff --git a/tests/PHPStan/Rules/Properties/data/properties-assigned-default-value-types.php b/tests/PHPStan/Rules/Properties/data/properties-assigned-default-value-types.php index 3dcc476244..866496ef0b 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-assigned-default-value-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-assigned-default-value-types.php @@ -4,26 +4,24 @@ class Foo { + /** @var string */ + private $propertyWithoutDefaultValue; - /** @var string */ - private $propertyWithoutDefaultValue; + /** @var string */ + private $stringProperty = 'foo'; - /** @var string */ - private $stringProperty = 'foo'; + /** @var string */ + private $stringPropertyWithWrongDefaultValue = 1; - /** @var string */ - private $stringPropertyWithWrongDefaultValue = 1; + /** @var string */ + private static $staticStringPropertyWithWrongDefaultValue = 1; - /** @var string */ - private static $staticStringPropertyWithWrongDefaultValue = 1; - - /** @var string */ - private $stringPropertyWithDefaultNullValue = null; - - /** @var array */ - private static $windowsNtVersions = [ - '2000' => '5.0', - 'xp' => '5.1', - ]; + /** @var string */ + private $stringPropertyWithDefaultNullValue = null; + /** @var array */ + private static $windowsNtVersions = [ + '2000' => '5.0', + 'xp' => '5.1', + ]; } diff --git a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php index 47288cdfed..9ea2abcd46 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php @@ -1,247 +1,232 @@ -stringProperty = 'foo'; - $this->stringProperty = 1; - $this->intProperty = 1; - $this->intProperty = 'foo'; - $this->fooProperty = new self(); - $this->fooProperty = new Bar(); - self::$staticStringProperty = 'foo'; - self::$staticStringProperty = 1; - Foo::$staticStringProperty = 'foo'; - Foo::$staticStringProperty = 1; - parent::$parentStringProperty = 'foo'; - parent::$parentStringProperty = 1; - $this->nonexistentProperty = 'foo'; - $this->nonexistentProperty = 1; - $this->unionPropertySelf = [new self()]; - $this->unionPropertySelf = new Collection(); - $this->unionPropertySelf = new self(); - $this->unionPropertySelf = [new Bar()]; - $this->unionPropertySelf = new Bar(); - $this->parentStringProperty = 'foo'; - $this->parentStringProperty = 1; - self::$parentStaticStringProperty = 'foo'; - self::$parentStaticStringProperty = 1; - - if ($this->intProperty === null) { - $this->intProperty = 1; - } - } - - public function doBar() - { - $this->intProperty += 1; // OK - $this->intProperty .= 'test'; // property will be string, report error - } - + /** @var string */ + private $stringProperty; + + /** @var int */ + private $intProperty; + + /** @var self */ + private $fooProperty; + + /** @var string */ + private static $staticStringProperty; + + /** @var self[]|Collection|array */ + private $unionPropertySelf; + + /** @var Bar[]|self */ + private $unionPropertyBar; + + public function doFoo() + { + $this->stringProperty = 'foo'; + $this->stringProperty = 1; + $this->intProperty = 1; + $this->intProperty = 'foo'; + $this->fooProperty = new self(); + $this->fooProperty = new Bar(); + self::$staticStringProperty = 'foo'; + self::$staticStringProperty = 1; + Foo::$staticStringProperty = 'foo'; + Foo::$staticStringProperty = 1; + parent::$parentStringProperty = 'foo'; + parent::$parentStringProperty = 1; + $this->nonexistentProperty = 'foo'; + $this->nonexistentProperty = 1; + $this->unionPropertySelf = [new self()]; + $this->unionPropertySelf = new Collection(); + $this->unionPropertySelf = new self(); + $this->unionPropertySelf = [new Bar()]; + $this->unionPropertySelf = new Bar(); + $this->parentStringProperty = 'foo'; + $this->parentStringProperty = 1; + self::$parentStaticStringProperty = 'foo'; + self::$parentStaticStringProperty = 1; + + if ($this->intProperty === null) { + $this->intProperty = 1; + } + } + + public function doBar() + { + $this->intProperty += 1; // OK + $this->intProperty .= 'test'; // property will be string, report error + } } class Ipsum { - - /** @var string */ - protected $parentStringProperty; - - /** @var string */ - protected static $parentStaticStringProperty; - - /** @var int|null */ - private $nullableIntProperty; - - /** @var mixed[]*/ - private $mixedArrayProperty; - - /** @var mixed[]|iterable */ - private $iterableProperty; - - /** @var iterable */ - private $iterableData; - - /** @var Ipsum */ - private $foo; - - /** @var Ipsum */ - private static $fooStatic; - - public function doIpsum() - { - if ($this->nullableIntProperty === null) { - return; - } - - $this->nullableIntProperty = null; - } - - /** - * @param mixed[]|string $scope - */ - public function setScope($scope) - { - if (!is_array($scope)) { - $this->mixedArrayProperty = explode(',', $scope); - } else { - $this->mixedArrayProperty = $scope; - } - } - - /** - * @param int[]|iterable $integers - * @param string[]|iterable $strings - * @param mixed[]|iterable $mixeds - * @param iterable $justIterableInPhpDoc - * @param iterable $justIterableInPhpDocWithCheck - */ - public function setIterable( - iterable $integers, - iterable $strings, - iterable $mixeds, - $justIterableInPhpDoc, - $justIterableInPhpDocWithCheck - ) - { - $this->iterableProperty = $integers; - $this->iterableProperty = $strings; - $this->iterableProperty = $mixeds; - $this->iterableData = $justIterableInPhpDoc; - - if (!is_iterable($justIterableInPhpDocWithCheck)) { - throw new \Exception(); - } - - $this->iterableData = $justIterableInPhpDocWithCheck; - } - - public function doIntersection() - { - if ($this->foo instanceof SomeInterface) { - $this->foo->foo = new Bar(); - self::$fooStatic::$fooStatic = new Bar(); - } - } - + /** @var string */ + protected $parentStringProperty; + + /** @var string */ + protected static $parentStaticStringProperty; + + /** @var int|null */ + private $nullableIntProperty; + + /** @var mixed[]*/ + private $mixedArrayProperty; + + /** @var mixed[]|iterable */ + private $iterableProperty; + + /** @var iterable */ + private $iterableData; + + /** @var Ipsum */ + private $foo; + + /** @var Ipsum */ + private static $fooStatic; + + public function doIpsum() + { + if ($this->nullableIntProperty === null) { + return; + } + + $this->nullableIntProperty = null; + } + + /** + * @param mixed[]|string $scope + */ + public function setScope($scope) + { + if (!is_array($scope)) { + $this->mixedArrayProperty = explode(',', $scope); + } else { + $this->mixedArrayProperty = $scope; + } + } + + /** + * @param int[]|iterable $integers + * @param string[]|iterable $strings + * @param mixed[]|iterable $mixeds + * @param iterable $justIterableInPhpDoc + * @param iterable $justIterableInPhpDocWithCheck + */ + public function setIterable( + iterable $integers, + iterable $strings, + iterable $mixeds, + $justIterableInPhpDoc, + $justIterableInPhpDocWithCheck + ) { + $this->iterableProperty = $integers; + $this->iterableProperty = $strings; + $this->iterableProperty = $mixeds; + $this->iterableData = $justIterableInPhpDoc; + + if (!is_iterable($justIterableInPhpDocWithCheck)) { + throw new \Exception(); + } + + $this->iterableData = $justIterableInPhpDocWithCheck; + } + + public function doIntersection() + { + if ($this->foo instanceof SomeInterface) { + $this->foo->foo = new Bar(); + self::$fooStatic::$fooStatic = new Bar(); + } + } } interface SomeInterface { - } class Collection implements \IteratorAggregate { - - public function getIterator() - { - return new \ArrayIterator([]); - } - + public function getIterator() + { + return new \ArrayIterator([]); + } } class SimpleXMLElementAccepts { - - public function doFoo(\SimpleXMLElement $xml) - { - $xml->foo = 'foo'; - $xml->bar = 1.234; - $xml->baz = true; - $xml->lorem = false; - $xml->ipsum = 1024; - $xml->test = $xml; - - $this->takeSimpleXmlElement($xml->foo); - $this->takeSimpleXmlElement($xml->bar); - $this->takeSimpleXmlElement($xml->baz); - $this->takeSimpleXmlElement($xml->lorem); - $this->takeSimpleXmlElement($xml->ipsum); - $this->takeSimpleXmlElement($xml->test); - } - - public function takeSimpleXmlElement(\SimpleXMLElement $_) - { - } - + public function doFoo(\SimpleXMLElement $xml) + { + $xml->foo = 'foo'; + $xml->bar = 1.234; + $xml->baz = true; + $xml->lorem = false; + $xml->ipsum = 1024; + $xml->test = $xml; + + $this->takeSimpleXmlElement($xml->foo); + $this->takeSimpleXmlElement($xml->bar); + $this->takeSimpleXmlElement($xml->baz); + $this->takeSimpleXmlElement($xml->lorem); + $this->takeSimpleXmlElement($xml->ipsum); + $this->takeSimpleXmlElement($xml->test); + } + + public function takeSimpleXmlElement(\SimpleXMLElement $_) + { + } } class MultipleCallableItems { - - /** @var callable[] */ - private $rules = []; - - public function __construct() - { - $this->rules = [ - [$this, 'doSomething'], - [$this, 'somethingElse'], - ]; - } - - private function doSomething() - { - - } - private function somethingElse() - { - - } - + /** @var callable[] */ + private $rules = []; + + public function __construct() + { + $this->rules = [ + [$this, 'doSomething'], + [$this, 'somethingElse'], + ]; + } + + private function doSomething() + { + } + private function somethingElse() + { + } } class ConcreteIterableAcceptsMixedIterable { - - /** - * @var Foo[] - */ - private $array; - - /** - * @var \Traversable - */ - private $traversable; - - /** - * @var iterable - */ - private $iterable; - - public function __construct( - array $array, - \Traversable $traversable, - iterable $iterable - ) - { - $this->array = $array; - $this->traversable = $traversable; - $this->iterable = $iterable; - } - + /** + * @var Foo[] + */ + private $array; + + /** + * @var \Traversable + */ + private $traversable; + + /** + * @var iterable + */ + private $iterable; + + public function __construct( + array $array, + \Traversable $traversable, + iterable $iterable + ) { + $this->array = $array; + $this->traversable = $traversable; + $this->iterable = $iterable; + } } /** @@ -249,67 +234,61 @@ public function __construct( */ class GenericClass { - - /** - * @param T $type - */ - public function __construct($type) - { - - } - + /** + * @param T $type + */ + public function __construct($type) + { + } } class ClassWithPropertyThatAcceptsGenericClass { - - /** @var GenericClass */ - private $genericProp; - - /** @var GenericClass */ - private $genericProp2; - - /** - * @param GenericClass $a - */ - public function doFoo($a) - { - $this->genericProp = $a; - } - - /** - * @param GenericClass $a - */ - public function doBar($a) - { - $this->genericProp2 = $a; - } - + /** @var GenericClass */ + private $genericProp; + + /** @var GenericClass */ + private $genericProp2; + + /** + * @param GenericClass $a + */ + public function doFoo($a) + { + $this->genericProp = $a; + } + + /** + * @param GenericClass $a + */ + public function doBar($a) + { + $this->genericProp2 = $a; + } } /** * @template T */ -class Baz { - /** @var array{array} */ - private $var; - - function test(): void - { - $this->var = [[]]; - } +class Baz +{ + /** @var array{array} */ + private $var; + + public function test(): void + { + $this->var = [[]]; + } } class AssignRefFoo { - - /** @var string */ - private $stringProperty; - - public function doFoo() - { - $i = 1; - $this->stringProperty = &$i; - } - + /** @var string */ + private $stringProperty; + + public function doFoo() + { + $i = 1; + $this->stringProperty = &$i; + } } diff --git a/tests/PHPStan/Rules/Properties/data/properties-from-array-into-object.php b/tests/PHPStan/Rules/Properties/data/properties-from-array-into-object.php index 95fa8c9a32..6a7daab330 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-from-array-into-object.php +++ b/tests/PHPStan/Rules/Properties/data/properties-from-array-into-object.php @@ -1,153 +1,161 @@ -data() as $property => $value) { - $self->{$property} = $value; - } - - return $self; - } - - public function create_simple_1(): self { - $self = new self(); - - $data = $this->data(); - - foreach($data as $property => $value) { - $self->{$property} = $value; - } - - return $self; - } - - public function create_complex(): self { - $self = new self(); - - foreach($this->data() as $property => $value) { - if ($property === 'test') { - if ($self->{$property} === null) { - $self->{$property} = new \stdClass(); - } - } else { - $self->{$property} = $value; - } - - if ($property === 'foo') { - $self->{$property} += 1; - } - if ($property === 'foo') { - $self->{$property} .= ' '; - } - if ($property === 'lall') { - $self->{$property} += 1; - } - $tmp = 1.1; - if ($property === 'foo') { - $self->{$property} += $tmp; - } - } - - return $self; - } - - public function create_simple_2(): self { - $self = new self(); - - $data = $this->data(); - - $property = 'foo'; - foreach($data as $value) { - $self->{$property} = $value; - } - - return $self; - } - - public function create_double_loop(): self { - $self = new self(); - - $data = $this->data(); - - foreach($data as $property => $value) { - foreach([1, 2, 3] as $value_2) { - $self->{$property} = $value; - } - } - - return $self; - } + /** + * @var string + */ + public $foo = ''; + + /** + * @var float + */ + public $float_test = 0.0; + + /** + * @var int + */ + public $lall = 0; + + /** + * @var int|null + */ + public $test; + + /** + * @phpstan-return array{float_test: float, foo: 'bar', lall: string, noop: int, test: int} + */ + public function data(): array + { + /** @var mixed $array */ + $array = []; + + return $array; + } + + public function create_simple_0(): self + { + $self = new self(); + + foreach ($this->data() as $property => $value) { + $self->{$property} = $value; + } + + return $self; + } + + public function create_simple_1(): self + { + $self = new self(); + + $data = $this->data(); + + foreach ($data as $property => $value) { + $self->{$property} = $value; + } + + return $self; + } + + public function create_complex(): self + { + $self = new self(); + + foreach ($this->data() as $property => $value) { + if ($property === 'test') { + if ($self->{$property} === null) { + $self->{$property} = new \stdClass(); + } + } else { + $self->{$property} = $value; + } + + if ($property === 'foo') { + $self->{$property} += 1; + } + if ($property === 'foo') { + $self->{$property} .= ' '; + } + if ($property === 'lall') { + $self->{$property} += 1; + } + $tmp = 1.1; + if ($property === 'foo') { + $self->{$property} += $tmp; + } + } + + return $self; + } + + public function create_simple_2(): self + { + $self = new self(); + + $data = $this->data(); + + $property = 'foo'; + foreach ($data as $value) { + $self->{$property} = $value; + } + + return $self; + } + + public function create_double_loop(): self + { + $self = new self(); + + $data = $this->data(); + + foreach ($data as $property => $value) { + foreach ([1, 2, 3] as $value_2) { + $self->{$property} = $value; + } + } + + return $self; + } } class FooBar { - /** - * @var string - */ - public $foo = ''; - - /** - * @var null|\stdClass - */ - public $lall; - - public function data(): array - { - return ['foo' => 'bar', 'lall' => 'lall', 'noop' => 1]; - } - - public function create(): self { - $self = new self(); - - foreach($this->data() as $property => $value) { - $this->{$property} = $value; - - if ($property === 'lall') { - $this->{$property} = null; - } - - if ($property === 'foo') { - $this->{$property} = 1.1; - } - } - - return $self; - } -} \ No newline at end of file + /** + * @var string + */ + public $foo = ''; + + /** + * @var null|\stdClass + */ + public $lall; + + public function data(): array + { + return ['foo' => 'bar', 'lall' => 'lall', 'noop' => 1]; + } + + public function create(): self + { + $self = new self(); + + foreach ($this->data() as $property => $value) { + $this->{$property} = $value; + + if ($property === 'lall') { + $this->{$property} = null; + } + + if ($property === 'foo') { + $this->{$property} = 1.1; + } + } + + return $self; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/properties-from-array-into-static-object.php b/tests/PHPStan/Rules/Properties/data/properties-from-array-into-static-object.php index 047f11d5f5..79414aa014 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-from-array-into-static-object.php +++ b/tests/PHPStan/Rules/Properties/data/properties-from-array-into-static-object.php @@ -1,78 +1,82 @@ - 'bar', 'lall' => 'lall', 'noop' => 1]; - } - - public function create(): self { - $self = new self(); - - foreach($this->data() as $property => $value) { - self::${$property} = $value; - - if ($property === 'lall') { - self::${$property} = null; - } - - if ($property === 'foo') { - self::${$property} = 1.1; - } - } - - return $self; - } + /** + * @var string + */ + public static $foo = ''; + + /** + * @var null|\stdClass + */ + public static $lall; + + /** + * @phpstan-return array{foo: 'bar', lall: string, noop: int} + */ + public function data(): array + { + return ['foo' => 'bar', 'lall' => 'lall', 'noop' => 1]; + } + + public function create(): self + { + $self = new self(); + + foreach ($this->data() as $property => $value) { + self::${$property} = $value; + + if ($property === 'lall') { + self::${$property} = null; + } + + if ($property === 'foo') { + self::${$property} = 1.1; + } + } + + return $self; + } } class FooBar { - /** - * @var string - */ - public static $foo = ''; - - /** - * @var null|\stdClass - */ - public static $lall; - - public function data(): array - { - return ['foo' => 'bar', 'lall' => 'lall', 'noop' => 1]; - } - - public function create(): self { - $self = new self(); - - foreach($this->data() as $property => $value) { - self::${$property} = $value; - - if ($property === 'lall') { - self::${$property} = null; - } - - if ($property === 'foo') { - self::${$property} = 1.1; - } - } - - return $self; - } -} \ No newline at end of file + /** + * @var string + */ + public static $foo = ''; + + /** + * @var null|\stdClass + */ + public static $lall; + + public function data(): array + { + return ['foo' => 'bar', 'lall' => 'lall', 'noop' => 1]; + } + + public function create(): self + { + $self = new self(); + + foreach ($this->data() as $property => $value) { + self::${$property} = $value; + + if ($property === 'lall') { + self::${$property} = null; + } + + if ($property === 'foo') { + self::${$property} = 1.1; + } + } + + return $self; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-object.php b/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-object.php index b6e0252e16..7a33c4552a 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-object.php +++ b/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-object.php @@ -1,30 +1,33 @@ -{$property} = $data; + $data = 'foo'; + $property = 'lall'; + $self->{$property} = $data; - $data = 'foo'; - $property = 'noop'; - $self->{$property} = $data; + $data = 'foo'; + $property = 'noop'; + $self->{$property} = $data; - return $self; - } + return $self; + } } diff --git a/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-static-object.php b/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-static-object.php index 7f6fef24a5..a3ce1f4133 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-static-object.php +++ b/tests/PHPStan/Rules/Properties/data/properties-from-variable-into-static-object.php @@ -1,30 +1,33 @@ -{$property} = $data; - - $data = 'foo'; - $property = 'noop'; - $self->{$property} = $data; - - return $self; - } -} \ No newline at end of file + /** + * @var string + */ + public $foo = ''; + + /** + * @var int + */ + public $lall = 0; + + public function create(): self + { + $self = new self(); + + $data = 'foo'; + $property = 'lall'; + $self->{$property} = $data; + + $data = 'foo'; + $property = 'noop'; + $self->{$property} = $data; + + return $self; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/properties-native-types.php b/tests/PHPStan/Rules/Properties/data/properties-native-types.php index 15eadd944d..52359c94a9 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-native-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-native-types.php @@ -1,15 +1,15 @@ -= 7.4 += 7.4 namespace PropertiesNativeTypes; class Foo { + private Foo $foo; - private Foo $foo; - - private Bar $bar; - - /** @var Baz */ - private Baz $baz; + private Bar $bar; + /** @var Baz */ + private Baz $baz; } diff --git a/tests/PHPStan/Rules/Properties/data/properties-promoted-types.php b/tests/PHPStan/Rules/Properties/data/properties-promoted-types.php index 1932476df4..4e39f46ce9 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-promoted-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-promoted-types.php @@ -1,22 +1,25 @@ -= 8.0 += 8.0 namespace PromotedPropertiesExistingClasses; class Foo { - - public function __construct( - public \stdClass $foo, - /** @var \stdClass */ public $bar, - public SomeTrait $baz, - /** @var SomeTrait */ public $lorem, - public Bar $ipsum, - /** @var Bar */ public $dolor - ) { } - + public function __construct( + public \stdClass $foo, + /** @var \stdClass */ + public $bar, + public SomeTrait $baz, + /** @var SomeTrait */ + public $lorem, + public Bar $ipsum, + /** @var Bar */ + public $dolor + ) { + } } trait SomeTrait { - } diff --git a/tests/PHPStan/Rules/Properties/data/properties-types.php b/tests/PHPStan/Rules/Properties/data/properties-types.php index d14aff3624..6406ed788d 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-types.php @@ -4,37 +4,34 @@ class Foo { + /** @var Foo */ + private $foo; - /** @var Foo */ - private $foo; + /** @var Bar */ + private $bar; - /** @var Bar */ - private $bar; + /** @var Foo[] */ + private $foos; - /** @var Foo[] */ - private $foos; + /** @var Bar[] */ + private $bars; - /** @var Bar[] */ - private $bars; + /** @var Ipsum|Dolor[] */ + private $dolors; - /** @var Ipsum|Dolor[] */ - private $dolors; + /** @var FOO|Fooo|BAR */ + private $fooWithWrongCase; - /** @var FOO|Fooo|BAR */ - private $fooWithWrongCase; + /** @var SomeTrait */ + private $withTrait; - /** @var SomeTrait */ - private $withTrait; - - /** @var \Datetime */ - private $datetime; - - /** @var \InvalidPhpDocDefinitions\FooGeneric */ - private $nonexistentClassInGenericObjectType; + /** @var \Datetime */ + private $datetime; + /** @var \InvalidPhpDocDefinitions\FooGeneric */ + private $nonexistentClassInGenericObjectType; } trait SomeTrait { - } diff --git a/tests/PHPStan/Rules/Properties/data/property-attributes.php b/tests/PHPStan/Rules/Properties/data/property-attributes.php index 0598cfe058..ebc0e9d56b 100644 --- a/tests/PHPStan/Rules/Properties/data/property-attributes.php +++ b/tests/PHPStan/Rules/Properties/data/property-attributes.php @@ -5,41 +5,32 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Foo { - } #[\Attribute(\Attribute::TARGET_PROPERTY)] class Bar { - } #[\Attribute(\Attribute::TARGET_ALL)] class Baz { - } class Lorem { - - #[Foo] - private $foo; - + #[Foo] + private $foo; } class Ipsum { - - #[Bar] - private $foo; - + #[Bar] + private $foo; } class Dolor { - - #[Baz] - private $foo; - + #[Baz] + private $foo; } diff --git a/tests/PHPStan/Rules/Properties/data/reading-write-only-properties-nullsafe.php b/tests/PHPStan/Rules/Properties/data/reading-write-only-properties-nullsafe.php index 33051d7ead..20f9ee46a7 100644 --- a/tests/PHPStan/Rules/Properties/data/reading-write-only-properties-nullsafe.php +++ b/tests/PHPStan/Rules/Properties/data/reading-write-only-properties-nullsafe.php @@ -1,10 +1,11 @@ -= 8.0 += 8.0 namespace ReadingWriteOnlyProperties; -function (?Foo $foo): void -{ - echo $foo?->readOnlyProperty; - echo $foo?->usualProperty; - echo $foo?->writeOnlyProperty; +function (?Foo $foo): void { + echo $foo?->readOnlyProperty; + echo $foo?->usualProperty; + echo $foo?->writeOnlyProperty; }; diff --git a/tests/PHPStan/Rules/Properties/data/reading-write-only-properties.php b/tests/PHPStan/Rules/Properties/data/reading-write-only-properties.php index 9dd1f23695..f1cb37c658 100644 --- a/tests/PHPStan/Rules/Properties/data/reading-write-only-properties.php +++ b/tests/PHPStan/Rules/Properties/data/reading-write-only-properties.php @@ -9,17 +9,15 @@ */ class Foo { + public function doFoo() + { + echo $this->readOnlyProperty; + echo $this->usualProperty; + echo $this->writeOnlyProperty; - public function doFoo() - { - echo $this->readOnlyProperty; - echo $this->usualProperty; - echo $this->writeOnlyProperty; - - $self = new self(); - echo $self->readOnlyProperty; - echo $self->usualProperty; - echo $self->writeOnlyProperty; - } - + $self = new self(); + echo $self->readOnlyProperty; + echo $self->usualProperty; + echo $self->writeOnlyProperty; + } } diff --git a/tests/PHPStan/Rules/Properties/data/static-properties-class-exists.php b/tests/PHPStan/Rules/Properties/data/static-properties-class-exists.php index 92da869c5f..2535479b5b 100644 --- a/tests/PHPStan/Rules/Properties/data/static-properties-class-exists.php +++ b/tests/PHPStan/Rules/Properties/data/static-properties-class-exists.php @@ -4,14 +4,12 @@ class Foo { - - public function doFoo(): void - { - if (!class_exists(Bar::class)) { - return; - } - - echo Bar::$foo; - } - + public function doFoo(): void + { + if (!class_exists(Bar::class)) { + return; + } + + echo Bar::$foo; + } } diff --git a/tests/PHPStan/Rules/Properties/data/uninitialized-property-promoted.php b/tests/PHPStan/Rules/Properties/data/uninitialized-property-promoted.php index ea2c55c283..e099962ef1 100644 --- a/tests/PHPStan/Rules/Properties/data/uninitialized-property-promoted.php +++ b/tests/PHPStan/Rules/Properties/data/uninitialized-property-promoted.php @@ -1,41 +1,33 @@ -= 8.0 += 8.0 namespace UninitializedPropertyPromoted; class Foo { + private int $x; - private int $x; - - public function __construct( - private int $y - ) - { - $this->x = $this->y; - } - + public function __construct( + private int $y + ) { + $this->x = $this->y; + } } class Bar { - - public function __construct( - private int $x - ) - { - - } - + public function __construct( + private int $x + ) { + } } class Baz { - - public function __construct( - private int $x - ) - { - assert($this->x >= 0.0); - } - + public function __construct( + private int $x + ) { + assert($this->x >= 0.0); + } } diff --git a/tests/PHPStan/Rules/Properties/data/uninitialized-property.php b/tests/PHPStan/Rules/Properties/data/uninitialized-property.php index b2b7406014..07b1866584 100644 --- a/tests/PHPStan/Rules/Properties/data/uninitialized-property.php +++ b/tests/PHPStan/Rules/Properties/data/uninitialized-property.php @@ -1,124 +1,111 @@ -= 7.4 += 7.4 namespace UninitializedProperty; class Foo { + private int $foo; - private int $foo; - - private int $bar; + private int $bar; - private int $baz; + private int $baz; - public function __construct() - { - $this->foo = 1; - } - - public function setBaz() - { - $this->baz = 1; - } + public function __construct() + { + $this->foo = 1; + } + public function setBaz() + { + $this->baz = 1; + } } class Bar { + private int $foo; - private int $foo; - - public function __construct() - { - $this->foo += 1; - $this->foo = 2; - } - + public function __construct() + { + $this->foo += 1; + $this->foo = 2; + } } class Baz { + private int $foo; - private int $foo; - - public function __construct() - { - $this->foo = 2; - $this->foo += 1; - } - + public function __construct() + { + $this->foo = 2; + $this->foo += 1; + } } class Lorem { + private int $foo; - private int $foo; - - private int $bar; + private int $bar; - private int $baz; + private int $baz; - private int $lorem; + private int $lorem; - private int $ipsum; + private int $ipsum; - private function assign() - { - $this->bar = 2; - $this->assignAgain(); - self::assignAgainAgain(); - } + private function assign() + { + $this->bar = 2; + $this->assignAgain(); + self::assignAgainAgain(); + } - public function __construct() - { - $this->foo = 1; - $this->assign(); - } + public function __construct() + { + $this->foo = 1; + $this->assign(); + } - private function assignAgain() - { - $this->lorem = 4; - } + private function assignAgain() + { + $this->lorem = 4; + } - private function assignAgainAgain() - { - $this->ipsum = 5; - } - - public function notCalled() - { - $this->baz = 3; - } + private function assignAgainAgain() + { + $this->ipsum = 5; + } + public function notCalled() + { + $this->baz = 3; + } } class TestCase { - - protected function setUp() - { - - } - + protected function setUp() + { + } } class MyTestCase extends TestCase { + private int $foo; - private int $foo; - - protected function setUp() - { - $this->foo = 1; - } - + protected function setUp() + { + $this->foo = 1; + } } class TestExtension { + private int $inited; - private int $inited; - - private int $uninited; - + private int $uninited; } diff --git a/tests/PHPStan/Rules/Properties/data/writing-to-read-only-properties.php b/tests/PHPStan/Rules/Properties/data/writing-to-read-only-properties.php index 23ea31efdc..d3f8f27683 100644 --- a/tests/PHPStan/Rules/Properties/data/writing-to-read-only-properties.php +++ b/tests/PHPStan/Rules/Properties/data/writing-to-read-only-properties.php @@ -9,30 +9,28 @@ */ class Foo { + public function doFoo() + { + $this->readOnlyProperty = 1; + $this->readOnlyProperty += 1; - public function doFoo() - { - $this->readOnlyProperty = 1; - $this->readOnlyProperty += 1; + $this->usualProperty = 1; + $this->usualProperty .= 1; - $this->usualProperty = 1; - $this->usualProperty .= 1; + $this->writeOnlyProperty = 1; + $this->writeOnlyProperty .= 1; - $this->writeOnlyProperty = 1; - $this->writeOnlyProperty .= 1; + $self = new self(); + $self->readOnlyProperty = 1; + $self->readOnlyProperty += 1; - $self = new self(); - $self->readOnlyProperty = 1; - $self->readOnlyProperty += 1; + $self->usualProperty = 1; + $self->usualProperty .= 1; - $self->usualProperty = 1; - $self->usualProperty .= 1; - - $self->writeOnlyProperty = 1; - $self->writeOnlyProperty .= 1; - - $s = 'foo'; - $self->readOnlyProperty = &$s; - } + $self->writeOnlyProperty = 1; + $self->writeOnlyProperty .= 1; + $s = 'foo'; + $self->readOnlyProperty = &$s; + } } diff --git a/tests/PHPStan/Rules/Regexp/RegularExpressionPatternRuleTest.php b/tests/PHPStan/Rules/Regexp/RegularExpressionPatternRuleTest.php index 3c53137056..f6f4ee98f5 100644 --- a/tests/PHPStan/Rules/Regexp/RegularExpressionPatternRuleTest.php +++ b/tests/PHPStan/Rules/Regexp/RegularExpressionPatternRuleTest.php @@ -1,4 +1,6 @@ -= 70300) { - $this->markTestSkipped('This test requires PHP < 7.3.0'); - } - - $this->analyse( - [__DIR__ . '/data/valid-regex-pattern.php'], - [ - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 6, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 7, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 11, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 12, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 16, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 17, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 21, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 22, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 26, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 27, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 29, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 29, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 32, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 33, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 35, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 35, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 38, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 39, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 41, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 41, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 43, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 43, - ], - ] - ); - } + public function testValidRegexPatternBefore73(): void + { + if (PHP_VERSION_ID >= 70300) { + $this->markTestSkipped('This test requires PHP < 7.3.0'); + } - public function testValidRegexPatternAfter73(): void - { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('This test requires PHP >= 7.3.0'); - } + $this->analyse( + [__DIR__ . '/data/valid-regex-pattern.php'], + [ + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 6, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 7, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 11, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 12, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 16, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 17, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 21, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 22, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 26, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 27, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 29, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 29, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 32, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 33, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 35, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 35, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 38, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 39, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 41, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 41, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 43, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', + 43, + ], + ] + ); + } - $this->analyse( - [__DIR__ . '/data/valid-regex-pattern.php'], - [ - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 6, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 7, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 11, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 12, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 16, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 17, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 21, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 22, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 26, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 27, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 29, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 29, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 32, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 33, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 35, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 35, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 38, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 39, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 41, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 41, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 43, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', - 43, - ], - ] - ); - } + public function testValidRegexPatternAfter73(): void + { + if (PHP_VERSION_ID < 70300) { + $this->markTestSkipped('This test requires PHP >= 7.3.0'); + } + $this->analyse( + [__DIR__ . '/data/valid-regex-pattern.php'], + [ + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 6, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 7, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 11, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 12, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 16, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 17, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 21, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 22, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 26, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 27, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 29, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 29, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 32, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 33, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 35, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 35, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 38, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 39, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 41, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 41, + ], + [ + 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', + 43, + ], + [ + 'Regex pattern is invalid: Compilation failed: missing closing parenthesis at offset 1 in pattern: ~(~', + 43, + ], + ] + ); + } } diff --git a/tests/PHPStan/Rules/Regexp/data/valid-regex-pattern.php b/tests/PHPStan/Rules/Regexp/data/valid-regex-pattern.php index 7aca854cae..3cf6c6d309 100644 --- a/tests/PHPStan/Rules/Regexp/data/valid-regex-pattern.php +++ b/tests/PHPStan/Rules/Regexp/data/valid-regex-pattern.php @@ -1,6 +1,7 @@ function () {}, - 'nok' => function () {}, - '~(~' => function () {}, - ], - '' + [ + '~ok~' => function () { + }, + 'nok' => function () { + }, + '~(~' => function () { + }, + ], + '' ); diff --git a/tests/PHPStan/Rules/RegistryTest.php b/tests/PHPStan/Rules/RegistryTest.php index 77469e4362..37761c72f3 100644 --- a/tests/PHPStan/Rules/RegistryTest.php +++ b/tests/PHPStan/Rules/RegistryTest.php @@ -1,4 +1,6 @@ -getRules(\PhpParser\Node\Expr\FuncCall::class); - $this->assertCount(1, $rules); - $this->assertSame($rule, $rules[0]); - - $this->assertCount(0, $registry->getRules(\PhpParser\Node\Expr\MethodCall::class)); - } - - public function testGetRulesWithTwoDifferentInstances(): void - { - $fooRule = new UniversalRule(\PhpParser\Node\Expr\FuncCall::class, static function (\PhpParser\Node\Expr\FuncCall $node, Scope $scope): array { - return ['Foo error']; - }); - $barRule = new UniversalRule(\PhpParser\Node\Expr\FuncCall::class, static function (\PhpParser\Node\Expr\FuncCall $node, Scope $scope): array { - return ['Bar error']; - }); - - $registry = new Registry([ - $fooRule, - $barRule, - ]); - - $rules = $registry->getRules(\PhpParser\Node\Expr\FuncCall::class); - $this->assertCount(2, $rules); - $this->assertSame($fooRule, $rules[0]); - $this->assertSame($barRule, $rules[1]); - - $this->assertCount(0, $registry->getRules(\PhpParser\Node\Expr\MethodCall::class)); - } - + public function testGetRules(): void + { + $rule = new DummyRule(); + + $registry = new Registry([ + $rule, + ]); + + $rules = $registry->getRules(\PhpParser\Node\Expr\FuncCall::class); + $this->assertCount(1, $rules); + $this->assertSame($rule, $rules[0]); + + $this->assertCount(0, $registry->getRules(\PhpParser\Node\Expr\MethodCall::class)); + } + + public function testGetRulesWithTwoDifferentInstances(): void + { + $fooRule = new UniversalRule(\PhpParser\Node\Expr\FuncCall::class, static function (\PhpParser\Node\Expr\FuncCall $node, Scope $scope): array { + return ['Foo error']; + }); + $barRule = new UniversalRule(\PhpParser\Node\Expr\FuncCall::class, static function (\PhpParser\Node\Expr\FuncCall $node, Scope $scope): array { + return ['Bar error']; + }); + + $registry = new Registry([ + $fooRule, + $barRule, + ]); + + $rules = $registry->getRules(\PhpParser\Node\Expr\FuncCall::class); + $this->assertCount(2, $rules); + $this->assertSame($fooRule, $rules[0]); + $this->assertSame($barRule, $rules[1]); + + $this->assertCount(0, $registry->getRules(\PhpParser\Node\Expr\MethodCall::class)); + } } diff --git a/tests/PHPStan/Rules/RuleErrorBuilderTest.php b/tests/PHPStan/Rules/RuleErrorBuilderTest.php index 345b48f77e..b1e8a20a90 100644 --- a/tests/PHPStan/Rules/RuleErrorBuilderTest.php +++ b/tests/PHPStan/Rules/RuleErrorBuilderTest.php @@ -1,4 +1,6 @@ -build(); - $this->assertSame('Foo', $ruleError->getMessage()); - } - - public function testMessageAndLineAndBuild(): void - { - $builder = RuleErrorBuilder::message('Foo')->line(25); - $ruleError = $builder->build(); - $this->assertSame('Foo', $ruleError->getMessage()); - - $this->assertInstanceOf(LineRuleError::class, $ruleError); - $this->assertSame(25, $ruleError->getLine()); - } - - public function testMessageAndFileAndBuild(): void - { - $builder = RuleErrorBuilder::message('Foo')->file('Bar.php'); - $ruleError = $builder->build(); - $this->assertSame('Foo', $ruleError->getMessage()); - - $this->assertInstanceOf(FileRuleError::class, $ruleError); - $this->assertSame('Bar.php', $ruleError->getFile()); - } - - public function testMessageAndLineAndFileAndBuild(): void - { - $builder = RuleErrorBuilder::message('Foo')->line(25)->file('Bar.php'); - $ruleError = $builder->build(); - $this->assertSame('Foo', $ruleError->getMessage()); - - $this->assertInstanceOf(LineRuleError::class, $ruleError); - $this->assertInstanceOf(FileRuleError::class, $ruleError); - $this->assertSame(25, $ruleError->getLine()); - $this->assertSame('Bar.php', $ruleError->getFile()); - } - + public function testMessageAndBuild(): void + { + $builder = RuleErrorBuilder::message('Foo'); + $ruleError = $builder->build(); + $this->assertSame('Foo', $ruleError->getMessage()); + } + + public function testMessageAndLineAndBuild(): void + { + $builder = RuleErrorBuilder::message('Foo')->line(25); + $ruleError = $builder->build(); + $this->assertSame('Foo', $ruleError->getMessage()); + + $this->assertInstanceOf(LineRuleError::class, $ruleError); + $this->assertSame(25, $ruleError->getLine()); + } + + public function testMessageAndFileAndBuild(): void + { + $builder = RuleErrorBuilder::message('Foo')->file('Bar.php'); + $ruleError = $builder->build(); + $this->assertSame('Foo', $ruleError->getMessage()); + + $this->assertInstanceOf(FileRuleError::class, $ruleError); + $this->assertSame('Bar.php', $ruleError->getFile()); + } + + public function testMessageAndLineAndFileAndBuild(): void + { + $builder = RuleErrorBuilder::message('Foo')->line(25)->file('Bar.php'); + $ruleError = $builder->build(); + $this->assertSame('Foo', $ruleError->getMessage()); + + $this->assertInstanceOf(LineRuleError::class, $ruleError); + $this->assertInstanceOf(FileRuleError::class, $ruleError); + $this->assertSame(25, $ruleError->getLine()); + $this->assertSame('Bar.php', $ruleError->getFile()); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php index 00b517040d..f4b1f3f0ec 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/tooWideArrowFunctionReturnType.php'], [ - [ - 'Anonymous function never returns null so it can be removed from the return typehint.', - 14, - ], - ]); - } - + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/tooWideArrowFunctionReturnType.php'], [ + [ + 'Anonymous function never returns null so it can be removed from the return typehint.', + 14, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php index 13e17f6aa6..8bf2964c6f 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/tooWideClosureReturnType.php'], [ - [ - 'Anonymous function never returns null so it can be removed from the return typehint.', - 20, - ], - ]); - } - + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/tooWideClosureReturnType.php'], [ + [ + 'Anonymous function never returns null so it can be removed from the return typehint.', + 20, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index 916cc3cefd..5b8984ed08 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/tooWideFunctionReturnType.php'], [ - [ - 'Function TooWideFunctionReturnType\bar() never returns string so it can be removed from the return typehint.', - 11, - ], - [ - 'Function TooWideFunctionReturnType\baz() never returns null so it can be removed from the return typehint.', - 15, - ], - [ - 'Function TooWideFunctionReturnType\ipsum() never returns null so it can be removed from the return typehint.', - 27, - ], - ]); - } - + public function testRule(): void + { + require_once __DIR__ . '/data/tooWideFunctionReturnType.php'; + $this->analyse([__DIR__ . '/data/tooWideFunctionReturnType.php'], [ + [ + 'Function TooWideFunctionReturnType\bar() never returns string so it can be removed from the return typehint.', + 11, + ], + [ + 'Function TooWideFunctionReturnType\baz() never returns null so it can be removed from the return typehint.', + 15, + ], + [ + 'Function TooWideFunctionReturnType\ipsum() never returns null so it can be removed from the return typehint.', + 27, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 7744509e76..ddcd2deb6d 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/tooWideMethodReturnType-private.php'], [ - [ - 'Method TooWideMethodReturnType\Foo::bar() never returns string so it can be removed from the return typehint.', - 14, - ], - [ - 'Method TooWideMethodReturnType\Foo::baz() never returns null so it can be removed from the return typehint.', - 18, - ], - [ - 'Method TooWideMethodReturnType\Foo::dolor() never returns null so it can be removed from the return typehint.', - 34, - ], - ]); - } - - public function testPublicProtected(): void - { - $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected.php'], [ - [ - 'Method TooWideMethodReturnType\Bar::bar() never returns string so it can be removed from the return typehint.', - 14, - ], - [ - 'Method TooWideMethodReturnType\Bar::baz() never returns null so it can be removed from the return typehint.', - 18, - ], - [ - 'Method TooWideMethodReturnType\Bazz::lorem() never returns string so it can be removed from the return typehint.', - 35, - ], - ]); - } + public function testPrivate(): void + { + $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-private.php'], [ + [ + 'Method TooWideMethodReturnType\Foo::bar() never returns string so it can be removed from the return typehint.', + 14, + ], + [ + 'Method TooWideMethodReturnType\Foo::baz() never returns null so it can be removed from the return typehint.', + 18, + ], + [ + 'Method TooWideMethodReturnType\Foo::dolor() never returns null so it can be removed from the return typehint.', + 34, + ], + ]); + } - public function testPublicProtectedWithInheritance(): void - { - $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected-inheritance.php'], [ - [ - 'Method TooWideMethodReturnType\Baz::baz() never returns null so it can be removed from the return typehint.', - 27, - ], - [ - 'Method TooWideMethodReturnType\BarClass::doFoo() never returns null so it can be removed from the return typehint.', - 51, - ], - ]); - } + public function testPublicProtected(): void + { + $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected.php'], [ + [ + 'Method TooWideMethodReturnType\Bar::bar() never returns string so it can be removed from the return typehint.', + 14, + ], + [ + 'Method TooWideMethodReturnType\Bar::baz() never returns null so it can be removed from the return typehint.', + 18, + ], + [ + 'Method TooWideMethodReturnType\Bazz::lorem() never returns string so it can be removed from the return typehint.', + 35, + ], + ]); + } + public function testPublicProtectedWithInheritance(): void + { + $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected-inheritance.php'], [ + [ + 'Method TooWideMethodReturnType\Baz::baz() never returns null so it can be removed from the return typehint.', + 27, + ], + [ + 'Method TooWideMethodReturnType\BarClass::doFoo() never returns null so it can be removed from the return typehint.', + 51, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideArrowFunctionReturnType.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideArrowFunctionReturnType.php index 632430bdc9..f02fcfcd3d 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideArrowFunctionReturnType.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideArrowFunctionReturnType.php @@ -1,26 +1,26 @@ -= 7.4 += 7.4 namespace TooWideArrowFunctionReturnType; class Foo { + public function doFoo(?string $nullableString) + { + fn (): \Generator => yield 1; - public function doFoo(?string $nullableString) - { - fn (): \Generator => yield 1; - - fn (): ?string => null; - - fn (): ?string => 'foo'; + fn (): ?string => null; - fn (): ?string => $nullableString; - } + fn (): ?string => 'foo'; - public function doBar() - { - /** @var string[][] $data */ - $data = doFoo(); - array_reduce($data, static fn (int $carry, array $item) => $carry + $item['total_count'], 0); - } + fn (): ?string => $nullableString; + } + public function doBar() + { + /** @var string[][] $data */ + $data = doFoo(); + array_reduce($data, static fn (int $carry, array $item) => $carry + $item['total_count'], 0); + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideClosureReturnType.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideClosureReturnType.php index ae4bec7ffa..f105d0d1e3 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideClosureReturnType.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideClosureReturnType.php @@ -4,30 +4,28 @@ class Foo { - - public function doFoo() - { - function (): \Generator { - yield 1; - yield 2; - return 3; - }; - - function (): ?string { - return null; - }; - - function (): ?string { - return 'foo'; - }; - - function (): ?string { - if (rand(0, 1)) { - return '1'; - } - - return null; - }; - } - + public function doFoo() + { + function (): \Generator { + yield 1; + yield 2; + return 3; + }; + + function (): ?string { + return null; + }; + + function (): ?string { + return 'foo'; + }; + + function (): ?string { + if (rand(0, 1)) { + return '1'; + } + + return null; + }; + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php index 513870cff7..437eac17cd 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php @@ -2,38 +2,44 @@ namespace TooWideFunctionReturnType; -function foo(): \Generator { - yield 1; - yield 2; - return 3; +function foo(): \Generator +{ + yield 1; + yield 2; + return 3; } -function bar(): ?string { - return null; +function bar(): ?string +{ + return null; } -function baz(): ?string { - return 'foo'; +function baz(): ?string +{ + return 'foo'; } -function lorem(): ?string { - if (rand(0, 1)) { - return '1'; - } +function lorem(): ?string +{ + if (rand(0, 1)) { + return '1'; + } - return null; + return null; } -function ipsum(): ?string { - $f = function () { - return null; - }; +function ipsum(): ?string +{ + $f = function () { + return null; + }; - $c = new class () { - public function doFoo() { - return null; - } - }; + $c = new class() { + public function doFoo() + { + return null; + } + }; - return 'str'; + return 'str'; } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php index 475b2319a5..744a0564b1 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php @@ -4,45 +4,50 @@ class Foo { - - private function foo(): \Generator { - yield 1; - yield 2; - return 3; - } - - private function bar(): ?string { - return null; - } - - private function baz(): ?string { - return 'foo'; - } - - private function lorem(): ?string { - if (rand(0, 1)) { - return '1'; - } - - return null; - } - - public function ipsum(): ?string { - return null; - } - - private function dolor(): ?string { - $f = function () { - return null; - }; - - $c = new class () { - public function doFoo() { - return null; - } - }; - - return 'str'; - } - + private function foo(): \Generator + { + yield 1; + yield 2; + return 3; + } + + private function bar(): ?string + { + return null; + } + + private function baz(): ?string + { + return 'foo'; + } + + private function lorem(): ?string + { + if (rand(0, 1)) { + return '1'; + } + + return null; + } + + public function ipsum(): ?string + { + return null; + } + + private function dolor(): ?string + { + $f = function () { + return null; + }; + + $c = new class() { + public function doFoo() + { + return null; + } + }; + + return 'str'; + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected-inheritance.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected-inheritance.php index 8d59bf9539..4fb6464239 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected-inheritance.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected-inheritance.php @@ -4,53 +4,50 @@ class Ancestor { - - public function bar(): ?string { - return null; - } - + public function bar(): ?string + { + return null; + } } final class Baz extends Ancestor { - - public function foo(): \Generator { - yield 1; - yield 2; - return 3; - } - - public function bar(): ?string { - return null; - } - - protected function baz(): ?string { - return 'foo'; - } - - public function lorem(): ?string { - if (rand(0, 1)) { - return '1'; - } - - return null; - } - + public function foo(): \Generator + { + yield 1; + yield 2; + return 3; + } + + public function bar(): ?string + { + return null; + } + + protected function baz(): ?string + { + return 'foo'; + } + + public function lorem(): ?string + { + if (rand(0, 1)) { + return '1'; + } + + return null; + } } interface FooInterface { - - public function doFoo(): ?string; - + public function doFoo(): ?string; } class BarClass implements FooInterface { - - public function doFoo(): ?string - { - return 'fooo'; - } - + public function doFoo(): ?string + { + return 'fooo'; + } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected.php index aaacca51bc..d2d01bab6a 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-public-protected.php @@ -4,37 +4,37 @@ final class Bar { - - public function foo(): \Generator { - yield 1; - yield 2; - return 3; - } - - public function bar(): ?string { - return null; - } - - protected function baz(): ?string { - return 'foo'; - } - - public function lorem(): ?string { - if (rand(0, 1)) { - return '1'; - } - - return null; - } - + public function foo(): \Generator + { + yield 1; + yield 2; + return 3; + } + + public function bar(): ?string + { + return null; + } + + protected function baz(): ?string + { + return 'foo'; + } + + public function lorem(): ?string + { + if (rand(0, 1)) { + return '1'; + } + + return null; + } } class Bazz { - - final public function lorem(): ?string - { - return null; - } - + final public function lorem(): ?string + { + return null; + } } diff --git a/tests/PHPStan/Rules/UniversalRule.php b/tests/PHPStan/Rules/UniversalRule.php index 7d4f225c45..9346c358cb 100644 --- a/tests/PHPStan/Rules/UniversalRule.php +++ b/tests/PHPStan/Rules/UniversalRule.php @@ -1,4 +1,6 @@ - */ - private $nodeType; - - /** @var (callable(TNodeType, Scope): array) */ - private $processNodeCallback; - - /** - * @param class-string $nodeType - * @param (callable(TNodeType, Scope): array) $processNodeCallback - */ - public function __construct(string $nodeType, callable $processNodeCallback) - { - $this->nodeType = $nodeType; - $this->processNodeCallback = $processNodeCallback; - } - - public function getNodeType(): string - { - return $this->nodeType; - } - - /** - * @param TNodeType $node - * @param \PHPStan\Analyser\Scope $scope - * @return array - */ - public function processNode(Node $node, Scope $scope): array - { - $callback = $this->processNodeCallback; - return $callback($node, $scope); - } - + /** @phpstan-var class-string */ + private $nodeType; + + /** @var (callable(TNodeType, Scope): array) */ + private $processNodeCallback; + + /** + * @param class-string $nodeType + * @param (callable(TNodeType, Scope): array) $processNodeCallback + */ + public function __construct(string $nodeType, callable $processNodeCallback) + { + $this->nodeType = $nodeType; + $this->processNodeCallback = $processNodeCallback; + } + + public function getNodeType(): string + { + return $this->nodeType; + } + + /** + * @param TNodeType $node + * @param \PHPStan\Analyser\Scope $scope + * @return array + */ + public function processNode(Node $node, Scope $scope): array + { + $callback = $this->processNodeCallback; + return $callback($node, $scope); + } } diff --git a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php index 20dfb5bc4d..30766db465 100644 --- a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php +++ b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php @@ -1,4 +1,6 @@ -checkMaybeUndefinedVariables); - } - - public function testCompactVariables(): void - { - $this->checkMaybeUndefinedVariables = true; - $this->analyse([__DIR__ . '/data/compact-variables.php'], [ - [ - 'Call to function compact() contains undefined variable $bar.', - 22, - ], - [ - 'Call to function compact() contains possibly undefined variable $baz.', - 23, - ], - [ - 'Call to function compact() contains undefined variable $foo.', - 29, - ], - ]); - } + protected function getRule(): Rule + { + return new CompactVariablesRule($this->checkMaybeUndefinedVariables); + } + public function testCompactVariables(): void + { + $this->checkMaybeUndefinedVariables = true; + $this->analyse([__DIR__ . '/data/compact-variables.php'], [ + [ + 'Call to function compact() contains undefined variable $bar.', + 22, + ], + [ + 'Call to function compact() contains possibly undefined variable $baz.', + 23, + ], + [ + 'Call to function compact() contains undefined variable $foo.', + 29, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index c481973ded..12f0aef3d2 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1,4 +1,6 @@ -cliArgumentsVariablesRegistered, - $this->checkMaybeUndefinedVariables - ); - } - - protected function shouldPolluteScopeWithLoopInitialAssignments(): bool - { - return $this->polluteScopeWithLoopInitialAssignments; - } - - protected function shouldPolluteCatchScopeWithTryAssignments(): bool - { - return $this->polluteCatchScopeWithTryAssignments; - } - - protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool - { - return $this->polluteScopeWithAlwaysIterableForeach; - } - - public function testDefinedVariables(): void - { - require_once __DIR__ . '/data/defined-variables-definition.php'; - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables.php'], [ - [ - 'Undefined variable: $definedLater', - 5, - ], - [ - 'Variable $definedInIfOnly might not be defined.', - 10, - ], - [ - 'Variable $definedInCases might not be defined.', - 21, - ], - [ - 'Undefined variable: $fooParameterBeforeDeclaration', - 29, - ], - [ - 'Undefined variable: $parseStrParameter', - 34, - ], - [ - 'Undefined variable: $foo', - 39, - ], - [ - 'Undefined variable: $willBeUnset', - 44, - ], - [ - 'Undefined variable: $mustAlreadyExistWhenDividing', - 50, - ], - [ - 'Undefined variable: $arrayDoesNotExist', - 57, - ], - [ - 'Undefined variable: $undefinedVariable', - 59, - ], - [ - 'Undefined variable: $this', - 96, - ], - [ - 'Undefined variable: $this', - 99, - ], - [ - 'Undefined variable: $variableInEmpty', - 145, - ], - [ - 'Undefined variable: $negatedVariableInEmpty', - 152, - ], - [ - 'Undefined variable: $variableInEmpty', - 155, - ], - [ - 'Undefined variable: $negatedVariableInEmpty', - 156, - ], - [ - 'Undefined variable: $variableInIsset', - 159, - ], - [ - 'Undefined variable: $anotherVariableInIsset', - 159, - ], - [ - 'Undefined variable: $variableInIsset', - 161, - ], - [ - 'Undefined variable: $anotherVariableInIsset', - 161, - ], - [ - 'Undefined variable: $http_response_header', - 185, - ], - [ - 'Undefined variable: $http_response_header', - 191, - ], - [ - 'Undefined variable: $assignedInKey', - 203, - ], - [ - 'Undefined variable: $assignedInKey', - 204, - ], - [ - 'Variable $forI might not be defined.', - 250, - ], - [ - 'Variable $forJ might not be defined.', - 251, - ], - [ - 'Variable $variableAvailableInAllCatches might not be defined.', - 266, - ], - [ - 'Variable $variableDefinedOnlyInOneCatch might not be defined.', - 267, - ], - [ - 'Undefined variable: $variableInBitwiseAndAssign', - 277, - ], - [ - 'Variable $mightBeUndefinedInDoWhile might not be defined.', - 282, - ], - [ - 'Undefined variable: $variableInSecondCase', - 290, - ], - [ - 'Variable $variableInSecondCase might not be defined.', - 293, - ], - [ - 'Undefined variable: $variableAssignedInSecondCase', - 300, - ], - [ - 'Variable $variableInFallthroughCase might not be defined.', - 302, - ], - [ - 'Variable $variableFromDefaultFirst might not be defined.', - 312, - ], - [ - 'Undefined variable: $undefinedVariableInForeach', - 315, - ], - [ - 'Variable $anotherForLoopVariable might not be defined.', - 328, - ], - [ - 'Variable $maybeDefinedInTernary might not be defined.', - 351, - ], - [ - 'Variable $anotherMaybeDefinedInTernary might not be defined.', - 354, - ], - [ - 'Variable $whileVariableUsedAndThenDefined might not be defined.', - 356, - ], - [ - 'Variable $forVariableUsedAndThenDefined might not be defined.', - 360, - ], - [ - 'Undefined variable: $variableInWhileIsset', - 365, - ], - [ - 'Undefined variable: $unknownVariablePassedToReset', - 368, - ], - [ - 'Undefined variable: $unknownVariablePassedToReset', - 369, - ], - [ - 'Undefined variable: $variableInAssign', - 384, - ], - [ - 'Undefined variable: $undefinedArrayIndex', - 409, - ], - [ - 'Undefined variable: $anotherUndefinedArrayIndex', - 409, - ], - [ - 'Variable $str might not be defined.', - 423, - ], - [ - 'Variable $str might not be defined.', - 428, - ], - ]); - } - - public function testDefinedVariablesInClosures(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables-closures.php'], [ - [ - 'Undefined variable: $this', - 14, - ], - ]); - } - - public function testDefinedVariablesInShortArrayDestructuringSyntax(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables-array-destructuring-short-syntax.php'], [ - [ - 'Undefined variable: $f', - 11, - ], - [ - 'Undefined variable: $f', - 14, - ], - [ - 'Undefined variable: $var3', - 32, - ], - ]); - } - - public function testCliArgumentsVariablesNotRegistered(): void - { - $this->cliArgumentsVariablesRegistered = false; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ - [ - 'Variable $argc might not be defined.', - 3, - ], - [ - 'Undefined variable: $argc', - 5, - ], - ]); - } - - public function testCliArgumentsVariablesRegistered(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ - [ - 'Undefined variable: $argc', - 5, - ], - ]); - } - - public function dataLoopInitialAssignments(): array - { - return [ - [ - false, - false, - [], - ], - [ - false, - true, - [ - [ - 'Variable $i might not be defined.', - 7, - ], - [ - 'Variable $whileVar might not be defined.', - 13, - ], - ], - ], - [ - true, - false, - [], - ], - [ - true, - true, - [], - ], - ]; - } - - /** - * @dataProvider dataLoopInitialAssignments - * @param bool $polluteScopeWithLoopInitialAssignments - * @param bool $checkMaybeUndefinedVariables - * @param mixed[][] $expectedErrors - */ - public function testLoopInitialAssignments( - bool $polluteScopeWithLoopInitialAssignments, - bool $checkMaybeUndefinedVariables, - array $expectedErrors - ): void - { - $this->cliArgumentsVariablesRegistered = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; - $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/loop-initial-assignments.php'], $expectedErrors); - } - - public function testDefineVariablesInClass(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/define-variables-class.php'], []); - } - - public function testDeadBranches(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/dead-branches.php'], [ - [ - 'Undefined variable: $test', - 21, - ], - [ - 'Undefined variable: $test', - 33, - ], - [ - 'Undefined variable: $test', - 55, - ], - [ - 'Undefined variable: $test', - 66, - ], - [ - 'Undefined variable: $test', - 94, - ], - ]); - } - - public function testForeach(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/foreach.php'], [ - [ - 'Variable $val might not be defined.', - 9, - ], - [ - 'Variable $test might not be defined.', - 10, - ], - [ - 'Undefined variable: $val', - 46, - ], - [ - 'Undefined variable: $test', - 47, - ], - [ - 'Variable $val might not be defined.', - 62, - ], - [ - 'Variable $test might not be defined.', - 63, - ], - [ - 'Undefined variable: $val', - 171, - ], - [ - 'Undefined variable: $test', - 172, - ], - [ - 'Undefined variable: $val', - 187, - ], - [ - 'Undefined variable: $test', - 188, - ], - [ - 'Variable $val might not be defined.', - 217, - ], - [ - 'Variable $test might not be defined.', - 218, - ], - ]); - } - - public function dataForeachPolluteScopeWithAlwaysIterableForeach(): array - { - return [ - [ - true, - [ - [ - 'Undefined variable: $key', - 8, - ], - [ - 'Undefined variable: $val', - 9, - ], - [ - 'Undefined variable: $test', - 10, - ], - [ - 'Variable $test might not be defined.', - 34, - ], - [ - 'Variable $key might not be defined.', - 47, - ], - [ - 'Variable $test might not be defined.', - 48, - ], - [ - 'Variable $key might not be defined.', - 61, - ], - [ - 'Variable $test might not be defined.', - 62, - ], - ], - ], - [ - false, - [ - [ - 'Undefined variable: $key', - 8, - ], - [ - 'Undefined variable: $val', - 9, - ], - [ - 'Undefined variable: $test', - 10, - ], - [ - 'Variable $key might not be defined.', - 19, - ], - [ - 'Variable $val might not be defined.', - 20, - ], - [ - 'Variable $test might not be defined.', - 21, - ], - [ - 'Variable $key might not be defined.', - 32, - ], - [ - 'Variable $val might not be defined.', - 33, - ], - [ - 'Variable $test might not be defined.', - 34, - ], - [ - 'Variable $key might not be defined.', - 47, - ], - [ - 'Variable $test might not be defined.', - 48, - ], - [ - 'Variable $key might not be defined.', - 61, - ], - [ - 'Variable $test might not be defined.', - 62, - ], - [ - 'Variable $key might not be defined.', - 75, - ], - [ - 'Variable $test might not be defined.', - 76, - ], - ], - ], - ]; - } - - /** - * @dataProvider dataForeachPolluteScopeWithAlwaysIterableForeach - * - * @param bool $polluteScopeWithAlwaysIterableForeach - * @param mixed[] $errors - */ - public function testForeachPolluteScopeWithAlwaysIterableForeach(bool $polluteScopeWithAlwaysIterableForeach, array $errors): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; - $this->analyse([__DIR__ . '/data/foreach-always-iterable.php'], $errors); - } - - public function testBooleanOperatorsTruthyFalsey(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/boolean-op-truthy-falsey.php'], [ - [ - 'Variable $matches might not be defined.', - 9, - ], - [ - 'Variable $matches might not be defined.', - 15, - ], - ]); - } - - public function testArrowFunctions(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables-arrow-functions.php'], [ - [ - 'Undefined variable: $a', - 10, - ], - [ - 'Undefined variable: $this', - 19, - ], - ]); - } - - public function testCoalesceAssign(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables-coalesce-assign.php'], [ - [ - 'Undefined variable: $b', - 16, - ], - ]); - } - - public function testBug2748(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/bug-2748.php'], [ - [ - 'Undefined variable: $foo', - 10, - ], - [ - 'Undefined variable: $foo', - 15, - ], - ]); - } - - public function testGlobalVariables(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/global-variables.php'], []); - } - - public function testRootScopeMaybeDefined(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = false; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], []); - } - - public function testRootScopeMaybeDefinedCheck(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], [ - [ - 'Variable $maybe might not be defined.', - 3, - ], - [ - 'Variable $this might not be defined.', - 5, - ], - ]); - } - - public function testFormerThisVariableRule(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/this.php'], [ - [ - 'Undefined variable: $this', - 16, - ], - [ - 'Undefined variable: $this', - 20, - ], - [ - 'Undefined variable: $this', - 26, - ], - [ - 'Undefined variable: $this', - 38, - ], - ]); - } - - public function testClosureUse(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/defined-variables-anonymous-function-use.php'], [ - [ - 'Variable $bar might not be defined.', - 5, - ], - [ - 'Variable $wrongErrorHandler might not be defined.', - 22, - ], - [ - 'Variable $onlyInIf might not be defined.', - 23, - ], - [ - 'Variable $forI might not be defined.', - 24, - ], - [ - 'Variable $forJ might not be defined.', - 25, - ], - [ - 'Variable $anotherVariableFromForCond might not be defined.', - 26, - ], - ]); - } - - public function testNullsafeIsset(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/variable-nullsafe-isset.php'], []); - } - - public function testBug1306(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/bug-1306.php'], []); - } - - public function testBug3515(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/bug-3515.php'], [ - [ - 'Undefined variable: $anArray', - 19, - ], - [ - 'Undefined variable: $anArray', - 20, - ], - ]); - } - - public function testBug4412(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/bug-4412.php'], [ - [ - 'Undefined variable: $a', - 17, - ], - ]); - } - + /** @var bool */ + private $cliArgumentsVariablesRegistered; + + /** @var bool */ + private $checkMaybeUndefinedVariables; + + /** @var bool */ + private $polluteScopeWithLoopInitialAssignments; + + /** @var bool */ + private $polluteCatchScopeWithTryAssignments; + + /** @var bool */ + private $polluteScopeWithAlwaysIterableForeach; + + protected function getRule(): \PHPStan\Rules\Rule + { + return new DefinedVariableRule( + $this->cliArgumentsVariablesRegistered, + $this->checkMaybeUndefinedVariables + ); + } + + protected function shouldPolluteScopeWithLoopInitialAssignments(): bool + { + return $this->polluteScopeWithLoopInitialAssignments; + } + + protected function shouldPolluteCatchScopeWithTryAssignments(): bool + { + return $this->polluteCatchScopeWithTryAssignments; + } + + protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool + { + return $this->polluteScopeWithAlwaysIterableForeach; + } + + public function testDefinedVariables(): void + { + require_once __DIR__ . '/data/defined-variables-definition.php'; + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables.php'], [ + [ + 'Undefined variable: $definedLater', + 5, + ], + [ + 'Variable $definedInIfOnly might not be defined.', + 10, + ], + [ + 'Variable $definedInCases might not be defined.', + 21, + ], + [ + 'Undefined variable: $fooParameterBeforeDeclaration', + 29, + ], + [ + 'Undefined variable: $parseStrParameter', + 34, + ], + [ + 'Undefined variable: $foo', + 39, + ], + [ + 'Undefined variable: $willBeUnset', + 44, + ], + [ + 'Undefined variable: $mustAlreadyExistWhenDividing', + 50, + ], + [ + 'Undefined variable: $arrayDoesNotExist', + 57, + ], + [ + 'Undefined variable: $undefinedVariable', + 59, + ], + [ + 'Undefined variable: $this', + 96, + ], + [ + 'Undefined variable: $this', + 99, + ], + [ + 'Undefined variable: $variableInEmpty', + 145, + ], + [ + 'Undefined variable: $negatedVariableInEmpty', + 152, + ], + [ + 'Undefined variable: $variableInEmpty', + 155, + ], + [ + 'Undefined variable: $negatedVariableInEmpty', + 156, + ], + [ + 'Undefined variable: $variableInIsset', + 159, + ], + [ + 'Undefined variable: $anotherVariableInIsset', + 159, + ], + [ + 'Undefined variable: $variableInIsset', + 161, + ], + [ + 'Undefined variable: $anotherVariableInIsset', + 161, + ], + [ + 'Undefined variable: $http_response_header', + 185, + ], + [ + 'Undefined variable: $http_response_header', + 191, + ], + [ + 'Undefined variable: $assignedInKey', + 203, + ], + [ + 'Undefined variable: $assignedInKey', + 204, + ], + [ + 'Variable $forI might not be defined.', + 250, + ], + [ + 'Variable $forJ might not be defined.', + 251, + ], + [ + 'Variable $variableAvailableInAllCatches might not be defined.', + 266, + ], + [ + 'Variable $variableDefinedOnlyInOneCatch might not be defined.', + 267, + ], + [ + 'Undefined variable: $variableInBitwiseAndAssign', + 277, + ], + [ + 'Variable $mightBeUndefinedInDoWhile might not be defined.', + 282, + ], + [ + 'Undefined variable: $variableInSecondCase', + 290, + ], + [ + 'Variable $variableInSecondCase might not be defined.', + 293, + ], + [ + 'Undefined variable: $variableAssignedInSecondCase', + 300, + ], + [ + 'Variable $variableInFallthroughCase might not be defined.', + 302, + ], + [ + 'Variable $variableFromDefaultFirst might not be defined.', + 312, + ], + [ + 'Undefined variable: $undefinedVariableInForeach', + 315, + ], + [ + 'Variable $anotherForLoopVariable might not be defined.', + 328, + ], + [ + 'Variable $maybeDefinedInTernary might not be defined.', + 351, + ], + [ + 'Variable $anotherMaybeDefinedInTernary might not be defined.', + 354, + ], + [ + 'Variable $whileVariableUsedAndThenDefined might not be defined.', + 356, + ], + [ + 'Variable $forVariableUsedAndThenDefined might not be defined.', + 360, + ], + [ + 'Undefined variable: $variableInWhileIsset', + 365, + ], + [ + 'Undefined variable: $unknownVariablePassedToReset', + 368, + ], + [ + 'Undefined variable: $unknownVariablePassedToReset', + 369, + ], + [ + 'Undefined variable: $variableInAssign', + 384, + ], + [ + 'Undefined variable: $undefinedArrayIndex', + 409, + ], + [ + 'Undefined variable: $anotherUndefinedArrayIndex', + 409, + ], + [ + 'Variable $str might not be defined.', + 423, + ], + [ + 'Variable $str might not be defined.', + 428, + ], + ]); + } + + public function testDefinedVariablesInClosures(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables-closures.php'], [ + [ + 'Undefined variable: $this', + 14, + ], + ]); + } + + public function testDefinedVariablesInShortArrayDestructuringSyntax(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables-array-destructuring-short-syntax.php'], [ + [ + 'Undefined variable: $f', + 11, + ], + [ + 'Undefined variable: $f', + 14, + ], + [ + 'Undefined variable: $var3', + 32, + ], + ]); + } + + public function testCliArgumentsVariablesNotRegistered(): void + { + $this->cliArgumentsVariablesRegistered = false; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ + [ + 'Variable $argc might not be defined.', + 3, + ], + [ + 'Undefined variable: $argc', + 5, + ], + ]); + } + + public function testCliArgumentsVariablesRegistered(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ + [ + 'Undefined variable: $argc', + 5, + ], + ]); + } + + public function dataLoopInitialAssignments(): array + { + return [ + [ + false, + false, + [], + ], + [ + false, + true, + [ + [ + 'Variable $i might not be defined.', + 7, + ], + [ + 'Variable $whileVar might not be defined.', + 13, + ], + ], + ], + [ + true, + false, + [], + ], + [ + true, + true, + [], + ], + ]; + } + + /** + * @dataProvider dataLoopInitialAssignments + * @param bool $polluteScopeWithLoopInitialAssignments + * @param bool $checkMaybeUndefinedVariables + * @param mixed[][] $expectedErrors + */ + public function testLoopInitialAssignments( + bool $polluteScopeWithLoopInitialAssignments, + bool $checkMaybeUndefinedVariables, + array $expectedErrors + ): void { + $this->cliArgumentsVariablesRegistered = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; + $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/loop-initial-assignments.php'], $expectedErrors); + } + + public function testDefineVariablesInClass(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/define-variables-class.php'], []); + } + + public function testDeadBranches(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/dead-branches.php'], [ + [ + 'Undefined variable: $test', + 21, + ], + [ + 'Undefined variable: $test', + 33, + ], + [ + 'Undefined variable: $test', + 55, + ], + [ + 'Undefined variable: $test', + 66, + ], + [ + 'Undefined variable: $test', + 94, + ], + ]); + } + + public function testForeach(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/foreach.php'], [ + [ + 'Variable $val might not be defined.', + 9, + ], + [ + 'Variable $test might not be defined.', + 10, + ], + [ + 'Undefined variable: $val', + 46, + ], + [ + 'Undefined variable: $test', + 47, + ], + [ + 'Variable $val might not be defined.', + 62, + ], + [ + 'Variable $test might not be defined.', + 63, + ], + [ + 'Undefined variable: $val', + 171, + ], + [ + 'Undefined variable: $test', + 172, + ], + [ + 'Undefined variable: $val', + 187, + ], + [ + 'Undefined variable: $test', + 188, + ], + [ + 'Variable $val might not be defined.', + 217, + ], + [ + 'Variable $test might not be defined.', + 218, + ], + ]); + } + + public function dataForeachPolluteScopeWithAlwaysIterableForeach(): array + { + return [ + [ + true, + [ + [ + 'Undefined variable: $key', + 8, + ], + [ + 'Undefined variable: $val', + 9, + ], + [ + 'Undefined variable: $test', + 10, + ], + [ + 'Variable $test might not be defined.', + 34, + ], + [ + 'Variable $key might not be defined.', + 47, + ], + [ + 'Variable $test might not be defined.', + 48, + ], + [ + 'Variable $key might not be defined.', + 61, + ], + [ + 'Variable $test might not be defined.', + 62, + ], + ], + ], + [ + false, + [ + [ + 'Undefined variable: $key', + 8, + ], + [ + 'Undefined variable: $val', + 9, + ], + [ + 'Undefined variable: $test', + 10, + ], + [ + 'Variable $key might not be defined.', + 19, + ], + [ + 'Variable $val might not be defined.', + 20, + ], + [ + 'Variable $test might not be defined.', + 21, + ], + [ + 'Variable $key might not be defined.', + 32, + ], + [ + 'Variable $val might not be defined.', + 33, + ], + [ + 'Variable $test might not be defined.', + 34, + ], + [ + 'Variable $key might not be defined.', + 47, + ], + [ + 'Variable $test might not be defined.', + 48, + ], + [ + 'Variable $key might not be defined.', + 61, + ], + [ + 'Variable $test might not be defined.', + 62, + ], + [ + 'Variable $key might not be defined.', + 75, + ], + [ + 'Variable $test might not be defined.', + 76, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataForeachPolluteScopeWithAlwaysIterableForeach + * + * @param bool $polluteScopeWithAlwaysIterableForeach + * @param mixed[] $errors + */ + public function testForeachPolluteScopeWithAlwaysIterableForeach(bool $polluteScopeWithAlwaysIterableForeach, array $errors): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; + $this->analyse([__DIR__ . '/data/foreach-always-iterable.php'], $errors); + } + + public function testBooleanOperatorsTruthyFalsey(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/boolean-op-truthy-falsey.php'], [ + [ + 'Variable $matches might not be defined.', + 9, + ], + [ + 'Variable $matches might not be defined.', + 15, + ], + ]); + } + + public function testArrowFunctions(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables-arrow-functions.php'], [ + [ + 'Undefined variable: $a', + 10, + ], + [ + 'Undefined variable: $this', + 19, + ], + ]); + } + + public function testCoalesceAssign(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables-coalesce-assign.php'], [ + [ + 'Undefined variable: $b', + 16, + ], + ]); + } + + public function testBug2748(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-2748.php'], [ + [ + 'Undefined variable: $foo', + 10, + ], + [ + 'Undefined variable: $foo', + 15, + ], + ]); + } + + public function testGlobalVariables(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/global-variables.php'], []); + } + + public function testRootScopeMaybeDefined(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = false; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], []); + } + + public function testRootScopeMaybeDefinedCheck(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], [ + [ + 'Variable $maybe might not be defined.', + 3, + ], + [ + 'Variable $this might not be defined.', + 5, + ], + ]); + } + + public function testFormerThisVariableRule(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/this.php'], [ + [ + 'Undefined variable: $this', + 16, + ], + [ + 'Undefined variable: $this', + 20, + ], + [ + 'Undefined variable: $this', + 26, + ], + [ + 'Undefined variable: $this', + 38, + ], + ]); + } + + public function testClosureUse(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/defined-variables-anonymous-function-use.php'], [ + [ + 'Variable $bar might not be defined.', + 5, + ], + [ + 'Variable $wrongErrorHandler might not be defined.', + 22, + ], + [ + 'Variable $onlyInIf might not be defined.', + 23, + ], + [ + 'Variable $forI might not be defined.', + 24, + ], + [ + 'Variable $forJ might not be defined.', + 25, + ], + [ + 'Variable $anotherVariableFromForCond might not be defined.', + 26, + ], + ]); + } + + public function testNullsafeIsset(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/variable-nullsafe-isset.php'], []); + } + + public function testBug1306(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-1306.php'], []); + } + + public function testBug3515(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-3515.php'], [ + [ + 'Undefined variable: $anArray', + 19, + ], + [ + 'Undefined variable: $anArray', + 20, + ], + ]); + } + + public function testBug4412(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-4412.php'], [ + [ + 'Undefined variable: $a', + 17, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 7e4ee5150a..234209602e 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/isset.php'], [ - [ - 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', - 32, - ], - [ - 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', - 45, - ], - [ - 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', - 49, - ], - [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', - 67, - ], - [ - 'Offset \'b\' on array() in isset() does not exist.', - 79, - ], - [ - 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', - 85, - ], - [ - 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', - 87, - ], - [ - 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', - 89, - ], - [ - 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', - 95, - ], - [ - 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', - 97, - ], - [ - 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', - 116, - ], - [ - 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', - 118, - ], - [ - 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', - 123, - ], - [ - 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', - 124, - ], - ]); - } - - public function testNativePropertyTypes(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [ - /*[ - // no way to achieve this with current PHP Reflection API - // There's ReflectionClass::getDefaultProperties() - // but it cannot differentiate between `public int $foo` and `public int $foo = null`; - 'Property IssetNativePropertyTypes\Foo::$hasDefaultValue (int) in isset() is not nullable.', - 17, - ],*/ - [ - 'Property IssetNativePropertyTypes\Foo::$isAssignedBefore (int) in isset() is not nullable.', - 20, - ], - ]); - } + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/isset.php'], [ + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 32, + ], + [ + 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', + 45, + ], + [ + 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', + 49, + ], + [ + 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', + 67, + ], + [ + 'Offset \'b\' on array() in isset() does not exist.', + 79, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 85, + ], + [ + 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', + 87, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 89, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', + 95, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', + 97, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 116, + ], + [ + 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', + 118, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', + 123, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', + 124, + ], + ]); + } - public function testBug4290(): void - { - $this->analyse([__DIR__ . '/data/bug-4290.php'], []); - } + public function testNativePropertyTypes(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [ + /*[ + // no way to achieve this with current PHP Reflection API + // There's ReflectionClass::getDefaultProperties() + // but it cannot differentiate between `public int $foo` and `public int $foo = null`; + 'Property IssetNativePropertyTypes\Foo::$hasDefaultValue (int) in isset() is not nullable.', + 17, + ],*/ + [ + 'Property IssetNativePropertyTypes\Foo::$isAssignedBefore (int) in isset() is not nullable.', + 20, + ], + ]); + } - public function testBug4671(): void - { - $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ - 'Offset string&numeric on array in isset() does not exist.', - 13, - ]]); - } + public function testBug4290(): void + { + $this->analyse([__DIR__ . '/data/bug-4290.php'], []); + } + public function testBug4671(): void + { + $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ + 'Offset string&numeric on array in isset() does not exist.', + 13, + ]]); + } } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 681ee02a24..3e5f063643 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/null-coalesce.php'], [ - [ - 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', - 32, - ], - [ - 'Offset \'string\' on array(1, 2, 3) on left side of ?? does not exist.', - 45, - ], - [ - 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ?? does not exist.', - 49, - ], - [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? always exists and is not nullable.', - 67, - ], - [ - 'Offset \'b\' on array() on left side of ?? does not exist.', - 79, - ], - [ - 'Expression on left side of ?? is not nullable.', - 81, - ], - [ - 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', - 89, - ], - [ - 'Property CoalesceRule\FooCoalesce::$alwaysNull (null) on left side of ?? is always null.', - 91, - ], - [ - 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', - 93, - ], - [ - 'Static property CoalesceRule\FooCoalesce::$staticString (string) on left side of ?? is not nullable.', - 99, - ], - [ - 'Static property CoalesceRule\FooCoalesce::$staticAlwaysNull (null) on left side of ?? is always null.', - 101, - ], - [ - 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', - 120, - ], - [ - 'Property CoalesceRule\FooCoalesce::$alwaysNull (null) on left side of ?? is always null.', - 122, - ], - [ - 'Expression on left side of ?? is not nullable.', - 124, - ], - [ - 'Expression on left side of ?? is always null.', - 125, - ], - [ - 'Static property CoalesceRule\FooCoalesce::$staticAlwaysNull (null) on left side of ?? is always null.', - 130, - ], - [ - 'Static property CoalesceRule\FooCoalesce::$staticString (string) on left side of ?? is not nullable.', - 131, - ], - [ - 'Property ReflectionClass::$name (class-string) on left side of ?? is not nullable.', - 136, - ], - ]); - } - - public function testCoalesceAssignRule(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + public function testCoalesceRule(): void + { + $this->analyse([__DIR__ . '/data/null-coalesce.php'], [ + [ + 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', + 32, + ], + [ + 'Offset \'string\' on array(1, 2, 3) on left side of ?? does not exist.', + 45, + ], + [ + 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ?? does not exist.', + 49, + ], + [ + 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? always exists and is not nullable.', + 67, + ], + [ + 'Offset \'b\' on array() on left side of ?? does not exist.', + 79, + ], + [ + 'Expression on left side of ?? is not nullable.', + 81, + ], + [ + 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', + 89, + ], + [ + 'Property CoalesceRule\FooCoalesce::$alwaysNull (null) on left side of ?? is always null.', + 91, + ], + [ + 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', + 93, + ], + [ + 'Static property CoalesceRule\FooCoalesce::$staticString (string) on left side of ?? is not nullable.', + 99, + ], + [ + 'Static property CoalesceRule\FooCoalesce::$staticAlwaysNull (null) on left side of ?? is always null.', + 101, + ], + [ + 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', + 120, + ], + [ + 'Property CoalesceRule\FooCoalesce::$alwaysNull (null) on left side of ?? is always null.', + 122, + ], + [ + 'Expression on left side of ?? is not nullable.', + 124, + ], + [ + 'Expression on left side of ?? is always null.', + 125, + ], + [ + 'Static property CoalesceRule\FooCoalesce::$staticAlwaysNull (null) on left side of ?? is always null.', + 130, + ], + [ + 'Static property CoalesceRule\FooCoalesce::$staticString (string) on left side of ?? is not nullable.', + 131, + ], + [ + 'Property ReflectionClass::$name (class-string) on left side of ?? is not nullable.', + 136, + ], + ]); + } - $this->analyse([__DIR__ . '/data/null-coalesce-assign.php'], [ - [ - 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', - 32, - ], - [ - 'Offset \'string\' on array(1, 2, 3) on left side of ??= does not exist.', - 45, - ], - [ - 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ??= does not exist.', - 49, - ], - [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= always exists and is not nullable.', - 67, - ], - [ - 'Offset \'b\' on array() on left side of ??= does not exist.', - 79, - ], - [ - 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', - 89, - ], - [ - 'Property CoalesceAssignRule\FooCoalesce::$alwaysNull (null) on left side of ??= is always null.', - 91, - ], - [ - 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', - 93, - ], - [ - 'Static property CoalesceAssignRule\FooCoalesce::$staticString (string) on left side of ??= is not nullable.', - 99, - ], - [ - 'Static property CoalesceAssignRule\FooCoalesce::$staticAlwaysNull (null) on left side of ??= is always null.', - 101, - ], - ]); - } + public function testCoalesceAssignRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + $this->analyse([__DIR__ . '/data/null-coalesce-assign.php'], [ + [ + 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', + 32, + ], + [ + 'Offset \'string\' on array(1, 2, 3) on left side of ??= does not exist.', + 45, + ], + [ + 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ??= does not exist.', + 49, + ], + [ + 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= always exists and is not nullable.', + 67, + ], + [ + 'Offset \'b\' on array() on left side of ??= does not exist.', + 79, + ], + [ + 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', + 89, + ], + [ + 'Property CoalesceAssignRule\FooCoalesce::$alwaysNull (null) on left side of ??= is always null.', + 91, + ], + [ + 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', + 93, + ], + [ + 'Static property CoalesceAssignRule\FooCoalesce::$staticString (string) on left side of ??= is not nullable.', + 99, + ], + [ + 'Static property CoalesceAssignRule\FooCoalesce::$staticAlwaysNull (null) on left side of ??= is always null.', + 101, + ], + ]); + } - $this->analyse([__DIR__ . '/data/null-coalesce-nullsafe.php'], []); - } + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/null-coalesce-nullsafe.php'], []); + } } diff --git a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php index f674af7523..4c4beff9e5 100644 --- a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new ThrowTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testRule(): void - { - $this->analyse( - [__DIR__ . '/data/throw-values.php'], - [ - [ - 'Invalid type int to throw.', - 29, - ], - [ - 'Invalid type ThrowValues\InvalidException to throw.', - 32, - ], - [ - 'Invalid type ThrowValues\InvalidInterfaceException to throw.', - 35, - ], - [ - 'Invalid type Exception|null to throw.', - 38, - ], - [ - 'Throwing object of an unknown class ThrowValues\NonexistentClass.', - 44, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ] - ); - } - - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/throw-class-exists.php'], []); - } + public function testRule(): void + { + $this->analyse( + [__DIR__ . '/data/throw-values.php'], + [ + [ + 'Invalid type int to throw.', + 29, + ], + [ + 'Invalid type ThrowValues\InvalidException to throw.', + 32, + ], + [ + 'Invalid type ThrowValues\InvalidInterfaceException to throw.', + 35, + ], + [ + 'Invalid type Exception|null to throw.', + 38, + ], + [ + 'Throwing object of an unknown class ThrowValues\NonexistentClass.', + 44, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ] + ); + } + public function testClassExists(): void + { + $this->analyse([__DIR__ . '/data/throw-class-exists.php'], []); + } } diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index f1903324ce..6ec233e7c8 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/unset.php'], [ - [ - 'Call to function unset() contains undefined variable $notSetVariable.', - 6, - ], - [ - 'Cannot unset offset \'a\' on 3.', - 10, - ], - [ - 'Cannot unset offset \'b\' on 1.', - 14, - ], - [ - 'Cannot unset offset \'c\' on 1.', - 18, - ], - [ - 'Cannot unset offset \'b\' on 1.', - 18, - ], - [ - 'Cannot unset offset \'string\' on iterable.', - 31, - ], - [ - 'Call to function unset() contains undefined variable $notSetVariable.', - 36, - ], - ]); - } - - public function testBug2752(): void - { - $this->analyse([__DIR__ . '/data/bug-2752.php'], []); - } + public function testUnsetRule(): void + { + require_once __DIR__ . '/data/unset.php'; + $this->analyse([__DIR__ . '/data/unset.php'], [ + [ + 'Call to function unset() contains undefined variable $notSetVariable.', + 6, + ], + [ + 'Cannot unset offset \'a\' on 3.', + 10, + ], + [ + 'Cannot unset offset \'b\' on 1.', + 14, + ], + [ + 'Cannot unset offset \'c\' on 1.', + 18, + ], + [ + 'Cannot unset offset \'b\' on 1.', + 18, + ], + [ + 'Cannot unset offset \'string\' on iterable.', + 31, + ], + [ + 'Call to function unset() contains undefined variable $notSetVariable.', + 36, + ], + ]); + } - public function testBug4289(): void - { - $this->analyse([__DIR__ . '/data/bug-4289.php'], []); - } + public function testBug2752(): void + { + $this->analyse([__DIR__ . '/data/bug-2752.php'], []); + } + public function testBug4289(): void + { + $this->analyse([__DIR__ . '/data/bug-4289.php'], []); + } } diff --git a/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php index 6d588de94a..f3954c2ab6 100644 --- a/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 14, - ], - [ - 'Variable $neverDefinedVariable in isset() is never defined.', - 22, - ], - [ - 'Variable $anotherNeverDefinedVariable in isset() is never defined.', - 42, - ], - [ - 'Variable $yetAnotherNeverDefinedVariable in isset() is never defined.', - 46, - ], - [ - 'Variable $yetYetAnotherNeverDefinedVariableInIsset in isset() is never defined.', - 56, - ], - [ - 'Variable $anotherVariableInDoWhile in isset() always exists and is not nullable.', - 104, - ], - [ - 'Variable $variableInSecondCase in isset() is never defined.', - 110, - ], - [ - 'Variable $variableInFirstCase in isset() always exists and is not nullable.', - 112, - ], - [ - 'Variable $variableInFirstCase in isset() always exists and is not nullable.', - 116, - ], - [ - 'Variable $variableInSecondCase in isset() always exists and is not nullable.', - 117, - ], - [ - 'Variable $variableAssignedInSecondCase in isset() is never defined.', - 119, - ], - [ - 'Variable $alwaysDefinedForSwitchCondition in isset() always exists and is not nullable.', - 139, - ], - [ - 'Variable $alwaysDefinedForCaseNodeCondition in isset() always exists and is not nullable.', - 140, - ], - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 152, - ], - [ - 'Variable $neverDefinedVariable in isset() is never defined.', - 152, - ], - [ - 'Variable $a in isset() always exists and is not nullable.', - 214, - ], - [ - 'Variable $null in isset() is always null.', - 225, - ], - ]); - } - - public function testIssetInGlobalScope(): void - { - $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 8, - ], - ]); - } + public function testVariableCertaintyInIsset(): void + { + $this->analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 14, + ], + [ + 'Variable $neverDefinedVariable in isset() is never defined.', + 22, + ], + [ + 'Variable $anotherNeverDefinedVariable in isset() is never defined.', + 42, + ], + [ + 'Variable $yetAnotherNeverDefinedVariable in isset() is never defined.', + 46, + ], + [ + 'Variable $yetYetAnotherNeverDefinedVariableInIsset in isset() is never defined.', + 56, + ], + [ + 'Variable $anotherVariableInDoWhile in isset() always exists and is not nullable.', + 104, + ], + [ + 'Variable $variableInSecondCase in isset() is never defined.', + 110, + ], + [ + 'Variable $variableInFirstCase in isset() always exists and is not nullable.', + 112, + ], + [ + 'Variable $variableInFirstCase in isset() always exists and is not nullable.', + 116, + ], + [ + 'Variable $variableInSecondCase in isset() always exists and is not nullable.', + 117, + ], + [ + 'Variable $variableAssignedInSecondCase in isset() is never defined.', + 119, + ], + [ + 'Variable $alwaysDefinedForSwitchCondition in isset() always exists and is not nullable.', + 139, + ], + [ + 'Variable $alwaysDefinedForCaseNodeCondition in isset() always exists and is not nullable.', + 140, + ], + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 152, + ], + [ + 'Variable $neverDefinedVariable in isset() is never defined.', + 152, + ], + [ + 'Variable $a in isset() always exists and is not nullable.', + 214, + ], + [ + 'Variable $null in isset() is always null.', + 225, + ], + ]); + } - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } + public function testIssetInGlobalScope(): void + { + $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 8, + ], + ]); + } - $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); - } + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); + } } diff --git a/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php index c9c7fe7b22..8719c7275f 100644 --- a/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/variable-certainty-null.php'], [ - [ - 'Variable $scalar on left side of ?? always exists and is not nullable.', - 6, - ], - [ - 'Variable $doesNotExist on left side of ?? is never defined.', - 8, - ], - [ - 'Variable $a on left side of ?? is always null.', - 13, - ], - ]); - } - - public function testVariableCertaintyInNullCoalesceAssign(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } + public function testVariableCertaintyInNullCoalesce(): void + { + $this->analyse([__DIR__ . '/data/variable-certainty-null.php'], [ + [ + 'Variable $scalar on left side of ?? always exists and is not nullable.', + 6, + ], + [ + 'Variable $doesNotExist on left side of ?? is never defined.', + 8, + ], + [ + 'Variable $a on left side of ?? is always null.', + 13, + ], + ]); + } - $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ - [ - 'Variable $scalar on left side of ??= always exists and is not nullable.', - 6, - ], - [ - 'Variable $doesNotExist on left side of ??= is never defined.', - 8, - ], - [ - 'Variable $a on left side of ??= is always null.', - 13, - ], - ]); - } + public function testVariableCertaintyInNullCoalesceAssign(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } - public function testNullCoalesceInGlobalScope(): void - { - $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ - [ - 'Variable $bar on left side of ?? always exists and is not nullable.', - 6, - ], - ]); - } + $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ + [ + 'Variable $scalar on left side of ??= always exists and is not nullable.', + 6, + ], + [ + 'Variable $doesNotExist on left side of ??= is never defined.', + 8, + ], + [ + 'Variable $a on left side of ??= is always null.', + 13, + ], + ]); + } + public function testNullCoalesceInGlobalScope(): void + { + $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ + [ + 'Variable $bar on left side of ?? always exists and is not nullable.', + 6, + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php index 4371c48567..28897c38f1 100644 --- a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php @@ -1,4 +1,6 @@ -createReflectionProvider(), true, false, true, false)); + } - protected function getRule(): \PHPStan\Rules\Rule - { - return new VariableCloningRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); - } - - public function testClone(): void - { - $this->analyse([__DIR__ . '/data/variable-cloning.php'], [ - [ - 'Cannot clone int|string.', - 11, - ], - [ - 'Cannot clone non-object variable $stringData of type string.', - 14, - ], - [ - 'Cannot clone string.', - 15, - ], - [ - 'Cannot clone non-object variable $bar of type string|VariableCloning\Foo.', - 19, - ], - [ - 'Cloning object of an unknown class VariableCloning\Bar.', - 23, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ]); - } - + public function testClone(): void + { + $this->analyse([__DIR__ . '/data/variable-cloning.php'], [ + [ + 'Cannot clone int|string.', + 11, + ], + [ + 'Cannot clone non-object variable $stringData of type string.', + 14, + ], + [ + 'Cannot clone string.', + 15, + ], + [ + 'Cannot clone non-object variable $bar of type string|VariableCloning\Foo.', + 19, + ], + [ + 'Cloning object of an unknown class VariableCloning\Bar.', + 23, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } } diff --git a/tests/PHPStan/Rules/Variables/data/boolean-op-truthy-falsey.php b/tests/PHPStan/Rules/Variables/data/boolean-op-truthy-falsey.php index aea86d23f7..a5765e2fce 100644 --- a/tests/PHPStan/Rules/Variables/data/boolean-op-truthy-falsey.php +++ b/tests/PHPStan/Rules/Variables/data/boolean-op-truthy-falsey.php @@ -3,33 +3,33 @@ namespace DefinedVariablesBooleanOperatorTruthyFalsey; function (bool $a, string $subject) { - if ($a && preg_match('#a#', $subject, $matches)) { - var_dump($matches); - } else { - var_dump($matches); - } + if ($a && preg_match('#a#', $subject, $matches)) { + var_dump($matches); + } else { + var_dump($matches); + } }; function (bool $a, string $subject) { - if ($a || preg_match('#a#', $subject, $matches)) { - var_dump($matches); - } else { - var_dump($matches); - } + if ($a || preg_match('#a#', $subject, $matches)) { + var_dump($matches); + } else { + var_dump($matches); + } }; function (bool $a, string $subject) { - if (preg_match('#a#', $subject, $matches) && $a) { - var_dump($matches); - } else { - var_dump($matches); - } + if (preg_match('#a#', $subject, $matches) && $a) { + var_dump($matches); + } else { + var_dump($matches); + } }; function (bool $a, string $subject) { - if (preg_match('#a#', $subject, $matches) || $a) { - var_dump($matches); - } else { - var_dump($matches); - } + if (preg_match('#a#', $subject, $matches) || $a) { + var_dump($matches); + } else { + var_dump($matches); + } }; diff --git a/tests/PHPStan/Rules/Variables/data/bug-1306.php b/tests/PHPStan/Rules/Variables/data/bug-1306.php index d073514343..43793656fc 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-1306.php +++ b/tests/PHPStan/Rules/Variables/data/bug-1306.php @@ -2,11 +2,12 @@ namespace Bug1306; -function bar($foo = null) { - if ($foo !== null) { - $someBoolean = false; - } +function bar($foo = null) +{ + if ($foo !== null) { + $someBoolean = false; + } - if ($foo !== null && $someBoolean === false) { - } + if ($foo !== null && $someBoolean === false) { + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-2748.php b/tests/PHPStan/Rules/Variables/data/bug-2748.php index 57e1580140..0f2d5010c4 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-2748.php +++ b/tests/PHPStan/Rules/Variables/data/bug-2748.php @@ -4,20 +4,18 @@ class Foo { + public function doBar() + { + $foo->bar = 'test2'; + } - public function doBar() - { - $foo->bar = 'test2'; - } - - public function doBaz() - { - $foo::$bar = 'test2'; - } - - public function doLorem(string $foo) - { - $foo::$bar = 'test3'; - } + public function doBaz() + { + $foo::$bar = 'test2'; + } + public function doLorem(string $foo) + { + $foo::$bar = 'test3'; + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-2752.php b/tests/PHPStan/Rules/Variables/data/bug-2752.php index 3c23f00ddb..d99c345ad6 100755 --- a/tests/PHPStan/Rules/Variables/data/bug-2752.php +++ b/tests/PHPStan/Rules/Variables/data/bug-2752.php @@ -4,10 +4,8 @@ class Foo extends \SimpleXMLElement { - - public function doFoo() - { - unset($this[0]); - } - + public function doFoo() + { + unset($this[0]); + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-3515.php b/tests/PHPStan/Rules/Variables/data/bug-3515.php index da9a2c1242..e0423a75de 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-3515.php +++ b/tests/PHPStan/Rules/Variables/data/bug-3515.php @@ -11,11 +11,10 @@ /** @var int $foo */ $bar = $foo + 1; -function (): void -{ - /** - * @var mixed[] $anArray - */ - $value1 = $anArray[0]; - $value2 = $anArray[1]; +function (): void { + /** + * @var mixed[] $anArray + */ + $value1 = $anArray[0]; + $value2 = $anArray[1]; }; diff --git a/tests/PHPStan/Rules/Variables/data/bug-4289.php b/tests/PHPStan/Rules/Variables/data/bug-4289.php index 2b5326f79b..3bcd236128 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-4289.php +++ b/tests/PHPStan/Rules/Variables/data/bug-4289.php @@ -1,28 +1,30 @@ -fields = [ - 'foo' => 'bar', - 'some' => 'what', - ]; - } + public function populateFields(): void + { + $this->fields = [ + 'foo' => 'bar', + 'some' => 'what', + ]; + } } class ChildClass extends BaseClass { - public function populateFields(): void - { - if (empty($this->fields)) { - parent::populateFields(); + public function populateFields(): void + { + if (empty($this->fields)) { + parent::populateFields(); - unset($this->fields['foo']); - } - } + unset($this->fields['foo']); + } + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-4290.php b/tests/PHPStan/Rules/Variables/data/bug-4290.php index fd06e3e0bb..14589eae3a 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-4290.php +++ b/tests/PHPStan/Rules/Variables/data/bug-4290.php @@ -4,27 +4,27 @@ class HelloWorld { - public function test(): void - { - $array = self::getArray(); + public function test(): void + { + $array = self::getArray(); - $data = array_filter([ - 'status' => isset($array['status']) ? $array['status'] : null, - 'value' => isset($array['value']) ? $array['value'] : null, - ]); + $data = array_filter([ + 'status' => isset($array['status']) ? $array['status'] : null, + 'value' => isset($array['value']) ? $array['value'] : null, + ]); - if (count($data) === 0) { - return; - } + if (count($data) === 0) { + return; + } - isset($data['status']) ? 1 : 0; - } + isset($data['status']) ? 1 : 0; + } - /** - * @return string[] - */ - public static function getArray(): array - { - return ['value' => '100']; - } + /** + * @return string[] + */ + public static function getArray(): array + { + return ['value' => '100']; + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-4412.php b/tests/PHPStan/Rules/Variables/data/bug-4412.php index a8fd1970fe..76be777995 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-4412.php +++ b/tests/PHPStan/Rules/Variables/data/bug-4412.php @@ -7,14 +7,14 @@ */ class B { - /** @var self<\Exception> $a */ - public $a; - - /** - * @phpstan-return T - */ - public function get() { - return $a->get(); - } + /** @var self<\Exception> $a */ + public $a; + /** + * @phpstan-return T + */ + public function get() + { + return $a->get(); + } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-4671.php b/tests/PHPStan/Rules/Variables/data/bug-4671.php index beba71f7a5..6ee6ba3af6 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-4671.php +++ b/tests/PHPStan/Rules/Variables/data/bug-4671.php @@ -4,14 +4,12 @@ class Foo { - - /** - * @param array $strings - */ - public function doFoo(int $intput, array $strings): void - { - if (isset($strings[(string) $intput])) { - } - } - + /** + * @param array $strings + */ + public function doFoo(int $intput, array $strings): void + { + if (isset($strings[(string) $intput])) { + } + } } diff --git a/tests/PHPStan/Rules/Variables/data/cli-arguments-variables.php b/tests/PHPStan/Rules/Variables/data/cli-arguments-variables.php index 3ce05af649..25622934ce 100644 --- a/tests/PHPStan/Rules/Variables/data/cli-arguments-variables.php +++ b/tests/PHPStan/Rules/Variables/data/cli-arguments-variables.php @@ -2,10 +2,10 @@ echo $argc; function () { - echo $argc; + echo $argc; }; function () { - global $argv; - var_dump($argv); + global $argv; + var_dump($argv); }; diff --git a/tests/PHPStan/Rules/Variables/data/compact-variables.php b/tests/PHPStan/Rules/Variables/data/compact-variables.php index c6b6dd0d01..9e3ba477f6 100644 --- a/tests/PHPStan/Rules/Variables/data/compact-variables.php +++ b/tests/PHPStan/Rules/Variables/data/compact-variables.php @@ -4,28 +4,28 @@ class Foo { - /** - * @return string[] - */ - public function doFoo(string $foo): array - { - $methodFoo = 'foo'; - $methodBar = 'bar'; + /** + * @return string[] + */ + public function doFoo(string $foo): array + { + $methodFoo = 'foo'; + $methodBar = 'bar'; - if ($foo === 'defined') { - $baz = 'maybe defined'; - } + if ($foo === 'defined') { + $baz = 'maybe defined'; + } - return compact( - $foo, - $methodFoo, - $methodBar, - 'baz' - ); - } + return compact( + $foo, + $methodFoo, + $methodBar, + 'baz' + ); + } - public function doBar(): void - { - compact([[['foo']]]); - } + public function doBar(): void + { + compact([[['foo']]]); + } } diff --git a/tests/PHPStan/Rules/Variables/data/dead-branches.php b/tests/PHPStan/Rules/Variables/data/dead-branches.php index 55b65bad98..9d75d481e9 100644 --- a/tests/PHPStan/Rules/Variables/data/dead-branches.php +++ b/tests/PHPStan/Rules/Variables/data/dead-branches.php @@ -1,95 +1,76 @@ = 7.4 += 7.4 namespace DefinedVariablesArrowFunctions; class Foo { + public function doFoo() + { + fn () => $a; - public function doFoo() - { - fn() => $a; - - fn(int $a) => $a; - - $local = 1; - fn() => $local; + fn (int $a) => $a; - fn() => $this->test; + $local = 1; + fn () => $local; - static fn() => $this->test; - } + fn () => $this->test; + static fn () => $this->test; + } } diff --git a/tests/PHPStan/Rules/Variables/data/defined-variables-closures.php b/tests/PHPStan/Rules/Variables/data/defined-variables-closures.php index 0f42a952a1..04a4b5c1a8 100644 --- a/tests/PHPStan/Rules/Variables/data/defined-variables-closures.php +++ b/tests/PHPStan/Rules/Variables/data/defined-variables-closures.php @@ -4,14 +4,14 @@ class Foo { - public function doFoo() - { - function () { - var_dump($this); - }; + public function doFoo() + { + function () { + var_dump($this); + }; - static function () { - var_dump($this); - }; - } + static function () { + var_dump($this); + }; + } } diff --git a/tests/PHPStan/Rules/Variables/data/defined-variables-coalesce-assign.php b/tests/PHPStan/Rules/Variables/data/defined-variables-coalesce-assign.php index baa7ba802d..14a667d7c4 100644 --- a/tests/PHPStan/Rules/Variables/data/defined-variables-coalesce-assign.php +++ b/tests/PHPStan/Rules/Variables/data/defined-variables-coalesce-assign.php @@ -1,19 +1,19 @@ -= 7.4 += 7.4 namespace DefinedVariablesCoalesceAssign; class Foo { + public function doFoo() + { + $a ??= 'foo'; + $b['foo'] ??= 'bar'; + } - public function doFoo() - { - $a ??= 'foo'; - $b['foo'] ??= 'bar'; - } - - public function doBar() - { - $a ??= $b; - } - + public function doBar() + { + $a ??= $b; + } } diff --git a/tests/PHPStan/Rules/Variables/data/defined-variables-definition.php b/tests/PHPStan/Rules/Variables/data/defined-variables-definition.php index 9424e98dbe..94877d485d 100644 --- a/tests/PHPStan/Rules/Variables/data/defined-variables-definition.php +++ b/tests/PHPStan/Rules/Variables/data/defined-variables-definition.php @@ -2,30 +2,30 @@ namespace DefinedVariables; -function &refFunction() { - $obj = new \stdClass(); - return $obj; +function &refFunction() +{ + $obj = new \stdClass(); + return $obj; }; -function funcWithSpecialParameter($one, $two, &$three) { - $three = 'test'; +function funcWithSpecialParameter($one, $two, &$three) +{ + $three = 'test'; } -function functionWithByRefParameter(&$test) { - +function functionWithByRefParameter(&$test) +{ } class Foo { - - public function doFoo($one, $two, &$three) - { - $three = 'test'; - } - - public static function doStaticFoo($one, $two, &$three) - { - $three = 'anotherTest'; - } - + public function doFoo($one, $two, &$three) + { + $three = 'test'; + } + + public static function doStaticFoo($one, $two, &$three) + { + $three = 'anotherTest'; + } } diff --git a/tests/PHPStan/Rules/Variables/data/defined-variables.php b/tests/PHPStan/Rules/Variables/data/defined-variables.php index 121ee4779f..6c6da7efba 100644 --- a/tests/PHPStan/Rules/Variables/data/defined-variables.php +++ b/tests/PHPStan/Rules/Variables/data/defined-variables.php @@ -1,437 +1,438 @@ foo(); - - switch (foo()) { - case 1: - $definedInCases = foo(); - break; - case 2: - $definedInCases = bar(); - break; - } - - $definedInCases->foo(); - - - do { - $doWhileVar = 1; - } while ($doWhileVar > 1); - - - foo($fooParameterBeforeDeclaration, $fooParameterBeforeDeclaration = 1); - bar($barParameter = 1, $barParameter); - - preg_match('#.*#', 'foo', $matches); - parse_str( - $parseStrParameter, - $parseStrParameter, - $parseStrParameter - ); - $foo ?? $foo; // $foo undefined (once - after ??) - $bar[0] ?? null; // OK - - $willBeUnset = 'foo'; - unset($willBeUnset); - $willBeUnset; - - $arrayVariableCannotBeUnsetByDimFetch = ['foo' => 1]; - unset($arrayVariableCannotBeUnsetByDimFetch['foo']); - $arrayVariableCannotBeUnsetByDimFetch; - - $mustAlreadyExistWhenDividing /= 5; - - $anonymousClassObject = new class {}; - - $newArrayCreatedByDimFetch[] = 'foo'; - echo $newArrayCreatedByDimFetch[0]; - - $arrayDoesNotExist['foo']; - - $undefinedVariable; - - $containerBuilder = getContainer(); - $serviceDefinition = $containerBuilder->addDefinition($serviceName = prefix('cache')) - ->setAutowired(false); +function () { + if ($definedLater) { + $definedLater = 1; + $definedInIfOnly = foo(); + } - instantiate($serviceName); + $definedInIfOnly->foo(); - function () use (&$errorHandler) { - $errorHandler->handle(); // variable is fine here - }; + switch (foo()) { + case 1: + $definedInCases = foo(); + break; + case 2: + $definedInCases = bar(); + break; + } - $refObject = &refFunction(); - $refObject->foo; - - funcWithSpecialParameter(1, 2, $variableDefinedInsideTheFunction); - echo $variableDefinedInsideTheFunction; + $definedInCases->foo(); - $fooObject = new Foo(); - $fooObject->doFoo(1, 2, $anotherVariableDefinedInsideTheFunction); - echo $anotherVariableDefinedInsideTheFunction; - - if ($fooInCondition = doFoo()) { - $fooInCondition->foo(); - } elseif ($barInCondition = $fooInCondition) { - $barInCondition->bar(); - } elseif (doBar()) { - $barInCondition->differentBar(); - } else { - $fooInCondition->differentFoo(); - $barInCondition->totallyDifferentBar(); - } - - \Closure::bind(function () { - $this->doFoo(); - }, $fooObject); - \Closure::bind(function () { - $this->doFoo(); // $this undefined - }); - \Closure::bind(function () { - $this->doFoo(); // $this undefined - }, null); - - $someArray = [1, 2, [3, 4]]; - list($variableInList, $anotherVariableInList, list($yetAnotherVariableInList, $yetAnotherAnotherVariableInList)) = $someArray; - - foreach ($someArray as list($destructuredA, $destructuredB, list($destructuredC, $destructuredD))) { - echo $destructuredA, $destructuredB, $destructuredC, $destructuredD; - } - - $str = '12'; - $resource = fopen(); - sscanf($str, '%d%d', $sscanfArgument, $anotherSscanfArgument); - fscanf($resource, '%d%d', $fscanfArgument, $anotherFscanfArgument); - doFoo($sscanfArgument, $anotherSscanfArgument, $fscanfArgument, $anotherFscanfArgument); - - Foo::doStaticFoo(1, 2, $variableDefinedInStaticMethodPassedByReference); - echo $variableDefinedInStaticMethodPassedByReference; - echo $echoedVariable = 1; - echo $echoedVariable; + do { + $doWhileVar = 1; + } while ($doWhileVar > 1); - print $printedVariable = 2; - print $printedVariable; - - foreach ($variableAssignedInForeach = [] as $v) { - echo $variableAssignedInForeach; - } - echo $variableAssignedInForeach; - - $someArray[$variableDefinedInDimFetch = 1]; - - if (isset($anotherAnotherInIsset::$anotherInIsset::$_[$variableAssignedInIsset = 123]) && $variableAssignedInIsset > 0) { - doFoo($variableAssignedInIsset); // defined here - } - doFoo($variableAssignedInIsset); - - unset($unsettingUndefinedVariable); // it's fine from PHP POV - - ($variableInBooleanAnd = 123) && $variableInBooleanAnd; - - function () use (&$variablePassedByReferenceToClosure) { - - }; - echo $variablePassedByReferenceToClosure; - if (empty($variableInEmpty) && empty($anotherVariableInEmpty['foo'])) { - echo $variableInEmpty; // does not exist here - return; - } else { - //echo $variableInEmpty; // exists here - not yet supported - } - - if (!empty($negatedVariableInEmpty)) { - echo $negatedVariableInEmpty; // exists here - } - - echo $variableInEmpty; - echo $negatedVariableInEmpty; // does not exist here - - if (isset($variableInIsset) && isset($anotherVariableInIsset['foo'])) { - echo $variableInIsset && $anotherVariableInIsset; - } else { - echo $variableInIsset && $anotherVariableInIsset; // does not exist - } - - switch ('foo') { - case 1: - $variableInSwitchWithEarlyTerminatingStatement = 'foo'; - break; - case 2: - $variableInSwitchWithEarlyTerminatingStatement = 'bar'; - break; - default: - return 'test'; - } - - echo $variableInSwitchWithEarlyTerminatingStatement; - - foreach ($someArray as $someArrayKey => &$valueByReference) { - if (is_array($valueByReference)) { - $valueByReference = implode(',', $valueByReference); - } - } - unset($valueByReference); - - function () { - var_dump($http_response_header); - fopen('http://www.google.com', 'r'); - var_dump($http_response_header); - }; - - function () { - var_dump($http_response_header); - file_get_contents('http://www.google.com'); - var_dump($http_response_header); - }; - - ($variableDefinedInTernary = doFoo()) ? ('foo' . $variableDefinedInTernary): 'bar'; - echo $variableDefinedInTernary; - - $fooObject->select($parameterValue = 'test')->from($parameterValue); - echo $parameterValue; - - $arrayWithAssignmentInKey = [ - $assignedInKey => 'baz', - 'baz' => $assignedInKey, - $assignedInKey = 'foo' => $assignedInKey . 'bar' . ($assignedInValue = 'foo'), - $assignedInKey . $assignedInValue => $assignedInKey . $assignedInValue, - ]; - echo $assignedInKey; - - if (($isInstanceOf = $fooObject) instanceof Foo && $isInstanceOf) { - - } - echo $isInstanceOf; - - isset($nonexistentVariableInIsset); - - if (doFoo()) { - $definedInIfWithElseIfElse = 'foo'; - } else { - if (doFoo()) { - return; - } elseif (doBar()) { - return; - } else { - return; - } - } - - echo $definedInIfWithElseIfElse; - - try { - $definedInTryCatchIfElse = 'foo'; - } catch (Exception $e) { - if (doFoo()) { - throw $e; - } else { - return; - } - } - echo $definedInTryCatchIfElse; - - foreach ($someArray as $someKey => list($destructuredAa, $destructuredBb, list($destructuredCc, $destructuredDd))) { - - } - - for ($forI = 0; $forI < 10, $forK = 5; $forI++, $forK++, $forJ = $forI) { - echo $forI; - } - - echo $forI; - echo $forJ; - - try { - $variableDefinedInTry = 1; - $variableDefinedInTryAndAllCatches = 1; maybeThrow(); - } catch (\FooException $e) { - $variableDefinedInTryAndAllCatches = 1; - $variableAvailableInAllCatches = 1; - $variableDefinedOnlyInOneCatch = 'foo'; - echo $variableDefinedInTry; - } catch (\BarException $e) { - $variableDefinedInTryAndAllCatches = 1; - $variableAvailableInAllCatches = 2; - } finally { - echo $variableDefinedInTryAndAllCatches; - echo $variableAvailableInAllCatches; - echo $variableDefinedOnlyInOneCatch; - $variableDefinedInFinally = 1; - echo $variableDefinedInFinally; - } - - echo $variableDefinedInFinally; - - list(, $variableInListWithMissingItem) = $someArray; - echo $variableInListWithMissingItem; - - $variableInBitwiseAndAssign &= $anotherVariableBitwiseAndAssign = doFoo(); - echo $variableInBitwiseAndAssign; - echo $anotherVariableBitwiseAndAssign; - - do { - echo $mightBeUndefinedInDoWhile; - $definedInDoWhile = 1; - } while ($mightBeUndefinedInDoWhile = 1 && rand(0, 1)); - - echo $definedInDoWhile; - - switch (true) { - case $variableInFirstCase = false: - echo $variableInSecondCase; // does not exist yet - case $variableInSecondCase = false: - echo $variableInFirstCase; - echo $variableInSecondCase; - echo $variableAssignedInSecondCase = true; - break; - case whatever(): - echo $variableInFirstCase; - echo $variableInSecondCase; - $variableInFallthroughCase = true; - echo $variableAssignedInSecondCase; // surely undefined - case foo(): - echo $variableInFallthroughCase; // might be undefined - echo $variableInFirstCase; - default: - - } - - switch (true) { - default: - $variableFromDefaultFirst = true; - case 1: - echo $variableFromDefaultFirst; // might be undefined - } - - foreach ($undefinedVariableInForeach as $v) { - - } - - require $fileA='includeA.php'; - echo $fileA; - - include($fileB='includeB.php'); - echo $fileB; - - for ($forLoopVariableInit = 0; $forLoopVariableInit < 5; $forLoopVariableInit = $forLoopVariable, $anotherForLoopVariable = 1) { - $forLoopVariable = 2; - } - echo $anotherForLoopVariable; - - - switch ('test') { - case 'blah': - $weirdSwitchVariable = 'something'; - break; - - case 'foo': - $weirdSwitchVariable = 'muhehee'; - break; - default: - return; - break; - } - - echo $weirdSwitchVariable; - - [] ? ($definedInTernary = 'foo') : ($definedInTernary = 'bar'); - echo $definedInTernary; - - [] ? ($maybeDefinedInTernary = 'foo') : false; - echo $maybeDefinedInTernary; - - [] ? true : ($anotherMaybeDefinedInTernary = 'foo'); - echo $anotherMaybeDefinedInTernary; - - while ($whileVariableUsedAndThenDefined && $whileVariableUsedAndThenDefined = 1 && rand(0, 1)) { - - } + foo($fooParameterBeforeDeclaration, $fooParameterBeforeDeclaration = 1); + bar($barParameter = 1, $barParameter); - for (; $forVariableUsedAndThenDefined && $forVariableUsedAndThenDefined = 1;) { + preg_match('#.*#', 'foo', $matches); + parse_str( + $parseStrParameter, + $parseStrParameter, + $parseStrParameter + ); - } + $foo ?? $foo; // $foo undefined (once - after ??) + $bar[0] ?? null; // OK - while (isset($variableInWhileIsset)) { - echo $variableInWhileIsset; - } + $willBeUnset = 'foo'; + unset($willBeUnset); + $willBeUnset; - reset($unknownVariablePassedToReset); - echo $unknownVariablePassedToReset; + $arrayVariableCannotBeUnsetByDimFetch = ['foo' => 1]; + unset($arrayVariableCannotBeUnsetByDimFetch['foo']); + $arrayVariableCannotBeUnsetByDimFetch; - function ($bm) { - $t = []; - for ($i = 0; ($b = 2 ** $i) <= $bm; ++$i) { - if ($bm & $b) { - $t[] = $b; - } - } + $mustAlreadyExistWhenDividing /= 5; - return $t; - }; + $anonymousClassObject = new class() {}; - echo $_GET['test']; + $newArrayCreatedByDimFetch[] = 'foo'; + echo $newArrayCreatedByDimFetch[0]; - $variableInAssign = $variableInAssign; - $anotherVariableInAssign = &$anotherVariableInAssign; + $arrayDoesNotExist['foo']; - static $staticVariable = 'foo'; - echo $staticVariable; + $undefinedVariable; - function (): \Generator { - yield $x = 1; - yield $x + 1; - }; + $containerBuilder = getContainer(); + $serviceDefinition = $containerBuilder->addDefinition($serviceName = prefix('cache')) + ->setAutowired(false); - function y() - { - yield from ($x = x()); - yield $x; - } + instantiate($serviceName); - switch ($definedInSwitchCond = true) { - default: - echo $definedInSwitchCond; - } + function () use (&$errorHandler) { + $errorHandler->handle(); // variable is fine here + }; + + $refObject = &refFunction(); + $refObject->foo; + + funcWithSpecialParameter(1, 2, $variableDefinedInsideTheFunction); + echo $variableDefinedInsideTheFunction; + + $fooObject = new Foo(); + $fooObject->doFoo(1, 2, $anotherVariableDefinedInsideTheFunction); + echo $anotherVariableDefinedInsideTheFunction; + + if ($fooInCondition = doFoo()) { + $fooInCondition->foo(); + } elseif ($barInCondition = $fooInCondition) { + $barInCondition->bar(); + } elseif (doBar()) { + $barInCondition->differentBar(); + } else { + $fooInCondition->differentFoo(); + $barInCondition->totallyDifferentBar(); + } + + \Closure::bind(function () { + $this->doFoo(); + }, $fooObject); + \Closure::bind(function () { + $this->doFoo(); // $this undefined + }); + \Closure::bind(function () { + $this->doFoo(); // $this undefined + }, null); + + $someArray = [1, 2, [3, 4]]; + list($variableInList, $anotherVariableInList, list($yetAnotherVariableInList, $yetAnotherAnotherVariableInList)) = $someArray; + + foreach ($someArray as list($destructuredA, $destructuredB, list($destructuredC, $destructuredD))) { + echo $destructuredA, $destructuredB, $destructuredC, $destructuredD; + } + + $str = '12'; + $resource = fopen(); + sscanf($str, '%d%d', $sscanfArgument, $anotherSscanfArgument); + fscanf($resource, '%d%d', $fscanfArgument, $anotherFscanfArgument); + doFoo($sscanfArgument, $anotherSscanfArgument, $fscanfArgument, $anotherFscanfArgument); + + Foo::doStaticFoo(1, 2, $variableDefinedInStaticMethodPassedByReference); + echo $variableDefinedInStaticMethodPassedByReference; + + echo $echoedVariable = 1; + echo $echoedVariable; + + print $printedVariable = 2; + print $printedVariable; + + foreach ($variableAssignedInForeach = [] as $v) { + echo $variableAssignedInForeach; + } + echo $variableAssignedInForeach; + + $someArray[$variableDefinedInDimFetch = 1]; + + if (isset($anotherAnotherInIsset::$anotherInIsset::$_[$variableAssignedInIsset = 123]) && $variableAssignedInIsset > 0) { + doFoo($variableAssignedInIsset); // defined here + } + doFoo($variableAssignedInIsset); + + unset($unsettingUndefinedVariable); // it's fine from PHP POV + + ($variableInBooleanAnd = 123) && $variableInBooleanAnd; + + function () use (&$variablePassedByReferenceToClosure) { + }; + echo $variablePassedByReferenceToClosure; + if (empty($variableInEmpty) && empty($anotherVariableInEmpty['foo'])) { + echo $variableInEmpty; // does not exist here + return; + } else { + //echo $variableInEmpty; // exists here - not yet supported + } + + if (!empty($negatedVariableInEmpty)) { + echo $negatedVariableInEmpty; // exists here + } + + echo $variableInEmpty; + echo $negatedVariableInEmpty; // does not exist here + + if (isset($variableInIsset) && isset($anotherVariableInIsset['foo'])) { + echo $variableInIsset && $anotherVariableInIsset; + } else { + echo $variableInIsset && $anotherVariableInIsset; // does not exist + } + + switch ('foo') { + case 1: + $variableInSwitchWithEarlyTerminatingStatement = 'foo'; + break; + case 2: + $variableInSwitchWithEarlyTerminatingStatement = 'bar'; + break; + default: + return 'test'; + } + + echo $variableInSwitchWithEarlyTerminatingStatement; + + foreach ($someArray as $someArrayKey => &$valueByReference) { + if (is_array($valueByReference)) { + $valueByReference = implode(',', $valueByReference); + } + } + unset($valueByReference); + + function () { + var_dump($http_response_header); + fopen('http://www.google.com', 'r'); + var_dump($http_response_header); + }; + + function () { + var_dump($http_response_header); + file_get_contents('http://www.google.com'); + var_dump($http_response_header); + }; + + ($variableDefinedInTernary = doFoo()) ? ('foo' . $variableDefinedInTernary) : 'bar'; + echo $variableDefinedInTernary; + + $fooObject->select($parameterValue = 'test')->from($parameterValue); + echo $parameterValue; + + $arrayWithAssignmentInKey = [ + $assignedInKey => 'baz', + 'baz' => $assignedInKey, + $assignedInKey = 'foo' => $assignedInKey . 'bar' . ($assignedInValue = 'foo'), + $assignedInKey . $assignedInValue => $assignedInKey . $assignedInValue, + ]; + echo $assignedInKey; + + if (($isInstanceOf = $fooObject) instanceof Foo && $isInstanceOf) { + } + echo $isInstanceOf; + + isset($nonexistentVariableInIsset); + + if (doFoo()) { + $definedInIfWithElseIfElse = 'foo'; + } else { + if (doFoo()) { + return; + } elseif (doBar()) { + return; + } else { + return; + } + } + + echo $definedInIfWithElseIfElse; + + try { + $definedInTryCatchIfElse = 'foo'; + } catch (Exception $e) { + if (doFoo()) { + throw $e; + } else { + return; + } + } + echo $definedInTryCatchIfElse; + + foreach ($someArray as $someKey => list($destructuredAa, $destructuredBb, list($destructuredCc, $destructuredDd))) { + } + + for ($forI = 0; $forI < 10, $forK = 5; $forI++, $forK++, $forJ = $forI) { + echo $forI; + } + + echo $forI; + echo $forJ; + + try { + $variableDefinedInTry = 1; + $variableDefinedInTryAndAllCatches = 1; + maybeThrow(); + } catch (\FooException $e) { + $variableDefinedInTryAndAllCatches = 1; + $variableAvailableInAllCatches = 1; + $variableDefinedOnlyInOneCatch = 'foo'; + echo $variableDefinedInTry; + } catch (\BarException $e) { + $variableDefinedInTryAndAllCatches = 1; + $variableAvailableInAllCatches = 2; + } finally { + echo $variableDefinedInTryAndAllCatches; + echo $variableAvailableInAllCatches; + echo $variableDefinedOnlyInOneCatch; + $variableDefinedInFinally = 1; + echo $variableDefinedInFinally; + } + + echo $variableDefinedInFinally; + + list(, $variableInListWithMissingItem) = $someArray; + echo $variableInListWithMissingItem; + + $variableInBitwiseAndAssign &= $anotherVariableBitwiseAndAssign = doFoo(); + echo $variableInBitwiseAndAssign; + echo $anotherVariableBitwiseAndAssign; + + do { + echo $mightBeUndefinedInDoWhile; + $definedInDoWhile = 1; + } while ($mightBeUndefinedInDoWhile = 1 && rand(0, 1)); + + echo $definedInDoWhile; + + switch (true) { + case $variableInFirstCase = false: + echo $variableInSecondCase; // does not exist yet + // no break + case $variableInSecondCase = false: + echo $variableInFirstCase; + echo $variableInSecondCase; + echo $variableAssignedInSecondCase = true; + break; + case whatever(): + echo $variableInFirstCase; + echo $variableInSecondCase; + $variableInFallthroughCase = true; + echo $variableAssignedInSecondCase; // surely undefined + // no break + case foo(): + echo $variableInFallthroughCase; // might be undefined + echo $variableInFirstCase; + // no break + default: + + } + + switch (true) { + default: + $variableFromDefaultFirst = true; + // no break + case 1: + echo $variableFromDefaultFirst; // might be undefined + } + + foreach ($undefinedVariableInForeach as $v) { + } + + require $fileA='includeA.php'; + echo $fileA; + + include($fileB='includeB.php'); + echo $fileB; + + for ($forLoopVariableInit = 0; $forLoopVariableInit < 5; $forLoopVariableInit = $forLoopVariable, $anotherForLoopVariable = 1) { + $forLoopVariable = 2; + } + echo $anotherForLoopVariable; + + + switch ('test') { + case 'blah': + $weirdSwitchVariable = 'something'; + break; + + case 'foo': + $weirdSwitchVariable = 'muhehee'; + break; + + default: + return; + break; + } + + echo $weirdSwitchVariable; + + [] ? ($definedInTernary = 'foo') : ($definedInTernary = 'bar'); + echo $definedInTernary; + + [] ? ($maybeDefinedInTernary = 'foo') : false; + echo $maybeDefinedInTernary; + + [] ? true : ($anotherMaybeDefinedInTernary = 'foo'); + echo $anotherMaybeDefinedInTernary; + + while ($whileVariableUsedAndThenDefined && $whileVariableUsedAndThenDefined = 1 && rand(0, 1)) { + } + + for (; $forVariableUsedAndThenDefined && $forVariableUsedAndThenDefined = 1;) { + } + + while (isset($variableInWhileIsset)) { + echo $variableInWhileIsset; + } + + reset($unknownVariablePassedToReset); + echo $unknownVariablePassedToReset; + + function ($bm) { + $t = []; + for ($i = 0; ($b = 2 ** $i) <= $bm; ++$i) { + if ($bm & $b) { + $t[] = $b; + } + } + + return $t; + }; + + echo $_GET['test']; + + $variableInAssign = $variableInAssign; + $anotherVariableInAssign = &$anotherVariableInAssign; + + static $staticVariable = 'foo'; + echo $staticVariable; + + function (): \Generator { + yield $x = 1; + yield $x + 1; + }; + + function y() + { + yield from ($x = x()); + yield $x; + } - echo $definedInSwitchCond; + switch ($definedInSwitchCond = true) { + default: + echo $definedInSwitchCond; + } - $someOtherArray = []; - $someOtherArray[$undefinedArrayIndex][$anotherUndefinedArrayIndex] = 0; + echo $definedInSwitchCond; - function () { - if (rand(0, 1)) { - $test = 1; - } + $someOtherArray = []; + $someOtherArray[$undefinedArrayIndex][$anotherUndefinedArrayIndex] = 0; - echo $test ?? 'foo'; - }; + function () { + if (rand(0, 1)) { + $test = 1; + } - function () { - if (rand(0, 1)) { - functionWithByRefParameter($str); - } - if ($str === "hello") {} - }; + echo $test ?? 'foo'; + }; - function () { - if (rand(0, 1) || functionWithByRefParameter($str)) { - if ($str === "hello") {} - } - }; + function () { + if (rand(0, 1)) { + functionWithByRefParameter($str); + } + if ($str === "hello") { + } + }; - if (!Foo::doStaticFoo(1, 2, $variableDefinedInBooleaNotByReference)) { + function () { + if (rand(0, 1) || functionWithByRefParameter($str)) { + if ($str === "hello") { + } + } + }; - } + if (!Foo::doStaticFoo(1, 2, $variableDefinedInBooleaNotByReference)) { + } - echo $variableDefinedInBooleaNotByReference; + echo $variableDefinedInBooleaNotByReference; }; diff --git a/tests/PHPStan/Rules/Variables/data/foreach-always-iterable.php b/tests/PHPStan/Rules/Variables/data/foreach-always-iterable.php index c7c5cb9dd2..403d19c1e0 100644 --- a/tests/PHPStan/Rules/Variables/data/foreach-always-iterable.php +++ b/tests/PHPStan/Rules/Variables/data/foreach-always-iterable.php @@ -1,77 +1,77 @@ $val) { - $test = 1; - } + foreach ([] as $key => $val) { + $test = 1; + } - echo $key; - echo $val; - echo $test; + echo $key; + echo $val; + echo $test; }; function () { - foreach ([1, 2, 3] as $key => $val) { - $test = 1; - } + foreach ([1, 2, 3] as $key => $val) { + $test = 1; + } - echo $key; - echo $val; - echo $test; + echo $key; + echo $val; + echo $test; }; function () { - foreach ([1, 2, 3] as $key => $val) { - if (rand(0, 1)) { - break; - } - $test = 1; - } - - echo $key; - echo $val; - echo $test; + foreach ([1, 2, 3] as $key => $val) { + if (rand(0, 1)) { + break; + } + $test = 1; + } + + echo $key; + echo $val; + echo $test; }; function () { - if (rand(0, 1) === 0) { - $key = 1; - $test = 1; - } + if (rand(0, 1) === 0) { + $key = 1; + $test = 1; + } - foreach ([] as $key => $val) { - $test = 1; - } + foreach ([] as $key => $val) { + $test = 1; + } - echo $key; - echo $test; + echo $key; + echo $test; }; function (array $array) { - if (rand(0, 1) === 0) { - $key = 1; - $test = 1; - } + if (rand(0, 1) === 0) { + $key = 1; + $test = 1; + } - foreach ($array as $key => $val) { - $test = 1; - } + foreach ($array as $key => $val) { + $test = 1; + } - echo $key; - echo $test; + echo $key; + echo $test; }; function () { - if (rand(0, 1) === 0) { - $key = 1; - $test = 1; - } + if (rand(0, 1) === 0) { + $key = 1; + $test = 1; + } - foreach ([1, 2, 3] as $key => $val) { - $test = 1; - } + foreach ([1, 2, 3] as $key => $val) { + $test = 1; + } - echo $key; - echo $test; + echo $key; + echo $test; }; diff --git a/tests/PHPStan/Rules/Variables/data/foreach.php b/tests/PHPStan/Rules/Variables/data/foreach.php index 7d145465d8..7215f260cd 100644 --- a/tests/PHPStan/Rules/Variables/data/foreach.php +++ b/tests/PHPStan/Rules/Variables/data/foreach.php @@ -1,220 +1,192 @@ 0) { - foreach ($arr as $val) { - $test = 1; - } + if (count($arr) > 0) { + foreach ($arr as $val) { + $test = 1; + } - echo $val; - echo $test; - } + echo $val; + echo $test; + } }; function (array $arr) { - if (count($arr) >= 1) { - foreach ($arr as $val) { - $test = 1; - } + if (count($arr) >= 1) { + foreach ($arr as $val) { + $test = 1; + } - echo $val; - echo $test; - } + echo $val; + echo $test; + } };*/ function (array $arr) { + if ($arr === []) { + return; + } - if ($arr === []) { - return; - } - - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; + foreach ($arr as $val) { + $test = 1; + } + echo $val; + echo $test; }; function (array $arr) { - - if ($arr !== []) { - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; - } - + if ($arr !== []) { + foreach ($arr as $val) { + $test = 1; + } + + echo $val; + echo $test; + } }; function (array $arr) { - - if (count($arr) === 0) { - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; - } - + if (count($arr) === 0) { + foreach ($arr as $val) { + $test = 1; + } + + echo $val; + echo $test; + } }; function (array $arr) { + if (count($arr) !== 0) { + return; + } - if (count($arr) !== 0) { - return; - } - - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; + foreach ($arr as $val) { + $test = 1; + } + echo $val; + echo $test; }; function (array $arr) { + if (count($arr) !== 1) { + return; + } - if (count($arr) !== 1) { - return; - } - - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; + foreach ($arr as $val) { + $test = 1; + } + echo $val; + echo $test; }; function (array $arr) { + if (count($arr) === 1) { + return; + } - if (count($arr) === 1) { - return; - } - - foreach ($arr as $val) { - $test = 1; - } - - echo $val; - echo $test; + foreach ($arr as $val) { + $test = 1; + } + echo $val; + echo $test; }; diff --git a/tests/PHPStan/Rules/Variables/data/global-variables.php b/tests/PHPStan/Rules/Variables/data/global-variables.php index ec433b4b7c..88575a7a1b 100644 --- a/tests/PHPStan/Rules/Variables/data/global-variables.php +++ b/tests/PHPStan/Rules/Variables/data/global-variables.php @@ -4,13 +4,11 @@ class Foo { + public function doFoo() + { + global $foo, $bar; - public function doFoo() - { - global $foo, $bar; - - echo $foo; - echo $bar; - } - + echo $foo; + echo $bar; + } } diff --git a/tests/PHPStan/Rules/Variables/data/isset-global-scope.php b/tests/PHPStan/Rules/Variables/data/isset-global-scope.php index 73407e1bbf..6681a55b40 100644 --- a/tests/PHPStan/Rules/Variables/data/isset-global-scope.php +++ b/tests/PHPStan/Rules/Variables/data/isset-global-scope.php @@ -2,13 +2,12 @@ $alwaysDefinedNotNullable = 'string'; if (doFoo()) { - $sometimesDefinedVariable = 1; + $sometimesDefinedVariable = 1; } if (isset( - $alwaysDefinedNotNullable, // always true - $sometimesDefinedVariable, // fine, this is what's isset() is for - $neverDefinedVariable // always false - do not report in global scope + $alwaysDefinedNotNullable, // always true + $sometimesDefinedVariable, // fine, this is what's isset() is for + $neverDefinedVariable // always false - do not report in global scope )) { - } diff --git a/tests/PHPStan/Rules/Variables/data/isset-native-property-types.php b/tests/PHPStan/Rules/Variables/data/isset-native-property-types.php index f493c1fac6..77fd12e285 100644 --- a/tests/PHPStan/Rules/Variables/data/isset-native-property-types.php +++ b/tests/PHPStan/Rules/Variables/data/isset-native-property-types.php @@ -1,23 +1,23 @@ -= 7.4 += 7.4 namespace IssetNativePropertyTypes; class Foo { + public int $hasDefaultValue = 0; - public int $hasDefaultValue = 0; - - public int $isAssignedBefore; - - public int $canBeUninitialized; + public int $isAssignedBefore; + public int $canBeUninitialized; } function (Foo $foo): void { - echo isset($foo->hasDefaultValue) ? $foo->hasDefaultValue : null; + echo isset($foo->hasDefaultValue) ? $foo->hasDefaultValue : null; - $foo->isAssignedBefore = 5; - echo isset($foo->isAssignedBefore) ? $foo->isAssignedBefore : null; + $foo->isAssignedBefore = 5; + echo isset($foo->isAssignedBefore) ? $foo->isAssignedBefore : null; - echo isset($foo->canBeUninitialized) ? $foo->canBeUninitialized : null; + echo isset($foo->canBeUninitialized) ? $foo->canBeUninitialized : null; }; diff --git a/tests/PHPStan/Rules/Variables/data/isset-nullsafe.php b/tests/PHPStan/Rules/Variables/data/isset-nullsafe.php index f174a8afd3..c3198699eb 100644 --- a/tests/PHPStan/Rules/Variables/data/isset-nullsafe.php +++ b/tests/PHPStan/Rules/Variables/data/isset-nullsafe.php @@ -1,13 +1,14 @@ -= 8.0 += 8.0 namespace IssetNullsafe; function () { - if (rand(0, 2)) { - $foo = 'blabla'; - } - - if (isset($foo?->bla)) { + if (rand(0, 2)) { + $foo = 'blabla'; + } - } + if (isset($foo?->bla)) { + } }; diff --git a/tests/PHPStan/Rules/Variables/data/isset.php b/tests/PHPStan/Rules/Variables/data/isset.php index 508bbac2ce..35228793e8 100644 --- a/tests/PHPStan/Rules/Variables/data/isset.php +++ b/tests/PHPStan/Rules/Variables/data/isset.php @@ -4,97 +4,97 @@ class FooCoalesce { - /** @var string|null */ - public static $staticStringOrNull = null; + /** @var string|null */ + public static $staticStringOrNull = null; - /** @var string */ - public static $staticString = ''; + /** @var string */ + public static $staticString = ''; - /** @var null */ - public static $staticAlwaysNull; + /** @var null */ + public static $staticAlwaysNull; - /** @var string|null */ - public $stringOrNull = null; + /** @var string|null */ + public $stringOrNull = null; - /** @var string */ - public $string = ''; + /** @var string */ + public $string = ''; - /** @var null */ - public $alwaysNull; + /** @var null */ + public $alwaysNull; - /** @var FooCoalesce|null */ - public $fooCoalesceOrNull; + /** @var FooCoalesce|null */ + public $fooCoalesceOrNull; - /** @var FooCoalesce */ - public $fooCoalesce; + /** @var FooCoalesce */ + public $fooCoalesce; - public function thisCoalesce() { - echo isset($this->string) ? $this->string : null; - } + public function thisCoalesce() + { + echo isset($this->string) ? $this->string : null; + } } function coalesce() { + $scalar = 3; - $scalar = 3; + echo isset($scalar) ? $scalar : 4; - echo isset($scalar) ? $scalar : 4; + $array = [1, 2, 3]; - $array = [1, 2, 3]; + echo isset($array['string']) ? $array['string'] : 0; - echo isset($array['string']) ? $array['string'] : 0; + $multiDimArray = [[1], [2], [3]]; - $multiDimArray = [[1], [2], [3]]; + echo isset($multiDimArray['string']) ? $multiDimArray['string'] : 0; - echo isset($multiDimArray['string']) ? $multiDimArray['string'] : 0; + echo isset($doesNotExist) ? $doesNotExist : 0; - echo isset($doesNotExist) ? $doesNotExist : 0; + if (rand() > 0.5) { + $maybeVariable = 3; + } - if (rand() > 0.5) { - $maybeVariable = 3; - } + echo isset($maybeVariable) ? $maybeVariable : 0; - echo isset($maybeVariable) ? $maybeVariable : 0; + $fixedDimArray = [ + 'dim' => 1, + 'dim-null' => rand() > 0.5 ? null : 1, + 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], + 'dim-empty' => [] + ]; - $fixedDimArray = [ - 'dim' => 1, - 'dim-null' => rand() > 0.5 ? null : 1, - 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], - 'dim-empty' => [] - ]; + // Always set + echo isset($fixedDimArray['dim']) ? $fixedDimArray['dim'] : 0; - // Always set - echo isset($fixedDimArray['dim']) ? $fixedDimArray['dim'] : 0; + // Maybe set + echo isset($fixedDimArray['dim-null']) ? $fixedDimArray['dim-null'] : 0; - // Maybe set - echo isset($fixedDimArray['dim-null']) ? $fixedDimArray['dim-null'] : 0; + // Never set, then unknown + echo isset($fixedDimArray['dim-null-not-set']['a']) ? $fixedDimArray['dim-null-not-set']['a'] : 0; - // Never set, then unknown - echo isset($fixedDimArray['dim-null-not-set']['a']) ? $fixedDimArray['dim-null-not-set']['a'] : 0; + // Always set, then always set + echo isset($fixedDimArray['dim-null-offset']['a']) ? $fixedDimArray['dim-null-offset']['a'] : 0; - // Always set, then always set - echo isset($fixedDimArray['dim-null-offset']['a']) ? $fixedDimArray['dim-null-offset']['a'] : 0; + // Always set, then never set + echo isset($fixedDimArray['dim-empty']['b']) ? $fixedDimArray['dim-empty']['b'] : 0; - // Always set, then never set - echo isset($fixedDimArray['dim-empty']['b']) ? $fixedDimArray['dim-empty']['b'] : 0; + $foo = new FooCoalesce(); - $foo = new FooCoalesce(); + echo isset($foo->stringOrNull) ? $foo->stringOrNull : ''; - echo isset($foo->stringOrNull) ? $foo->stringOrNull : ''; + echo isset($foo->string) ? $foo->string : ''; - echo isset($foo->string) ? $foo->string : ''; + echo isset($foo->alwaysNull) ? $foo->alwaysNull : ''; - echo isset($foo->alwaysNull) ? $foo->alwaysNull : ''; + echo isset($foo->fooCoalesce->string) ? $foo->fooCoalesce->string : ''; - echo isset($foo->fooCoalesce->string) ? $foo->fooCoalesce->string : ''; + echo isset($foo->fooCoalesceOrNull->string) ? $foo->fooCoalesceOrNull->string : ''; - echo isset($foo->fooCoalesceOrNull->string) ? $foo->fooCoalesceOrNull->string : ''; + echo isset(FooCoalesce::$staticStringOrNull) ? FooCoalesce::$staticStringOrNull : ''; - echo isset(FooCoalesce::$staticStringOrNull) ? FooCoalesce::$staticStringOrNull : ''; + echo isset(FooCoalesce::$staticString) ? FooCoalesce::$staticString : ''; - echo isset(FooCoalesce::$staticString) ? FooCoalesce::$staticString : ''; - - echo isset(FooCoalesce::$staticAlwaysNull) ? FooCoalesce::$staticAlwaysNull : ''; + echo isset(FooCoalesce::$staticAlwaysNull) ? FooCoalesce::$staticAlwaysNull : ''; } /** @@ -102,27 +102,26 @@ function coalesce() */ function coalesceStringOffset(array $array) { - echo isset($array['string']) ? $array['string'] : 0; + echo isset($array['string']) ? $array['string'] : 0; } -function alwaysNullCoalesce (?string $a): void +function alwaysNullCoalesce(?string $a): void { - if (!is_string($a)) { - echo isset($a) ? $a : 'foo'; - } + if (!is_string($a)) { + echo isset($a) ? $a : 'foo'; + } } function (): void { - echo isset((new FooCoalesce())->string) ? (new FooCoalesce())->string : 'foo'; - echo isset((new FooCoalesce())->stringOrNull) ? (new FooCoalesce())->stringOrNull : 'foo'; - echo isset((new FooCoalesce())->alwaysNull) ? (new FooCoalesce())->alwaysNull : 'foo'; + echo isset((new FooCoalesce())->string) ? (new FooCoalesce())->string : 'foo'; + echo isset((new FooCoalesce())->stringOrNull) ? (new FooCoalesce())->stringOrNull : 'foo'; + echo isset((new FooCoalesce())->alwaysNull) ? (new FooCoalesce())->alwaysNull : 'foo'; }; -function (FooCoalesce $foo): void -{ - echo isset($foo::$staticAlwaysNull) ? $foo::$staticAlwaysNull : 'foo'; - echo isset($foo::$staticString) ? $foo::$staticString : 'foo'; - echo isset($foo::$staticStringOrNull) ? $foo::$staticStringOrNull : 'foo'; +function (FooCoalesce $foo): void { + echo isset($foo::$staticAlwaysNull) ? $foo::$staticAlwaysNull : 'foo'; + echo isset($foo::$staticString) ? $foo::$staticString : 'foo'; + echo isset($foo::$staticStringOrNull) ? $foo::$staticStringOrNull : 'foo'; }; /** @@ -131,32 +130,31 @@ function (FooCoalesce $foo): void */ class SomeMagicProperties { - } function (SomeMagicProperties $foo, \stdClass $std): void { - echo isset($foo->integerProperty) ? $foo->integerProperty : null; + echo isset($foo->integerProperty) ? $foo->integerProperty : null; - echo isset($foo->foo->string) ? $foo->foo->string : null; + echo isset($foo->foo->string) ? $foo->foo->string : null; - echo isset($std->foo) ? $std->foo : null; + echo isset($std->foo) ? $std->foo : null; }; function numericStringOffset(string $code): string { - $array = [1, 2, 3]; + $array = [1, 2, 3]; - if (isset($array[$code])) { - return (string) $array[$code]; - } + if (isset($array[$code])) { + return (string) $array[$code]; + } - $mappings = [ - '21021200' => '21028800', - ]; + $mappings = [ + '21021200' => '21028800', + ]; - if (isset($mappings[$code])) { - return (string) $mappings[$code]; - } + if (isset($mappings[$code])) { + return (string) $mappings[$code]; + } - throw new \RuntimeException(); + throw new \RuntimeException(); } diff --git a/tests/PHPStan/Rules/Variables/data/loop-initial-assignments.php b/tests/PHPStan/Rules/Variables/data/loop-initial-assignments.php index 3591a3ec74..4682070528 100644 --- a/tests/PHPStan/Rules/Variables/data/loop-initial-assignments.php +++ b/tests/PHPStan/Rules/Variables/data/loop-initial-assignments.php @@ -1,13 +1,11 @@ = 7.4 += 7.4 namespace CoalesceAssignRule; class FooCoalesce { - /** @var string|null */ - public static $staticStringOrNull = null; + /** @var string|null */ + public static $staticStringOrNull = null; - /** @var string */ - public static $staticString = ''; + /** @var string */ + public static $staticString = ''; - /** @var null */ - public static $staticAlwaysNull; + /** @var null */ + public static $staticAlwaysNull; - /** @var string|null */ - public $stringOrNull = null; + /** @var string|null */ + public $stringOrNull = null; - /** @var string */ - public $string = ''; + /** @var string */ + public $string = ''; - /** @var null */ - public $alwaysNull; + /** @var null */ + public $alwaysNull; - /** @var FooCoalesce|null */ - public $fooCoalesceOrNull; + /** @var FooCoalesce|null */ + public $fooCoalesceOrNull; - /** @var FooCoalesce */ - public $fooCoalesce; + /** @var FooCoalesce */ + public $fooCoalesce; - public function thisCoalesce() { - echo $this->string ??= null; - } + public function thisCoalesce() + { + echo $this->string ??= null; + } } function coalesce() { + $scalar = 3; - $scalar = 3; - - echo $scalar ??= 4; + echo $scalar ??= 4; - $array = [1, 2, 3]; + $array = [1, 2, 3]; - echo $array['string'] ??= 0; + echo $array['string'] ??= 0; - $multiDimArray = [[1], [2], [3]]; + $multiDimArray = [[1], [2], [3]]; - echo $multiDimArray['string'] ??= 0; + echo $multiDimArray['string'] ??= 0; - echo $doesNotExist ??= 0; + echo $doesNotExist ??= 0; - if (rand() > 0.5) { - $maybeVariable = 3; - } + if (rand() > 0.5) { + $maybeVariable = 3; + } - echo $maybeVariable ??= 0; + echo $maybeVariable ??= 0; - $fixedDimArray = [ - 'dim' => 1, - 'dim-null' => rand() > 0.5 ? null : 1, - 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], - 'dim-empty' => [] - ]; + $fixedDimArray = [ + 'dim' => 1, + 'dim-null' => rand() > 0.5 ? null : 1, + 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], + 'dim-empty' => [] + ]; - // Always set - echo $fixedDimArray['dim'] ??= 0; + // Always set + echo $fixedDimArray['dim'] ??= 0; - // Maybe set - echo $fixedDimArray['dim-null'] ??= 0; + // Maybe set + echo $fixedDimArray['dim-null'] ??= 0; - // Never set, then unknown - echo $fixedDimArray['dim-null-not-set']['a'] ??= 0; + // Never set, then unknown + echo $fixedDimArray['dim-null-not-set']['a'] ??= 0; - // Always set, then always set - echo $fixedDimArray['dim-null-offset']['a'] ??= 0; + // Always set, then always set + echo $fixedDimArray['dim-null-offset']['a'] ??= 0; - // Always set, then never set - echo $fixedDimArray['dim-empty']['b'] ??= 0; + // Always set, then never set + echo $fixedDimArray['dim-empty']['b'] ??= 0; -// echo rand() ??= 0; // not valid for assignment + // echo rand() ??= 0; // not valid for assignment -// echo preg_replace('', '', '') ??= 0; // not valid for assignment + // echo preg_replace('', '', '') ??= 0; // not valid for assignment - $foo = new FooCoalesce(); + $foo = new FooCoalesce(); - echo $foo->stringOrNull ??= ''; + echo $foo->stringOrNull ??= ''; - echo $foo->string ??= ''; + echo $foo->string ??= ''; - echo $foo->alwaysNull ??= ''; + echo $foo->alwaysNull ??= ''; - echo $foo->fooCoalesce->string ??= ''; + echo $foo->fooCoalesce->string ??= ''; - echo $foo->fooCoalesceOrNull->string ??= ''; + echo $foo->fooCoalesceOrNull->string ??= ''; - echo FooCoalesce::$staticStringOrNull ??= ''; + echo FooCoalesce::$staticStringOrNull ??= ''; - echo FooCoalesce::$staticString ??= ''; + echo FooCoalesce::$staticString ??= ''; - echo FooCoalesce::$staticAlwaysNull ??= ''; + echo FooCoalesce::$staticAlwaysNull ??= ''; } /** @@ -106,12 +108,12 @@ function coalesce() */ function coalesceStringOffset(array $array) { - echo $array['string'] ??= 0; + echo $array['string'] ??= 0; } -function alwaysNullCoalesce (?string $a): void +function alwaysNullCoalesce(?string $a): void { - if (!is_string($a)) { - echo $a ??= 'foo'; - } + if (!is_string($a)) { + echo $a ??= 'foo'; + } } diff --git a/tests/PHPStan/Rules/Variables/data/null-coalesce-nullsafe.php b/tests/PHPStan/Rules/Variables/data/null-coalesce-nullsafe.php index 357ddb6056..20bee89795 100644 --- a/tests/PHPStan/Rules/Variables/data/null-coalesce-nullsafe.php +++ b/tests/PHPStan/Rules/Variables/data/null-coalesce-nullsafe.php @@ -1,19 +1,18 @@ -= 8.0 += 8.0 namespace NullCoalesceNullsafe; class Foo { - - public function doFoo( - $mixed, - \Exception $nonNullable, - ?\Exception $nullable - ) - { - $mixed?->foo; - $nonNullable?->foo; - $nullable?->foo; - } - + public function doFoo( + $mixed, + \Exception $nonNullable, + ?\Exception $nullable + ) { + $mixed?->foo; + $nonNullable?->foo; + $nullable?->foo; + } } diff --git a/tests/PHPStan/Rules/Variables/data/null-coalesce.php b/tests/PHPStan/Rules/Variables/data/null-coalesce.php index a694fa67aa..40b4313d98 100644 --- a/tests/PHPStan/Rules/Variables/data/null-coalesce.php +++ b/tests/PHPStan/Rules/Variables/data/null-coalesce.php @@ -4,101 +4,101 @@ class FooCoalesce { - /** @var string|null */ - public static $staticStringOrNull = null; + /** @var string|null */ + public static $staticStringOrNull = null; - /** @var string */ - public static $staticString = ''; + /** @var string */ + public static $staticString = ''; - /** @var null */ - public static $staticAlwaysNull; + /** @var null */ + public static $staticAlwaysNull; - /** @var string|null */ - public $stringOrNull = null; + /** @var string|null */ + public $stringOrNull = null; - /** @var string */ - public $string = ''; + /** @var string */ + public $string = ''; - /** @var null */ - public $alwaysNull; + /** @var null */ + public $alwaysNull; - /** @var FooCoalesce|null */ - public $fooCoalesceOrNull; + /** @var FooCoalesce|null */ + public $fooCoalesceOrNull; - /** @var FooCoalesce */ - public $fooCoalesce; + /** @var FooCoalesce */ + public $fooCoalesce; - public function thisCoalesce() { - echo $this->string ?? null; - } + public function thisCoalesce() + { + echo $this->string ?? null; + } } function coalesce() { + $scalar = 3; - $scalar = 3; + echo $scalar ?? 4; - echo $scalar ?? 4; + $array = [1, 2, 3]; - $array = [1, 2, 3]; + echo $array['string'] ?? 0; - echo $array['string'] ?? 0; + $multiDimArray = [[1], [2], [3]]; - $multiDimArray = [[1], [2], [3]]; + echo $multiDimArray['string'] ?? 0; - echo $multiDimArray['string'] ?? 0; + echo $doesNotExist ?? 0; - echo $doesNotExist ?? 0; + if (rand() > 0.5) { + $maybeVariable = 3; + } - if (rand() > 0.5) { - $maybeVariable = 3; - } + echo $maybeVariable ?? 0; - echo $maybeVariable ?? 0; + $fixedDimArray = [ + 'dim' => 1, + 'dim-null' => rand() > 0.5 ? null : 1, + 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], + 'dim-empty' => [] + ]; - $fixedDimArray = [ - 'dim' => 1, - 'dim-null' => rand() > 0.5 ? null : 1, - 'dim-null-offset' => ['a' => rand() > 0.5 ? true : null], - 'dim-empty' => [] - ]; + // Always set + echo $fixedDimArray['dim'] ?? 0; - // Always set - echo $fixedDimArray['dim'] ?? 0; + // Maybe set + echo $fixedDimArray['dim-null'] ?? 0; - // Maybe set - echo $fixedDimArray['dim-null'] ?? 0; + // Never set, then unknown + echo $fixedDimArray['dim-null-not-set']['a'] ?? 0; - // Never set, then unknown - echo $fixedDimArray['dim-null-not-set']['a'] ?? 0; + // Always set, then always set + echo $fixedDimArray['dim-null-offset']['a'] ?? 0; - // Always set, then always set - echo $fixedDimArray['dim-null-offset']['a'] ?? 0; + // Always set, then never set + echo $fixedDimArray['dim-empty']['b'] ?? 0; - // Always set, then never set - echo $fixedDimArray['dim-empty']['b'] ?? 0; + echo rand() ?? 0; - echo rand() ?? 0; + echo preg_replace('', '', '') ?? 0; - echo preg_replace('', '', '') ?? 0; + $foo = new FooCoalesce(); - $foo = new FooCoalesce(); + echo $foo->stringOrNull ?? ''; - echo $foo->stringOrNull ?? ''; + echo $foo->string ?? ''; - echo $foo->string ?? ''; + echo $foo->alwaysNull ?? ''; - echo $foo->alwaysNull ?? ''; + echo $foo->fooCoalesce->string ?? ''; - echo $foo->fooCoalesce->string ?? ''; + echo $foo->fooCoalesceOrNull->string ?? ''; - echo $foo->fooCoalesceOrNull->string ?? ''; + echo FooCoalesce::$staticStringOrNull ?? ''; - echo FooCoalesce::$staticStringOrNull ?? ''; + echo FooCoalesce::$staticString ?? ''; - echo FooCoalesce::$staticString ?? ''; - - echo FooCoalesce::$staticAlwaysNull ?? ''; + echo FooCoalesce::$staticAlwaysNull ?? ''; } /** @@ -106,33 +106,32 @@ function coalesce() */ function coalesceStringOffset(array $array) { - echo $array['string'] ?? 0; + echo $array['string'] ?? 0; } -function alwaysNullCoalesce (?string $a): void +function alwaysNullCoalesce(?string $a): void { - if (!is_string($a)) { - echo $a ?? 'foo'; - } + if (!is_string($a)) { + echo $a ?? 'foo'; + } } function (): void { - echo (new FooCoalesce())->string ?? 'foo'; - echo (new FooCoalesce())->stringOrNull ?? 'foo'; - echo (new FooCoalesce())->alwaysNull ?? 'foo'; + echo (new FooCoalesce())->string ?? 'foo'; + echo (new FooCoalesce())->stringOrNull ?? 'foo'; + echo (new FooCoalesce())->alwaysNull ?? 'foo'; - (new FooCoalesce()) ?? 'foo'; - null ?? 'foo'; + (new FooCoalesce()) ?? 'foo'; + null ?? 'foo'; }; -function (FooCoalesce $foo): void -{ - echo $foo::$staticAlwaysNull ?? 'foo'; - echo $foo::$staticString ?? 'foo'; - echo $foo::$staticStringOrNull ?? 'foo'; +function (FooCoalesce $foo): void { + echo $foo::$staticAlwaysNull ?? 'foo'; + echo $foo::$staticString ?? 'foo'; + echo $foo::$staticStringOrNull ?? 'foo'; }; function (\ReflectionClass $ref): void { - echo $ref->name ?? 'foo'; - echo $ref->nonexistent ?? 'bar'; + echo $ref->name ?? 'foo'; + echo $ref->nonexistent ?? 'bar'; }; diff --git a/tests/PHPStan/Rules/Variables/data/this.php b/tests/PHPStan/Rules/Variables/data/this.php index 1c71aca1f3..ece7b3eae7 100644 --- a/tests/PHPStan/Rules/Variables/data/this.php +++ b/tests/PHPStan/Rules/Variables/data/this.php @@ -4,44 +4,39 @@ class Foo { + public function doFoo() + { + $this->test; + } - public function doFoo() - { - $this->test; + public static function doBar() + { + $this->test; - } - public static function doBar() - { - $this->test; - - - - $this->blabla = 'fooo'; - } + $this->blabla = 'fooo'; + } } function () { - $this->foo; + $this->foo; }; -new class () { - - public function doFoo() - { - $this->foo; - } - - public static function doBar() - { - $this->foo; - } +new class() { + public function doFoo() + { + $this->foo; + } + public static function doBar() + { + $this->foo; + } }; function () { - \Closure::bind(function (int $time) { - $this->setTimestamp($time); - }, new \DateTime()); + \Closure::bind(function (int $time) { + $this->setTimestamp($time); + }, new \DateTime()); }; diff --git a/tests/PHPStan/Rules/Variables/data/throw-class-exists.php b/tests/PHPStan/Rules/Variables/data/throw-class-exists.php index f819307398..845d032b98 100644 --- a/tests/PHPStan/Rules/Variables/data/throw-class-exists.php +++ b/tests/PHPStan/Rules/Variables/data/throw-class-exists.php @@ -6,14 +6,12 @@ class Foo { + public function doFoo(): void + { + if (!class_exists(Bar::class)) { + return; + } - public function doFoo(): void - { - if (!class_exists(Bar::class)) { - return; - } - - throw new Bar(); - } - + throw new Bar(); + } } diff --git a/tests/PHPStan/Rules/Variables/data/throw-values.php b/tests/PHPStan/Rules/Variables/data/throw-values.php index 5582923fa2..c4327cf058 100644 --- a/tests/PHPStan/Rules/Variables/data/throw-values.php +++ b/tests/PHPStan/Rules/Variables/data/throw-values.php @@ -2,61 +2,68 @@ namespace ThrowValues; -class InvalidException {}; -interface InvalidInterfaceException {}; -interface ValidInterfaceException extends \Throwable {}; +class InvalidException +{ +}; +interface InvalidInterfaceException +{ +}; +interface ValidInterfaceException extends \Throwable +{ +}; /** * @template T of \Exception * @param class-string $genericExceptionClassName * @param T $genericException */ -function test($genericExceptionClassName, $genericException) { - /** @var ValidInterfaceException $validInterface */ - $validInterface = new \Exception(); - /** @var InvalidInterfaceException $invalidInterface */ - $invalidInterface = new \Exception(); - /** @var \Exception|null $nullableException */ - $nullableException = new \Exception(); +function test($genericExceptionClassName, $genericException) +{ + /** @var ValidInterfaceException $validInterface */ + $validInterface = new \Exception(); + /** @var InvalidInterfaceException $invalidInterface */ + $invalidInterface = new \Exception(); + /** @var \Exception|null $nullableException */ + $nullableException = new \Exception(); - if (rand(0, 1)) { - throw new \Exception(); - } - if (rand(0, 1)) { - throw $validInterface; - } - if (rand(0, 1)) { - throw 123; - } - if (rand(0, 1)) { - throw new InvalidException(); - } - if (rand(0, 1)) { - throw $invalidInterface; - } - if (rand(0, 1)) { - throw $nullableException; - } - if (rand(0, 1)) { - throw foo(); - } - if (rand(0, 1)) { - throw new NonexistentClass(); - } - if (rand(0, 1)) { - throw new $genericExceptionClassName; - } - if (rand(0, 1)) { - throw $genericException; - } + if (rand(0, 1)) { + throw new \Exception(); + } + if (rand(0, 1)) { + throw $validInterface; + } + if (rand(0, 1)) { + throw 123; + } + if (rand(0, 1)) { + throw new InvalidException(); + } + if (rand(0, 1)) { + throw $invalidInterface; + } + if (rand(0, 1)) { + throw $nullableException; + } + if (rand(0, 1)) { + throw foo(); + } + if (rand(0, 1)) { + throw new NonexistentClass(); + } + if (rand(0, 1)) { + throw new $genericExceptionClassName(); + } + if (rand(0, 1)) { + throw $genericException; + } } function (\stdClass $foo) { - /** @var \Exception $foo */ - throw $foo; + /** @var \Exception $foo */ + throw $foo; }; function (\stdClass $foo) { - /** @var \Exception */ - throw $foo; + /** @var \Exception */ + throw $foo; }; diff --git a/tests/PHPStan/Rules/Variables/data/unset.php b/tests/PHPStan/Rules/Variables/data/unset.php index e253891736..aa3832fe9f 100644 --- a/tests/PHPStan/Rules/Variables/data/unset.php +++ b/tests/PHPStan/Rules/Variables/data/unset.php @@ -1,37 +1,35 @@ 1]; - $singleDimArray = ['a' => 1]; + unset($singleDimArray['a']['b']); - unset($singleDimArray['a']['b']); - - $multiDimArray = ['a' => ['b' => 1]]; - - unset($multiDimArray['a']['b']['c'], $scalar, $singleDimArray['a']['b']); + $multiDimArray = ['a' => ['b' => 1]]; + unset($multiDimArray['a']['b']['c'], $scalar, $singleDimArray['a']['b']); } /** @param iterable $iterable */ function unsetOnMaybeIterable(iterable $iterable) { - unset($iterable['string']); + unset($iterable['string']); } /** @param iterable $iterable */ function unsetOnYesIterable(iterable $iterable) { - unset($iterable['string']); + unset($iterable['string']); } function unsetArrayOffsetOnUndefinedVariable() { - unset($notSetVariable['a']); + unset($notSetVariable['a']); } diff --git a/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php b/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php index 866b7d33e4..65cbd0ca1a 100644 --- a/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php +++ b/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php @@ -3,227 +3,211 @@ function foo() { - /** @var string|null $alwaysDefinedNullable */ - $alwaysDefinedNullable = doFoo(); + /** @var string|null $alwaysDefinedNullable */ + $alwaysDefinedNullable = doFoo(); - if (isset($alwaysDefinedNullable)) { // fine, checking for nullability - - } - - $alwaysDefinedNotNullable = 'string'; - if (isset($alwaysDefinedNotNullable)) { // always true - - } - - if (doFoo()) { - $sometimesDefinedVariable = 1; - } - - if (isset( - $sometimesDefinedVariable, // fine, this is what's isset() is for - $neverDefinedVariable // always false - )) { - - } - - /** @var string|null $anotherAlwaysDefinedNullable */ - $anotherAlwaysDefinedNullable = doFoo(); - - if (isset($anotherAlwaysDefinedNullable['test']['test'])) { // fine, checking for nullability - - } - - $anotherAlwaysDefinedNotNullable = 'string'; - - if (isset($anotherAlwaysDefinedNotNullable['test']['test'])) { // fine, variable always exists, but what about the array index? - - } - - if (isset($anotherNeverDefinedVariable['test']['test']->test['test']['test'])) { // always false - - } - - if (isset($yetAnotherNeverDefinedVariable::$test['test'])) { // always false + if (isset($alwaysDefinedNullable)) { // fine, checking for nullability + } - } + $alwaysDefinedNotNullable = 'string'; + if (isset($alwaysDefinedNotNullable)) { // always true + } - if (isset($_COOKIE['test'])) { // fine + if (doFoo()) { + $sometimesDefinedVariable = 1; + } - } + if (isset( + $sometimesDefinedVariable, // fine, this is what's isset() is for + $neverDefinedVariable // always false + )) { + } - if (something()) { + /** @var string|null $anotherAlwaysDefinedNullable */ + $anotherAlwaysDefinedNullable = doFoo(); - } elseif (isset($yetYetAnotherNeverDefinedVariableInIsset)) { // always false + if (isset($anotherAlwaysDefinedNullable['test']['test'])) { // fine, checking for nullability + } - } + $anotherAlwaysDefinedNotNullable = 'string'; - if (doFoo()) { - $yetAnotherVariableThatSometimesExists = 1; - } + if (isset($anotherAlwaysDefinedNotNullable['test']['test'])) { // fine, variable always exists, but what about the array index? + } - if (something()) { + if (isset($anotherNeverDefinedVariable['test']['test']->test['test']['test'])) { // always false + } - } elseif (isset($yetAnotherVariableThatSometimesExists)) { // fine + if (isset($yetAnotherNeverDefinedVariable::$test['test'])) { // always false + } - } + if (isset($_COOKIE['test'])) { // fine + } - /** @var string|null $nullableVariableUsedInTernary */ - $nullableVariableUsedInTernary = doFoo(); - echo isset($nullableVariableUsedInTernary) ? 'foo' : 'bar'; // fine + if (something()) { + } elseif (isset($yetYetAnotherNeverDefinedVariableInIsset)) { // always false + } - /** @var int|null $forVariableInit */ - $forVariableInit = doFoo(); + if (doFoo()) { + $yetAnotherVariableThatSometimesExists = 1; + } - /** @var int|null $forVariableCond */ - $forVariableCond = doFoo(); + if (something()) { + } elseif (isset($yetAnotherVariableThatSometimesExists)) { // fine + } - /** @var int|null $forVariableLoop */ - $forVariableLoop = doFoo(); + /** @var string|null $nullableVariableUsedInTernary */ + $nullableVariableUsedInTernary = doFoo(); + echo isset($nullableVariableUsedInTernary) ? 'foo' : 'bar'; // fine - for ($i = 0, $init = isset($forVariableInit); $i < 10 && isset($forVariableCond); $i++, $loop = isset($forVariableLoop)) { + /** @var int|null $forVariableInit */ + $forVariableInit = doFoo(); - } + /** @var int|null $forVariableCond */ + $forVariableCond = doFoo(); - if (something()) { - $variableInWhile = 1; - } + /** @var int|null $forVariableLoop */ + $forVariableLoop = doFoo(); - while (isset($variableInWhile)) { - unset($variableInWhile); - } + for ($i = 0, $init = isset($forVariableInit); $i < 10 && isset($forVariableCond); $i++, $loop = isset($forVariableLoop)) { + } - if (something()) { - $variableInDoWhile = 1; - } + if (something()) { + $variableInWhile = 1; + } - do { - $anotherVariableInDoWhile = 1; - echo isset($yetAnotherVariableInDoWhile); // fine - } while ( - isset($variableInDoWhile) // fine - && isset($anotherVariableInDoWhile) // always defined - && ($yetAnotherVariableInDoWhile = 1) - ); + while (isset($variableInWhile)) { + unset($variableInWhile); + } - switch (true) { - case $variableInFirstCase = true: - isset($variableInSecondCase); // does not exist yet - case $variableInSecondCase = true: - isset($variableInFirstCase); // always defined - $variableAssignedInSecondCase = true; - break; - case whatever(): - isset($variableInFirstCase); // always defined - isset($variableInSecondCase); // always defined - $variableInFallthroughCase = true; - isset($variableAssignedInSecondCase); // surely undefined - case foo(): - isset($variableInFallthroughCase); // fine - default: + if (something()) { + $variableInDoWhile = 1; + } - } + do { + $anotherVariableInDoWhile = 1; + echo isset($yetAnotherVariableInDoWhile); // fine + } while ( + isset($variableInDoWhile) // fine + && isset($anotherVariableInDoWhile) // always defined + && ($yetAnotherVariableInDoWhile = 1) + ); + + switch (true) { + case $variableInFirstCase = true: + isset($variableInSecondCase); // does not exist yet + // no break + case $variableInSecondCase = true: + isset($variableInFirstCase); // always defined + $variableAssignedInSecondCase = true; + break; + case whatever(): + isset($variableInFirstCase); // always defined + isset($variableInSecondCase); // always defined + $variableInFallthroughCase = true; + isset($variableAssignedInSecondCase); // surely undefined + // no break + case foo(): + isset($variableInFallthroughCase); // fine + // no break + default: - if (foo()) { - $mightBeUndefinedForSwitchCondition = 1; - $mightBeUndefinedForCaseNodeCondition = 1; - } + } - switch (isset($mightBeUndefinedForSwitchCondition)) { // fine - case isset($mightBeUndefinedForCaseNodeCondition): // fine - break; - } + if (foo()) { + $mightBeUndefinedForSwitchCondition = 1; + $mightBeUndefinedForCaseNodeCondition = 1; + } - $alwaysDefinedForSwitchCondition = 1; - $alwaysDefinedForCaseNodeCondition = 1; + switch (isset($mightBeUndefinedForSwitchCondition)) { // fine + case isset($mightBeUndefinedForCaseNodeCondition): // fine + break; + } - switch (isset($alwaysDefinedForSwitchCondition)) { - case isset($alwaysDefinedForCaseNodeCondition): - break; - } + $alwaysDefinedForSwitchCondition = 1; + $alwaysDefinedForCaseNodeCondition = 1; + switch (isset($alwaysDefinedForSwitchCondition)) { + case isset($alwaysDefinedForCaseNodeCondition): + break; + } } function () { - $alwaysDefinedNotNullable = 'string'; - if (doFoo()) { - $sometimesDefinedVariable = 1; - } - - if (isset( - $alwaysDefinedNotNullable, // always true - $sometimesDefinedVariable, // fine, this is what's isset() is for - $neverDefinedVariable // always false - )) { - - } + $alwaysDefinedNotNullable = 'string'; + if (doFoo()) { + $sometimesDefinedVariable = 1; + } + + if (isset( + $alwaysDefinedNotNullable, // always true + $sometimesDefinedVariable, // fine, this is what's isset() is for + $neverDefinedVariable // always false + )) { + } }; function () { - try { - if (something()) { - throw new \Exception(); - } - $test = 'fooo'; - } finally { - if (isset($test)) { - - } - } + try { + if (something()) { + throw new \Exception(); + } + $test = 'fooo'; + } finally { + if (isset($test)) { + } + } }; function () { - /** @var string[] $strings */ - $strings = doFoo(); - foreach ($strings as $string) { - - } - - if (isset($string)) { + /** @var string[] $strings */ + $strings = doFoo(); + foreach ($strings as $string) { + } - } + if (isset($string)) { + } }; function () { - /** @var mixed $bar */ - $bar = $this->get('bar'); - if (isset($bar)) { - $bar = (int) $bar; - } - if (isset($bar)) { - echo $bar; - } + /** @var mixed $bar */ + $bar = $this->get('bar'); + if (isset($bar)) { + $bar = (int) $bar; + } + if (isset($bar)) { + echo $bar; + } }; function () { - while (true) { - if (rand() === 1) { - $a = 'a'; - continue; - } - - if (!isset($a)) { - continue; - } - - unset($a); - } + while (true) { + if (rand() === 1) { + $a = 'a'; + continue; + } + + if (!isset($a)) { + continue; + } + + unset($a); + } }; function () { - ($a = rand(0, 5)) && rand(0, 1); - isset($a); + ($a = rand(0, 5)) && rand(0, 1); + isset($a); }; function () { - rand(0, 1) && ($a = rand(0, 5)); - isset($a); + rand(0, 1) && ($a = rand(0, 5)); + isset($a); }; function () { $null = null; if (isset($null)) { // always false - } }; diff --git a/tests/PHPStan/Rules/Variables/data/variable-certainty-null-assign.php b/tests/PHPStan/Rules/Variables/data/variable-certainty-null-assign.php index 8500b14063..1250d5f7cb 100644 --- a/tests/PHPStan/Rules/Variables/data/variable-certainty-null-assign.php +++ b/tests/PHPStan/Rules/Variables/data/variable-certainty-null-assign.php @@ -1,15 +1,17 @@ -= 7.4 += 7.4 function (): void { - $scalar = 3; + $scalar = 3; - echo $scalar ??= 4; + echo $scalar ??= 4; - echo $doesNotExist ??= 0; + echo $doesNotExist ??= 0; }; function (?string $a): void { - if (!is_string($a)) { - echo $a ??= 'foo'; - } + if (!is_string($a)) { + echo $a ??= 'foo'; + } }; diff --git a/tests/PHPStan/Rules/Variables/data/variable-certainty-null.php b/tests/PHPStan/Rules/Variables/data/variable-certainty-null.php index 4b01678e81..2b9c287136 100644 --- a/tests/PHPStan/Rules/Variables/data/variable-certainty-null.php +++ b/tests/PHPStan/Rules/Variables/data/variable-certainty-null.php @@ -1,16 +1,15 @@ = 8.0 += 8.0 namespace VariableNullsafeIsset; function (): void { - if (rand(0, 2)) { - $foo = 'blabla'; - } - - if (isset($foo->bla)) { + if (rand(0, 2)) { + $foo = 'blabla'; + } - } + if (isset($foo->bla)) { + } }; function (): void { - if (rand(0, 2)) { - $foo = 'blabla'; - } - - if (isset($foo?->bla)) { + if (rand(0, 2)) { + $foo = 'blabla'; + } - } + if (isset($foo?->bla)) { + } }; diff --git a/tests/PHPStan/Rules/Whitespace/FileWhitespaceRuleTest.php b/tests/PHPStan/Rules/Whitespace/FileWhitespaceRuleTest.php index b379a0c9f5..54f1e60c7a 100644 --- a/tests/PHPStan/Rules/Whitespace/FileWhitespaceRuleTest.php +++ b/tests/PHPStan/Rules/Whitespace/FileWhitespaceRuleTest.php @@ -1,4 +1,6 @@ -analyse([__DIR__ . '/data/bom.php'], [ - [ - 'File begins with UTF-8 BOM character. This may cause problems when running the code in the web browser.', - 1, - ], - ]); - } - - public function testCorrectFile(): void - { - $this->analyse([__DIR__ . '/data/correct.php'], []); - } - - public function testTrailingWhitespaceWithoutNamespace(): void - { - $this->analyse([__DIR__ . '/data/trailing.php'], [ - [ - 'File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.', - 6, - ], - ]); - } - - public function testTrailingWhitespace(): void - { - $this->analyse([__DIR__ . '/data/trailing-namespace.php'], [ - [ - 'File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.', - 8, - ], - ]); - } - - public function testHtmlAfterClose(): void - { - $this->analyse([__DIR__ . '/data/html-after-close.php'], []); - } - + protected function getRule(): Rule + { + return new FileWhitespaceRule(); + } + + public function testBom(): void + { + $this->analyse([__DIR__ . '/data/bom.php'], [ + [ + 'File begins with UTF-8 BOM character. This may cause problems when running the code in the web browser.', + 1, + ], + ]); + } + + public function testCorrectFile(): void + { + $this->analyse([__DIR__ . '/data/correct.php'], []); + } + + public function testTrailingWhitespaceWithoutNamespace(): void + { + $this->analyse([__DIR__ . '/data/trailing.php'], [ + [ + 'File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.', + 6, + ], + ]); + } + + public function testTrailingWhitespace(): void + { + $this->analyse([__DIR__ . '/data/trailing-namespace.php'], [ + [ + 'File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing ?> mark or remove the whitespace.', + 8, + ], + ]); + } + + public function testHtmlAfterClose(): void + { + $this->analyse([__DIR__ . '/data/html-after-close.php'], []); + } } diff --git a/tests/PHPStan/Rules/Whitespace/data/bom.php b/tests/PHPStan/Rules/Whitespace/data/bom.php index d17f0c1edf..c656e6562d 100644 --- a/tests/PHPStan/Rules/Whitespace/data/bom.php +++ b/tests/PHPStan/Rules/Whitespace/data/bom.php @@ -1,3 +1,3 @@ -nullContext = $nullContext; - } - - public function getClass(): string - { - return AssertionClass::class; - } - - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - if ($this->nullContext === null) { - return $methodReflection->getName() === 'assertString'; - } - - if ($this->nullContext) { - return $methodReflection->getName() === 'assertString' && $context->null(); - } - - return $methodReflection->getName() === 'assertString' && !$context->null(); - } - - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return new SpecifiedTypes(['$foo' => [$node->args[0]->value, new StringType()]]); - } - + /** @var bool|null */ + private $nullContext; + + public function __construct(?bool $nullContext) + { + $this->nullContext = $nullContext; + } + + public function getClass(): string + { + return AssertionClass::class; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool { + if ($this->nullContext === null) { + return $methodReflection->getName() === 'assertString'; + } + + if ($this->nullContext) { + return $methodReflection->getName() === 'assertString' && $context->null(); + } + + return $methodReflection->getName() === 'assertString' && !$context->null(); + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + return new SpecifiedTypes(['$foo' => [$node->args[0]->value, new StringType()]]); + } } diff --git a/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php b/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php index 0961cd6f8f..ec4d197f15 100644 --- a/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php +++ b/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -nullContext = $nullContext; - } - - public function getClass(): string - { - return AssertionClass::class; - } - - public function isStaticMethodSupported( - MethodReflection $staticMethodReflection, - StaticCall $node, - TypeSpecifierContext $context - ): bool - { - if ($this->nullContext === null) { - return $staticMethodReflection->getName() === 'assertInt'; - } - - if ($this->nullContext) { - return $staticMethodReflection->getName() === 'assertInt' && $context->null(); - } - - return $staticMethodReflection->getName() === 'assertInt' && !$context->null(); - } - - public function specifyTypes( - MethodReflection $staticMethodReflection, - StaticCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return new SpecifiedTypes(['$bar' => [$node->args[0]->value, new IntegerType()]]); - } - + /** @var bool|null */ + private $nullContext; + + public function __construct(?bool $nullContext) + { + $this->nullContext = $nullContext; + } + + public function getClass(): string + { + return AssertionClass::class; + } + + public function isStaticMethodSupported( + MethodReflection $staticMethodReflection, + StaticCall $node, + TypeSpecifierContext $context + ): bool { + if ($this->nullContext === null) { + return $staticMethodReflection->getName() === 'assertInt'; + } + + if ($this->nullContext) { + return $staticMethodReflection->getName() === 'assertInt' && $context->null(); + } + + return $staticMethodReflection->getName() === 'assertInt' && !$context->null(); + } + + public function specifyTypes( + MethodReflection $staticMethodReflection, + StaticCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes { + return new SpecifiedTypes(['$bar' => [$node->args[0]->value, new IntegerType()]]); + } } diff --git a/tests/PHPStan/Tests/AssertionException.php b/tests/PHPStan/Tests/AssertionException.php index af86b5a256..52197cc9b4 100644 --- a/tests/PHPStan/Tests/AssertionException.php +++ b/tests/PHPStan/Tests/AssertionException.php @@ -1,8 +1,9 @@ -assertTrue($expectedResult->equals($value->and(...$operands))); - } - - public function dataOr(): array - { - return [ - [TrinaryLogic::createNo(), TrinaryLogic::createNo()], - [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], - [TrinaryLogic::createYes(), TrinaryLogic::createYes()], - - [TrinaryLogic::createNo(), TrinaryLogic::createNo(), TrinaryLogic::createNo()], - [TrinaryLogic::createMaybe(), TrinaryLogic::createNo(), TrinaryLogic::createMaybe()], - [TrinaryLogic::createYes(), TrinaryLogic::createNo(), TrinaryLogic::createYes()], - - [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createNo()], - [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], - [TrinaryLogic::createYes(), TrinaryLogic::createMaybe(), TrinaryLogic::createYes()], - - [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createNo()], - [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createMaybe()], - [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createYes()], - ]; - } - - /** - * @dataProvider dataOr - * @param TrinaryLogic $expectedResult - * @param TrinaryLogic $value - * @param TrinaryLogic ...$operands - */ - public function testOr( - TrinaryLogic $expectedResult, - TrinaryLogic $value, - TrinaryLogic ...$operands - ): void - { - $this->assertTrue($expectedResult->equals($value->or(...$operands))); - } - - public function dataNegate(): array - { - return [ - [TrinaryLogic::createNo(), TrinaryLogic::createYes()], - [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], - [TrinaryLogic::createYes(), TrinaryLogic::createNo()], - ]; - } - - /** - * @dataProvider dataNegate - * @param TrinaryLogic $expectedResult - * @param TrinaryLogic $operand - */ - public function testNegate(TrinaryLogic $expectedResult, TrinaryLogic $operand): void - { - $this->assertTrue($expectedResult->equals($operand->negate())); - } - - public function dataCompareTo(): array - { - $yes = TrinaryLogic::createYes(); - $maybe = TrinaryLogic::createMaybe(); - $no = TrinaryLogic::createNo(); - return [ - [ - $yes, - $yes, - null, - ], - [ - $maybe, - $maybe, - null, - ], - [ - $no, - $no, - null, - ], - [ - $yes, - $maybe, - $yes, - ], - [ - $yes, - $no, - $yes, - ], - [ - $maybe, - $no, - $maybe, - ], - ]; - } - - /** - * @dataProvider dataCompareTo - * @param TrinaryLogic $first - * @param TrinaryLogic $second - * @param TrinaryLogic|null $expected - */ - public function testCompareTo(TrinaryLogic $first, TrinaryLogic $second, ?TrinaryLogic $expected): void - { - $this->assertSame( - $expected, - $first->compareTo($second) - ); - } - - /** - * @dataProvider dataCompareTo - * @param TrinaryLogic $first - * @param TrinaryLogic $second - * @param TrinaryLogic|null $expected - */ - public function testCompareToInversed(TrinaryLogic $first, TrinaryLogic $second, ?TrinaryLogic $expected): void - { - $this->assertSame( - $expected, - $second->compareTo($first) - ); - } - + public function dataAnd(): array + { + return [ + [TrinaryLogic::createNo(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createYes()], + + [TrinaryLogic::createNo(), TrinaryLogic::createNo(), TrinaryLogic::createNo()], + [TrinaryLogic::createNo(), TrinaryLogic::createNo(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createNo(), TrinaryLogic::createNo(), TrinaryLogic::createYes()], + + [TrinaryLogic::createNo(), TrinaryLogic::createMaybe(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createYes()], + + [TrinaryLogic::createNo(), TrinaryLogic::createYes(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createYes(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createYes()], + ]; + } + + /** + * @dataProvider dataAnd + * @param TrinaryLogic $expectedResult + * @param TrinaryLogic $value + * @param TrinaryLogic ...$operands + */ + public function testAnd( + TrinaryLogic $expectedResult, + TrinaryLogic $value, + TrinaryLogic ...$operands + ): void { + $this->assertTrue($expectedResult->equals($value->and(...$operands))); + } + + public function dataOr(): array + { + return [ + [TrinaryLogic::createNo(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createYes()], + + [TrinaryLogic::createNo(), TrinaryLogic::createNo(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createNo(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createNo(), TrinaryLogic::createYes()], + + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createNo()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createMaybe(), TrinaryLogic::createYes()], + + [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createNo()], + [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createYes(), TrinaryLogic::createYes()], + ]; + } + + /** + * @dataProvider dataOr + * @param TrinaryLogic $expectedResult + * @param TrinaryLogic $value + * @param TrinaryLogic ...$operands + */ + public function testOr( + TrinaryLogic $expectedResult, + TrinaryLogic $value, + TrinaryLogic ...$operands + ): void { + $this->assertTrue($expectedResult->equals($value->or(...$operands))); + } + + public function dataNegate(): array + { + return [ + [TrinaryLogic::createNo(), TrinaryLogic::createYes()], + [TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe()], + [TrinaryLogic::createYes(), TrinaryLogic::createNo()], + ]; + } + + /** + * @dataProvider dataNegate + * @param TrinaryLogic $expectedResult + * @param TrinaryLogic $operand + */ + public function testNegate(TrinaryLogic $expectedResult, TrinaryLogic $operand): void + { + $this->assertTrue($expectedResult->equals($operand->negate())); + } + + public function dataCompareTo(): array + { + $yes = TrinaryLogic::createYes(); + $maybe = TrinaryLogic::createMaybe(); + $no = TrinaryLogic::createNo(); + return [ + [ + $yes, + $yes, + null, + ], + [ + $maybe, + $maybe, + null, + ], + [ + $no, + $no, + null, + ], + [ + $yes, + $maybe, + $yes, + ], + [ + $yes, + $no, + $yes, + ], + [ + $maybe, + $no, + $maybe, + ], + ]; + } + + /** + * @dataProvider dataCompareTo + * @param TrinaryLogic $first + * @param TrinaryLogic $second + * @param TrinaryLogic|null $expected + */ + public function testCompareTo(TrinaryLogic $first, TrinaryLogic $second, ?TrinaryLogic $expected): void + { + $this->assertSame( + $expected, + $first->compareTo($second) + ); + } + + /** + * @dataProvider dataCompareTo + * @param TrinaryLogic $first + * @param TrinaryLogic $second + * @param TrinaryLogic|null $expected + */ + public function testCompareToInversed(TrinaryLogic $first, TrinaryLogic $second, ?TrinaryLogic $expected): void + { + $this->assertSame( + $expected, + $second->compareTo($first) + ); + } } diff --git a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php index 525b1f6f16..ef2da7f313 100644 --- a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): array - { - return [ - [ - new HasMethodType('foo'), - new HasMethodType('foo'), - TrinaryLogic::createYes(), - ], - [ - new HasMethodType('foo'), - new UnionType([ - new HasMethodType('foo'), - new NullType(), - ]), - TrinaryLogic::createYes(), - ], - [ - new HasMethodType('foo'), - new IntersectionType([ - new HasMethodType('foo'), - new HasMethodType('bar'), - ]), - TrinaryLogic::createMaybe(), - ], - [ - new HasMethodType('format'), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), // an intentional imprecision - ], - [ - new HasMethodType('format'), - new ObjectType(\DateTimeImmutable::class), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param HasMethodType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(HasMethodType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSubTypeOf - * @param HasMethodType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(HasMethodType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSubTypeOf(): array + { + return [ + [ + new HasMethodType('foo'), + new HasMethodType('foo'), + TrinaryLogic::createYes(), + ], + [ + new HasMethodType('foo'), + new UnionType([ + new HasMethodType('foo'), + new NullType(), + ]), + TrinaryLogic::createYes(), + ], + [ + new HasMethodType('foo'), + new IntersectionType([ + new HasMethodType('foo'), + new HasMethodType('bar'), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new HasMethodType('format'), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), // an intentional imprecision + ], + [ + new HasMethodType('format'), + new ObjectType(\DateTimeImmutable::class), + TrinaryLogic::createMaybe(), + ], + ]; + } - /** - * @dataProvider dataIsSubTypeOf - * @param HasMethodType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(HasMethodType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } + /** + * @dataProvider dataIsSubTypeOf + * @param HasMethodType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(HasMethodType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + /** + * @dataProvider dataIsSubTypeOf + * @param HasMethodType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(HasMethodType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php index 9e0012051a..05f18a67c3 100644 --- a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): array - { - return [ - [ - new HasPropertyType('foo'), - new HasPropertyType('foo'), - TrinaryLogic::createYes(), - ], - [ - new HasPropertyType('foo'), - new UnionType([ - new HasPropertyType('foo'), - new NullType(), - ]), - TrinaryLogic::createYes(), - ], - [ - new HasPropertyType('foo'), - new IntersectionType([ - new HasPropertyType('foo'), - new HasPropertyType('bar'), - ]), - TrinaryLogic::createMaybe(), - ], - [ - new HasPropertyType('d'), - new ObjectType(\DateInterval::class), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param HasPropertyType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(HasPropertyType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSubTypeOf - * @param HasPropertyType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(HasPropertyType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSubTypeOf(): array + { + return [ + [ + new HasPropertyType('foo'), + new HasPropertyType('foo'), + TrinaryLogic::createYes(), + ], + [ + new HasPropertyType('foo'), + new UnionType([ + new HasPropertyType('foo'), + new NullType(), + ]), + TrinaryLogic::createYes(), + ], + [ + new HasPropertyType('foo'), + new IntersectionType([ + new HasPropertyType('foo'), + new HasPropertyType('bar'), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new HasPropertyType('d'), + new ObjectType(\DateInterval::class), + TrinaryLogic::createMaybe(), + ], + ]; + } - /** - * @dataProvider dataIsSubTypeOf - * @param HasPropertyType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(HasPropertyType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } + /** + * @dataProvider dataIsSubTypeOf + * @param HasPropertyType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(HasPropertyType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + /** + * @dataProvider dataIsSubTypeOf + * @param HasPropertyType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(HasPropertyType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index 1ec0a20c9c..fe1e316144 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataAccepts(): array - { - $reflectionProvider = Broker::getInstance(); + /** + * @dataProvider dataIsSuperTypeOf + * @param ArrayType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(ArrayType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - return [ - [ - new ArrayType(new MixedType(), new StringType()), - TypeCombinator::union( - new ConstantArrayType([], []), - new ConstantArrayType( - [new ConstantIntegerType(0)], - [new MixedType()] - ), - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new StringType(), - new MixedType(), - ]) - ), - TrinaryLogic::createYes(), - ], - [ - new ArrayType(new MixedType(), new CallableType()), - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ThisType($reflectionProvider->getClass(self::class)), - new ConstantStringType('dataAccepts'), - ]), - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ThisType($reflectionProvider->getClass(self::class)), - new ConstantStringType('dataIsSuperTypeOf'), - ]), - ]), - TrinaryLogic::createYes(), - ], - ]; - } + public function dataAccepts(): array + { + $reflectionProvider = Broker::getInstance(); - /** - * @dataProvider dataAccepts - * @param ArrayType $acceptingType - * @param Type $acceptedType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts( - ArrayType $acceptingType, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $actualResult = $acceptingType->accepts($acceptedType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } + return [ + [ + new ArrayType(new MixedType(), new StringType()), + TypeCombinator::union( + new ConstantArrayType([], []), + new ConstantArrayType( + [new ConstantIntegerType(0)], + [new MixedType()] + ), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new StringType(), + new MixedType(), + ]) + ), + TrinaryLogic::createYes(), + ], + [ + new ArrayType(new MixedType(), new CallableType()), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ThisType($reflectionProvider->getClass(self::class)), + new ConstantStringType('dataAccepts'), + ]), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ThisType($reflectionProvider->getClass(self::class)), + new ConstantStringType('dataIsSuperTypeOf'), + ]), + ]), + TrinaryLogic::createYes(), + ], + ]; + } - public function dataDescribe(): array - { - return [ - [ - new ArrayType(new BenevolentUnionType([ - new IntegerType(), - new StringType(), - ]), new IntegerType()), - 'array', - ], - ]; - } + /** + * @dataProvider dataAccepts + * @param ArrayType $acceptingType + * @param Type $acceptedType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts( + ArrayType $acceptingType, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $actualResult = $acceptingType->accepts($acceptedType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataDescribe - * @param ArrayType $type - * @param string $expectedDescription - */ - public function testDescribe( - ArrayType $type, - string $expectedDescription - ): void - { - $this->assertSame($expectedDescription, $type->describe(VerbosityLevel::precise())); - } + public function dataDescribe(): array + { + return [ + [ + new ArrayType(new BenevolentUnionType([ + new IntegerType(), + new StringType(), + ]), new IntegerType()), + 'array', + ], + ]; + } - public function dataInferTemplateTypes(): array - { - $templateType = static function (string $name): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; + /** + * @dataProvider dataDescribe + * @param ArrayType $type + * @param string $expectedDescription + */ + public function testDescribe( + ArrayType $type, + string $expectedDescription + ): void { + $this->assertSame($expectedDescription, $type->describe(VerbosityLevel::precise())); + } - return [ - 'valid templated item' => [ - new ArrayType( - new MixedType(), - new ObjectType('DateTime') - ), - new ArrayType( - new MixedType(), - $templateType('T') - ), - ['T' => 'DateTime'], - ], - 'receive mixed' => [ - new MixedType(), - new ArrayType( - new MixedType(), - $templateType('T') - ), - [], - ], - 'receive non-accepted' => [ - new StringType(), - new ArrayType( - new MixedType(), - $templateType('T') - ), - [], - ], - 'receive union items' => [ - new ArrayType( - new MixedType(), - new UnionType([ - new StringType(), - new IntegerType(), - ]) - ), - new ArrayType( - new MixedType(), - $templateType('T') - ), - ['T' => 'int|string'], - ], - 'receive union' => [ - new UnionType([ - new StringType(), - new ArrayType( - new MixedType(), - new StringType() - ), - new ArrayType( - new MixedType(), - new IntegerType() - ), - ]), - new ArrayType( - new MixedType(), - $templateType('T') - ), - ['T' => 'int|string'], - ], - ]; - } + public function dataInferTemplateTypes(): array + { + $templateType = static function (string $name): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); + return [ + 'valid templated item' => [ + new ArrayType( + new MixedType(), + new ObjectType('DateTime') + ), + new ArrayType( + new MixedType(), + $templateType('T') + ), + ['T' => 'DateTime'], + ], + 'receive mixed' => [ + new MixedType(), + new ArrayType( + new MixedType(), + $templateType('T') + ), + [], + ], + 'receive non-accepted' => [ + new StringType(), + new ArrayType( + new MixedType(), + $templateType('T') + ), + [], + ], + 'receive union items' => [ + new ArrayType( + new MixedType(), + new UnionType([ + new StringType(), + new IntegerType(), + ]) + ), + new ArrayType( + new MixedType(), + $templateType('T') + ), + ['T' => 'int|string'], + ], + 'receive union' => [ + new UnionType([ + new StringType(), + new ArrayType( + new MixedType(), + new StringType() + ), + new ArrayType( + new MixedType(), + new IntegerType() + ), + ]), + new ArrayType( + new MixedType(), + $templateType('T') + ), + ['T' => 'int|string'], + ], + ]; + } - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } } diff --git a/tests/PHPStan/Type/BooleanTypeTest.php b/tests/PHPStan/Type/BooleanTypeTest.php index 0d52f3f64c..7524047815 100644 --- a/tests/PHPStan/Type/BooleanTypeTest.php +++ b/tests/PHPStan/Type/BooleanTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSuperTypeOf(): iterable - { - yield [ - new BooleanType(), - new BooleanType(), - TrinaryLogic::createYes(), - ]; + /** + * @dataProvider dataAccepts + * @param BooleanType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(BooleanType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - yield [ - new BooleanType(), - new ConstantBooleanType(true), - TrinaryLogic::createYes(), - ]; + public function dataIsSuperTypeOf(): iterable + { + yield [ + new BooleanType(), + new BooleanType(), + TrinaryLogic::createYes(), + ]; - yield [ - new BooleanType(), - new MixedType(), - TrinaryLogic::createMaybe(), - ]; + yield [ + new BooleanType(), + new ConstantBooleanType(true), + TrinaryLogic::createYes(), + ]; - yield [ - new BooleanType(), - new UnionType([new BooleanType(), new StringType()]), - TrinaryLogic::createMaybe(), - ]; + yield [ + new BooleanType(), + new MixedType(), + TrinaryLogic::createMaybe(), + ]; - yield [ - new BooleanType(), - new StringType(), - TrinaryLogic::createNo(), - ]; - } + yield [ + new BooleanType(), + new UnionType([new BooleanType(), new StringType()]), + TrinaryLogic::createMaybe(), + ]; - /** - * @dataProvider dataIsSuperTypeOf - * @param BooleanType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(BooleanType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + yield [ + new BooleanType(), + new StringType(), + TrinaryLogic::createNo(), + ]; + } - public function dataEquals(): array - { - return [ - [ - new BooleanType(), - new BooleanType(), - true, - ], - [ - new ConstantBooleanType(false), - new ConstantBooleanType(false), - true, - ], - [ - new ConstantBooleanType(true), - new ConstantBooleanType(false), - false, - ], - [ - new BooleanType(), - new ConstantBooleanType(false), - false, - ], - [ - new ConstantBooleanType(false), - new BooleanType(), - false, - ], - [ - new BooleanType(), - new IntegerType(), - false, - ], - [ - new ConstantBooleanType(false), - new ConstantIntegerType(0), - false, - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param BooleanType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(BooleanType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataEquals - * @param BooleanType $type - * @param Type $otherType - * @param bool $expectedResult - */ - public function testEquals(BooleanType $type, Type $otherType, bool $expectedResult): void - { - $actualResult = $type->equals($otherType); - $this->assertSame( - $expectedResult, - $actualResult, - sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataEquals(): array + { + return [ + [ + new BooleanType(), + new BooleanType(), + true, + ], + [ + new ConstantBooleanType(false), + new ConstantBooleanType(false), + true, + ], + [ + new ConstantBooleanType(true), + new ConstantBooleanType(false), + false, + ], + [ + new BooleanType(), + new ConstantBooleanType(false), + false, + ], + [ + new ConstantBooleanType(false), + new BooleanType(), + false, + ], + [ + new BooleanType(), + new IntegerType(), + false, + ], + [ + new ConstantBooleanType(false), + new ConstantIntegerType(0), + false, + ], + ]; + } + /** + * @dataProvider dataEquals + * @param BooleanType $type + * @param Type $otherType + * @param bool $expectedResult + */ + public function testEquals(BooleanType $type, Type $otherType, bool $expectedResult): void + { + $actualResult = $type->equals($otherType); + $this->assertSame( + $expectedResult, + $actualResult, + sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 73cdec0230..5153397d2e 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): array - { - return [ - [ - new CallableType(), - new CallableType(), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - new StringType(), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new IntegerType(), - TrinaryLogic::createNo(), - ], - [ - new CallableType(), - new UnionType([new CallableType(), new NullType()]), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - new UnionType([new StringType(), new NullType()]), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new UnionType([new IntegerType(), new NullType()]), - TrinaryLogic::createNo(), - ], - [ - new CallableType(), - new IntersectionType([new CallableType()]), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - new IntersectionType([new StringType()]), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new IntersectionType([new IntegerType()]), - TrinaryLogic::createNo(), - ], - [ - new CallableType(), - new IntersectionType([new CallableType(), new StringType()]), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new IntersectionType([new CallableType(), new ObjectType('Unknown')]), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new HasMethodType('foo'), - TrinaryLogic::createMaybe(), - ], - [ - new CallableType(), - new HasMethodType('__invoke'), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param CallableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(CallableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSubTypeOf - * @param CallableType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(CallableType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSubTypeOf(): array + { + return [ + [ + new CallableType(), + new CallableType(), + TrinaryLogic::createYes(), + ], + [ + new CallableType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new IntegerType(), + TrinaryLogic::createNo(), + ], + [ + new CallableType(), + new UnionType([new CallableType(), new NullType()]), + TrinaryLogic::createYes(), + ], + [ + new CallableType(), + new UnionType([new StringType(), new NullType()]), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new UnionType([new IntegerType(), new NullType()]), + TrinaryLogic::createNo(), + ], + [ + new CallableType(), + new IntersectionType([new CallableType()]), + TrinaryLogic::createYes(), + ], + [ + new CallableType(), + new IntersectionType([new StringType()]), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new IntersectionType([new IntegerType()]), + TrinaryLogic::createNo(), + ], + [ + new CallableType(), + new IntersectionType([new CallableType(), new StringType()]), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new IntersectionType([new CallableType(), new ObjectType('Unknown')]), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new HasMethodType('foo'), + TrinaryLogic::createMaybe(), + ], + [ + new CallableType(), + new HasMethodType('__invoke'), + TrinaryLogic::createMaybe(), + ], + ]; + } - /** - * @dataProvider dataIsSubTypeOf - * @param CallableType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(CallableType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } + /** + * @dataProvider dataIsSubTypeOf + * @param CallableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(CallableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - public function dataInferTemplateTypes(): array - { - $param = static function (Type $type): NativeParameterReflection { - return new NativeParameterReflection( - '', - false, - $type, - PassedByReference::createNo(), - false, - null - ); - }; + /** + * @dataProvider dataIsSubTypeOf + * @param CallableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(CallableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } - $templateType = static function (string $name): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; + public function dataInferTemplateTypes(): array + { + $param = static function (Type $type): NativeParameterReflection { + return new NativeParameterReflection( + '', + false, + $type, + PassedByReference::createNo(), + false, + null + ); + }; - return [ - 'template param' => [ - new CallableType( - [ - $param(new StringType()), - ], - new IntegerType() - ), - new CallableType( - [ - $param($templateType('T')), - ], - new IntegerType() - ), - ['T' => 'string'], - ], - 'template return' => [ - new CallableType( - [ - $param(new StringType()), - ], - new IntegerType() - ), - new CallableType( - [ - $param(new StringType()), - ], - $templateType('T') - ), - ['T' => 'int'], - ], - 'multiple templates' => [ - new CallableType( - [ - $param(new StringType()), - $param(new ObjectType('DateTime')), - ], - new IntegerType() - ), - new CallableType( - [ - $param(new StringType()), - $param($templateType('A')), - ], - $templateType('B') - ), - ['A' => 'DateTime', 'B' => 'int'], - ], - 'receive union' => [ - new UnionType([ - new NullType(), - new CallableType( - [ - $param(new StringType()), - $param(new ObjectType('DateTime')), - ], - new IntegerType() - ), - ]), - new CallableType( - [ - $param(new StringType()), - $param($templateType('A')), - ], - $templateType('B') - ), - ['A' => 'DateTime', 'B' => 'int'], - ], - 'receive non-accepted' => [ - new NullType(), - new CallableType( - [ - $param(new StringType()), - $param($templateType('A')), - ], - $templateType('B') - ), - [], - ], - ]; - } + $templateType = static function (string $name): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); + return [ + 'template param' => [ + new CallableType( + [ + $param(new StringType()), + ], + new IntegerType() + ), + new CallableType( + [ + $param($templateType('T')), + ], + new IntegerType() + ), + ['T' => 'string'], + ], + 'template return' => [ + new CallableType( + [ + $param(new StringType()), + ], + new IntegerType() + ), + new CallableType( + [ + $param(new StringType()), + ], + $templateType('T') + ), + ['T' => 'int'], + ], + 'multiple templates' => [ + new CallableType( + [ + $param(new StringType()), + $param(new ObjectType('DateTime')), + ], + new IntegerType() + ), + new CallableType( + [ + $param(new StringType()), + $param($templateType('A')), + ], + $templateType('B') + ), + ['A' => 'DateTime', 'B' => 'int'], + ], + 'receive union' => [ + new UnionType([ + new NullType(), + new CallableType( + [ + $param(new StringType()), + $param(new ObjectType('DateTime')), + ], + new IntegerType() + ), + ]), + new CallableType( + [ + $param(new StringType()), + $param($templateType('A')), + ], + $templateType('B') + ), + ['A' => 'DateTime', 'B' => 'int'], + ], + 'receive non-accepted' => [ + new NullType(), + new CallableType( + [ + $param(new StringType()), + $param($templateType('A')), + ], + $templateType('B') + ), + [], + ], + ]; + } - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); - public function dataAccepts(): array - { - return [ - [ - new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType([ - new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), - ], new MixedType(), false), - new CallableType([ - new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), - ], new MixedType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType([ - new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('bar', false, new StringType(), PassedByReference::createNo(), false, null), - ], new MixedType(), false), - new CallableType([ - new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), - ], new MixedType(), false), - TrinaryLogic::createNo(), - ], - [ - new CallableType([], new MixedType(), false), - new CallableType([], new MixedType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType([], new IntegerType(), false), - new CallableType([], new MixedType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType([], new MixedType(), false), - new CallableType([], new IntegerType(), false), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new CallableType()), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new GenericClassStringType(new ObjectType(\Closure::class)), - new ConstantStringType('bind'), - ]), - TrinaryLogic::createYes(), - ], - ]; - } + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } - /** - * @dataProvider dataAccepts - * @param \PHPStan\Type\CallableType $type - * @param Type $acceptedType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts( - CallableType $type, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $this->assertSame( - $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } + public function dataAccepts(): array + { + return [ + [ + new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), + new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), + new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType([ + new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), + ], new MixedType(), false), + new CallableType([ + new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), + ], new MixedType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType([ + new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('bar', false, new StringType(), PassedByReference::createNo(), false, null), + ], new MixedType(), false), + new CallableType([ + new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('bar', true, new IntegerType(), PassedByReference::createNo(), false, null), + ], new MixedType(), false), + TrinaryLogic::createNo(), + ], + [ + new CallableType([], new MixedType(), false), + new CallableType([], new MixedType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType([], new IntegerType(), false), + new CallableType([], new MixedType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType([], new MixedType(), false), + new CallableType([], new IntegerType(), false), + TrinaryLogic::createYes(), + ], + [ + new CallableType(), + TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new CallableType()), + TrinaryLogic::createYes(), + ], + [ + new CallableType(), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new GenericClassStringType(new ObjectType(\Closure::class)), + new ConstantStringType('bind'), + ]), + TrinaryLogic::createYes(), + ], + ]; + } + /** + * @dataProvider dataAccepts + * @param \PHPStan\Type\CallableType $type + * @param Type $acceptedType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts( + CallableType $type, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $this->assertSame( + $expectedResult->describe(), + $type->accepts($acceptedType, true)->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/ClassStringTypeTest.php b/tests/PHPStan/Type/ClassStringTypeTest.php index 5d715a948c..323f9a7767 100644 --- a/tests/PHPStan/Type/ClassStringTypeTest.php +++ b/tests/PHPStan/Type/ClassStringTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataAccepts(): iterable - { - yield [ - new ClassStringType(), - new ClassStringType(), - TrinaryLogic::createYes(), - ]; - - yield [ - new ClassStringType(), - new StringType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ClassStringType(), - new IntegerType(), - TrinaryLogic::createNo(), - ]; - - yield [ - new ClassStringType(), - new ConstantStringType(\stdClass::class), - TrinaryLogic::createYes(), - ]; - - yield [ - new ClassStringType(), - new ConstantStringType('NonexistentClass'), - TrinaryLogic::createNo(), - ]; - - yield [ - new ClassStringType(), - new UnionType([new ConstantStringType(\stdClass::class), new ConstantStringType(self::class)]), - TrinaryLogic::createYes(), - ]; - - yield [ - new ClassStringType(), - new UnionType([new ConstantStringType(\stdClass::class), new ConstantStringType('Nonexistent')]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ClassStringType(), - new UnionType([new ConstantStringType('Nonexistent'), new ConstantStringType('Nonexistent2')]), - TrinaryLogic::createNo(), - ]; - } - - /** - * @dataProvider dataAccepts - * @param \PHPStan\Type\ClassStringType $type - * @param Type $otherType - * @param \PHPStan\TrinaryLogic $expectedResult - */ - public function testAccepts(ClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataEquals(): array - { - return [ - [ - new ClassStringType(), - new ClassStringType(), - true, - ], - [ - new ClassStringType(), - new StringType(), - false, - ], - ]; - } - - /** - * @dataProvider dataEquals - * @param ClassStringType $type - * @param Type $otherType - * @param bool $expectedResult - */ - public function testEquals(ClassStringType $type, Type $otherType, bool $expectedResult): void - { - $actualResult = $type->equals($otherType); - $this->assertSame( - $expectedResult, - $actualResult, - sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - + public function dataIsSuperTypeOf(): array + { + return [ + [ + new ClassStringType(), + new GenericClassStringType(new ObjectType(\Exception::class)), + TrinaryLogic::createYes(), + ], + [ + new ClassStringType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + [ + new ClassStringType(), + new ConstantStringType(\stdClass::class), + TrinaryLogic::createYes(), + ], + [ + new ClassStringType(), + new ConstantStringType('Nonexistent'), + TrinaryLogic::createNo(), + ], + ]; + } + + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf(ClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataAccepts(): iterable + { + yield [ + new ClassStringType(), + new ClassStringType(), + TrinaryLogic::createYes(), + ]; + + yield [ + new ClassStringType(), + new StringType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ClassStringType(), + new IntegerType(), + TrinaryLogic::createNo(), + ]; + + yield [ + new ClassStringType(), + new ConstantStringType(\stdClass::class), + TrinaryLogic::createYes(), + ]; + + yield [ + new ClassStringType(), + new ConstantStringType('NonexistentClass'), + TrinaryLogic::createNo(), + ]; + + yield [ + new ClassStringType(), + new UnionType([new ConstantStringType(\stdClass::class), new ConstantStringType(self::class)]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ClassStringType(), + new UnionType([new ConstantStringType(\stdClass::class), new ConstantStringType('Nonexistent')]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ClassStringType(), + new UnionType([new ConstantStringType('Nonexistent'), new ConstantStringType('Nonexistent2')]), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataAccepts + * @param \PHPStan\Type\ClassStringType $type + * @param Type $otherType + * @param \PHPStan\TrinaryLogic $expectedResult + */ + public function testAccepts(ClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataEquals(): array + { + return [ + [ + new ClassStringType(), + new ClassStringType(), + true, + ], + [ + new ClassStringType(), + new StringType(), + false, + ], + ]; + } + + /** + * @dataProvider dataEquals + * @param ClassStringType $type + * @param Type $otherType + * @param bool $expectedResult + */ + public function testEquals(ClassStringType $type, Type $otherType, bool $expectedResult): void + { + $actualResult = $type->equals($otherType); + $this->assertSame( + $expectedResult, + $actualResult, + sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/ClosureTypeTest.php b/tests/PHPStan/Type/ClosureTypeTest.php index c788121178..0539826ace 100644 --- a/tests/PHPStan/Type/ClosureTypeTest.php +++ b/tests/PHPStan/Type/ClosureTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - + /** + * @dataProvider dataIsSuperTypeOf + * @param Type $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf( + Type $type, + Type $otherType, + TrinaryLogic $expectedResult + ): void { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 8ffce8f7ad..2256e8d32d 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSuperTypeOf(): iterable - { - yield [ - new ConstantArrayType([], []), - new ConstantArrayType([], []), - TrinaryLogic::createYes(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - TrinaryLogic::createYes(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ConstantArrayType([], []), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([], []), - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ConstantArrayType([new ConstantIntegerType(7)], [new ConstantIntegerType(2)]), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(7)]), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ArrayType(new IntegerType(), new IntegerType()), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ArrayType(new StringType(), new StringType()), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), - new ArrayType(new MixedType(), new MixedType()), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ConstantArrayType([], []), - new IterableType(new MixedType(false), new MixedType(true)), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ConstantArrayType([ - new ConstantStringType('foo'), - ], [ - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('foo'), - ], [ - new IntegerType(), - ]), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ], 2), - new ConstantArrayType([], []), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ], 2, [0]), - new ConstantArrayType([], []), - TrinaryLogic::createNo(), - ]; - - yield [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ], 2, [0, 1]), - new ConstantArrayType([], []), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new IntegerType(), - new IntegerType(), - ], 2, [0, 1]), - TrinaryLogic::createMaybe(), - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - * @param ConstantArrayType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(ConstantArrayType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataInferTemplateTypes(): array - { - $templateType = static function (string $name): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; - - return [ - 'receive constant array' => [ - new ConstantArrayType( - [ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], - [ - new StringType(), - new IntegerType(), - ] - ), - new ConstantArrayType( - [ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], - [ - $templateType('T'), - $templateType('U'), - ] - ), - ['T' => 'string', 'U' => 'int'], - ], - 'receive constant array int' => [ - new ConstantArrayType( - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], - [ - new StringType(), - new IntegerType(), - ] - ), - new ConstantArrayType( - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], - [ - $templateType('T'), - $templateType('U'), - ] - ), - ['T' => 'string', 'U' => 'int'], - ], - 'receive incompatible constant array' => [ - new ConstantArrayType( - [ - new ConstantStringType('c'), - ], - [ - new StringType(), - ] - ), - new ConstantArrayType( - [ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], - [ - $templateType('T'), - $templateType('U'), - ] - ), - [], - ], - 'receive mixed' => [ - new MixedType(), - new ConstantArrayType( - [ - new ConstantStringType('a'), - ], - [ - $templateType('T'), - ] - ), - [], - ], - 'receive array' => [ - new ArrayType(new MixedType(), new StringType()), - new ConstantArrayType( - [ - new ConstantStringType('a'), - ], - [ - $templateType('T'), - ] - ), - ['T' => 'string'], - ], - ]; - } - - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); - - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } - - /** - * @dataProvider dataIsCallable - */ - public function testIsCallable(ConstantArrayType $type, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isCallable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsCallable(): iterable - { - yield 'zero items' => [ - new ConstantArrayType([], []), - TrinaryLogic::createNo(), - ]; - - yield 'function name' => [ - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new ConstantStringType('strlen'), - ]), - TrinaryLogic::createNo(), - ]; - - yield 'existing static method' => [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ConstantStringType(\Closure::class, true), - new ConstantStringType('bind'), - ]), - TrinaryLogic::createYes(), - ]; - - yield 'non-existing static method' => [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ConstantStringType(\Closure::class, true), - new ConstantStringType('foobar'), - ]), - TrinaryLogic::createNo(), - ]; - - yield 'existing static method but not a class string' => [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ConstantStringType('Closure'), - new ConstantStringType('bind'), - ]), - TrinaryLogic::createYes(), - ]; - - yield 'existing static method but with string keys' => [ - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new ConstantStringType(\Closure::class, true), - new ConstantStringType('bind'), - ]), - TrinaryLogic::createNo(), - ]; - - yield 'class-string' => [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new GenericClassStringType(new ObjectType(\Closure::class)), - new ConstantStringType('bind'), - ]), - TrinaryLogic::createYes(), - ]; - } - + public function dataAccepts(): iterable + { + yield [ + new ConstantArrayType([], []), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([], []), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(7)], [new ConstantIntegerType(2)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(7)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new IntegerType(), new IntegerType()), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new MixedType(), new MixedType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new IterableType(new MixedType(), new IntegerType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantStringType('foo')], [new CallableType()]), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantStringType('foo')], [new StringType()]), + new ConstantArrayType([new ConstantStringType('foo'), new ConstantStringType('bar')], [new StringType(), new StringType()]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([new ConstantStringType('foo')], [new StringType()]), + new ConstantArrayType([new ConstantStringType('bar')], [new StringType()]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantStringType('foo')], [new StringType()]), + new ConstantArrayType([new ConstantStringType('foo')], [new ConstantStringType('bar')]), + TrinaryLogic::createYes(), + ]; + + yield [ + TypeCombinator::union( + new ConstantArrayType([ + new ConstantStringType('name'), + ], [ + new StringType(), + ]), + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + ], [ + new StringType(), + new StringType(), + ]) + ), + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + new ConstantStringType('year'), + ], [ + new StringType(), + new StringType(), + new IntegerType(), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + new ConstantStringType('year'), + ], [ + new StringType(), + new StringType(), + new IntegerType(), + ]), + new MixedType(), + TrinaryLogic::createYes(), + ]; + + yield [ + TypeCombinator::union( + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + ], [ + new StringType(), + new StringType(), + ]) + ), + new ConstantArrayType([ + new ConstantStringType('surname'), + ], [ + new StringType(), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new StringType(), + new IntegerType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new ConstantStringType('test'), + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new StringType(), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new ConstantStringType('test'), + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new StringType(), + new IntegerType(), + ], 0, [1]), + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new ConstantStringType('test'), + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('limit'), + ], [ + new IntegerType(), + ], 0, [0]), + new ConstantArrayType([ + new ConstantStringType('limit'), + ], [ + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('limit'), + ], [ + new IntegerType(), + ], 0), + new ConstantArrayType([ + new ConstantStringType('limit'), + ], [ + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new StringType(), + new StringType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new ConstantStringType('test'), + new ConstantStringType('true'), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + ], [ + new StringType(), + new StringType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('color'), + ], [ + new ConstantStringType('test'), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('name'), + new ConstantStringType('color'), + ], [ + new StringType(), + new StringType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('sound'), + ], [ + new ConstantStringType('test'), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new StringType(), + new StringType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new ConstantStringType('s'), + new ConstantStringType('m'), + ], 0, [0, 1]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new StringType(), + new IntegerType(), + ], 0, [0, 1]), + new ConstantArrayType([ + new ConstantStringType('sorton'), + new ConstantStringType('limit'), + ], [ + new ConstantStringType('test'), + new ConstantStringType('true'), + ]), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataAccepts + * @param Type $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(Type $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsSuperTypeOf(): iterable + { + yield [ + new ConstantArrayType([], []), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([], []), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(7)], [new ConstantIntegerType(2)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(7)]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new IntegerType(), new IntegerType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([new ConstantIntegerType(1)], [new ConstantIntegerType(2)]), + new ArrayType(new MixedType(), new MixedType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([], []), + new IterableType(new MixedType(false), new MixedType(true)), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + ], [ + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('foo'), + ], [ + new IntegerType(), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ], 2), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ], 2, [0]), + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ], 2, [0, 1]), + new ConstantArrayType([], []), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new IntegerType(), + new IntegerType(), + ], 2, [0, 1]), + TrinaryLogic::createMaybe(), + ]; + } + + /** + * @dataProvider dataIsSuperTypeOf + * @param ConstantArrayType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(ConstantArrayType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataInferTemplateTypes(): array + { + $templateType = static function (string $name): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; + + return [ + 'receive constant array' => [ + new ConstantArrayType( + [ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], + [ + new StringType(), + new IntegerType(), + ] + ), + new ConstantArrayType( + [ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], + [ + $templateType('T'), + $templateType('U'), + ] + ), + ['T' => 'string', 'U' => 'int'], + ], + 'receive constant array int' => [ + new ConstantArrayType( + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], + [ + new StringType(), + new IntegerType(), + ] + ), + new ConstantArrayType( + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], + [ + $templateType('T'), + $templateType('U'), + ] + ), + ['T' => 'string', 'U' => 'int'], + ], + 'receive incompatible constant array' => [ + new ConstantArrayType( + [ + new ConstantStringType('c'), + ], + [ + new StringType(), + ] + ), + new ConstantArrayType( + [ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], + [ + $templateType('T'), + $templateType('U'), + ] + ), + [], + ], + 'receive mixed' => [ + new MixedType(), + new ConstantArrayType( + [ + new ConstantStringType('a'), + ], + [ + $templateType('T'), + ] + ), + [], + ], + 'receive array' => [ + new ArrayType(new MixedType(), new StringType()), + new ConstantArrayType( + [ + new ConstantStringType('a'), + ], + [ + $templateType('T'), + ] + ), + ['T' => 'string'], + ], + ]; + } + + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); + + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } + + /** + * @dataProvider dataIsCallable + */ + public function testIsCallable(ConstantArrayType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isCallable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsCallable(): iterable + { + yield 'zero items' => [ + new ConstantArrayType([], []), + TrinaryLogic::createNo(), + ]; + + yield 'function name' => [ + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new ConstantStringType('strlen'), + ]), + TrinaryLogic::createNo(), + ]; + + yield 'existing static method' => [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ConstantStringType(\Closure::class, true), + new ConstantStringType('bind'), + ]), + TrinaryLogic::createYes(), + ]; + + yield 'non-existing static method' => [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ConstantStringType(\Closure::class, true), + new ConstantStringType('foobar'), + ]), + TrinaryLogic::createNo(), + ]; + + yield 'existing static method but not a class string' => [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ConstantStringType('Closure'), + new ConstantStringType('bind'), + ]), + TrinaryLogic::createYes(), + ]; + + yield 'existing static method but with string keys' => [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantStringType(\Closure::class, true), + new ConstantStringType('bind'), + ]), + TrinaryLogic::createNo(), + ]; + + yield 'class-string' => [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new GenericClassStringType(new ObjectType(\Closure::class)), + new ConstantStringType('bind'), + ]), + TrinaryLogic::createYes(), + ]; + } } diff --git a/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php b/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php index b8261bbea7..928c804995 100644 --- a/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php @@ -1,4 +1,6 @@ -assertSame($expectedDescription, $type->describe(VerbosityLevel::precise())); - } - + /** + * @dataProvider dataDescribe + * @param ConstantFloatType $type + * @param string $expectedDescription + */ + public function testDescribe( + ConstantFloatType $type, + string $expectedDescription + ): void { + $this->assertSame($expectedDescription, $type->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php index 5a23289569..fb02d3d8f4 100644 --- a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + yield [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + TrinaryLogic::createNo(), + ]; + } - public function dataIsSuperTypeOf(): iterable - { - yield [ - new ConstantIntegerType(1), - new ConstantIntegerType(1), - TrinaryLogic::createYes(), - ]; + /** + * @dataProvider dataAccepts + * @param ConstantIntegerType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(ConstantIntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - yield [ - new ConstantIntegerType(1), - new IntegerType(), - TrinaryLogic::createMaybe(), - ]; + public function dataIsSuperTypeOf(): iterable + { + yield [ + new ConstantIntegerType(1), + new ConstantIntegerType(1), + TrinaryLogic::createYes(), + ]; - yield [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - TrinaryLogic::createNo(), - ]; - } + yield [ + new ConstantIntegerType(1), + new IntegerType(), + TrinaryLogic::createMaybe(), + ]; - /** - * @dataProvider dataIsSuperTypeOf - * @param ConstantIntegerType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(ConstantIntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + yield [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + TrinaryLogic::createNo(), + ]; + } + /** + * @dataProvider dataIsSuperTypeOf + * @param ConstantIntegerType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(ConstantIntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 5e28ae9a68..d0fa0cde84 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -1,4 +1,6 @@ - [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\Exception::class)), + TrinaryLogic::createMaybe(), + ], + 1 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\Throwable::class)), + TrinaryLogic::createMaybe(), + ], + 2 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + TrinaryLogic::createNo(), + ], + 3 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\stdClass::class)), + TrinaryLogic::createNo(), + ], + 4 => [ + new ConstantStringType(\Exception::class), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 5 => [ + new ConstantStringType(\Exception::class), + new ConstantStringType(\InvalidArgumentException::class), + TrinaryLogic::createNo(), + ], + 6 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\Exception::class)), + TrinaryLogic::createMaybe(), + ], + 7 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\stdClass::class)), + TrinaryLogic::createNo(), + ], + 8 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createMaybe(), + ], + 9 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createMaybe(), + ], + 10 => [ + new ConstantStringType(\InvalidArgumentException::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createMaybe(), + ], + 11 => [ + new ConstantStringType(\Throwable::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createNo(), + ], + 12 => [ + new ConstantStringType(\stdClass::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createNo(), + ], + 13 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new StaticType(\Exception::class)), + TrinaryLogic::createMaybe(), + ], + 14 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), + TrinaryLogic::createNo(), + ], + 15 => [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new StaticType(\Throwable::class)), + TrinaryLogic::createMaybe(), + ], + ]; + } - public function dataIsSuperTypeOf(): array - { - return [ - 0 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\Exception::class)), - TrinaryLogic::createMaybe(), - ], - 1 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\Throwable::class)), - TrinaryLogic::createMaybe(), - ], - 2 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - TrinaryLogic::createNo(), - ], - 3 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\stdClass::class)), - TrinaryLogic::createNo(), - ], - 4 => [ - new ConstantStringType(\Exception::class), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 5 => [ - new ConstantStringType(\Exception::class), - new ConstantStringType(\InvalidArgumentException::class), - TrinaryLogic::createNo(), - ], - 6 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\Exception::class)), - TrinaryLogic::createMaybe(), - ], - 7 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\stdClass::class)), - TrinaryLogic::createNo(), - ], - 8 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - null, - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createMaybe(), - ], - 9 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createMaybe(), - ], - 10 => [ - new ConstantStringType(\InvalidArgumentException::class), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createMaybe(), - ], - 11 => [ - new ConstantStringType(\Throwable::class), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createNo(), - ], - 12 => [ - new ConstantStringType(\stdClass::class), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createNo(), - ], - 13 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\Exception::class)), - TrinaryLogic::createMaybe(), - ], - 14 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), - TrinaryLogic::createNo(), - ], - 15 => [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\Throwable::class)), - TrinaryLogic::createMaybe(), - ], - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - */ - public function testIsSuperTypeOf(ConstantStringType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function testGeneralize(): void - { - $this->assertSame('string', (new ConstantStringType('NonexistentClass'))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('string', (new ConstantStringType(\stdClass::class))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('class-string', (new ConstantStringType(\stdClass::class, true))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('class-string', (new ConstantStringType('NonexistentClass', true))->generalize()->describe(VerbosityLevel::precise())); - } + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf(ConstantStringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - public function testTextInvalidEncoding(): void - { - $this->assertSame("'\xc3Lorem ipsum dolor s\u{2026}'", (new ConstantStringType("\xc3Lorem ipsum dolor sit"))->describe(VerbosityLevel::value())); - } + public function testGeneralize(): void + { + $this->assertSame('string', (new ConstantStringType('NonexistentClass'))->generalize()->describe(VerbosityLevel::precise())); + $this->assertSame('string', (new ConstantStringType(\stdClass::class))->generalize()->describe(VerbosityLevel::precise())); + $this->assertSame('class-string', (new ConstantStringType(\stdClass::class, true))->generalize()->describe(VerbosityLevel::precise())); + $this->assertSame('class-string', (new ConstantStringType('NonexistentClass', true))->generalize()->describe(VerbosityLevel::precise())); + } - public function testShortTextInvalidEncoding(): void - { - $this->assertSame("'\xc3Lorem ipsum dolor'", (new ConstantStringType("\xc3Lorem ipsum dolor"))->describe(VerbosityLevel::value())); - } + public function testTextInvalidEncoding(): void + { + $this->assertSame("'\xc3Lorem ipsum dolor s\u{2026}'", (new ConstantStringType("\xc3Lorem ipsum dolor sit"))->describe(VerbosityLevel::value())); + } + public function testShortTextInvalidEncoding(): void + { + $this->assertSame("'\xc3Lorem ipsum dolor'", (new ConstantStringType("\xc3Lorem ipsum dolor"))->describe(VerbosityLevel::value())); + } } diff --git a/tests/PHPStan/Type/FileTypeMapperTest.php b/tests/PHPStan/Type/FileTypeMapperTest.php index a9b34cf944..5881316eeb 100644 --- a/tests/PHPStan/Type/FileTypeMapperTest.php +++ b/tests/PHPStan/Type/FileTypeMapperTest.php @@ -1,16 +1,17 @@ -getByType(FileTypeMapper::class); - public function testGetResolvedPhpDoc(): void - { - /** @var FileTypeMapper $fileTypeMapper */ - $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); - - $resolvedA = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/annotations.php', 'Foo', null, null, '/** + $resolvedA = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/annotations.php', 'Foo', null, null, '/** * @property int | float $numericBazBazProperty * @property X $singleLetterObjectName * @@ -22,164 +23,163 @@ public function testGetResolvedPhpDoc(): void * @method Image rotate(float $angle, $backgroundColor) * @method int | float paramMultipleTypesWithExtraSpaces(string | null $string, stdClass | null $object) */'); - $this->assertCount(0, $resolvedA->getVarTags()); - $this->assertCount(0, $resolvedA->getParamTags()); - $this->assertCount(2, $resolvedA->getPropertyTags()); - $this->assertNull($resolvedA->getReturnTag()); - $this->assertSame('float|int', $resolvedA->getPropertyTags()['numericBazBazProperty']->getType()->describe(VerbosityLevel::precise())); - $this->assertSame('X', $resolvedA->getPropertyTags()['singleLetterObjectName']->getType()->describe(VerbosityLevel::precise())); - - $this->assertCount(6, $resolvedA->getMethodTags()); - $this->assertArrayNotHasKey('complicatedParameters', $resolvedA->getMethodTags()); // ambiguous parameter types - $simpleMethod = $resolvedA->getMethodTags()['simpleMethod']; - $this->assertSame('void', $simpleMethod->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($simpleMethod->isStatic()); - $this->assertCount(0, $simpleMethod->getParameters()); - - $returningMethod = $resolvedA->getMethodTags()['returningMethod']; - $this->assertSame('string', $returningMethod->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($returningMethod->isStatic()); - $this->assertCount(0, $returningMethod->getParameters()); - - $returningNullableScalar = $resolvedA->getMethodTags()['returningNullableScalar']; - $this->assertSame('float|null', $returningNullableScalar->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($returningNullableScalar->isStatic()); - $this->assertCount(0, $returningNullableScalar->getParameters()); - - $returningNullableObject = $resolvedA->getMethodTags()['returningNullableObject']; - $this->assertSame('stdClass|null', $returningNullableObject->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($returningNullableObject->isStatic()); - $this->assertCount(0, $returningNullableObject->getParameters()); - - $rotate = $resolvedA->getMethodTags()['rotate']; - $this->assertSame('Image', $rotate->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($rotate->isStatic()); - $this->assertCount(2, $rotate->getParameters()); - $this->assertSame('float', $rotate->getParameters()['angle']->getType()->describe(VerbosityLevel::precise())); - $this->assertTrue($rotate->getParameters()['angle']->passedByReference()->no()); - $this->assertFalse($rotate->getParameters()['angle']->isOptional()); - $this->assertFalse($rotate->getParameters()['angle']->isVariadic()); - $this->assertSame('mixed', $rotate->getParameters()['backgroundColor']->getType()->describe(VerbosityLevel::precise())); - $this->assertTrue($rotate->getParameters()['backgroundColor']->passedByReference()->no()); - $this->assertFalse($rotate->getParameters()['backgroundColor']->isOptional()); - $this->assertFalse($rotate->getParameters()['backgroundColor']->isVariadic()); - - $paramMultipleTypesWithExtraSpaces = $resolvedA->getMethodTags()['paramMultipleTypesWithExtraSpaces']; - $this->assertSame('float|int', $paramMultipleTypesWithExtraSpaces->getReturnType()->describe(VerbosityLevel::precise())); - $this->assertFalse($paramMultipleTypesWithExtraSpaces->isStatic()); - $this->assertCount(2, $paramMultipleTypesWithExtraSpaces->getParameters()); - $this->assertSame('string|null', $paramMultipleTypesWithExtraSpaces->getParameters()['string']->getType()->describe(VerbosityLevel::precise())); - $this->assertTrue($paramMultipleTypesWithExtraSpaces->getParameters()['string']->passedByReference()->no()); - $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['string']->isOptional()); - $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['string']->isVariadic()); - $this->assertSame('stdClass|null', $paramMultipleTypesWithExtraSpaces->getParameters()['object']->getType()->describe(VerbosityLevel::precise())); - $this->assertTrue($paramMultipleTypesWithExtraSpaces->getParameters()['object']->passedByReference()->no()); - $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['object']->isOptional()); - $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['object']->isVariadic()); - } - - public function testFileWithDependentPhpDocs(): void - { - /** @var FileTypeMapper $fileTypeMapper */ - $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); - - $realpath = realpath(__DIR__ . '/data/dependent-phpdocs.php'); - if ($realpath === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $resolved = $fileTypeMapper->getResolvedPhpDoc( - $realpath, - \DependentPhpDocs\Foo::class, - null, - 'addPages', - '/** @param Foo[]|Foo|\Iterator $pages */' - ); - - $this->assertCount(1, $resolved->getParamTags()); - $this->assertSame( - '(DependentPhpDocs\Foo&iterable)|(iterable&Iterator)', - $resolved->getParamTags()['pages']->getType()->describe(VerbosityLevel::precise()) - ); - } - - public function testFileThrowsPhpDocs(): void - { - /** @var FileTypeMapper $fileTypeMapper */ - $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); - - $realpath = realpath(__DIR__ . '/data/throws-phpdocs.php'); - if ($realpath === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeException', '/** + $this->assertCount(0, $resolvedA->getVarTags()); + $this->assertCount(0, $resolvedA->getParamTags()); + $this->assertCount(2, $resolvedA->getPropertyTags()); + $this->assertNull($resolvedA->getReturnTag()); + $this->assertSame('float|int', $resolvedA->getPropertyTags()['numericBazBazProperty']->getType()->describe(VerbosityLevel::precise())); + $this->assertSame('X', $resolvedA->getPropertyTags()['singleLetterObjectName']->getType()->describe(VerbosityLevel::precise())); + + $this->assertCount(6, $resolvedA->getMethodTags()); + $this->assertArrayNotHasKey('complicatedParameters', $resolvedA->getMethodTags()); // ambiguous parameter types + $simpleMethod = $resolvedA->getMethodTags()['simpleMethod']; + $this->assertSame('void', $simpleMethod->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($simpleMethod->isStatic()); + $this->assertCount(0, $simpleMethod->getParameters()); + + $returningMethod = $resolvedA->getMethodTags()['returningMethod']; + $this->assertSame('string', $returningMethod->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($returningMethod->isStatic()); + $this->assertCount(0, $returningMethod->getParameters()); + + $returningNullableScalar = $resolvedA->getMethodTags()['returningNullableScalar']; + $this->assertSame('float|null', $returningNullableScalar->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($returningNullableScalar->isStatic()); + $this->assertCount(0, $returningNullableScalar->getParameters()); + + $returningNullableObject = $resolvedA->getMethodTags()['returningNullableObject']; + $this->assertSame('stdClass|null', $returningNullableObject->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($returningNullableObject->isStatic()); + $this->assertCount(0, $returningNullableObject->getParameters()); + + $rotate = $resolvedA->getMethodTags()['rotate']; + $this->assertSame('Image', $rotate->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($rotate->isStatic()); + $this->assertCount(2, $rotate->getParameters()); + $this->assertSame('float', $rotate->getParameters()['angle']->getType()->describe(VerbosityLevel::precise())); + $this->assertTrue($rotate->getParameters()['angle']->passedByReference()->no()); + $this->assertFalse($rotate->getParameters()['angle']->isOptional()); + $this->assertFalse($rotate->getParameters()['angle']->isVariadic()); + $this->assertSame('mixed', $rotate->getParameters()['backgroundColor']->getType()->describe(VerbosityLevel::precise())); + $this->assertTrue($rotate->getParameters()['backgroundColor']->passedByReference()->no()); + $this->assertFalse($rotate->getParameters()['backgroundColor']->isOptional()); + $this->assertFalse($rotate->getParameters()['backgroundColor']->isVariadic()); + + $paramMultipleTypesWithExtraSpaces = $resolvedA->getMethodTags()['paramMultipleTypesWithExtraSpaces']; + $this->assertSame('float|int', $paramMultipleTypesWithExtraSpaces->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertFalse($paramMultipleTypesWithExtraSpaces->isStatic()); + $this->assertCount(2, $paramMultipleTypesWithExtraSpaces->getParameters()); + $this->assertSame('string|null', $paramMultipleTypesWithExtraSpaces->getParameters()['string']->getType()->describe(VerbosityLevel::precise())); + $this->assertTrue($paramMultipleTypesWithExtraSpaces->getParameters()['string']->passedByReference()->no()); + $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['string']->isOptional()); + $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['string']->isVariadic()); + $this->assertSame('stdClass|null', $paramMultipleTypesWithExtraSpaces->getParameters()['object']->getType()->describe(VerbosityLevel::precise())); + $this->assertTrue($paramMultipleTypesWithExtraSpaces->getParameters()['object']->passedByReference()->no()); + $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['object']->isOptional()); + $this->assertFalse($paramMultipleTypesWithExtraSpaces->getParameters()['object']->isVariadic()); + } + + public function testFileWithDependentPhpDocs(): void + { + /** @var FileTypeMapper $fileTypeMapper */ + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); + + $realpath = realpath(__DIR__ . '/data/dependent-phpdocs.php'); + if ($realpath === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $resolved = $fileTypeMapper->getResolvedPhpDoc( + $realpath, + \DependentPhpDocs\Foo::class, + null, + 'addPages', + '/** @param Foo[]|Foo|\Iterator $pages */' + ); + + $this->assertCount(1, $resolved->getParamTags()); + $this->assertSame( + '(DependentPhpDocs\Foo&iterable)|(iterable&Iterator)', + $resolved->getParamTags()['pages']->getType()->describe(VerbosityLevel::precise()) + ); + } + + public function testFileThrowsPhpDocs(): void + { + /** @var FileTypeMapper $fileTypeMapper */ + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); + + $realpath = realpath(__DIR__ . '/data/throws-phpdocs.php'); + if ($realpath === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeException', '/** * @throws RuntimeException */'); - $this->assertNotNull($resolved->getThrowsTag()); - $this->assertSame( - \RuntimeException::class, - $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) - ); + $this->assertNotNull($resolved->getThrowsTag()); + $this->assertSame( + \RuntimeException::class, + $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) + ); - $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeAndLogicException', '/** + $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeAndLogicException', '/** * @throws RuntimeException|LogicException */'); - $this->assertNotNull($resolved->getThrowsTag()); - $this->assertSame( - 'LogicException|RuntimeException', - $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) - ); + $this->assertNotNull($resolved->getThrowsTag()); + $this->assertSame( + 'LogicException|RuntimeException', + $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) + ); - $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeAndLogicException2', '/** + $resolved = $fileTypeMapper->getResolvedPhpDoc($realpath, \ThrowsPhpDocs\Foo::class, null, 'throwRuntimeAndLogicException2', '/** * @throws RuntimeException * @throws LogicException */'); - $this->assertNotNull($resolved->getThrowsTag()); - $this->assertSame( - 'LogicException|RuntimeException', - $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) - ); - } - - public function testFileWithCyclicPhpDocs(): void - { - self::getContainer()->getByType(\PHPStan\Broker\Broker::class); - - /** @var FileTypeMapper $fileTypeMapper */ - $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); - - $realpath = realpath(__DIR__ . '/data/cyclic-phpdocs.php'); - if ($realpath === false) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $resolved = $fileTypeMapper->getResolvedPhpDoc( - $realpath, - \CyclicPhpDocs\Foo::class, - null, - 'getIterator', - '/** @return iterable | Foo */' - ); - - /** @var \PHPStan\PhpDoc\Tag\ReturnTag $returnTag */ - $returnTag = $resolved->getReturnTag(); - $this->assertSame('CyclicPhpDocs\Foo|iterable', $returnTag->getType()->describe(VerbosityLevel::precise())); - } - - public function testFilesWithIdenticalPhpDocsUsingDifferentAliases(): void - { - /** @var FileTypeMapper $fileTypeMapper */ - $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); - - $doc1 = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/alias-collision1.php', null, null, null, '/** @var Foo $x */'); - $doc2 = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/alias-collision2.php', null, null, null, '/** @var Foo $x */'); - - $this->assertSame('AliasCollisionNamespace1\Foo', $doc1->getVarTags()['x']->getType()->describe(VerbosityLevel::precise())); - $this->assertSame('AliasCollisionNamespace2\Foo', $doc2->getVarTags()['x']->getType()->describe(VerbosityLevel::precise())); - } - + $this->assertNotNull($resolved->getThrowsTag()); + $this->assertSame( + 'LogicException|RuntimeException', + $resolved->getThrowsTag()->getType()->describe(VerbosityLevel::precise()) + ); + } + + public function testFileWithCyclicPhpDocs(): void + { + self::getContainer()->getByType(\PHPStan\Broker\Broker::class); + + /** @var FileTypeMapper $fileTypeMapper */ + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); + + $realpath = realpath(__DIR__ . '/data/cyclic-phpdocs.php'); + if ($realpath === false) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $resolved = $fileTypeMapper->getResolvedPhpDoc( + $realpath, + \CyclicPhpDocs\Foo::class, + null, + 'getIterator', + '/** @return iterable | Foo */' + ); + + /** @var \PHPStan\PhpDoc\Tag\ReturnTag $returnTag */ + $returnTag = $resolved->getReturnTag(); + $this->assertSame('CyclicPhpDocs\Foo|iterable', $returnTag->getType()->describe(VerbosityLevel::precise())); + } + + public function testFilesWithIdenticalPhpDocsUsingDifferentAliases(): void + { + /** @var FileTypeMapper $fileTypeMapper */ + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); + + $doc1 = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/alias-collision1.php', null, null, null, '/** @var Foo $x */'); + $doc2 = $fileTypeMapper->getResolvedPhpDoc(__DIR__ . '/data/alias-collision2.php', null, null, null, '/** @var Foo $x */'); + + $this->assertSame('AliasCollisionNamespace1\Foo', $doc1->getVarTags()['x']->getType()->describe(VerbosityLevel::precise())); + $this->assertSame('AliasCollisionNamespace2\Foo', $doc2->getVarTags()['x']->getType()->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Type/FloatTypeTest.php b/tests/PHPStan/Type/FloatTypeTest.php index 903beaf221..4606a7ee92 100644 --- a/tests/PHPStan/Type/FloatTypeTest.php +++ b/tests/PHPStan/Type/FloatTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataEquals(): array - { - return [ - [ - new FloatType(), - new FloatType(), - true, - ], - [ - new ConstantFloatType(0.0), - new ConstantFloatType(0.0), - true, - ], - [ - new ConstantFloatType(0.0), - new ConstantFloatType(1.0), - false, - ], - [ - new FloatType(), - new ConstantFloatType(0.0), - false, - ], - [ - new ConstantFloatType(0.0), - new FloatType(), - false, - ], - [ - new FloatType(), - new IntegerType(), - false, - ], - [ - new ConstantFloatType(0.0), - new ConstantIntegerType(0), - false, - ], - [ - new ConstantFloatType(0.0), - new ConstantStringType('0.0'), - false, - ], - ]; - } + /** + * @dataProvider dataAccepts + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(Type $otherType, TrinaryLogic $expectedResult): void + { + $type = new FloatType(); + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataEquals - * @param FloatType $type - * @param Type $otherType - * @param bool $expectedResult - */ - public function testEquals(FloatType $type, Type $otherType, bool $expectedResult): void - { - $actualResult = $type->equals($otherType); - $this->assertSame( - $expectedResult, - $actualResult, - sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataEquals(): array + { + return [ + [ + new FloatType(), + new FloatType(), + true, + ], + [ + new ConstantFloatType(0.0), + new ConstantFloatType(0.0), + true, + ], + [ + new ConstantFloatType(0.0), + new ConstantFloatType(1.0), + false, + ], + [ + new FloatType(), + new ConstantFloatType(0.0), + false, + ], + [ + new ConstantFloatType(0.0), + new FloatType(), + false, + ], + [ + new FloatType(), + new IntegerType(), + false, + ], + [ + new ConstantFloatType(0.0), + new ConstantIntegerType(0), + false, + ], + [ + new ConstantFloatType(0.0), + new ConstantStringType('0.0'), + false, + ], + ]; + } + /** + * @dataProvider dataEquals + * @param FloatType $type + * @param Type $otherType + * @param bool $expectedResult + */ + public function testEquals(FloatType $type, Type $otherType, bool $expectedResult): void + { + $actualResult = $type->equals($otherType); + $this->assertSame( + $expectedResult, + $actualResult, + sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index 277a3875f5..5bcc494f79 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -1,4 +1,6 @@ - [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ClassStringType(), + TrinaryLogic::createMaybe(), + ], + 1 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new StringType(), + TrinaryLogic::createMaybe(), + ], + 2 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + TrinaryLogic::createYes(), + ], + 3 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Throwable::class)), + TrinaryLogic::createMaybe(), + ], + 4 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + TrinaryLogic::createYes(), + ], + 5 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\stdClass::class)), + TrinaryLogic::createNo(), + ], + 6 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 7 => [ + new GenericClassStringType(new ObjectType(\Throwable::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 8 => [ + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createNo(), + ], + 9 => [ + new GenericClassStringType(new ObjectType(\stdClass::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createNo(), + ], + 10 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 11 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 12 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType(\stdClass::class), + TrinaryLogic::createNo(), + ], + 13 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType(\InvalidArgumentException::class), + TrinaryLogic::createYes(), + ], + 14 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType(\Throwable::class), + TrinaryLogic::createNo(), + ], + 15 => [ + new GenericClassStringType(new StaticType(\Exception::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 16 => [ + new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createNo(), + ], + 17 => [ + new GenericClassStringType(new StaticType(\Throwable::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + ]; + } - public function dataIsSuperTypeOf(): array - { - return [ - 0 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ClassStringType(), - TrinaryLogic::createMaybe(), - ], - 1 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new StringType(), - TrinaryLogic::createMaybe(), - ], - 2 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Exception::class)), - TrinaryLogic::createYes(), - ], - 3 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Throwable::class)), - TrinaryLogic::createMaybe(), - ], - 4 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - TrinaryLogic::createYes(), - ], - 5 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\stdClass::class)), - TrinaryLogic::createNo(), - ], - 6 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 7 => [ - new GenericClassStringType(new ObjectType(\Throwable::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 8 => [ - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createNo(), - ], - 9 => [ - new GenericClassStringType(new ObjectType(\stdClass::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createNo(), - ], - 10 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - null, - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 11 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 12 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType(\stdClass::class), - TrinaryLogic::createNo(), - ], - 13 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType(\InvalidArgumentException::class), - TrinaryLogic::createYes(), - ], - 14 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType(\Throwable::class), - TrinaryLogic::createNo(), - ], - 15 => [ - new GenericClassStringType(new StaticType(\Exception::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 16 => [ - new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createNo(), - ], - 17 => [ - new GenericClassStringType(new StaticType(\Throwable::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - */ - public function testIsSuperTypeOf(GenericClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataAccepts(): array - { - return [ - 0 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\Throwable::class), - TrinaryLogic::createNo(), - ], - 1 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\Exception::class), - TrinaryLogic::createYes(), - ], - 2 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\InvalidArgumentException::class), - TrinaryLogic::createYes(), - ], - 3 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new StringType(), - TrinaryLogic::createMaybe(), - ], - 4 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ObjectType(\Exception::class), - TrinaryLogic::createNo(), - ], - 5 => [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Exception::class)), - TrinaryLogic::createYes(), - ], - 6 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - null, - TemplateTypeVariance::createInvariant() - )), - new ConstantStringType('NonexistentClass'), - TrinaryLogic::createNo(), - ], - 7 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Foo'), - 'T', - null, - TemplateTypeVariance::createInvariant() - )), - new UnionType([ - new ConstantStringType(\DateTime::class), - new ConstantStringType(\Exception::class), - ]), - TrinaryLogic::createYes(), - ], - 8 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Foo'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - )), - new ClassStringType(), - TrinaryLogic::createYes(), - ], - 9 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Foo'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - )), - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Boo'), - 'U', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - )), - TrinaryLogic::createMaybe(), - ], - 10 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Foo'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - )), - new UnionType([new IntegerType(), new StringType()]), - TrinaryLogic::createMaybe(), - ], - 11 => [ - new GenericClassStringType(TemplateTypeFactory::create( - TemplateTypeScope::createWithClass('Foo'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - )), - new BenevolentUnionType([new IntegerType(), new StringType()]), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf(GenericClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataAccepts - */ - public function testAccepts( - GenericClassStringType $acceptingType, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $actualResult = $acceptingType->accepts($acceptedType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } + public function dataAccepts(): array + { + return [ + 0 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\Throwable::class), + TrinaryLogic::createNo(), + ], + 1 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\Exception::class), + TrinaryLogic::createYes(), + ], + 2 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\InvalidArgumentException::class), + TrinaryLogic::createYes(), + ], + 3 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new StringType(), + TrinaryLogic::createMaybe(), + ], + 4 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ObjectType(\Exception::class), + TrinaryLogic::createNo(), + ], + 5 => [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + TrinaryLogic::createYes(), + ], + 6 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + )), + new ConstantStringType('NonexistentClass'), + TrinaryLogic::createNo(), + ], + 7 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + )), + new UnionType([ + new ConstantStringType(\DateTime::class), + new ConstantStringType(\Exception::class), + ]), + TrinaryLogic::createYes(), + ], + 8 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + )), + new ClassStringType(), + TrinaryLogic::createYes(), + ], + 9 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + )), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Boo'), + 'U', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + )), + TrinaryLogic::createMaybe(), + ], + 10 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + )), + new UnionType([new IntegerType(), new StringType()]), + TrinaryLogic::createMaybe(), + ], + 11 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + )), + new BenevolentUnionType([new IntegerType(), new StringType()]), + TrinaryLogic::createMaybe(), + ], + ]; + } - public function dataEquals(): array - { - return [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Exception::class)), - true, - ], - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\stdClass::class)), - false, - ], - [ - new GenericClassStringType(new StaticType(\Exception::class)), - new GenericClassStringType(new StaticType(\Exception::class)), - true, - ], - [ - new GenericClassStringType(new StaticType(\Exception::class)), - new GenericClassStringType(new StaticType(\stdClass::class)), - false, - ], - ]; - } + /** + * @dataProvider dataAccepts + */ + public function testAccepts( + GenericClassStringType $acceptingType, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $actualResult = $acceptingType->accepts($acceptedType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataEquals - */ - public function testEquals(GenericClassStringType $type, Type $otherType, bool $expected): void - { - $verbosityLevel = VerbosityLevel::precise(); - $typeDescription = $type->describe($verbosityLevel); - $otherTypeDescription = $otherType->describe($verbosityLevel); + public function dataEquals(): array + { + return [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + true, + ], + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\stdClass::class)), + false, + ], + [ + new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType(\Exception::class)), + true, + ], + [ + new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType(\stdClass::class)), + false, + ], + ]; + } - $actual = $type->equals($otherType); - $this->assertSame( - $expected, - $actual, - sprintf('%s -> equals(%s)', $typeDescription, $otherTypeDescription) - ); + /** + * @dataProvider dataEquals + */ + public function testEquals(GenericClassStringType $type, Type $otherType, bool $expected): void + { + $verbosityLevel = VerbosityLevel::precise(); + $typeDescription = $type->describe($verbosityLevel); + $otherTypeDescription = $otherType->describe($verbosityLevel); - $actual = $otherType->equals($type); - $this->assertSame( - $expected, - $actual, - sprintf('%s -> equals(%s)', $otherTypeDescription, $typeDescription) - ); - } + $actual = $type->equals($otherType); + $this->assertSame( + $expected, + $actual, + sprintf('%s -> equals(%s)', $typeDescription, $otherTypeDescription) + ); + $actual = $otherType->equals($type); + $this->assertSame( + $expected, + $actual, + sprintf('%s -> equals(%s)', $otherTypeDescription, $typeDescription) + ); + } } diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index fe412c6971..4309aaad74 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -1,4 +1,6 @@ - [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'sub-class with static @extends with same type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new ObjectType(A\AOfDateTime::class), + TrinaryLogic::createYes(), + ], + 'sub-class with @extends with same type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new GenericObjectType(A\SubA::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'same class, different type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'same class, one naked' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), + new ObjectType(A\A::class), + TrinaryLogic::createMaybe(), + ], + 'implementation with @extends with same type args' => [ + new GenericObjectType(B\I::class, [new ObjectType('DateTime')]), + new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'implementation with @extends with different type args' => [ + new GenericObjectType(B\I::class, [new ObjectType('DateTimeInteface')]), + new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'invariant with equals types' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'invariant with sub type' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'invariant with super type' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), + TrinaryLogic::createNo(), + ], + 'covariant with equals types' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'covariant with sub type' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'covariant with super type' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), + TrinaryLogic::createMaybe(), + ], + [ + new ObjectType(\ReflectionClass::class), + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\stdClass::class), + ]), + TrinaryLogic::createYes(), + ], + [ + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\stdClass::class), + ]), + new ObjectType(\ReflectionClass::class), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ReflectionClass::class, [ + new ObjectWithoutClassType(), + ]), + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\stdClass::class), + ]), + TrinaryLogic::createYes(), + ], + [ + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\stdClass::class), + ]), + new GenericObjectType(\ReflectionClass::class, [ + new ObjectWithoutClassType(), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\Exception::class), + ]), + new GenericObjectType(\ReflectionClass::class, [ + new ObjectType(\stdClass::class), + ]), + TrinaryLogic::createNo(), + ], + ]; + } - public function dataIsSuperTypeOf(): array - { - return [ - 'equal type' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'sub-class with static @extends with same type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new ObjectType(A\AOfDateTime::class), - TrinaryLogic::createYes(), - ], - 'sub-class with @extends with same type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new GenericObjectType(A\SubA::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'same class, different type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - TrinaryLogic::createNo(), - ], - 'same class, one naked' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), - new ObjectType(A\A::class), - TrinaryLogic::createMaybe(), - ], - 'implementation with @extends with same type args' => [ - new GenericObjectType(B\I::class, [new ObjectType('DateTime')]), - new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'implementation with @extends with different type args' => [ - new GenericObjectType(B\I::class, [new ObjectType('DateTimeInteface')]), - new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), - TrinaryLogic::createNo(), - ], - 'invariant with equals types' => [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'invariant with sub type' => [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), - TrinaryLogic::createNo(), - ], - 'invariant with super type' => [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), - TrinaryLogic::createNo(), - ], - 'covariant with equals types' => [ - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'covariant with sub type' => [ - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'covariant with super type' => [ - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), - new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), - TrinaryLogic::createMaybe(), - ], - [ - new ObjectType(\ReflectionClass::class), - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\stdClass::class), - ]), - TrinaryLogic::createYes(), - ], - [ - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\stdClass::class), - ]), - new ObjectType(\ReflectionClass::class), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ReflectionClass::class, [ - new ObjectWithoutClassType(), - ]), - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\stdClass::class), - ]), - TrinaryLogic::createYes(), - ], - [ - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\stdClass::class), - ]), - new GenericObjectType(\ReflectionClass::class, [ - new ObjectWithoutClassType(), - ]), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\Exception::class), - ]), - new GenericObjectType(\ReflectionClass::class, [ - new ObjectType(\stdClass::class), - ]), - TrinaryLogic::createNo(), - ], - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - */ - public function testIsSuperTypeOf(Type $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataAccepts(): array - { - return [ - 'equal type' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'sub-class with static @extends with same type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new ObjectType(A\AOfDateTime::class), - TrinaryLogic::createYes(), - ], - 'sub-class with @extends with same type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - new GenericObjectType(A\SubA::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'same class, different type args' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), - new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), - TrinaryLogic::createNo(), - ], - 'same class, one naked' => [ - new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), - new ObjectType(A\A::class), - TrinaryLogic::createYes(), - ], - 'implementation with @extends with same type args' => [ - new GenericObjectType(B\I::class, [new ObjectType('DateTime')]), - new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), - TrinaryLogic::createYes(), - ], - 'implementation with @extends with different type args' => [ - new GenericObjectType(B\I::class, [new ObjectType('DateTimeInteface')]), - new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), - TrinaryLogic::createNo(), - ], - 'generic object accepts normal object of same type' => [ - new GenericObjectType(\Traversable::class, [new MixedType(true), new ObjectType('DateTimeInteface')]), - new ObjectType(\Traversable::class), - TrinaryLogic::createYes(), - ], - [ - new GenericObjectType(\Iterator::class, [new MixedType(true), new MixedType(true)]), - new ObjectType(\Iterator::class), - TrinaryLogic::createYes(), - ], - [ - new GenericObjectType(\Iterator::class, [new MixedType(true), new MixedType(true)]), - new IntersectionType([new ObjectType(\Iterator::class), new ObjectType(\DateTimeInterface::class)]), - TrinaryLogic::createYes(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf(Type $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataAccepts - */ - public function testAccepts( - Type $acceptingType, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $actualResult = $acceptingType->accepts($acceptedType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } + public function dataAccepts(): array + { + return [ + 'equal type' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'sub-class with static @extends with same type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new ObjectType(A\AOfDateTime::class), + TrinaryLogic::createYes(), + ], + 'sub-class with @extends with same type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + new GenericObjectType(A\SubA::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'same class, different type args' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(A\A::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'same class, one naked' => [ + new GenericObjectType(A\A::class, [new ObjectType('DateTimeInterface')]), + new ObjectType(A\A::class), + TrinaryLogic::createYes(), + ], + 'implementation with @extends with same type args' => [ + new GenericObjectType(B\I::class, [new ObjectType('DateTime')]), + new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'implementation with @extends with different type args' => [ + new GenericObjectType(B\I::class, [new ObjectType('DateTimeInteface')]), + new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'generic object accepts normal object of same type' => [ + new GenericObjectType(\Traversable::class, [new MixedType(true), new ObjectType('DateTimeInteface')]), + new ObjectType(\Traversable::class), + TrinaryLogic::createYes(), + ], + [ + new GenericObjectType(\Iterator::class, [new MixedType(true), new MixedType(true)]), + new ObjectType(\Iterator::class), + TrinaryLogic::createYes(), + ], + [ + new GenericObjectType(\Iterator::class, [new MixedType(true), new MixedType(true)]), + new IntersectionType([new ObjectType(\Iterator::class), new ObjectType(\DateTimeInterface::class)]), + TrinaryLogic::createYes(), + ], + ]; + } - /** @return array}> */ - public function dataInferTemplateTypes(): array - { - $templateType = static function (string $name, ?Type $bound = null): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - $bound ?? new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; + /** + * @dataProvider dataAccepts + */ + public function testAccepts( + Type $acceptingType, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $actualResult = $acceptingType->accepts($acceptedType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $acceptingType->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } - return [ - 'simple' => [ - new GenericObjectType(A\A::class, [ - new ObjectType(\DateTime::class), - ]), - new GenericObjectType(A\A::class, [ - $templateType('T'), - ]), - ['T' => 'DateTime'], - ], - 'two types' => [ - new GenericObjectType(A\A2::class, [ - new ObjectType(\DateTime::class), - new IntegerType(), - ]), - new GenericObjectType(A\A2::class, [ - $templateType('K'), - $templateType('V'), - ]), - ['K' => 'DateTime', 'V' => 'int'], - ], - 'union' => [ - new UnionType([ - new GenericObjectType(A\A2::class, [ - new ObjectType(\DateTime::class), - new IntegerType(), - ]), - new GenericObjectType(A\A2::class, [ - new IntegerType(), - new ObjectType(\DateTime::class), - ]), - ]), - new GenericObjectType(A\A2::class, [ - $templateType('K'), - $templateType('V'), - ]), - ['K' => 'DateTime|int', 'V' => 'DateTime|int'], - ], - 'nested' => [ - new GenericObjectType(A\A::class, [ - new GenericObjectType(A\A2::class, [ - new ObjectType(\DateTime::class), - new IntegerType(), - ]), - ]), - new GenericObjectType(A\A::class, [ - new GenericObjectType(A\A2::class, [ - $templateType('K'), - $templateType('V'), - ]), - ]), - ['K' => 'DateTime', 'V' => 'int'], - ], - 'missing type' => [ - new GenericObjectType(A\A2::class, [ - new ObjectType(\DateTime::class), - ]), - new GenericObjectType(A\A2::class, [ - $templateType('K', new ObjectType(\DateTimeInterface::class)), - $templateType('V', new ObjectType(\DateTimeInterface::class)), - ]), - ['K' => 'DateTime'], - ], - 'wrong class' => [ - new GenericObjectType(B\I::class, [ - new ObjectType(\DateTime::class), - ]), - new GenericObjectType(A\A::class, [ - $templateType('T', new ObjectType(\DateTimeInterface::class)), - ]), - [], - ], - 'wrong type' => [ - new IntegerType(), - new GenericObjectType(A\A::class, [ - $templateType('T', new ObjectType(\DateTimeInterface::class)), - ]), - [], - ], - 'sub type' => [ - new ObjectType(A\AOfDateTime::class), - new GenericObjectType(A\A::class, [ - $templateType('T'), - ]), - ['T' => 'DateTime'], - ], - ]; - } + /** @return array}> */ + public function dataInferTemplateTypes(): array + { + $templateType = static function (string $name, ?Type $bound = null): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + $bound ?? new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); + return [ + 'simple' => [ + new GenericObjectType(A\A::class, [ + new ObjectType(\DateTime::class), + ]), + new GenericObjectType(A\A::class, [ + $templateType('T'), + ]), + ['T' => 'DateTime'], + ], + 'two types' => [ + new GenericObjectType(A\A2::class, [ + new ObjectType(\DateTime::class), + new IntegerType(), + ]), + new GenericObjectType(A\A2::class, [ + $templateType('K'), + $templateType('V'), + ]), + ['K' => 'DateTime', 'V' => 'int'], + ], + 'union' => [ + new UnionType([ + new GenericObjectType(A\A2::class, [ + new ObjectType(\DateTime::class), + new IntegerType(), + ]), + new GenericObjectType(A\A2::class, [ + new IntegerType(), + new ObjectType(\DateTime::class), + ]), + ]), + new GenericObjectType(A\A2::class, [ + $templateType('K'), + $templateType('V'), + ]), + ['K' => 'DateTime|int', 'V' => 'DateTime|int'], + ], + 'nested' => [ + new GenericObjectType(A\A::class, [ + new GenericObjectType(A\A2::class, [ + new ObjectType(\DateTime::class), + new IntegerType(), + ]), + ]), + new GenericObjectType(A\A::class, [ + new GenericObjectType(A\A2::class, [ + $templateType('K'), + $templateType('V'), + ]), + ]), + ['K' => 'DateTime', 'V' => 'int'], + ], + 'missing type' => [ + new GenericObjectType(A\A2::class, [ + new ObjectType(\DateTime::class), + ]), + new GenericObjectType(A\A2::class, [ + $templateType('K', new ObjectType(\DateTimeInterface::class)), + $templateType('V', new ObjectType(\DateTimeInterface::class)), + ]), + ['K' => 'DateTime'], + ], + 'wrong class' => [ + new GenericObjectType(B\I::class, [ + new ObjectType(\DateTime::class), + ]), + new GenericObjectType(A\A::class, [ + $templateType('T', new ObjectType(\DateTimeInterface::class)), + ]), + [], + ], + 'wrong type' => [ + new IntegerType(), + new GenericObjectType(A\A::class, [ + $templateType('T', new ObjectType(\DateTimeInterface::class)), + ]), + [], + ], + 'sub type' => [ + new ObjectType(A\AOfDateTime::class), + new GenericObjectType(A\A::class, [ + $templateType('T'), + ]), + ['T' => 'DateTime'], + ], + ]; + } - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); - /** @return array}> */ - public function dataGetReferencedTypeArguments(): array - { - $templateType = static function (string $name, ?Type $bound = null): TemplateType { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - $bound ?? new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } - return [ - 'param: Invariant' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant() - ), - ], - ], - 'param: Out' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant() - ), - ], - ], - 'param: Out>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant() - ), - ], - ], - 'param: Out>>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant() - ), - ], - ], - 'return: Invariant' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant() - ), - ], - ], - 'return: Out' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant() - ), - ], - ], - 'return: Out>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant() - ), - ], - ], - 'return: Out>>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant() - ), - ], - ], - 'return: Out>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - ]), - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant() - ), - ], - ], - ]; - } + /** @return array}> */ + public function dataGetReferencedTypeArguments(): array + { + $templateType = static function (string $name, ?Type $bound = null): TemplateType { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + $bound ?? new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataGetReferencedTypeArguments - * - * @param array $expectedReferences - */ - public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, array $expectedReferences): void - { - $result = []; - foreach ($type->getReferencedTemplateTypes($positionVariance) as $r) { - $result[] = $r; - } + return [ + 'param: Invariant' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant() + ), + ], + ], + 'param: Out' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createContravariant() + ), + ], + ], + 'param: Out>' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createContravariant() + ), + ], + ], + 'param: Out>>' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createContravariant() + ), + ], + ], + 'return: Invariant' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant() + ), + ], + ], + 'return: Out' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createCovariant() + ), + ], + ], + 'return: Out>' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createCovariant() + ), + ], + ], + 'return: Out>>' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createCovariant() + ), + ], + ], + 'return: Out>' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + ]), + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant() + ), + ], + ], + ]; + } - $comparableResult = array_map(static function (TemplateTypeReference $ref): array { - return [ - 'type' => $ref->getType()->describe(VerbosityLevel::typeOnly()), - 'positionVariance' => $ref->getPositionVariance()->describe(), - ]; - }, $result); + /** + * @dataProvider dataGetReferencedTypeArguments + * + * @param array $expectedReferences + */ + public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, array $expectedReferences): void + { + $result = []; + foreach ($type->getReferencedTemplateTypes($positionVariance) as $r) { + $result[] = $r; + } - $comparableExpect = array_map(static function (TemplateTypeReference $ref): array { - return [ - 'type' => $ref->getType()->describe(VerbosityLevel::typeOnly()), - 'positionVariance' => $ref->getPositionVariance()->describe(), - ]; - }, $expectedReferences); + $comparableResult = array_map(static function (TemplateTypeReference $ref): array { + return [ + 'type' => $ref->getType()->describe(VerbosityLevel::typeOnly()), + 'positionVariance' => $ref->getPositionVariance()->describe(), + ]; + }, $result); - $this->assertSame($comparableExpect, $comparableResult); - } + $comparableExpect = array_map(static function (TemplateTypeReference $ref): array { + return [ + 'type' => $ref->getType()->describe(VerbosityLevel::typeOnly()), + 'positionVariance' => $ref->getPositionVariance()->describe(), + ]; + }, $expectedReferences); + $this->assertSame($comparableExpect, $comparableResult); + } } diff --git a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php index 279617324e..83410bcf42 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php @@ -1,4 +1,6 @@ - $templateType, - ]) - ); - - $this->assertEquals( - 'T (function a(), parameter)', - $type->describe(VerbosityLevel::precise()) - ); - - $type = TemplateTypeHelper::resolveTemplateTypes( - $templateType, - new TemplateTypeMap([ - 'T' => new IntersectionType([ - new ObjectType(\DateTime::class), - $templateType, - ]), - ]) - ); - - $this->assertEquals( - 'DateTime&T (function a(), parameter)', - $type->describe(VerbosityLevel::precise()) - ); - } - + public function testIssue2512(): void + { + $templateType = TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ); + + $type = TemplateTypeHelper::resolveTemplateTypes( + $templateType, + new TemplateTypeMap([ + 'T' => $templateType, + ]) + ); + + $this->assertEquals( + 'T (function a(), parameter)', + $type->describe(VerbosityLevel::precise()) + ); + + $type = TemplateTypeHelper::resolveTemplateTypes( + $templateType, + new TemplateTypeMap([ + 'T' => new IntersectionType([ + new ObjectType(\DateTime::class), + $templateType, + ]), + ]) + ); + + $this->assertEquals( + 'DateTime&T (function a(), parameter)', + $type->describe(VerbosityLevel::precise()) + ); + } } diff --git a/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php b/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php index f63858d142..fe93cff763 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php @@ -1,4 +1,6 @@ -assertSame( - $expected->describe(), - $variance->isValidVariance($a, $b)->describe(), - sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $a->describe(VerbosityLevel::precise()), $b->describe(VerbosityLevel::precise())) - ); - $this->assertSame( - $expectedInversed->describe(), - $variance->isValidVariance($b, $a)->describe(), - sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $b->describe(VerbosityLevel::precise()), $a->describe(VerbosityLevel::precise())) - ); - } + yield [ + $variance, + new UnionType([new IntegerType(), new StringType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ]; + } + } + /** + * @dataProvider dataIsValidVariance + */ + public function testIsValidVariance( + TemplateTypeVariance $variance, + Type $a, + Type $b, + TrinaryLogic $expected, + TrinaryLogic $expectedInversed + ): void { + $this->assertSame( + $expected->describe(), + $variance->isValidVariance($a, $b)->describe(), + sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $a->describe(VerbosityLevel::precise()), $b->describe(VerbosityLevel::precise())) + ); + $this->assertSame( + $expectedInversed->describe(), + $variance->isValidVariance($b, $a)->describe(), + sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $b->describe(VerbosityLevel::precise()), $a->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/Generic/data/generic-classes-a.php b/tests/PHPStan/Type/Generic/data/generic-classes-a.php index 47a6366b30..4e8ae2199a 100644 --- a/tests/PHPStan/Type/Generic/data/generic-classes-a.php +++ b/tests/PHPStan/Type/Generic/data/generic-classes-a.php @@ -1,21 +1,31 @@ - */ -class AOfDateTime extends A {} +class AOfDateTime extends A +{ +} /** * @template U * @extends A */ -class SubA extends A {} +class SubA extends A +{ +} /** * @template K * @template V */ -class A2 {} +class A2 +{ +} diff --git a/tests/PHPStan/Type/Generic/data/generic-classes-b.php b/tests/PHPStan/Type/Generic/data/generic-classes-b.php index b3d35eef17..6da6c3ab6c 100644 --- a/tests/PHPStan/Type/Generic/data/generic-classes-b.php +++ b/tests/PHPStan/Type/Generic/data/generic-classes-b.php @@ -1,12 +1,18 @@ - */ -class IImpl implements I {} +class IImpl implements I +{ +} diff --git a/tests/PHPStan/Type/Generic/data/generic-classes-c.php b/tests/PHPStan/Type/Generic/data/generic-classes-c.php index cd17cc9261..8e85fb3fe9 100644 --- a/tests/PHPStan/Type/Generic/data/generic-classes-c.php +++ b/tests/PHPStan/Type/Generic/data/generic-classes-c.php @@ -1,11 +1,15 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSuperTypeOf(): iterable - { - yield [ - new IntegerType(), - new IntegerType(), - TrinaryLogic::createYes(), - ]; + /** + * @dataProvider dataAccepts + * @param IntegerType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(IntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - yield [ - new IntegerType(), - new ConstantIntegerType(1), - TrinaryLogic::createYes(), - ]; + public function dataIsSuperTypeOf(): iterable + { + yield [ + new IntegerType(), + new IntegerType(), + TrinaryLogic::createYes(), + ]; - yield [ - new IntegerType(), - new MixedType(), - TrinaryLogic::createMaybe(), - ]; + yield [ + new IntegerType(), + new ConstantIntegerType(1), + TrinaryLogic::createYes(), + ]; - yield [ - new IntegerType(), - new UnionType([new IntegerType(), new StringType()]), - TrinaryLogic::createMaybe(), - ]; + yield [ + new IntegerType(), + new MixedType(), + TrinaryLogic::createMaybe(), + ]; - yield [ - new IntegerType(), - new StringType(), - TrinaryLogic::createNo(), - ]; - } + yield [ + new IntegerType(), + new UnionType([new IntegerType(), new StringType()]), + TrinaryLogic::createMaybe(), + ]; - /** - * @dataProvider dataIsSuperTypeOf - * @param IntegerType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(IntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + yield [ + new IntegerType(), + new StringType(), + TrinaryLogic::createNo(), + ]; + } - public function dataEquals(): array - { - return [ - [ - new IntegerType(), - new IntegerType(), - true, - ], - [ - new ConstantIntegerType(0), - new ConstantIntegerType(0), - true, - ], - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - false, - ], - [ - new IntegerType(), - new ConstantIntegerType(0), - false, - ], - [ - new ConstantIntegerType(0), - new IntegerType(), - false, - ], - [ - new IntegerType(), - new FloatType(), - false, - ], - [ - new ConstantIntegerType(0), - new ConstantFloatType(0.0), - false, - ], - [ - new ConstantIntegerType(0), - new ConstantStringType('0'), - false, - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param IntegerType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(IntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataEquals - * @param IntegerType $type - * @param Type $otherType - * @param bool $expectedResult - */ - public function testEquals(IntegerType $type, Type $otherType, bool $expectedResult): void - { - $actualResult = $type->equals($otherType); - $this->assertSame( - $expectedResult, - $actualResult, - sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataEquals(): array + { + return [ + [ + new IntegerType(), + new IntegerType(), + true, + ], + [ + new ConstantIntegerType(0), + new ConstantIntegerType(0), + true, + ], + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + false, + ], + [ + new IntegerType(), + new ConstantIntegerType(0), + false, + ], + [ + new ConstantIntegerType(0), + new IntegerType(), + false, + ], + [ + new IntegerType(), + new FloatType(), + false, + ], + [ + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + false, + ], + [ + new ConstantIntegerType(0), + new ConstantStringType('0'), + false, + ], + ]; + } + /** + * @dataProvider dataEquals + * @param IntegerType $type + * @param Type $otherType + * @param bool $expectedResult + */ + public function testEquals(IntegerType $type, Type $otherType, bool $expectedResult): void + { + $actualResult = $type->equals($otherType); + $this->assertSame( + $expectedResult, + $actualResult, + sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 634c11b0b4..3d67711323 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsCallable(): array - { - return [ - [ - new IntersectionType([ - new ConstantArrayType( - [new ConstantIntegerType(0), new ConstantIntegerType(1)], - [new ConstantStringType('Closure'), new ConstantStringType('bind')] - ), - new IterableType(new MixedType(), new ObjectType('Item')), - ]), - TrinaryLogic::createYes(), - ], - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new IterableType(new MixedType(), new ObjectType('Item')), - ]), - TrinaryLogic::createMaybe(), - ], - [ - new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('Item')), - ]), - TrinaryLogic::createMaybe(), - ], - ]; - } - - /** - * @dataProvider dataIsCallable - * @param IntersectionType $type - * @param TrinaryLogic $expectedResult - */ - public function testIsCallable(IntersectionType $type, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isCallable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSuperTypeOf(): \Iterator - { - $intersectionTypeA = new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('Item')), - ]); - - yield [ - $intersectionTypeA, - $intersectionTypeA, - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeA, - new ObjectType('ArrayObject'), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $intersectionTypeA, - new IterableType(new MixedType(), new ObjectType('Item')), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $intersectionTypeA, - new ArrayType(new MixedType(), new ObjectType('Item')), - TrinaryLogic::createNo(), - ]; - - $intersectionTypeB = new IntersectionType([ - new IntegerType(), - ]); - - yield [ - $intersectionTypeB, - $intersectionTypeB, - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - new ConstantStringType('c'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - ]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - new ConstantStringType('c'), - new ConstantStringType('d'), - new ConstantStringType('e'), - new ConstantStringType('f'), - new ConstantStringType('g'), - new ConstantStringType('h'), - new ConstantStringType('i'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - ]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\TestIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\TestIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - - yield [ - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(), new ObjectType(\stdClass::class)), - ]), - new IntersectionType([ - new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), - new IterableType(new MixedType(), new ObjectType(\stdClass::class)), - ]), - TrinaryLogic::createYes(), - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - * @param IntersectionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): \Iterator - { - $intersectionTypeA = new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('Item')), - ]); - - yield [ - $intersectionTypeA, - $intersectionTypeA, - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeA, - new ObjectType('ArrayObject'), - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeA, - new IterableType(new MixedType(), new ObjectType('Item')), - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeA, - new MixedType(), - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeA, - new IterableType(new MixedType(), new ObjectType('Unknown')), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $intersectionTypeA, - new ArrayType(new MixedType(), new ObjectType('Item')), - TrinaryLogic::createNo(), - ]; - - $intersectionTypeB = new IntersectionType([ - new IntegerType(), - ]); - - yield [ - $intersectionTypeB, - $intersectionTypeB, - TrinaryLogic::createYes(), - ]; - - $intersectionTypeC = new IntersectionType([ - new StringType(), - new CallableType(), - ]); - - yield [ - $intersectionTypeC, - $intersectionTypeC, - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeC, - new StringType(), - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeC, - new UnionType([new IntegerType(), new StringType()]), - TrinaryLogic::createYes(), - ]; - - $intersectionTypeD = new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('DatePeriod')), - ]); - - yield [ - $intersectionTypeD, - $intersectionTypeD, - TrinaryLogic::createYes(), - ]; - - yield [ - $intersectionTypeD, - new UnionType([ - $intersectionTypeD, - new IntegerType(), - ]), - TrinaryLogic::createYes(), - ]; - } - - /** - * @dataProvider dataIsSubTypeOf - * @param IntersectionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - /** - * @dataProvider dataIsSubTypeOf - * @param IntersectionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } - - public function testToBooleanCrash(): void - { - $type = new IntersectionType([new NeverType(), new NonEmptyArrayType()]); - $this->assertSame('bool', $type->toBoolean()->describe(VerbosityLevel::precise())); - } - + public function dataAccepts(): \Iterator + { + $intersectionType = new IntersectionType([ + new ObjectType('Collection'), + new IterableType(new MixedType(), new ObjectType('Item')), + ]); + + yield [ + $intersectionType, + $intersectionType, + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionType, + new ObjectType('Collection'), + TrinaryLogic::createNo(), + ]; + + yield [ + $intersectionType, + new IterableType(new MixedType(), new ObjectType('Item')), + TrinaryLogic::createNo(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(ClassWithToString::class), + new HasPropertyType('foo'), + ]), + new StringType(), + TrinaryLogic::createNo(), + ]; + + yield [ + TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new CallableType()), + new CallableType(), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataAccepts + * @param IntersectionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsCallable(): array + { + return [ + [ + new IntersectionType([ + new ConstantArrayType( + [new ConstantIntegerType(0), new ConstantIntegerType(1)], + [new ConstantStringType('Closure'), new ConstantStringType('bind')] + ), + new IterableType(new MixedType(), new ObjectType('Item')), + ]), + TrinaryLogic::createYes(), + ], + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new IterableType(new MixedType(), new ObjectType('Item')), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('Item')), + ]), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataIsCallable + * @param IntersectionType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsCallable(IntersectionType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isCallable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsSuperTypeOf(): \Iterator + { + $intersectionTypeA = new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('Item')), + ]); + + yield [ + $intersectionTypeA, + $intersectionTypeA, + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeA, + new ObjectType('ArrayObject'), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $intersectionTypeA, + new IterableType(new MixedType(), new ObjectType('Item')), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $intersectionTypeA, + new ArrayType(new MixedType(), new ObjectType('Item')), + TrinaryLogic::createNo(), + ]; + + $intersectionTypeB = new IntersectionType([ + new IntegerType(), + ]); + + yield [ + $intersectionTypeB, + $intersectionTypeB, + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new StringType()), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + new ConstantStringType('c'), + ], [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new StringType()), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + new ConstantStringType('c'), + new ConstantStringType('d'), + new ConstantStringType('e'), + new ConstantStringType('f'), + new ConstantStringType('g'), + new ConstantStringType('h'), + new ConstantStringType('i'), + ], [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\TestIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\TestIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(true), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + + yield [ + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(), new ObjectType(\stdClass::class)), + ]), + new IntersectionType([ + new ObjectType(\DoctrineIntersectionTypeIsSupertypeOf\Collection::class), + new IterableType(new MixedType(), new ObjectType(\stdClass::class)), + ]), + TrinaryLogic::createYes(), + ]; + } + + /** + * @dataProvider dataIsSuperTypeOf + * @param IntersectionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsSubTypeOf(): \Iterator + { + $intersectionTypeA = new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('Item')), + ]); + + yield [ + $intersectionTypeA, + $intersectionTypeA, + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeA, + new ObjectType('ArrayObject'), + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeA, + new IterableType(new MixedType(), new ObjectType('Item')), + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeA, + new MixedType(), + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeA, + new IterableType(new MixedType(), new ObjectType('Unknown')), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $intersectionTypeA, + new ArrayType(new MixedType(), new ObjectType('Item')), + TrinaryLogic::createNo(), + ]; + + $intersectionTypeB = new IntersectionType([ + new IntegerType(), + ]); + + yield [ + $intersectionTypeB, + $intersectionTypeB, + TrinaryLogic::createYes(), + ]; + + $intersectionTypeC = new IntersectionType([ + new StringType(), + new CallableType(), + ]); + + yield [ + $intersectionTypeC, + $intersectionTypeC, + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeC, + new StringType(), + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeC, + new UnionType([new IntegerType(), new StringType()]), + TrinaryLogic::createYes(), + ]; + + $intersectionTypeD = new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('DatePeriod')), + ]); + + yield [ + $intersectionTypeD, + $intersectionTypeD, + TrinaryLogic::createYes(), + ]; + + yield [ + $intersectionTypeD, + new UnionType([ + $intersectionTypeD, + new IntegerType(), + ]), + TrinaryLogic::createYes(), + ]; + } + + /** + * @dataProvider dataIsSubTypeOf + * @param IntersectionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + /** + * @dataProvider dataIsSubTypeOf + * @param IntersectionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } + + public function testToBooleanCrash(): void + { + $type = new IntersectionType([new NeverType(), new NonEmptyArrayType()]); + $this->assertSame('bool', $type->toBoolean()->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Type/IterableTypeTest.php b/tests/PHPStan/Type/IterableTypeTest.php index 7aaf86149c..8189a8c703 100644 --- a/tests/PHPStan/Type/IterableTypeTest.php +++ b/tests/PHPStan/Type/IterableTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): array - { - return [ - [ - new IterableType(new MixedType(), new StringType()), - new IterableType(new MixedType(), new StringType()), - TrinaryLogic::createYes(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new ObjectType('Unknown'), - TrinaryLogic::createMaybe(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new IntegerType(), - TrinaryLogic::createNo(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new IterableType(new MixedType(), new IntegerType()), - TrinaryLogic::createNo(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new IterableType(new MixedType(), new StringType()), new NullType()]), - TrinaryLogic::createYes(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new ArrayType(new MixedType(), new MixedType()), new ObjectType('Traversable')]), - TrinaryLogic::createYes(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new ArrayType(new MixedType(), new StringType()), new ObjectType('Traversable')]), - TrinaryLogic::createYes(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new ObjectType('Unknown'), new NullType()]), - TrinaryLogic::createMaybe(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new IntegerType(), new NullType()]), - TrinaryLogic::createNo(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new UnionType([new IterableType(new MixedType(), new IntegerType()), new NullType()]), - TrinaryLogic::createNo(), - ], - [ - new IterableType(new IntegerType(), new StringType()), - new IterableType(new MixedType(), new StringType()), - TrinaryLogic::createYes(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new IterableType(new IntegerType(), new StringType()), - TrinaryLogic::createMaybe(), - ], - [ - new IterableType(new StringType(), new StringType()), - new IterableType(new IntegerType(), new StringType()), - TrinaryLogic::createNo(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new HasMethodType('foo'), - TrinaryLogic::createMaybe(), - ], - [ - new IterableType(new MixedType(), new StringType()), - new HasPropertyType('foo'), - TrinaryLogic::createMaybe(), - ], - [ - new IterableType(new MixedType(true), new StringType()), - new ObjectType('Iterator'), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param IterableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(IterableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSubTypeOf - * @param IterableType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(IterableType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSubTypeOf(): array + { + return [ + [ + new IterableType(new MixedType(), new StringType()), + new IterableType(new MixedType(), new StringType()), + TrinaryLogic::createYes(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new ObjectType('Unknown'), + TrinaryLogic::createMaybe(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new IntegerType(), + TrinaryLogic::createNo(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new IterableType(new MixedType(), new IntegerType()), + TrinaryLogic::createNo(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new IterableType(new MixedType(), new StringType()), new NullType()]), + TrinaryLogic::createYes(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new ArrayType(new MixedType(), new MixedType()), new ObjectType('Traversable')]), + TrinaryLogic::createYes(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new ArrayType(new MixedType(), new StringType()), new ObjectType('Traversable')]), + TrinaryLogic::createYes(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new ObjectType('Unknown'), new NullType()]), + TrinaryLogic::createMaybe(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new IntegerType(), new NullType()]), + TrinaryLogic::createNo(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new UnionType([new IterableType(new MixedType(), new IntegerType()), new NullType()]), + TrinaryLogic::createNo(), + ], + [ + new IterableType(new IntegerType(), new StringType()), + new IterableType(new MixedType(), new StringType()), + TrinaryLogic::createYes(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new IterableType(new IntegerType(), new StringType()), + TrinaryLogic::createMaybe(), + ], + [ + new IterableType(new StringType(), new StringType()), + new IterableType(new IntegerType(), new StringType()), + TrinaryLogic::createNo(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new HasMethodType('foo'), + TrinaryLogic::createMaybe(), + ], + [ + new IterableType(new MixedType(), new StringType()), + new HasPropertyType('foo'), + TrinaryLogic::createMaybe(), + ], + [ + new IterableType(new MixedType(true), new StringType()), + new ObjectType('Iterator'), + TrinaryLogic::createMaybe(), + ], + ]; + } - /** - * @dataProvider dataIsSubTypeOf - * @param IterableType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(IterableType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } + /** + * @dataProvider dataIsSubTypeOf + * @param IterableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(IterableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - public function dataInferTemplateTypes(): array - { - $templateType = static function (string $name): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - $name, - new MixedType(), - TemplateTypeVariance::createInvariant() - ); - }; + /** + * @dataProvider dataIsSubTypeOf + * @param IterableType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(IterableType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } - return [ - 'receive iterable' => [ - new IterableType( - new MixedType(), - new ObjectType('DateTime') - ), - new IterableType( - new MixedType(), - $templateType('T') - ), - ['T' => 'DateTime'], - ], - 'receive iterable template key' => [ - new IterableType( - new StringType(), - new ObjectType('DateTime') - ), - new IterableType( - $templateType('U'), - $templateType('T') - ), - ['U' => 'string', 'T' => 'DateTime'], - ], - 'receive mixed' => [ - new MixedType(), - new IterableType( - new MixedType(), - $templateType('T') - ), - [], - ], - 'receive non-accepted' => [ - new StringType(), - new IterableType( - new MixedType(), - $templateType('T') - ), - [], - ], - ]; - } + public function dataInferTemplateTypes(): array + { + $templateType = static function (string $name): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + $name, + new MixedType(), + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); + return [ + 'receive iterable' => [ + new IterableType( + new MixedType(), + new ObjectType('DateTime') + ), + new IterableType( + new MixedType(), + $templateType('T') + ), + ['T' => 'DateTime'], + ], + 'receive iterable template key' => [ + new IterableType( + new StringType(), + new ObjectType('DateTime') + ), + new IterableType( + $templateType('U'), + $templateType('T') + ), + ['U' => 'string', 'T' => 'DateTime'], + ], + 'receive mixed' => [ + new MixedType(), + new IterableType( + new MixedType(), + $templateType('T') + ), + [], + ], + 'receive non-accepted' => [ + new StringType(), + new IterableType( + new MixedType(), + $templateType('T') + ), + [], + ], + ]; + } - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); - public function dataDescribe(): array - { - $templateType = TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ); + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } - return [ - [ - new IterableType(new IntegerType(), new StringType()), - 'iterable', - ], - [ - new IterableType(new MixedType(), new StringType()), - 'iterable', - ], - [ - new IterableType(new MixedType(), new MixedType()), - 'iterable', - ], - [ - new IterableType($templateType, new MixedType()), - 'iterable', - ], - [ - new IterableType(new MixedType(), $templateType), - 'iterable', - ], - [ - new IterableType($templateType, $templateType), - 'iterable', - ], - ]; - } + public function dataDescribe(): array + { + $templateType = TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ); - /** - * @dataProvider dataDescribe - */ - public function testDescribe(Type $type, string $expect): void - { - $result = $type->describe(VerbosityLevel::typeOnly()); + return [ + [ + new IterableType(new IntegerType(), new StringType()), + 'iterable', + ], + [ + new IterableType(new MixedType(), new StringType()), + 'iterable', + ], + [ + new IterableType(new MixedType(), new MixedType()), + 'iterable', + ], + [ + new IterableType($templateType, new MixedType()), + 'iterable', + ], + [ + new IterableType(new MixedType(), $templateType), + 'iterable', + ], + [ + new IterableType($templateType, $templateType), + 'iterable', + ], + ]; + } - $this->assertSame($expect, $result); - } + /** + * @dataProvider dataDescribe + */ + public function testDescribe(Type $type, string $expect): void + { + $result = $type->describe(VerbosityLevel::typeOnly()); - public function dataAccepts(): array - { - /** @var TemplateMixedType $t */ - $t = TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ); - return [ - [ - new IterableType( - new MixedType(), - $t - ), - new ConstantArrayType([], []), - TrinaryLogic::createYes(), - ], - [ - new IterableType( - new MixedType(), - $t->toArgument() - ), - new ConstantArrayType([], []), - TrinaryLogic::createYes(), - ], - ]; - } + $this->assertSame($expect, $result); + } - /** - * @dataProvider dataAccepts - * @param IterableType $iterableType - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts(IterableType $iterableType, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $iterableType->accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $iterableType->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataAccepts(): array + { + /** @var TemplateMixedType $t */ + $t = TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ); + return [ + [ + new IterableType( + new MixedType(), + $t + ), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ], + [ + new IterableType( + new MixedType(), + $t->toArgument() + ), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ], + ]; + } + /** + * @dataProvider dataAccepts + * @param IterableType $iterableType + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(IterableType $iterableType, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $iterableType->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $iterableType->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index acd24ecad6..5358e777a7 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -1,4 +1,6 @@ - [ + new MixedType(), + new MixedType(), + TrinaryLogic::createYes(), + ], + 1 => [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createYes(), + ], + 2 => [ + new MixedType(false, new IntegerType()), + new IntegerType(), + TrinaryLogic::createNo(), + ], + 3 => [ + new MixedType(false, new IntegerType()), + new ConstantIntegerType(1), + TrinaryLogic::createNo(), + ], + 4 => [ + new MixedType(false, new ConstantIntegerType(1)), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + 5 => [ + new MixedType(false, new ConstantIntegerType(1)), + new MixedType(), + TrinaryLogic::createMaybe(), + ], + 6 => [ + new MixedType(), + new MixedType(false, new ConstantIntegerType(1)), + TrinaryLogic::createYes(), + ], + 7 => [ + new MixedType(false, new ConstantIntegerType(1)), + new MixedType(false, new ConstantIntegerType(1)), + TrinaryLogic::createYes(), + ], + 8 => [ + new MixedType(false, new IntegerType()), + new MixedType(false, new ConstantIntegerType(1)), + TrinaryLogic::createMaybe(), + ], + 9 => [ + new MixedType(false, new ConstantIntegerType(1)), + new MixedType(false, new IntegerType()), + TrinaryLogic::createYes(), + ], + 10 => [ + new MixedType(false, new StringType()), + new MixedType(false, new IntegerType()), + TrinaryLogic::createMaybe(), + ], + 11 => [ + new MixedType(), + new ObjectWithoutClassType(), + TrinaryLogic::createYes(), + ], + 12 => [ + new MixedType(false, new ObjectWithoutClassType()), + new ObjectWithoutClassType(), + TrinaryLogic::createNo(), + ], + 13 => [ + new MixedType(false, new ObjectType('Exception')), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), + ], + 14 => [ + new MixedType(false, new ObjectType('Exception')), + new ObjectWithoutClassType(new ObjectType('Exception')), + TrinaryLogic::createYes(), + ], + 15 => [ + new MixedType(false, new ObjectType('Exception')), + new ObjectWithoutClassType(new ObjectType('InvalidArgumentException')), + TrinaryLogic::createMaybe(), + ], + 16 => [ + new MixedType(false, new ObjectType('InvalidArgumentException')), + new ObjectWithoutClassType(new ObjectType('Exception')), + TrinaryLogic::createYes(), + ], + 17 => [ + new MixedType(false, new ObjectType('Exception')), + new ObjectType('Exception'), + TrinaryLogic::createNo(), + ], + 18 => [ + new MixedType(false, new ObjectType('InvalidArgumentException')), + new ObjectType('Exception'), + TrinaryLogic::createMaybe(), + ], + 19 => [ + new MixedType(false, new ObjectType('Exception')), + new ObjectType('InvalidArgumentException'), + TrinaryLogic::createNo(), + ], + 20 => [ + new MixedType(false, new ObjectType('Exception')), + new MixedType(), + TrinaryLogic::createMaybe(), + ], + 21 => [ + new MixedType(false, new ObjectType('Exception')), + new MixedType(false, new ObjectType('stdClass')), + TrinaryLogic::createMaybe(), + ], + 22 => [ + new MixedType(), + new NeverType(), + TrinaryLogic::createYes(), + ], + 23 => [ + new MixedType(false, new NullType()), + new NeverType(), + TrinaryLogic::createYes(), + ], + 24 => [ + new MixedType(), + new UnionType([new StringType(), new IntegerType()]), + TrinaryLogic::createYes(), + ], + 25 => [ + new MixedType(false, new NullType()), + new UnionType([new StringType(), new IntegerType()]), + TrinaryLogic::createYes(), + ], + ]; + } - public function dataIsSuperTypeOf(): array - { - return [ - 0 => [ - new MixedType(), - new MixedType(), - TrinaryLogic::createYes(), - ], - 1 => [ - new MixedType(), - new IntegerType(), - TrinaryLogic::createYes(), - ], - 2 => [ - new MixedType(false, new IntegerType()), - new IntegerType(), - TrinaryLogic::createNo(), - ], - 3 => [ - new MixedType(false, new IntegerType()), - new ConstantIntegerType(1), - TrinaryLogic::createNo(), - ], - 4 => [ - new MixedType(false, new ConstantIntegerType(1)), - new IntegerType(), - TrinaryLogic::createMaybe(), - ], - 5 => [ - new MixedType(false, new ConstantIntegerType(1)), - new MixedType(), - TrinaryLogic::createMaybe(), - ], - 6 => [ - new MixedType(), - new MixedType(false, new ConstantIntegerType(1)), - TrinaryLogic::createYes(), - ], - 7 => [ - new MixedType(false, new ConstantIntegerType(1)), - new MixedType(false, new ConstantIntegerType(1)), - TrinaryLogic::createYes(), - ], - 8 => [ - new MixedType(false, new IntegerType()), - new MixedType(false, new ConstantIntegerType(1)), - TrinaryLogic::createMaybe(), - ], - 9 => [ - new MixedType(false, new ConstantIntegerType(1)), - new MixedType(false, new IntegerType()), - TrinaryLogic::createYes(), - ], - 10 => [ - new MixedType(false, new StringType()), - new MixedType(false, new IntegerType()), - TrinaryLogic::createMaybe(), - ], - 11 => [ - new MixedType(), - new ObjectWithoutClassType(), - TrinaryLogic::createYes(), - ], - 12 => [ - new MixedType(false, new ObjectWithoutClassType()), - new ObjectWithoutClassType(), - TrinaryLogic::createNo(), - ], - 13 => [ - new MixedType(false, new ObjectType('Exception')), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), - ], - 14 => [ - new MixedType(false, new ObjectType('Exception')), - new ObjectWithoutClassType(new ObjectType('Exception')), - TrinaryLogic::createYes(), - ], - 15 => [ - new MixedType(false, new ObjectType('Exception')), - new ObjectWithoutClassType(new ObjectType('InvalidArgumentException')), - TrinaryLogic::createMaybe(), - ], - 16 => [ - new MixedType(false, new ObjectType('InvalidArgumentException')), - new ObjectWithoutClassType(new ObjectType('Exception')), - TrinaryLogic::createYes(), - ], - 17 => [ - new MixedType(false, new ObjectType('Exception')), - new ObjectType('Exception'), - TrinaryLogic::createNo(), - ], - 18 => [ - new MixedType(false, new ObjectType('InvalidArgumentException')), - new ObjectType('Exception'), - TrinaryLogic::createMaybe(), - ], - 19 => [ - new MixedType(false, new ObjectType('Exception')), - new ObjectType('InvalidArgumentException'), - TrinaryLogic::createNo(), - ], - 20 => [ - new MixedType(false, new ObjectType('Exception')), - new MixedType(), - TrinaryLogic::createMaybe(), - ], - 21 => [ - new MixedType(false, new ObjectType('Exception')), - new MixedType(false, new ObjectType('stdClass')), - TrinaryLogic::createMaybe(), - ], - 22 => [ - new MixedType(), - new NeverType(), - TrinaryLogic::createYes(), - ], - 23 => [ - new MixedType(false, new NullType()), - new NeverType(), - TrinaryLogic::createYes(), - ], - 24 => [ - new MixedType(), - new UnionType([new StringType(), new IntegerType()]), - TrinaryLogic::createYes(), - ], - 25 => [ - new MixedType(false, new NullType()), - new UnionType([new StringType(), new IntegerType()]), - TrinaryLogic::createYes(), - ], - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - * @param \PHPStan\Type\MixedType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(MixedType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - + /** + * @dataProvider dataIsSuperTypeOf + * @param \PHPStan\Type\MixedType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(MixedType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index 848e97b029..d2e618881b 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -1,4 +1,6 @@ -isIterable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isIterable()', $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsCallable(): array - { - return [ - [new ObjectType('Closure'), TrinaryLogic::createYes()], - [new ObjectType('Unknown'), TrinaryLogic::createMaybe()], - [new ObjectType('DateTime'), TrinaryLogic::createMaybe()], - ]; - } + /** + * @dataProvider dataIsIterable + * @param ObjectType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsIterable(ObjectType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isIterable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isIterable()', $type->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsCallable - * @param ObjectType $type - * @param TrinaryLogic $expectedResult - */ - public function testIsCallable(ObjectType $type, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isCallable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) - ); - } + public function dataIsCallable(): array + { + return [ + [new ObjectType('Closure'), TrinaryLogic::createYes()], + [new ObjectType('Unknown'), TrinaryLogic::createMaybe()], + [new ObjectType('DateTime'), TrinaryLogic::createMaybe()], + ]; + } - public function dataIsSuperTypeOf(): array - { - return [ - 0 => [ - new ObjectType('UnknownClassA'), - new ObjectType('UnknownClassB'), - TrinaryLogic::createMaybe(), - ], - 1 => [ - new ObjectType(\ArrayAccess::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 2 => [ - new ObjectType(\Countable::class), - new ObjectType(\Countable::class), - TrinaryLogic::createYes(), - ], - 3 => [ - new ObjectType(\DateTimeImmutable::class), - new ObjectType(\DateTimeImmutable::class), - TrinaryLogic::createYes(), - ], - 4 => [ - new ObjectType(\Traversable::class), - new ObjectType(\ArrayObject::class), - TrinaryLogic::createYes(), - ], - 5 => [ - new ObjectType(\Traversable::class), - new ObjectType(\Iterator::class), - TrinaryLogic::createYes(), - ], - 6 => [ - new ObjectType(\ArrayObject::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 7 => [ - new ObjectType(\Iterator::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 8 => [ - new ObjectType(\ArrayObject::class), - new ObjectType(\DateTimeImmutable::class), - TrinaryLogic::createNo(), - ], - 9 => [ - new ObjectType(\DateTimeImmutable::class), - new UnionType([ - new ObjectType(\DateTimeImmutable::class), - new StringType(), - ]), - TrinaryLogic::createMaybe(), - ], - 10 => [ - new ObjectType(\DateTimeImmutable::class), - new UnionType([ - new ObjectType(\ArrayObject::class), - new StringType(), - ]), - TrinaryLogic::createNo(), - ], - 11 => [ - new ObjectType(\LogicException::class), - new ObjectType(\InvalidArgumentException::class), - TrinaryLogic::createYes(), - ], - 12 => [ - new ObjectType(\InvalidArgumentException::class), - new ObjectType(\LogicException::class), - TrinaryLogic::createMaybe(), - ], - 13 => [ - new ObjectType(\ArrayAccess::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 14 => [ - new ObjectType(\Countable::class), - new StaticType(\Countable::class), - TrinaryLogic::createYes(), - ], - 15 => [ - new ObjectType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), - TrinaryLogic::createYes(), - ], - 16 => [ - new ObjectType(\Traversable::class), - new StaticType(\ArrayObject::class), - TrinaryLogic::createYes(), - ], - 17 => [ - new ObjectType(\Traversable::class), - new StaticType(\Iterator::class), - TrinaryLogic::createYes(), - ], - 18 => [ - new ObjectType(\ArrayObject::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 19 => [ - new ObjectType(\Iterator::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 20 => [ - new ObjectType(\ArrayObject::class), - new StaticType(\DateTimeImmutable::class), - TrinaryLogic::createNo(), - ], - 21 => [ - new ObjectType(\DateTimeImmutable::class), - new UnionType([ - new StaticType(\DateTimeImmutable::class), - new StringType(), - ]), - TrinaryLogic::createMaybe(), - ], - 22 => [ - new ObjectType(\DateTimeImmutable::class), - new UnionType([ - new StaticType(\ArrayObject::class), - new StringType(), - ]), - TrinaryLogic::createNo(), - ], - 23 => [ - new ObjectType(\LogicException::class), - new StaticType(\InvalidArgumentException::class), - TrinaryLogic::createYes(), - ], - 24 => [ - new ObjectType(\InvalidArgumentException::class), - new StaticType(\LogicException::class), - TrinaryLogic::createMaybe(), - ], - 25 => [ - new ObjectType(\stdClass::class), - new ClosureType([], new MixedType(), false), - TrinaryLogic::createNo(), - ], - 26 => [ - new ObjectType(\Closure::class), - new ClosureType([], new MixedType(), false), - TrinaryLogic::createYes(), - ], - 27 => [ - new ObjectType(\Countable::class), - new IterableType(new MixedType(), new MixedType()), - TrinaryLogic::createMaybe(), - ], - 28 => [ - new ObjectType(\DateTimeImmutable::class), - new HasMethodType('format'), - TrinaryLogic::createMaybe(), - ], - 29 => [ - new ObjectType(\Closure::class), - new HasMethodType('format'), - TrinaryLogic::createNo(), - ], - 30 => [ - new ObjectType(\DateTimeImmutable::class), - new UnionType([ - new HasMethodType('format'), - new HasMethodType('getTimestamp'), - ]), - TrinaryLogic::createMaybe(), - ], - 31 => [ - new ObjectType(\DateInterval::class), - new HasPropertyType('d'), - TrinaryLogic::createMaybe(), - ], - 32 => [ - new ObjectType(\Closure::class), - new HasPropertyType('d'), - TrinaryLogic::createNo(), - ], - 33 => [ - new ObjectType(\DateInterval::class), - new UnionType([ - new HasPropertyType('d'), - new HasPropertyType('m'), - ]), - TrinaryLogic::createMaybe(), - ], - 34 => [ - new ObjectType('Exception'), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), - ], - 35 => [ - new ObjectType('Exception'), - new ObjectWithoutClassType(new ObjectType('Exception')), - TrinaryLogic::createNo(), - ], - 36 => [ - new ObjectType('Exception'), - new ObjectWithoutClassType(new ObjectType(\InvalidArgumentException::class)), - TrinaryLogic::createMaybe(), - ], - 37 => [ - new ObjectType(\InvalidArgumentException::class), - new ObjectWithoutClassType(new ObjectType('Exception')), - TrinaryLogic::createNo(), - ], - 38 => [ - new ObjectType(\Throwable::class, new ObjectType(\InvalidArgumentException::class)), - new ObjectType(\InvalidArgumentException::class), - TrinaryLogic::createNo(), - ], - 39 => [ - new ObjectType(\Throwable::class, new ObjectType(\InvalidArgumentException::class)), - new ObjectType('Exception'), - TrinaryLogic::createYes(), - ], - 40 => [ - new ObjectType(\Throwable::class, new ObjectType('Exception')), - new ObjectType(\InvalidArgumentException::class), - TrinaryLogic::createNo(), - ], - 41 => [ - new ObjectType(\Throwable::class, new ObjectType('Exception')), - new ObjectType('Exception'), - TrinaryLogic::createNo(), - ], - 42 => [ - new ObjectType(\Throwable::class, new ObjectType('Exception')), - new ObjectType(\Throwable::class), - TrinaryLogic::createYes(), - ], - 43 => [ - new ObjectType(\Throwable::class), - new ObjectType(\Throwable::class, new ObjectType('Exception')), - TrinaryLogic::createYes(), - ], - 44 => [ - new ObjectType(\Throwable::class), - new ObjectType(\Throwable::class, new ObjectType('Exception')), - TrinaryLogic::createYes(), - ], - 45 => [ - new ObjectType('Exception'), - new ObjectType(\Throwable::class, new ObjectType('Exception')), - TrinaryLogic::createNo(), - ], - 46 => [ - new ObjectType(\DateTimeInterface::class), - TemplateTypeFactory::create( - TemplateTypeScope::createWithClass(\DateTimeInterface::class), - 'T', - new ObjectType(\DateTimeInterface::class), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createYes(), - ], - 47 => [ - new ObjectType(\DateTimeInterface::class), - TemplateTypeFactory::create( - TemplateTypeScope::createWithClass(\DateTime::class), - 'T', - new ObjectType(\DateTime::class), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createYes(), - ], - 48 => [ - new ObjectType(\DateTime::class), - TemplateTypeFactory::create( - TemplateTypeScope::createWithClass(\DateTimeInterface::class), - 'T', - new ObjectType(\DateTimeInterface::class), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsCallable + * @param ObjectType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsCallable(ObjectType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isCallable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSuperTypeOf - * @param ObjectType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(ObjectType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSuperTypeOf(): array + { + return [ + 0 => [ + new ObjectType('UnknownClassA'), + new ObjectType('UnknownClassB'), + TrinaryLogic::createMaybe(), + ], + 1 => [ + new ObjectType(\ArrayAccess::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 2 => [ + new ObjectType(\Countable::class), + new ObjectType(\Countable::class), + TrinaryLogic::createYes(), + ], + 3 => [ + new ObjectType(\DateTimeImmutable::class), + new ObjectType(\DateTimeImmutable::class), + TrinaryLogic::createYes(), + ], + 4 => [ + new ObjectType(\Traversable::class), + new ObjectType(\ArrayObject::class), + TrinaryLogic::createYes(), + ], + 5 => [ + new ObjectType(\Traversable::class), + new ObjectType(\Iterator::class), + TrinaryLogic::createYes(), + ], + 6 => [ + new ObjectType(\ArrayObject::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 7 => [ + new ObjectType(\Iterator::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 8 => [ + new ObjectType(\ArrayObject::class), + new ObjectType(\DateTimeImmutable::class), + TrinaryLogic::createNo(), + ], + 9 => [ + new ObjectType(\DateTimeImmutable::class), + new UnionType([ + new ObjectType(\DateTimeImmutable::class), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + 10 => [ + new ObjectType(\DateTimeImmutable::class), + new UnionType([ + new ObjectType(\ArrayObject::class), + new StringType(), + ]), + TrinaryLogic::createNo(), + ], + 11 => [ + new ObjectType(\LogicException::class), + new ObjectType(\InvalidArgumentException::class), + TrinaryLogic::createYes(), + ], + 12 => [ + new ObjectType(\InvalidArgumentException::class), + new ObjectType(\LogicException::class), + TrinaryLogic::createMaybe(), + ], + 13 => [ + new ObjectType(\ArrayAccess::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 14 => [ + new ObjectType(\Countable::class), + new StaticType(\Countable::class), + TrinaryLogic::createYes(), + ], + 15 => [ + new ObjectType(\DateTimeImmutable::class), + new StaticType(\DateTimeImmutable::class), + TrinaryLogic::createYes(), + ], + 16 => [ + new ObjectType(\Traversable::class), + new StaticType(\ArrayObject::class), + TrinaryLogic::createYes(), + ], + 17 => [ + new ObjectType(\Traversable::class), + new StaticType(\Iterator::class), + TrinaryLogic::createYes(), + ], + 18 => [ + new ObjectType(\ArrayObject::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 19 => [ + new ObjectType(\Iterator::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 20 => [ + new ObjectType(\ArrayObject::class), + new StaticType(\DateTimeImmutable::class), + TrinaryLogic::createNo(), + ], + 21 => [ + new ObjectType(\DateTimeImmutable::class), + new UnionType([ + new StaticType(\DateTimeImmutable::class), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + 22 => [ + new ObjectType(\DateTimeImmutable::class), + new UnionType([ + new StaticType(\ArrayObject::class), + new StringType(), + ]), + TrinaryLogic::createNo(), + ], + 23 => [ + new ObjectType(\LogicException::class), + new StaticType(\InvalidArgumentException::class), + TrinaryLogic::createYes(), + ], + 24 => [ + new ObjectType(\InvalidArgumentException::class), + new StaticType(\LogicException::class), + TrinaryLogic::createMaybe(), + ], + 25 => [ + new ObjectType(\stdClass::class), + new ClosureType([], new MixedType(), false), + TrinaryLogic::createNo(), + ], + 26 => [ + new ObjectType(\Closure::class), + new ClosureType([], new MixedType(), false), + TrinaryLogic::createYes(), + ], + 27 => [ + new ObjectType(\Countable::class), + new IterableType(new MixedType(), new MixedType()), + TrinaryLogic::createMaybe(), + ], + 28 => [ + new ObjectType(\DateTimeImmutable::class), + new HasMethodType('format'), + TrinaryLogic::createMaybe(), + ], + 29 => [ + new ObjectType(\Closure::class), + new HasMethodType('format'), + TrinaryLogic::createNo(), + ], + 30 => [ + new ObjectType(\DateTimeImmutable::class), + new UnionType([ + new HasMethodType('format'), + new HasMethodType('getTimestamp'), + ]), + TrinaryLogic::createMaybe(), + ], + 31 => [ + new ObjectType(\DateInterval::class), + new HasPropertyType('d'), + TrinaryLogic::createMaybe(), + ], + 32 => [ + new ObjectType(\Closure::class), + new HasPropertyType('d'), + TrinaryLogic::createNo(), + ], + 33 => [ + new ObjectType(\DateInterval::class), + new UnionType([ + new HasPropertyType('d'), + new HasPropertyType('m'), + ]), + TrinaryLogic::createMaybe(), + ], + 34 => [ + new ObjectType('Exception'), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), + ], + 35 => [ + new ObjectType('Exception'), + new ObjectWithoutClassType(new ObjectType('Exception')), + TrinaryLogic::createNo(), + ], + 36 => [ + new ObjectType('Exception'), + new ObjectWithoutClassType(new ObjectType(\InvalidArgumentException::class)), + TrinaryLogic::createMaybe(), + ], + 37 => [ + new ObjectType(\InvalidArgumentException::class), + new ObjectWithoutClassType(new ObjectType('Exception')), + TrinaryLogic::createNo(), + ], + 38 => [ + new ObjectType(\Throwable::class, new ObjectType(\InvalidArgumentException::class)), + new ObjectType(\InvalidArgumentException::class), + TrinaryLogic::createNo(), + ], + 39 => [ + new ObjectType(\Throwable::class, new ObjectType(\InvalidArgumentException::class)), + new ObjectType('Exception'), + TrinaryLogic::createYes(), + ], + 40 => [ + new ObjectType(\Throwable::class, new ObjectType('Exception')), + new ObjectType(\InvalidArgumentException::class), + TrinaryLogic::createNo(), + ], + 41 => [ + new ObjectType(\Throwable::class, new ObjectType('Exception')), + new ObjectType('Exception'), + TrinaryLogic::createNo(), + ], + 42 => [ + new ObjectType(\Throwable::class, new ObjectType('Exception')), + new ObjectType(\Throwable::class), + TrinaryLogic::createYes(), + ], + 43 => [ + new ObjectType(\Throwable::class), + new ObjectType(\Throwable::class, new ObjectType('Exception')), + TrinaryLogic::createYes(), + ], + 44 => [ + new ObjectType(\Throwable::class), + new ObjectType(\Throwable::class, new ObjectType('Exception')), + TrinaryLogic::createYes(), + ], + 45 => [ + new ObjectType('Exception'), + new ObjectType(\Throwable::class, new ObjectType('Exception')), + TrinaryLogic::createNo(), + ], + 46 => [ + new ObjectType(\DateTimeInterface::class), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass(\DateTimeInterface::class), + 'T', + new ObjectType(\DateTimeInterface::class), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createYes(), + ], + 47 => [ + new ObjectType(\DateTimeInterface::class), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass(\DateTime::class), + 'T', + new ObjectType(\DateTime::class), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createYes(), + ], + 48 => [ + new ObjectType(\DateTime::class), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass(\DateTimeInterface::class), + 'T', + new ObjectType(\DateTimeInterface::class), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createMaybe(), + ], + ]; + } - public function dataAccepts(): array - { - return [ - [ - new ObjectType(\SimpleXMLElement::class), - new IntegerType(), - TrinaryLogic::createNo(), - ], - [ - new ObjectType(\SimpleXMLElement::class), - new ConstantStringType('foo'), - TrinaryLogic::createNo(), - ], - [ - new ObjectType(\Traversable::class), - new GenericObjectType(\Traversable::class, [new MixedType(true), new ObjectType('DateTimeInteface')]), - TrinaryLogic::createYes(), - ], - [ - new ObjectType(\DateTimeInterface::class), - TemplateTypeFactory::create( - TemplateTypeScope::createWithClass(\DateTimeInterface::class), - 'T', - new ObjectType(\DateTimeInterface::class), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createYes(), - ], - [ - new ObjectType(\DateTime::class), - TemplateTypeFactory::create( - TemplateTypeScope::createWithClass(\DateTimeInterface::class), - 'T', - new ObjectType(\DateTimeInterface::class), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createMaybe(), - ], - ]; - } + /** + * @dataProvider dataIsSuperTypeOf + * @param ObjectType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(ObjectType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataAccepts - * @param \PHPStan\Type\ObjectType $type - * @param Type $acceptedType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts( - ObjectType $type, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $this->assertSame( - $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } + public function dataAccepts(): array + { + return [ + [ + new ObjectType(\SimpleXMLElement::class), + new IntegerType(), + TrinaryLogic::createNo(), + ], + [ + new ObjectType(\SimpleXMLElement::class), + new ConstantStringType('foo'), + TrinaryLogic::createNo(), + ], + [ + new ObjectType(\Traversable::class), + new GenericObjectType(\Traversable::class, [new MixedType(true), new ObjectType('DateTimeInteface')]), + TrinaryLogic::createYes(), + ], + [ + new ObjectType(\DateTimeInterface::class), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass(\DateTimeInterface::class), + 'T', + new ObjectType(\DateTimeInterface::class), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createYes(), + ], + [ + new ObjectType(\DateTime::class), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass(\DateTimeInterface::class), + 'T', + new ObjectType(\DateTimeInterface::class), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createMaybe(), + ], + ]; + } - public function testGetClassReflectionOfGenericClass(): void - { - $objectType = new ObjectType(\Traversable::class); - $classReflection = $objectType->getClassReflection(); - $this->assertNotNull($classReflection); - $this->assertSame('Traversable', $classReflection->getDisplayName()); - } + /** + * @dataProvider dataAccepts + * @param \PHPStan\Type\ObjectType $type + * @param Type $acceptedType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts( + ObjectType $type, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $this->assertSame( + $expectedResult->describe(), + $type->accepts($acceptedType, true)->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } - public function dataHasOffsetValueType(): array - { - return [ - [ - new ObjectType(\stdClass::class), - new IntegerType(), - TrinaryLogic::createMaybe(), - ], - [ - new ObjectType(\Generator::class), - new IntegerType(), - TrinaryLogic::createNo(), - ], - [ - new ObjectType(\ArrayAccess::class), - new IntegerType(), - TrinaryLogic::createMaybe(), - ], - [ - new ObjectType(\Countable::class), - new IntegerType(), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), - new IntegerType(), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), - new MixedType(), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), - new StringType(), - TrinaryLogic::createNo(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTimeInterface::class), new MixedType()]), - new ObjectType(\DateTime::class), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTime::class), new MixedType()]), - new ObjectType(\DateTimeInterface::class), - TrinaryLogic::createMaybe(), - ], - [ - new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTime::class), new MixedType()]), - new ObjectType(\stdClass::class), - TrinaryLogic::createNo(), - ], - ]; - } + public function testGetClassReflectionOfGenericClass(): void + { + $objectType = new ObjectType(\Traversable::class); + $classReflection = $objectType->getClassReflection(); + $this->assertNotNull($classReflection); + $this->assertSame('Traversable', $classReflection->getDisplayName()); + } - /** - * @dataProvider dataHasOffsetValueType - * @param \PHPStan\Type\ObjectType $type - * @param Type $offsetType - * @param TrinaryLogic $expectedResult - */ - public function testHasOffsetValueType( - ObjectType $type, - Type $offsetType, - TrinaryLogic $expectedResult - ): void - { - $this->assertSame( - $expectedResult->describe(), - $type->hasOffsetValueType($offsetType)->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $offsetType->describe(VerbosityLevel::precise())) - ); - } + public function dataHasOffsetValueType(): array + { + return [ + [ + new ObjectType(\stdClass::class), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new ObjectType(\Generator::class), + new IntegerType(), + TrinaryLogic::createNo(), + ], + [ + new ObjectType(\ArrayAccess::class), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new ObjectType(\Countable::class), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), + new IntegerType(), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), + new MixedType(), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new IntegerType(), new MixedType()]), + new StringType(), + TrinaryLogic::createNo(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTimeInterface::class), new MixedType()]), + new ObjectType(\DateTime::class), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTime::class), new MixedType()]), + new ObjectType(\DateTimeInterface::class), + TrinaryLogic::createMaybe(), + ], + [ + new GenericObjectType(\ArrayAccess::class, [new ObjectType(\DateTime::class), new MixedType()]), + new ObjectType(\stdClass::class), + TrinaryLogic::createNo(), + ], + ]; + } + /** + * @dataProvider dataHasOffsetValueType + * @param \PHPStan\Type\ObjectType $type + * @param Type $offsetType + * @param TrinaryLogic $expectedResult + */ + public function testHasOffsetValueType( + ObjectType $type, + Type $offsetType, + TrinaryLogic $expectedResult + ): void { + $this->assertSame( + $expectedResult->describe(), + $type->hasOffsetValueType($offsetType)->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $offsetType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php b/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php index 12a2c179ae..67515e8405 100644 --- a/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php +++ b/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - + /** + * @dataProvider dataIsSuperTypeOf + * @param ObjectWithoutClassType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(ObjectWithoutClassType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/StaticTypeTest.php b/tests/PHPStan/Type/StaticTypeTest.php index 993b9c74c2..0b7009bdce 100644 --- a/tests/PHPStan/Type/StaticTypeTest.php +++ b/tests/PHPStan/Type/StaticTypeTest.php @@ -1,4 +1,6 @@ -isIterable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isIterable()', $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsCallable(): array - { - return [ - [new StaticType('Closure'), TrinaryLogic::createYes()], - [new StaticType('Unknown'), TrinaryLogic::createMaybe()], - [new StaticType('DateTime'), TrinaryLogic::createMaybe()], - ]; - } + /** + * @dataProvider dataIsIterable + * @param StaticType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsIterable(StaticType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isIterable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isIterable()', $type->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsCallable - * @param StaticType $type - * @param TrinaryLogic $expectedResult - */ - public function testIsCallable(StaticType $type, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isCallable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) - ); - } + public function dataIsCallable(): array + { + return [ + [new StaticType('Closure'), TrinaryLogic::createYes()], + [new StaticType('Unknown'), TrinaryLogic::createMaybe()], + [new StaticType('DateTime'), TrinaryLogic::createMaybe()], + ]; + } - public function dataIsSuperTypeOf(): array - { - $broker = $this->createBroker(); - return [ - 0 => [ - new StaticType('UnknownClassA'), - new ObjectType('UnknownClassB'), - TrinaryLogic::createMaybe(), - ], - 1 => [ - new StaticType(\ArrayAccess::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 2 => [ - new StaticType(\Countable::class), - new ObjectType(\Countable::class), - TrinaryLogic::createMaybe(), - ], - 3 => [ - new StaticType(\DateTimeImmutable::class), - new ObjectType(\DateTimeImmutable::class), - TrinaryLogic::createMaybe(), - ], - 4 => [ - new StaticType(\Traversable::class), - new ObjectType(\ArrayObject::class), - TrinaryLogic::createMaybe(), - ], - 5 => [ - new StaticType(\Traversable::class), - new ObjectType(\Iterator::class), - TrinaryLogic::createMaybe(), - ], - 6 => [ - new StaticType(\ArrayObject::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 7 => [ - new StaticType(\Iterator::class), - new ObjectType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 8 => [ - new StaticType(\ArrayObject::class), - new ObjectType(\DateTimeImmutable::class), - TrinaryLogic::createNo(), - ], - 9 => [ - new StaticType(\DateTimeImmutable::class), - new UnionType([ - new ObjectType(\DateTimeImmutable::class), - new StringType(), - ]), - TrinaryLogic::createMaybe(), - ], - 10 => [ - new StaticType(\DateTimeImmutable::class), - new UnionType([ - new ObjectType(\ArrayObject::class), - new StringType(), - ]), - TrinaryLogic::createNo(), - ], - 11 => [ - new StaticType(\LogicException::class), - new ObjectType(\InvalidArgumentException::class), - TrinaryLogic::createMaybe(), - ], - 12 => [ - new StaticType(\InvalidArgumentException::class), - new ObjectType(\LogicException::class), - TrinaryLogic::createMaybe(), - ], - 13 => [ - new StaticType(\ArrayAccess::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 14 => [ - new StaticType(\Countable::class), - new StaticType(\Countable::class), - TrinaryLogic::createYes(), - ], - 15 => [ - new StaticType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), - TrinaryLogic::createYes(), - ], - 16 => [ - new StaticType(\Traversable::class), - new StaticType(\ArrayObject::class), - TrinaryLogic::createYes(), - ], - 17 => [ - new StaticType(\Traversable::class), - new StaticType(\Iterator::class), - TrinaryLogic::createYes(), - ], - 18 => [ - new StaticType(\ArrayObject::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 19 => [ - new StaticType(\Iterator::class), - new StaticType(\Traversable::class), - TrinaryLogic::createMaybe(), - ], - 20 => [ - new StaticType(\ArrayObject::class), - new StaticType(\DateTimeImmutable::class), - TrinaryLogic::createNo(), - ], - 21 => [ - new StaticType(\DateTimeImmutable::class), - new UnionType([ - new StaticType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), - ]), - TrinaryLogic::createYes(), - ], - 22 => [ - new StaticType(\DateTimeImmutable::class), - new UnionType([ - new StaticType(\DateTimeImmutable::class), - new StringType(), - ]), - TrinaryLogic::createMaybe(), - ], - 23 => [ - new StaticType(\DateTimeImmutable::class), - new UnionType([ - new StaticType(\ArrayObject::class), - new StringType(), - ]), - TrinaryLogic::createNo(), - ], - 24 => [ - new StaticType(\LogicException::class), - new StaticType(\InvalidArgumentException::class), - TrinaryLogic::createYes(), - ], - 25 => [ - new StaticType(\InvalidArgumentException::class), - new StaticType(\LogicException::class), - TrinaryLogic::createMaybe(), - ], - 26 => [ - new StaticType(\stdClass::class), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), - ], - 27 => [ - new ObjectWithoutClassType(), - new StaticType(\stdClass::class), - TrinaryLogic::createYes(), - ], - 28 => [ - new ThisType($broker->getClass(\stdClass::class)), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), - ], - 29 => [ - new ObjectWithoutClassType(), - new ThisType($broker->getClass(\stdClass::class)), - TrinaryLogic::createYes(), - ], - [ - new StaticType(Base::class), - new ObjectType(Child::class), - TrinaryLogic::createMaybe(), - ], - [ - new StaticType(Base::class), - new StaticType(FinalChild::class), - TrinaryLogic::createYes(), - ], - [ - new StaticType(Base::class), - new StaticType(Child::class), - TrinaryLogic::createYes(), - ], - [ - new StaticType(Base::class), - new ObjectType(FinalChild::class), - TrinaryLogic::createYes(), - ], - ]; - } + /** + * @dataProvider dataIsCallable + * @param StaticType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsCallable(StaticType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isCallable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataIsSuperTypeOf - * @param Type $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(Type $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataIsSuperTypeOf(): array + { + $broker = $this->createBroker(); + return [ + 0 => [ + new StaticType('UnknownClassA'), + new ObjectType('UnknownClassB'), + TrinaryLogic::createMaybe(), + ], + 1 => [ + new StaticType(\ArrayAccess::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 2 => [ + new StaticType(\Countable::class), + new ObjectType(\Countable::class), + TrinaryLogic::createMaybe(), + ], + 3 => [ + new StaticType(\DateTimeImmutable::class), + new ObjectType(\DateTimeImmutable::class), + TrinaryLogic::createMaybe(), + ], + 4 => [ + new StaticType(\Traversable::class), + new ObjectType(\ArrayObject::class), + TrinaryLogic::createMaybe(), + ], + 5 => [ + new StaticType(\Traversable::class), + new ObjectType(\Iterator::class), + TrinaryLogic::createMaybe(), + ], + 6 => [ + new StaticType(\ArrayObject::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 7 => [ + new StaticType(\Iterator::class), + new ObjectType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 8 => [ + new StaticType(\ArrayObject::class), + new ObjectType(\DateTimeImmutable::class), + TrinaryLogic::createNo(), + ], + 9 => [ + new StaticType(\DateTimeImmutable::class), + new UnionType([ + new ObjectType(\DateTimeImmutable::class), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + 10 => [ + new StaticType(\DateTimeImmutable::class), + new UnionType([ + new ObjectType(\ArrayObject::class), + new StringType(), + ]), + TrinaryLogic::createNo(), + ], + 11 => [ + new StaticType(\LogicException::class), + new ObjectType(\InvalidArgumentException::class), + TrinaryLogic::createMaybe(), + ], + 12 => [ + new StaticType(\InvalidArgumentException::class), + new ObjectType(\LogicException::class), + TrinaryLogic::createMaybe(), + ], + 13 => [ + new StaticType(\ArrayAccess::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 14 => [ + new StaticType(\Countable::class), + new StaticType(\Countable::class), + TrinaryLogic::createYes(), + ], + 15 => [ + new StaticType(\DateTimeImmutable::class), + new StaticType(\DateTimeImmutable::class), + TrinaryLogic::createYes(), + ], + 16 => [ + new StaticType(\Traversable::class), + new StaticType(\ArrayObject::class), + TrinaryLogic::createYes(), + ], + 17 => [ + new StaticType(\Traversable::class), + new StaticType(\Iterator::class), + TrinaryLogic::createYes(), + ], + 18 => [ + new StaticType(\ArrayObject::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 19 => [ + new StaticType(\Iterator::class), + new StaticType(\Traversable::class), + TrinaryLogic::createMaybe(), + ], + 20 => [ + new StaticType(\ArrayObject::class), + new StaticType(\DateTimeImmutable::class), + TrinaryLogic::createNo(), + ], + 21 => [ + new StaticType(\DateTimeImmutable::class), + new UnionType([ + new StaticType(\DateTimeImmutable::class), + new StaticType(\DateTimeImmutable::class), + ]), + TrinaryLogic::createYes(), + ], + 22 => [ + new StaticType(\DateTimeImmutable::class), + new UnionType([ + new StaticType(\DateTimeImmutable::class), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + 23 => [ + new StaticType(\DateTimeImmutable::class), + new UnionType([ + new StaticType(\ArrayObject::class), + new StringType(), + ]), + TrinaryLogic::createNo(), + ], + 24 => [ + new StaticType(\LogicException::class), + new StaticType(\InvalidArgumentException::class), + TrinaryLogic::createYes(), + ], + 25 => [ + new StaticType(\InvalidArgumentException::class), + new StaticType(\LogicException::class), + TrinaryLogic::createMaybe(), + ], + 26 => [ + new StaticType(\stdClass::class), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), + ], + 27 => [ + new ObjectWithoutClassType(), + new StaticType(\stdClass::class), + TrinaryLogic::createYes(), + ], + 28 => [ + new ThisType($broker->getClass(\stdClass::class)), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), + ], + 29 => [ + new ObjectWithoutClassType(), + new ThisType($broker->getClass(\stdClass::class)), + TrinaryLogic::createYes(), + ], + [ + new StaticType(Base::class), + new ObjectType(Child::class), + TrinaryLogic::createMaybe(), + ], + [ + new StaticType(Base::class), + new StaticType(FinalChild::class), + TrinaryLogic::createYes(), + ], + [ + new StaticType(Base::class), + new StaticType(Child::class), + TrinaryLogic::createYes(), + ], + [ + new StaticType(Base::class), + new ObjectType(FinalChild::class), + TrinaryLogic::createYes(), + ], + ]; + } - public function dataEquals(): array - { - $reflectionProvider = Broker::getInstance(); + /** + * @dataProvider dataIsSuperTypeOf + * @param Type $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(Type $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - return [ - [ - new ThisType($reflectionProvider->getClass(\Exception::class)), - new ThisType($reflectionProvider->getClass(\Exception::class)), - true, - ], - [ - new ThisType($reflectionProvider->getClass(\Exception::class)), - new ThisType($reflectionProvider->getClass(\InvalidArgumentException::class)), - false, - ], - [ - new ThisType($reflectionProvider->getClass(\Exception::class)), - new StaticType(\Exception::class), - false, - ], - [ - new ThisType($reflectionProvider->getClass(\Exception::class)), - new StaticType(\InvalidArgumentException::class), - false, - ], - ]; - } + public function dataEquals(): array + { + $reflectionProvider = Broker::getInstance(); - /** - * @dataProvider dataEquals - * @param StaticType $type - * @param StaticType $otherType - * @param bool $expected - */ - public function testEquals(StaticType $type, StaticType $otherType, bool $expected): void - { - $this->assertSame($expected, $type->equals($otherType)); - $this->assertSame($expected, $otherType->equals($type)); - } + return [ + [ + new ThisType($reflectionProvider->getClass(\Exception::class)), + new ThisType($reflectionProvider->getClass(\Exception::class)), + true, + ], + [ + new ThisType($reflectionProvider->getClass(\Exception::class)), + new ThisType($reflectionProvider->getClass(\InvalidArgumentException::class)), + false, + ], + [ + new ThisType($reflectionProvider->getClass(\Exception::class)), + new StaticType(\Exception::class), + false, + ], + [ + new ThisType($reflectionProvider->getClass(\Exception::class)), + new StaticType(\InvalidArgumentException::class), + false, + ], + ]; + } + /** + * @dataProvider dataEquals + * @param StaticType $type + * @param StaticType $otherType + * @param bool $expected + */ + public function testEquals(StaticType $type, StaticType $otherType, bool $expected): void + { + $this->assertSame($expected, $type->equals($otherType)); + $this->assertSame($expected, $otherType->equals($type)); + } } diff --git a/tests/PHPStan/Type/StringTypeTest.php b/tests/PHPStan/Type/StringTypeTest.php index 2aabdb976a..b25c06d195 100644 --- a/tests/PHPStan/Type/StringTypeTest.php +++ b/tests/PHPStan/Type/StringTypeTest.php @@ -1,4 +1,6 @@ -isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataAccepts(): iterable - { - yield [ - new StringType(), - new IntersectionType([ - new ObjectType(ClassWithToString::class), - new HasPropertyType('foo'), - ]), - TrinaryLogic::createNo(), - ]; + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf(StringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - yield [ - new StringType(), - new ClassStringType(), - TrinaryLogic::createYes(), - ]; + public function dataAccepts(): iterable + { + yield [ + new StringType(), + new IntersectionType([ + new ObjectType(ClassWithToString::class), + new HasPropertyType('foo'), + ]), + TrinaryLogic::createNo(), + ]; - yield [ - new StringType(), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new StringType(), - TemplateTypeVariance::createInvariant() - ), - TrinaryLogic::createYes(), - ]; + yield [ + new StringType(), + new ClassStringType(), + TrinaryLogic::createYes(), + ]; - yield [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new StringType(), - TemplateTypeVariance::createInvariant() - ), - new StringType(), - TrinaryLogic::createYes(), - ]; + yield [ + new StringType(), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant() + ), + TrinaryLogic::createYes(), + ]; - yield [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new StringType(), - TemplateTypeVariance::createInvariant() - )->toArgument(), - new StringType(), - TrinaryLogic::createNo(), - ]; + yield [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant() + ), + new StringType(), + TrinaryLogic::createYes(), + ]; - yield [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new StringType(), - TemplateTypeVariance::createInvariant() - )->toArgument(), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('foo'), - 'T', - new StringType(), - TemplateTypeVariance::createInvariant() - )->toArgument(), - TrinaryLogic::createYes(), - ]; - } + yield [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant() + )->toArgument(), + new StringType(), + TrinaryLogic::createNo(), + ]; - /** - * @dataProvider dataAccepts - * @param \PHPStan\Type\StringType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts(StringType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->accepts($otherType, true); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + yield [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant() + )->toArgument(), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant() + )->toArgument(), + TrinaryLogic::createYes(), + ]; + } - public function dataEquals(): array - { - return [ - [ - new StringType(), - new StringType(), - true, - ], - [ - new ConstantStringType('foo'), - new ConstantStringType('foo'), - true, - ], - [ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - false, - ], - [ - new StringType(), - new ConstantStringType(''), - false, - ], - [ - new ConstantStringType(''), - new StringType(), - false, - ], - [ - new StringType(), - new ClassStringType(), - false, - ], - ]; - } + /** + * @dataProvider dataAccepts + * @param \PHPStan\Type\StringType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(StringType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - /** - * @dataProvider dataEquals - * @param StringType $type - * @param Type $otherType - * @param bool $expectedResult - */ - public function testEquals(StringType $type, Type $otherType, bool $expectedResult): void - { - $actualResult = $type->equals($otherType); - $this->assertSame( - $expectedResult, - $actualResult, - sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + public function dataEquals(): array + { + return [ + [ + new StringType(), + new StringType(), + true, + ], + [ + new ConstantStringType('foo'), + new ConstantStringType('foo'), + true, + ], + [ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + false, + ], + [ + new StringType(), + new ConstantStringType(''), + false, + ], + [ + new ConstantStringType(''), + new StringType(), + false, + ], + [ + new StringType(), + new ClassStringType(), + false, + ], + ]; + } + /** + * @dataProvider dataEquals + * @param StringType $type + * @param Type $otherType + * @param bool $expectedResult + */ + public function testEquals(StringType $type, Type $otherType, bool $expectedResult): void + { + $actualResult = $type->equals($otherType); + $this->assertSame( + $expectedResult, + $actualResult, + sprintf('%s->equals(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } } diff --git a/tests/PHPStan/Type/TemplateTypeTest.php b/tests/PHPStan/Type/TemplateTypeTest.php index f52ad4b3b0..2801ad1c1c 100644 --- a/tests/PHPStan/Type/TemplateTypeTest.php +++ b/tests/PHPStan/Type/TemplateTypeTest.php @@ -1,4 +1,6 @@ -accepts($otherType, true); - $this->assertSame( - $expectedAccept->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); + /** + * @dataProvider dataAccepts + */ + public function testAccepts( + Type $type, + Type $otherType, + TrinaryLogic $expectedAccept, + TrinaryLogic $expectedAcceptArg + ): void { + assert($type instanceof TemplateType); - $type = $type->toArgument(); + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedAccept->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); - $actualResult = $type->accepts($otherType, true); - $this->assertSame( - $expectedAcceptArg->describe(), - $actualResult->describe(), - sprintf('%s -> accepts(%s) (Argument strategy)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } + $type = $type->toArgument(); - public function dataIsSuperTypeOf(): array - { - $templateType = static function (string $name, ?Type $bound, ?string $functionName = null): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction($functionName ?? '_'), - $name, - $bound, - TemplateTypeVariance::createInvariant() - ); - }; + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedAcceptArg->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s) (Argument strategy)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } - return [ - 0 => [ - $templateType('T', new ObjectType('DateTime')), - new ObjectType('DateTime'), - TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo DateTime - TrinaryLogic::createYes(), // DateTime isSuperTypeTo (T of DateTime) - ], - 1 => [ - $templateType('T', new ObjectType('DateTime')), - $templateType('T', new ObjectType('DateTime')), - TrinaryLogic::createYes(), - TrinaryLogic::createYes(), - ], - 2 => [ - $templateType('T', new ObjectType('DateTime'), 'a'), - $templateType('T', new ObjectType('DateTime'), 'b'), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - 3 => [ - $templateType('T', new ObjectType('DateTime')), - new StringType(), - TrinaryLogic::createNo(), // (T of DateTime) isSuperTypeTo string - TrinaryLogic::createNo(), // string isSuperTypeTo (T of DateTime) - ], - 4 => [ - $templateType('T', new ObjectType('DateTime')), - new ObjectType('DateTimeInterface'), - TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo DateTimeInterface - TrinaryLogic::createYes(), // DateTimeInterface isSuperTypeTo (T of DateTime) - ], - 5 => [ - $templateType('T', new ObjectType('DateTime')), - $templateType('T', new ObjectType('DateTimeInterface')), - TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (T of DateTimeInterface) - TrinaryLogic::createMaybe(), // (T of DateTimeInterface) isSuperTypeTo (T of DateTime) - ], - 6 => [ - $templateType('T', new ObjectType('DateTime')), - new NullType(), - TrinaryLogic::createNo(), // (T of DateTime) isSuperTypeTo null - TrinaryLogic::createNo(), // null isSuperTypeTo (T of DateTime) - ], - 7 => [ - $templateType('T', new ObjectType('DateTime')), - new UnionType([ - new NullType(), - new ObjectType('DateTime'), - ]), - TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (DateTime|null) - TrinaryLogic::createYes(), // (DateTime|null) isSuperTypeTo (T of DateTime) - ], - 8 => [ - $templateType('T', new ObjectType('DateTimeInterface')), - new UnionType([ - new NullType(), - new ObjectType('DateTime'), - ]), - TrinaryLogic::createMaybe(), // (T of DateTimeInterface) isSuperTypeTo (DateTime|null) - TrinaryLogic::createMaybe(), // (DateTime|null) isSuperTypeTo (T of DateTimeInterface) - ], - 9 => [ - $templateType('T', new ObjectType('DateTime')), - new UnionType([ - new NullType(), - new ObjectType('DateTimeInterface'), - ]), - TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (DateTimeInterface|null) - TrinaryLogic::createYes(), // (DateTimeInterface|null) isSuperTypeTo (T of DateTime) - ], - 10 => [ - $templateType('T', null), - new MixedType(true), - TrinaryLogic::createMaybe(), // T isSuperTypeTo mixed - TrinaryLogic::createYes(), // mixed isSuperTypeTo T - ], - 11 => [ - $templateType('T', null), - new ObjectType(\DateTimeInterface::class), - TrinaryLogic::createMaybe(), // T isSuperTypeTo DateTimeInterface - TrinaryLogic::createMaybe(), // DateTimeInterface isSuperTypeTo T - ], - 12 => [ - $templateType('T', new ObjectWithoutClassType()), - new ObjectWithoutClassType(), - TrinaryLogic::createMaybe(), - TrinaryLogic::createYes(), - ], - 13 => [ - $templateType('T', new ObjectWithoutClassType()), - new ObjectType(\DateTimeInterface::class), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - 14 => [ - $templateType('T', new ObjectType(\Throwable::class)), - new ObjectType(\Exception::class), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - [ - $templateType('T', new MixedType(true)), - $templateType('U', new UnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - [ - $templateType('T', new MixedType(true)), - $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - [ - $templateType('T', new ObjectType(\stdClass::class)), - $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createNo(), - TrinaryLogic::createNo(), - ], - [ - $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), - $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createYes(), - TrinaryLogic::createYes(), - ], - [ - $templateType('T', new UnionType([new IntegerType(), new StringType()])), - $templateType('T', new UnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createYes(), - TrinaryLogic::createYes(), - ], - [ - $templateType('T', new UnionType([new IntegerType(), new StringType()])), - $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - [ - $templateType('T', new UnionType([new IntegerType(), new StringType()])), - $templateType('T', new IntegerType()), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), - ], - [ - $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), - new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType()]), - TrinaryLogic::createMaybe(), - TrinaryLogic::createYes(), - ], - ]; - } + public function dataIsSuperTypeOf(): array + { + $templateType = static function (string $name, ?Type $bound, ?string $functionName = null): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction($functionName ?? '_'), + $name, + $bound, + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataIsSuperTypeOf - */ - public function testIsSuperTypeOf( - Type $type, - Type $otherType, - TrinaryLogic $expectedIsSuperType, - TrinaryLogic $expectedIsSuperTypeInverse - ): void - { - assert($type instanceof TemplateType); + return [ + 0 => [ + $templateType('T', new ObjectType('DateTime')), + new ObjectType('DateTime'), + TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo DateTime + TrinaryLogic::createYes(), // DateTime isSuperTypeTo (T of DateTime) + ], + 1 => [ + $templateType('T', new ObjectType('DateTime')), + $templateType('T', new ObjectType('DateTime')), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ], + 2 => [ + $templateType('T', new ObjectType('DateTime'), 'a'), + $templateType('T', new ObjectType('DateTime'), 'b'), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + 3 => [ + $templateType('T', new ObjectType('DateTime')), + new StringType(), + TrinaryLogic::createNo(), // (T of DateTime) isSuperTypeTo string + TrinaryLogic::createNo(), // string isSuperTypeTo (T of DateTime) + ], + 4 => [ + $templateType('T', new ObjectType('DateTime')), + new ObjectType('DateTimeInterface'), + TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo DateTimeInterface + TrinaryLogic::createYes(), // DateTimeInterface isSuperTypeTo (T of DateTime) + ], + 5 => [ + $templateType('T', new ObjectType('DateTime')), + $templateType('T', new ObjectType('DateTimeInterface')), + TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (T of DateTimeInterface) + TrinaryLogic::createMaybe(), // (T of DateTimeInterface) isSuperTypeTo (T of DateTime) + ], + 6 => [ + $templateType('T', new ObjectType('DateTime')), + new NullType(), + TrinaryLogic::createNo(), // (T of DateTime) isSuperTypeTo null + TrinaryLogic::createNo(), // null isSuperTypeTo (T of DateTime) + ], + 7 => [ + $templateType('T', new ObjectType('DateTime')), + new UnionType([ + new NullType(), + new ObjectType('DateTime'), + ]), + TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (DateTime|null) + TrinaryLogic::createYes(), // (DateTime|null) isSuperTypeTo (T of DateTime) + ], + 8 => [ + $templateType('T', new ObjectType('DateTimeInterface')), + new UnionType([ + new NullType(), + new ObjectType('DateTime'), + ]), + TrinaryLogic::createMaybe(), // (T of DateTimeInterface) isSuperTypeTo (DateTime|null) + TrinaryLogic::createMaybe(), // (DateTime|null) isSuperTypeTo (T of DateTimeInterface) + ], + 9 => [ + $templateType('T', new ObjectType('DateTime')), + new UnionType([ + new NullType(), + new ObjectType('DateTimeInterface'), + ]), + TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (DateTimeInterface|null) + TrinaryLogic::createYes(), // (DateTimeInterface|null) isSuperTypeTo (T of DateTime) + ], + 10 => [ + $templateType('T', null), + new MixedType(true), + TrinaryLogic::createMaybe(), // T isSuperTypeTo mixed + TrinaryLogic::createYes(), // mixed isSuperTypeTo T + ], + 11 => [ + $templateType('T', null), + new ObjectType(\DateTimeInterface::class), + TrinaryLogic::createMaybe(), // T isSuperTypeTo DateTimeInterface + TrinaryLogic::createMaybe(), // DateTimeInterface isSuperTypeTo T + ], + 12 => [ + $templateType('T', new ObjectWithoutClassType()), + new ObjectWithoutClassType(), + TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), + ], + 13 => [ + $templateType('T', new ObjectWithoutClassType()), + new ObjectType(\DateTimeInterface::class), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + 14 => [ + $templateType('T', new ObjectType(\Throwable::class)), + new ObjectType(\Exception::class), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + [ + $templateType('T', new MixedType(true)), + $templateType('U', new UnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + [ + $templateType('T', new MixedType(true)), + $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + [ + $templateType('T', new ObjectType(\stdClass::class)), + $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createNo(), + TrinaryLogic::createNo(), + ], + [ + $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), + $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ], + [ + $templateType('T', new UnionType([new IntegerType(), new StringType()])), + $templateType('T', new UnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ], + [ + $templateType('T', new UnionType([new IntegerType(), new StringType()])), + $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + [ + $templateType('T', new UnionType([new IntegerType(), new StringType()])), + $templateType('T', new IntegerType()), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ], + [ + $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), + new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType()]), + TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), + ], + ]; + } - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedIsSuperType->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); + /** + * @dataProvider dataIsSuperTypeOf + */ + public function testIsSuperTypeOf( + Type $type, + Type $otherType, + TrinaryLogic $expectedIsSuperType, + TrinaryLogic $expectedIsSuperTypeInverse + ): void { + assert($type instanceof TemplateType); - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedIsSuperTypeInverse->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedIsSuperType->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); - /** @return array}> */ - public function dataInferTemplateTypes(): array - { - $templateType = static function (string $name, ?Type $bound = null, ?string $functionName = null): Type { - return TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction($functionName ?? '_'), - $name, - $bound, - TemplateTypeVariance::createInvariant() - ); - }; + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedIsSuperTypeInverse->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } - return [ - 'simple' => [ - new IntegerType(), - $templateType('T'), - ['T' => 'int'], - ], - 'object' => [ - new ObjectType(\DateTime::class), - $templateType('T'), - ['T' => 'DateTime'], - ], - 'object with bound' => [ - new ObjectType(\DateTime::class), - $templateType('T', new ObjectType(\DateTimeInterface::class)), - ['T' => 'DateTime'], - ], - 'wrong object with bound' => [ - new ObjectType(\stdClass::class), - $templateType('T', new ObjectType(\DateTimeInterface::class)), - [], - ], - 'template type' => [ - TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\DateTimeInterface::class))), - $templateType('T', new ObjectType(\DateTimeInterface::class)), - ['T' => 'T of DateTimeInterface (function _(), argument)'], - ], - 'foreign template type' => [ - TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\DateTimeInterface::class), 'a')), - $templateType('T', new ObjectType(\DateTimeInterface::class), 'b'), - ['T' => 'T of DateTimeInterface (function a(), argument)'], - ], - 'foreign template type, imcompatible bound' => [ - TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\stdClass::class), 'a')), - $templateType('T', new ObjectType(\DateTime::class), 'b'), - [], - ], - ]; - } + /** @return array}> */ + public function dataInferTemplateTypes(): array + { + $templateType = static function (string $name, ?Type $bound = null, ?string $functionName = null): Type { + return TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction($functionName ?? '_'), + $name, + $bound, + TemplateTypeVariance::createInvariant() + ); + }; - /** - * @dataProvider dataInferTemplateTypes - * @param array $expectedTypes - */ - public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void - { - $result = $template->inferTemplateTypes($received); + return [ + 'simple' => [ + new IntegerType(), + $templateType('T'), + ['T' => 'int'], + ], + 'object' => [ + new ObjectType(\DateTime::class), + $templateType('T'), + ['T' => 'DateTime'], + ], + 'object with bound' => [ + new ObjectType(\DateTime::class), + $templateType('T', new ObjectType(\DateTimeInterface::class)), + ['T' => 'DateTime'], + ], + 'wrong object with bound' => [ + new ObjectType(\stdClass::class), + $templateType('T', new ObjectType(\DateTimeInterface::class)), + [], + ], + 'template type' => [ + TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\DateTimeInterface::class))), + $templateType('T', new ObjectType(\DateTimeInterface::class)), + ['T' => 'T of DateTimeInterface (function _(), argument)'], + ], + 'foreign template type' => [ + TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\DateTimeInterface::class), 'a')), + $templateType('T', new ObjectType(\DateTimeInterface::class), 'b'), + ['T' => 'T of DateTimeInterface (function a(), argument)'], + ], + 'foreign template type, imcompatible bound' => [ + TemplateTypeHelper::toArgument($templateType('T', new ObjectType(\stdClass::class), 'a')), + $templateType('T', new ObjectType(\DateTime::class), 'b'), + [], + ], + ]; + } - $this->assertSame( - $expectedTypes, - array_map(static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, $result->getTypes()) - ); - } + /** + * @dataProvider dataInferTemplateTypes + * @param array $expectedTypes + */ + public function testResolveTemplateTypes(Type $received, Type $template, array $expectedTypes): void + { + $result = $template->inferTemplateTypes($received); + $this->assertSame( + $expectedTypes, + array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, $result->getTypes()) + ); + } } diff --git a/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtension.php b/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtension.php index 1e82f949a5..2c3de6fec3 100644 --- a/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtension.php +++ b/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtension.php @@ -1,4 +1,6 @@ -isSuperTypeOf(new ObjectType(TestDecimal::class))->yes() + && $rightSide->isSuperTypeOf(new ObjectType(TestDecimal::class))->yes(); + } - public function isOperatorSupported(string $operatorSigil, Type $leftSide, Type $rightSide): bool - { - return in_array($operatorSigil, ['-', '+', '*', '/'], true) - && $leftSide->isSuperTypeOf(new ObjectType(TestDecimal::class))->yes() - && $rightSide->isSuperTypeOf(new ObjectType(TestDecimal::class))->yes(); - } - - public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type - { - return new ObjectType(TestDecimal::class); - } - + public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type + { + return new ObjectType(TestDecimal::class); + } } diff --git a/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtensionTest.php b/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtensionTest.php index 546b79a7b2..81e473ea03 100644 --- a/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtensionTest.php +++ b/tests/PHPStan/Type/TestDecimalOperatorTypeSpecifyingExtensionTest.php @@ -1,4 +1,6 @@ -isOperatorSupported($sigil, $leftType, $rightType); - - self::assertTrue($result); - } - - public function dataSigilAndSidesProvider(): iterable - { - yield '+' => [ - '+', - new ObjectType(TestDecimal::class), - new ObjectType(TestDecimal::class), - ]; - - yield '-' => [ - '-', - new ObjectType(TestDecimal::class), - new ObjectType(TestDecimal::class), - ]; - - yield '*' => [ - '*', - new ObjectType(TestDecimal::class), - new ObjectType(TestDecimal::class), - ]; - - yield '/' => [ - '/', - new ObjectType(TestDecimal::class), - new ObjectType(TestDecimal::class), - ]; - } - - /** - * @dataProvider dataNotMatchingSidesProvider - */ - public function testNotSupportsNotMatchingSides(string $sigil, Type $leftType, Type $rightType): void - { - $extension = new TestDecimalOperatorTypeSpecifyingExtension(); - - $result = $extension->isOperatorSupported($sigil, $leftType, $rightType); - - self::assertFalse($result); - } - - public function dataNotMatchingSidesProvider(): iterable - { - yield 'left' => [ - '+', - new ObjectType(\stdClass::class), - new ObjectType(TestDecimal::class), - ]; - - yield 'right' => [ - '+', - new ObjectType(TestDecimal::class), - new ObjectType(\stdClass::class), - ]; - } - + /** + * @dataProvider dataSigilAndSidesProvider + */ + public function testSupportsMatchingSigilsAndSides(string $sigil, Type $leftType, Type $rightType): void + { + $extension = new TestDecimalOperatorTypeSpecifyingExtension(); + + $result = $extension->isOperatorSupported($sigil, $leftType, $rightType); + + self::assertTrue($result); + } + + public function dataSigilAndSidesProvider(): iterable + { + yield '+' => [ + '+', + new ObjectType(TestDecimal::class), + new ObjectType(TestDecimal::class), + ]; + + yield '-' => [ + '-', + new ObjectType(TestDecimal::class), + new ObjectType(TestDecimal::class), + ]; + + yield '*' => [ + '*', + new ObjectType(TestDecimal::class), + new ObjectType(TestDecimal::class), + ]; + + yield '/' => [ + '/', + new ObjectType(TestDecimal::class), + new ObjectType(TestDecimal::class), + ]; + } + + /** + * @dataProvider dataNotMatchingSidesProvider + */ + public function testNotSupportsNotMatchingSides(string $sigil, Type $leftType, Type $rightType): void + { + $extension = new TestDecimalOperatorTypeSpecifyingExtension(); + + $result = $extension->isOperatorSupported($sigil, $leftType, $rightType); + + self::assertFalse($result); + } + + public function dataNotMatchingSidesProvider(): iterable + { + yield 'left' => [ + '+', + new ObjectType(\stdClass::class), + new ObjectType(TestDecimal::class), + ]; + + yield 'right' => [ + '+', + new ObjectType(TestDecimal::class), + new ObjectType(\stdClass::class), + ]; + } } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 265362a6b7..da778c082f 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1,4 +1,6 @@ -)|null', + ], + [ + new UnionType([ + new IntersectionType([ + new IterableType(new MixedType(), new StringType()), + new ObjectType('ArrayObject'), + ]), + new NullType(), + ]), + UnionType::class, + '(ArrayObject&iterable)|null', + ], + ]; + } - public function dataAddNull(): array - { - return [ - [ - new MixedType(), - MixedType::class, - 'mixed', - ], - [ - new NullType(), - NullType::class, - 'null', - ], - [ - new VoidType(), - UnionType::class, - 'void|null', - ], - [ - new StringType(), - UnionType::class, - 'string|null', - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - UnionType::class, - 'int|string|null', - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - new NullType(), - ]), - UnionType::class, - 'int|string|null', - ], - [ - new IntersectionType([ - new IterableType(new MixedType(), new StringType()), - new ObjectType('ArrayObject'), - ]), - UnionType::class, - '(ArrayObject&iterable)|null', - ], - [ - new UnionType([ - new IntersectionType([ - new IterableType(new MixedType(), new StringType()), - new ObjectType('ArrayObject'), - ]), - new NullType(), - ]), - UnionType::class, - '(ArrayObject&iterable)|null', - ], - ]; - } - - /** - * @dataProvider dataAddNull - * @param \PHPStan\Type\Type $type - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testAddNull( - Type $type, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $result = TypeCombinator::addNull($type); - $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); - $this->assertInstanceOf($expectedTypeClass, $result); - } - - /** - * @dataProvider dataAddNull - * @param \PHPStan\Type\Type $type - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testUnionWithNull( - Type $type, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $result = TypeCombinator::union($type, new NullType()); - $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); - $this->assertInstanceOf($expectedTypeClass, $result); - } + /** + * @dataProvider dataAddNull + * @param \PHPStan\Type\Type $type + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testAddNull( + Type $type, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $result = TypeCombinator::addNull($type); + $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); + $this->assertInstanceOf($expectedTypeClass, $result); + } - public function dataRemoveNull(): array - { - $reflectionProvider = Broker::getInstance(); - return [ - [ - new MixedType(), - MixedType::class, - 'mixed', - ], - [ - new NullType(), - NeverType::class, - '*NEVER*', - ], - [ - new VoidType(), - VoidType::class, - 'void', - ], - [ - new StringType(), - StringType::class, - 'string', - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - new NullType(), - ]), - UnionType::class, - 'int|string', - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - UnionType::class, - 'int|string', - ], - [ - new UnionType([ - new IntersectionType([ - new IterableType(new MixedType(), new StringType()), - new ObjectType('ArrayObject'), - ]), - new NullType(), - ]), - IntersectionType::class, - 'ArrayObject&iterable', - ], - [ - new IntersectionType([ - new IterableType(new MixedType(), new StringType()), - new ObjectType('ArrayObject'), - ]), - IntersectionType::class, - 'ArrayObject&iterable', - ], - [ - new UnionType([ - new ThisType($reflectionProvider->getClass(\Exception::class)), - new NullType(), - ]), - ThisType::class, - '$this(Exception)', - ], - [ - new UnionType([ - new ThisType(\Exception::class), - new NullType(), - ]), - ThisType::class, - '$this(Exception)', - ], - [ - new UnionType([ - new IterableType(new MixedType(), new StringType()), - new NullType(), - ]), - IterableType::class, - 'iterable', - ], - ]; - } + /** + * @dataProvider dataAddNull + * @param \PHPStan\Type\Type $type + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testUnionWithNull( + Type $type, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $result = TypeCombinator::union($type, new NullType()); + $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); + $this->assertInstanceOf($expectedTypeClass, $result); + } - /** - * @dataProvider dataRemoveNull - * @param \PHPStan\Type\Type $type - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testRemoveNull( - Type $type, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $result = TypeCombinator::removeNull($type); - $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); - $this->assertInstanceOf($expectedTypeClass, $result); - } + public function dataRemoveNull(): array + { + $reflectionProvider = Broker::getInstance(); + return [ + [ + new MixedType(), + MixedType::class, + 'mixed', + ], + [ + new NullType(), + NeverType::class, + '*NEVER*', + ], + [ + new VoidType(), + VoidType::class, + 'void', + ], + [ + new StringType(), + StringType::class, + 'string', + ], + [ + new UnionType([ + new StringType(), + new IntegerType(), + new NullType(), + ]), + UnionType::class, + 'int|string', + ], + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + UnionType::class, + 'int|string', + ], + [ + new UnionType([ + new IntersectionType([ + new IterableType(new MixedType(), new StringType()), + new ObjectType('ArrayObject'), + ]), + new NullType(), + ]), + IntersectionType::class, + 'ArrayObject&iterable', + ], + [ + new IntersectionType([ + new IterableType(new MixedType(), new StringType()), + new ObjectType('ArrayObject'), + ]), + IntersectionType::class, + 'ArrayObject&iterable', + ], + [ + new UnionType([ + new ThisType($reflectionProvider->getClass(\Exception::class)), + new NullType(), + ]), + ThisType::class, + '$this(Exception)', + ], + [ + new UnionType([ + new ThisType(\Exception::class), + new NullType(), + ]), + ThisType::class, + '$this(Exception)', + ], + [ + new UnionType([ + new IterableType(new MixedType(), new StringType()), + new NullType(), + ]), + IterableType::class, + 'iterable', + ], + ]; + } - public function dataUnion(): array - { - return [ - [ - [ - new StringType(), - new NullType(), - ], - UnionType::class, - 'string|null', - ], - [ - [ - new MixedType(), - new IntegerType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new ConstantBooleanType(true), - new ConstantBooleanType(false), - ], - BooleanType::class, - 'bool', - ], - [ - [ - new StringType(), - new IntegerType(), - ], - UnionType::class, - 'int|string', - ], - [ - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new StringType(), - ], - UnionType::class, - 'int|string', - ], - [ - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new ConstantBooleanType(true), - ], - UnionType::class, - 'int|string|true', - ], - [ - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new NullType(), - ], - UnionType::class, - 'int|string|null', - ], - [ - [ - new UnionType([ - new StringType(), - new IntegerType(), - new NullType(), - ]), - new NullType(), - ], - UnionType::class, - 'int|string|null', - ], - [ - [ - new UnionType([ - new StringType(), - new IntegerType(), - ]), - new StringType(), - ], - UnionType::class, - 'int|string', - ], - [ - [ - new IntersectionType([ - new IterableType(new MixedType(), new IntegerType()), - new ObjectType('ArrayObject'), - ]), - new StringType(), - ], - UnionType::class, - '(ArrayObject&iterable)|string', - ], - [ - [ - new IntersectionType([ - new IterableType(new MixedType(), new IntegerType()), - new ObjectType('ArrayObject'), - ]), - new ArrayType(new MixedType(), new StringType()), - ], - UnionType::class, - 'array|(ArrayObject&iterable)', - ], - [ - [ - new UnionType([ - new ConstantBooleanType(true), - new IntegerType(), - ]), - new ArrayType(new MixedType(), new StringType()), - ], - UnionType::class, - 'array|int|true', - ], - [ - [ - new UnionType([ - new ArrayType(new MixedType(), new ObjectType('Foo')), - new ArrayType(new MixedType(), new ObjectType('Bar')), - ]), - new ArrayType(new MixedType(), new MixedType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IterableType(new MixedType(), new MixedType()), - new ArrayType(new MixedType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new IterableType(new MixedType(), new MixedType()), - new ArrayType(new MixedType(), new MixedType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ObjectType('ArrayObject'), - new ObjectType('ArrayIterator'), - new ArrayType(new MixedType(), new StringType()), - ], - UnionType::class, - 'array|ArrayIterator|ArrayObject', - ], - [ - [ - new ObjectType('ArrayObject'), - new ObjectType('ArrayIterator'), - new ArrayType(new MixedType(), new StringType()), - new ArrayType(new MixedType(), new IntegerType()), - ], - UnionType::class, - 'array|ArrayIterator|ArrayObject', - ], - [ - [ - new IntersectionType([ - new IterableType(new MixedType(), new IntegerType()), - new ObjectType('ArrayObject'), - ]), - new ArrayType(new MixedType(), new IntegerType()), - ], - UnionType::class, - 'array|(ArrayObject&iterable)', - ], - [ - [ - new ObjectType('UnknownClass'), - new ObjectType('UnknownClass'), - ], - ObjectType::class, - 'UnknownClass', - ], - [ - [ - new IntersectionType([ - new ObjectType('DateTimeInterface'), - new ObjectType('Traversable'), - ]), - new IntersectionType([ - new ObjectType('DateTimeInterface'), - new ObjectType('Traversable'), - ]), - ], - IntersectionType::class, - 'DateTimeInterface&Traversable', - ], - [ - [ - new ObjectType('UnknownClass'), - new ObjectType('UnknownClass'), - ], - ObjectType::class, - 'UnknownClass', - ], - [ - [ - new StringType(), - new NeverType(), - ], - StringType::class, - 'string', - ], - [ - [ - new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new StringType()), - ]), - new NeverType(), - ], - IntersectionType::class, - 'ArrayObject&iterable', - ], - [ - [ - new IterableType(new MixedType(), new MixedType()), - new IterableType(new MixedType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new IterableType(new MixedType(), new IntegerType()), - new IterableType(new MixedType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new IterableType(new MixedType(), new IntegerType()), - new IterableType(new IntegerType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new IterableType(new StringType(), new IntegerType()), - new IterableType(new IntegerType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new ArrayType(new MixedType(), new MixedType()), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ArrayType(new MixedType(), new IntegerType()), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ArrayType(new MixedType(), new IntegerType()), - new ArrayType(new IntegerType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ArrayType(new StringType(), new IntegerType()), - new ArrayType(new IntegerType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new UnionType([ - new StringType(), - new NullType(), - ]), - new UnionType([ - new StringType(), - new NullType(), - ]), - new UnionType([ - new ObjectType('Unknown'), - new NullType(), - ]), - ], - UnionType::class, - 'string|Unknown|null', - ], - [ - [ - new ObjectType(\RecursionCallable\Foo::class), - new CallableType(), - ], - UnionType::class, - '(callable(): mixed)|RecursionCallable\Foo', - ], - [ - [ - new IntegerType(), - new ConstantIntegerType(1), - ], - IntegerType::class, - 'int', - ], - [ - [ - new ConstantIntegerType(1), - new ConstantIntegerType(1), - ], - ConstantIntegerType::class, - '1', - ], - [ - [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ], - UnionType::class, - '1|2', - ], - [ - [ - new FloatType(), - new ConstantFloatType(1.0), - ], - FloatType::class, - 'float', - ], - [ - [ - new ConstantFloatType(1.0), - new ConstantFloatType(1.0), - ], - ConstantFloatType::class, - '1.0', - ], - [ - [ - new ConstantFloatType(1.0), - new ConstantFloatType(2.0), - ], - UnionType::class, - '1.0|2.0', - ], - [ - [ - new StringType(), - new ConstantStringType('A'), - ], - StringType::class, - 'string', - ], - [ - [ - new ConstantStringType('A'), - new ConstantStringType('A'), - ], - ConstantStringType::class, - '\'A\'', - ], - [ - [ - new ConstantStringType('A'), - new ConstantStringType('B'), - ], - UnionType::class, - '\'A\'|\'B\'', - ], - [ - [ - new BooleanType(), - new ConstantBooleanType(true), - ], - BooleanType::class, - 'bool', - ], - [ - [ - new ConstantBooleanType(true), - new ConstantBooleanType(true), - ], - ConstantBooleanType::class, - 'true', - ], - [ - [ - new ConstantBooleanType(false), - new ConstantBooleanType(false), - ], - ConstantBooleanType::class, - 'false', - ], - [ - [ - new ConstantBooleanType(true), - new ConstantBooleanType(false), - ], - BooleanType::class, - 'bool', - ], - [ - [ - new ObjectType(\Closure::class), - new ClosureType([], new MixedType(), false), - ], - ObjectType::class, - 'Closure', - ], - [ - [ - new ClosureType([], new MixedType(), false), - new CallableType(), - ], - CallableType::class, - 'callable(): mixed', - ], - [ - // same keys - can remain ConstantArrayType - [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new ObjectType(\DateTimeImmutable::class), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new NullType(), - new StringType(), - ]), - ], - ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string)', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new ObjectType(\DateTimeImmutable::class), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('foo'), - ], [ - new NullType(), - ]), - ], - ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, ?\'bar\' => int)', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new ObjectType(\DateTimeImmutable::class), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - new ConstantStringType('baz'), - ], [ - new NullType(), - new StringType(), - new IntegerType(), - ]), - ], - ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string, ?\'baz\' => int)', - ], - [ - [ - new ArrayType( - new IntegerType(), - new ObjectType(\stdClass::class) - ), - new ConstantArrayType([ - new ConstantStringType('foo'), - new ConstantStringType('bar'), - ], [ - new ObjectType(\DateTimeImmutable::class), - new IntegerType(), - ]), - ], - ArrayType::class, - 'array<\'bar\'|\'foo\'|int, DateTimeImmutable|int|stdClass>', - ], - [ - [ - new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()]), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ConstantArrayType([], []), - new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()]), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new UnionType([new IntegerType(), new StringType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new IntegerType(), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new StringType(), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new UnionType([new IntegerType(), new StringType(), new FloatType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new UnionType([new StringType(), new FloatType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new UnionType([new IntegerType(), new FloatType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new ConstantStringType('foo'), - new ConstantStringType('foo'), - new ConstantStringType('bar'), - new ConstantStringType('baz'), - new ConstantStringType('lorem'), - ], - UnionType::class, - "'bar'|'baz'|'foo'|'lorem'", - ], - [ - [ - new ConstantStringType('foo'), - new ConstantStringType('foo'), - new ConstantStringType('fooo'), - new ConstantStringType('bar'), - new ConstantStringType('barr'), - new ConstantStringType('baz'), - new ConstantStringType('bazz'), - new ConstantStringType('lorem'), - new ConstantStringType('loremm'), - new ConstantStringType('loremmm'), - ], - StringType::class, - 'string', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new StringType()), - new HasOffsetType(new StringType()), - ]), - new IntersectionType([ - new ArrayType(new MixedType(), new StringType()), - new HasOffsetType(new StringType()), - ]), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], - [ - [ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType('foo'), - ]), - new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType('foo'), - ]), - ], - IntersectionType::class, - 'object&hasProperty(foo)', - ], - [ - [ - new IntersectionType([ - new ConstantArrayType( - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], - [ - new ObjectWithoutClassType(), - new ConstantStringType('foo'), - ] - ), - new CallableType(), - ]), - new IntersectionType([ - new ConstantArrayType( - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], - [ - new ObjectWithoutClassType(), - new ConstantStringType('foo'), - ] - ), - new CallableType(), - ]), - ], - IntersectionType::class, - 'array(object, \'foo\')&callable(): mixed', - ], - [ - [ - new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType()]), - new ConstantArrayType([], []), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - ]), - new ArrayType(new MixedType(), new MixedType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - ]), - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('bar')), - ]), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - ]), - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - new HasOffsetType(new ConstantStringType('bar')), - ]), - ], - IntersectionType::class, - 'array&hasOffset(\'foo\')', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new MixedType(false, new IntegerType()), - new MixedType(false, new StringType()), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new IntegerType()), - new MixedType(false, new UnionType([ - new IntegerType(), - new StringType(), - ])), - ], - MixedType::class, - 'mixed~int=implicit', - ], - [ - [ - new MixedType(false, new IntegerType()), - new MixedType(false, new UnionType([ - new ConstantIntegerType(1), - new StringType(), - ])), - ], - MixedType::class, - 'mixed~1=implicit', - ], - [ - [ - new MixedType(false, new ConstantIntegerType(2)), - new MixedType(false, new UnionType([ - new ConstantIntegerType(1), - new StringType(), - ])), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new IntegerType()), - new MixedType(false, new ConstantIntegerType(1)), - ], - MixedType::class, - 'mixed~1=implicit', - ], - [ - [ - new MixedType(false), - new MixedType(false, new ConstantIntegerType(1)), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new NullType()), - new UnionType([ - new StringType(), - new NullType(), - ]), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(), - new ObjectWithoutClassType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(), - new ObjectWithoutClassType(new ObjectType('A')), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new IntegerType()), - new ObjectWithoutClassType(new ObjectType('A')), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new ObjectType('A')), - new ObjectWithoutClassType(new ObjectType('A')), - ], - MixedType::class, - 'mixed~A=implicit', - ], - [ - [ - new MixedType(false, new NullType()), - new NullType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new IntegerType()), - new IntegerType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new ConstantIntegerType(1)), - new ConstantIntegerType(1), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new ObjectType('Exception')), - new ObjectType('Throwable'), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new ObjectType('Exception')), - new ObjectType('Exception'), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(false, new ObjectType('Exception')), - new ObjectType('InvalidArgumentException'), - ], - MixedType::class, - 'mixed=implicit', // should be MixedType~Exception+InvalidArgumentException - ], - [ - [ - new NullType(), - new MixedType(false, new NullType()), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(), - new MixedType(false, new NullType()), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ), - new ObjectType('DateTime'), - ], - UnionType::class, - 'DateTime|T (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - new ObjectType('DateTime'), - ], - ObjectType::class, - 'DateTime', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - ], - TemplateType::class, - 'T of DateTime (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'U', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - ], - UnionType::class, - 'T of DateTime (function a(), parameter)|U of DateTime (function a(), parameter)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new IntegerType(), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new StringType(), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new IntegerType(), - new StringType(), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new UnionType([new IntegerType(), new StringType()]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new IntegerType(), - new StringType(), - new FloatType(), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new UnionType([new IntegerType(), new StringType(), new FloatType()]), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new UnionType([new ConstantIntegerType(1), new ConstantIntegerType(2)]), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new UnionType([new ConstantIntegerType(1), new ConstantIntegerType(2), new FloatType()]), - ], - UnionType::class, - 'float|int|string', - ], - [ - [ - new StringType(), - new ClassStringType(), - ], - StringType::class, - 'string', - ], - [ - [ - new ClassStringType(), - new ConstantStringType(\stdClass::class), - ], - ClassStringType::class, - 'class-string', - ], - [ - [ - new ClassStringType(), - new ConstantStringType('Nonexistent'), - ], - UnionType::class, - '\'Nonexistent\'|class-string', - ], - [ - [ - new ClassStringType(), - new IntegerType(), - ], - UnionType::class, - 'class-string|int', - ], - [ - [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\Exception::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ClassStringType(), - ], - ClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new StringType(), - ], - StringType::class, - 'string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Exception::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Throwable::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\stdClass::class)), - ], - UnionType::class, - 'class-string|class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\Exception::class), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Throwable::class)), - new ConstantStringType(\Exception::class), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - new ConstantStringType(\Exception::class), - ], - UnionType::class, - '\'Exception\'|class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\stdClass::class), - ], - UnionType::class, - '\'stdClass\'|class-string', - ], - [ - [ - new StringType(), - new IntersectionType([new StringType(), new CallableType()]), - ], - StringType::class, - 'string', - ], - [ - [ - new IntersectionType([new StringType(), new CallableType()]), - new ConstantStringType('test_function'), - ], - UnionType::class, - '\'test_function\'|(callable(): mixed&string)', - ], - [ - [ - new IntersectionType([new StringType(), new CallableType()]), - new IntegerType(), - ], - UnionType::class, - '(callable(): mixed&string)|int', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - IntegerRangeType::fromInterval(2, 5), - ], - IntegerRangeType::class, - 'int<1, 5>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 2), - IntegerRangeType::fromInterval(3, 5), - ], - IntegerRangeType::class, - 'int<1, 5>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - IntegerRangeType::fromInterval(7, 9), - ], - UnionType::class, - 'int<1, 3>|int<7, 9>', - ], - [ - [ - IntegerRangeType::fromInterval(7, 9), - IntegerRangeType::fromInterval(1, 3), - ], - UnionType::class, - 'int<1, 3>|int<7, 9>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new ConstantIntegerType(3), - ], - IntegerRangeType::class, - 'int<1, 3>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new ConstantIntegerType(4), - ], - IntegerRangeType::class, - 'int<1, 4>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new ConstantIntegerType(5), - ], - UnionType::class, - '5|int<1, 3>', - ], - [ - [ - new UnionType([ - IntegerRangeType::fromInterval(null, 1), - IntegerRangeType::fromInterval(3, null), - ]), - new ConstantIntegerType(2), - ], - IntegerType::class, - 'int', - ], - [ - [ - new MixedType(), - new MixedType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(true), - new MixedType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(true), - new MixedType(true), - ], - MixedType::class, - 'mixed=explicit', - ], - [ - [ - new GenericObjectType(Variance\Invariant::class, [ - new ObjectType(\DateTimeInterface::class), - ]), - new GenericObjectType(Variance\Invariant::class, [ - new ObjectType(\DateTimeInterface::class), - ]), - ], - GenericObjectType::class, - 'PHPStan\Type\Variance\Invariant', - ], - [ - [ - new GenericObjectType(Variance\Invariant::class, [ - new ObjectType(\DateTimeInterface::class), - ]), - new GenericObjectType(Variance\Invariant::class, [ - new ObjectType(\DateTime::class), - ]), - ], - UnionType::class, - 'PHPStan\Type\Variance\Invariant|PHPStan\Type\Variance\Invariant', - ], - [ - [ - new GenericObjectType(Variance\Covariant::class, [ - new ObjectType(\DateTimeInterface::class), - ]), - new GenericObjectType(Variance\Covariant::class, [ - new ObjectType(\DateTime::class), - ]), - ], - GenericObjectType::class, - 'PHPStan\Type\Variance\Covariant', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new ObjectWithoutClassType(), - ], - ObjectWithoutClassType::class, - 'object', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new ObjectType(\stdClass::class), - ], - UnionType::class, - 'stdClass|T of object (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new MixedType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'K', - null, - TemplateTypeVariance::createInvariant() - ), - ], - UnionType::class, - 'K (function a(), parameter)|T (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'K', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - ], - UnionType::class, - 'K of object (function a(), parameter)|T of object (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType(\Exception::class), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'K', - new ObjectType(\stdClass::class), - TemplateTypeVariance::createInvariant() - ), - ], - UnionType::class, - 'K of stdClass (function a(), parameter)|T of Exception (function a(), parameter)', - ], - [ - [ - new ObjectType(\DateTimeImmutable::class), - new ObjectType(\DateTimeInterface::class, new ObjectType(\DateTimeImmutable::class)), - ], - ObjectType::class, - \DateTimeInterface::class, - ], - [ - [ - new StringType(), - new MixedType(false, new StringType()), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new StringType(), - ]), - ], - UnionType::class, - 'array()|array(string)', - ], - [ - [ - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new StringType(), - ], 1, [0]), - ], - UnionType::class, - 'array()|array(?0 => string)', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('c'), - new ConstantStringType('d'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - ], - UnionType::class, - 'array(\'a\' => int, \'b\' => int)|array(\'c\' => int, \'d\' => int)', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('a'), - ], [ - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - ], - ConstantArrayType::class, - 'array(\'a\' => int, ?\'b\' => int)', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - new ConstantArrayType([ - new ConstantStringType('b'), - new ConstantStringType('c'), - ], [ - new IntegerType(), - new IntegerType(), - ]), - ], - UnionType::class, - 'array(\'a\' => int, \'b\' => int)|array(\'b\' => int, \'c\' => int)', - ], - [ - [ - TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), - TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), - ], - IntersectionType::class, - 'string&hasOffset(int)', - ], - [ - [ - TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), - TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), - ], - IntersectionType::class, - '\'abc\'&hasOffset(int)', - ], - [ - [ - StaticTypeFactory::falsey(), - StaticTypeFactory::falsey(), - ], - UnionType::class, - '0|0.0|\'\'|\'0\'|array()|false|null', - ], - [ - [ - StaticTypeFactory::truthy(), - StaticTypeFactory::truthy(), - ], - MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array()|false|null=implicit', - ], - [ - [ - StaticTypeFactory::falsey(), - StaticTypeFactory::truthy(), - ], - MixedType::class, - 'mixed=implicit', - ], - ]; - } + /** + * @dataProvider dataRemoveNull + * @param \PHPStan\Type\Type $type + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testRemoveNull( + Type $type, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $result = TypeCombinator::removeNull($type); + $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); + $this->assertInstanceOf($expectedTypeClass, $result); + } - /** - * @dataProvider dataUnion - * @param \PHPStan\Type\Type[] $types - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testUnion( - array $types, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $actualType = TypeCombinator::union(...$types); - $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); - if ($actualType instanceof MixedType) { - if ($actualType->isExplicitMixed()) { - $actualTypeDescription .= '=explicit'; - } else { - $actualTypeDescription .= '=implicit'; - } - } + public function dataUnion(): array + { + return [ + [ + [ + new StringType(), + new NullType(), + ], + UnionType::class, + 'string|null', + ], + [ + [ + new MixedType(), + new IntegerType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new ConstantBooleanType(true), + new ConstantBooleanType(false), + ], + BooleanType::class, + 'bool', + ], + [ + [ + new StringType(), + new IntegerType(), + ], + UnionType::class, + 'int|string', + ], + [ + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new StringType(), + ], + UnionType::class, + 'int|string', + ], + [ + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new ConstantBooleanType(true), + ], + UnionType::class, + 'int|string|true', + ], + [ + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new NullType(), + ], + UnionType::class, + 'int|string|null', + ], + [ + [ + new UnionType([ + new StringType(), + new IntegerType(), + new NullType(), + ]), + new NullType(), + ], + UnionType::class, + 'int|string|null', + ], + [ + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + new StringType(), + ], + UnionType::class, + 'int|string', + ], + [ + [ + new IntersectionType([ + new IterableType(new MixedType(), new IntegerType()), + new ObjectType('ArrayObject'), + ]), + new StringType(), + ], + UnionType::class, + '(ArrayObject&iterable)|string', + ], + [ + [ + new IntersectionType([ + new IterableType(new MixedType(), new IntegerType()), + new ObjectType('ArrayObject'), + ]), + new ArrayType(new MixedType(), new StringType()), + ], + UnionType::class, + 'array|(ArrayObject&iterable)', + ], + [ + [ + new UnionType([ + new ConstantBooleanType(true), + new IntegerType(), + ]), + new ArrayType(new MixedType(), new StringType()), + ], + UnionType::class, + 'array|int|true', + ], + [ + [ + new UnionType([ + new ArrayType(new MixedType(), new ObjectType('Foo')), + new ArrayType(new MixedType(), new ObjectType('Bar')), + ]), + new ArrayType(new MixedType(), new MixedType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IterableType(new MixedType(), new MixedType()), + new ArrayType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new IterableType(new MixedType(), new MixedType()), + new ArrayType(new MixedType(), new MixedType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ObjectType('ArrayObject'), + new ObjectType('ArrayIterator'), + new ArrayType(new MixedType(), new StringType()), + ], + UnionType::class, + 'array|ArrayIterator|ArrayObject', + ], + [ + [ + new ObjectType('ArrayObject'), + new ObjectType('ArrayIterator'), + new ArrayType(new MixedType(), new StringType()), + new ArrayType(new MixedType(), new IntegerType()), + ], + UnionType::class, + 'array|ArrayIterator|ArrayObject', + ], + [ + [ + new IntersectionType([ + new IterableType(new MixedType(), new IntegerType()), + new ObjectType('ArrayObject'), + ]), + new ArrayType(new MixedType(), new IntegerType()), + ], + UnionType::class, + 'array|(ArrayObject&iterable)', + ], + [ + [ + new ObjectType('UnknownClass'), + new ObjectType('UnknownClass'), + ], + ObjectType::class, + 'UnknownClass', + ], + [ + [ + new IntersectionType([ + new ObjectType('DateTimeInterface'), + new ObjectType('Traversable'), + ]), + new IntersectionType([ + new ObjectType('DateTimeInterface'), + new ObjectType('Traversable'), + ]), + ], + IntersectionType::class, + 'DateTimeInterface&Traversable', + ], + [ + [ + new ObjectType('UnknownClass'), + new ObjectType('UnknownClass'), + ], + ObjectType::class, + 'UnknownClass', + ], + [ + [ + new StringType(), + new NeverType(), + ], + StringType::class, + 'string', + ], + [ + [ + new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new StringType()), + ]), + new NeverType(), + ], + IntersectionType::class, + 'ArrayObject&iterable', + ], + [ + [ + new IterableType(new MixedType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new IterableType(new MixedType(), new IntegerType()), + new IterableType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new IterableType(new MixedType(), new IntegerType()), + new IterableType(new IntegerType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new IterableType(new StringType(), new IntegerType()), + new IterableType(new IntegerType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new ArrayType(new MixedType(), new MixedType()), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new MixedType(), new IntegerType()), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new MixedType(), new IntegerType()), + new ArrayType(new IntegerType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new StringType(), new IntegerType()), + new ArrayType(new IntegerType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new UnionType([ + new StringType(), + new NullType(), + ]), + new UnionType([ + new StringType(), + new NullType(), + ]), + new UnionType([ + new ObjectType('Unknown'), + new NullType(), + ]), + ], + UnionType::class, + 'string|Unknown|null', + ], + [ + [ + new ObjectType(\RecursionCallable\Foo::class), + new CallableType(), + ], + UnionType::class, + '(callable(): mixed)|RecursionCallable\Foo', + ], + [ + [ + new IntegerType(), + new ConstantIntegerType(1), + ], + IntegerType::class, + 'int', + ], + [ + [ + new ConstantIntegerType(1), + new ConstantIntegerType(1), + ], + ConstantIntegerType::class, + '1', + ], + [ + [ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ], + UnionType::class, + '1|2', + ], + [ + [ + new FloatType(), + new ConstantFloatType(1.0), + ], + FloatType::class, + 'float', + ], + [ + [ + new ConstantFloatType(1.0), + new ConstantFloatType(1.0), + ], + ConstantFloatType::class, + '1.0', + ], + [ + [ + new ConstantFloatType(1.0), + new ConstantFloatType(2.0), + ], + UnionType::class, + '1.0|2.0', + ], + [ + [ + new StringType(), + new ConstantStringType('A'), + ], + StringType::class, + 'string', + ], + [ + [ + new ConstantStringType('A'), + new ConstantStringType('A'), + ], + ConstantStringType::class, + '\'A\'', + ], + [ + [ + new ConstantStringType('A'), + new ConstantStringType('B'), + ], + UnionType::class, + '\'A\'|\'B\'', + ], + [ + [ + new BooleanType(), + new ConstantBooleanType(true), + ], + BooleanType::class, + 'bool', + ], + [ + [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], + ConstantBooleanType::class, + 'true', + ], + [ + [ + new ConstantBooleanType(false), + new ConstantBooleanType(false), + ], + ConstantBooleanType::class, + 'false', + ], + [ + [ + new ConstantBooleanType(true), + new ConstantBooleanType(false), + ], + BooleanType::class, + 'bool', + ], + [ + [ + new ObjectType(\Closure::class), + new ClosureType([], new MixedType(), false), + ], + ObjectType::class, + 'Closure', + ], + [ + [ + new ClosureType([], new MixedType(), false), + new CallableType(), + ], + CallableType::class, + 'callable(): mixed', + ], + [ + // same keys - can remain ConstantArrayType + [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new ObjectType(\DateTimeImmutable::class), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new NullType(), + new StringType(), + ]), + ], + ConstantArrayType::class, + 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string)', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new ObjectType(\DateTimeImmutable::class), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('foo'), + ], [ + new NullType(), + ]), + ], + ConstantArrayType::class, + 'array(\'foo\' => DateTimeImmutable|null, ?\'bar\' => int)', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new ObjectType(\DateTimeImmutable::class), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + new ConstantStringType('baz'), + ], [ + new NullType(), + new StringType(), + new IntegerType(), + ]), + ], + ConstantArrayType::class, + 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string, ?\'baz\' => int)', + ], + [ + [ + new ArrayType( + new IntegerType(), + new ObjectType(\stdClass::class) + ), + new ConstantArrayType([ + new ConstantStringType('foo'), + new ConstantStringType('bar'), + ], [ + new ObjectType(\DateTimeImmutable::class), + new IntegerType(), + ]), + ], + ArrayType::class, + 'array<\'bar\'|\'foo\'|int, DateTimeImmutable|int|stdClass>', + ], + [ + [ + new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()]), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ConstantArrayType([], []), + new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()]), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new UnionType([new IntegerType(), new StringType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new IntegerType(), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new StringType(), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new UnionType([new IntegerType(), new StringType(), new FloatType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new UnionType([new StringType(), new FloatType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new UnionType([new IntegerType(), new FloatType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new ConstantStringType('foo'), + new ConstantStringType('foo'), + new ConstantStringType('bar'), + new ConstantStringType('baz'), + new ConstantStringType('lorem'), + ], + UnionType::class, + "'bar'|'baz'|'foo'|'lorem'", + ], + [ + [ + new ConstantStringType('foo'), + new ConstantStringType('foo'), + new ConstantStringType('fooo'), + new ConstantStringType('bar'), + new ConstantStringType('barr'), + new ConstantStringType('baz'), + new ConstantStringType('bazz'), + new ConstantStringType('lorem'), + new ConstantStringType('loremm'), + new ConstantStringType('loremmm'), + ], + StringType::class, + 'string', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new StringType()), + new HasOffsetType(new StringType()), + ]), + new IntersectionType([ + new ArrayType(new MixedType(), new StringType()), + new HasOffsetType(new StringType()), + ]), + ], + IntersectionType::class, + 'array&hasOffset(string)', + ], + [ + [ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType('foo'), + ]), + new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType('foo'), + ]), + ], + IntersectionType::class, + 'object&hasProperty(foo)', + ], + [ + [ + new IntersectionType([ + new ConstantArrayType( + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], + [ + new ObjectWithoutClassType(), + new ConstantStringType('foo'), + ] + ), + new CallableType(), + ]), + new IntersectionType([ + new ConstantArrayType( + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], + [ + new ObjectWithoutClassType(), + new ConstantStringType('foo'), + ] + ), + new CallableType(), + ]), + ], + IntersectionType::class, + 'array(object, \'foo\')&callable(): mixed', + ], + [ + [ + new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType()]), + new ConstantArrayType([], []), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + new ArrayType(new MixedType(), new MixedType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('bar')), + ]), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + new HasOffsetType(new ConstantStringType('bar')), + ]), + ], + IntersectionType::class, + 'array&hasOffset(\'foo\')', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new MixedType(false, new IntegerType()), + new MixedType(false, new StringType()), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new IntegerType()), + new MixedType(false, new UnionType([ + new IntegerType(), + new StringType(), + ])), + ], + MixedType::class, + 'mixed~int=implicit', + ], + [ + [ + new MixedType(false, new IntegerType()), + new MixedType(false, new UnionType([ + new ConstantIntegerType(1), + new StringType(), + ])), + ], + MixedType::class, + 'mixed~1=implicit', + ], + [ + [ + new MixedType(false, new ConstantIntegerType(2)), + new MixedType(false, new UnionType([ + new ConstantIntegerType(1), + new StringType(), + ])), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new IntegerType()), + new MixedType(false, new ConstantIntegerType(1)), + ], + MixedType::class, + 'mixed~1=implicit', + ], + [ + [ + new MixedType(false), + new MixedType(false, new ConstantIntegerType(1)), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new NullType()), + new UnionType([ + new StringType(), + new NullType(), + ]), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(), + new ObjectWithoutClassType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(), + new ObjectWithoutClassType(new ObjectType('A')), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new IntegerType()), + new ObjectWithoutClassType(new ObjectType('A')), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new ObjectType('A')), + new ObjectWithoutClassType(new ObjectType('A')), + ], + MixedType::class, + 'mixed~A=implicit', + ], + [ + [ + new MixedType(false, new NullType()), + new NullType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new IntegerType()), + new IntegerType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new ConstantIntegerType(1)), + new ConstantIntegerType(1), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new ObjectType('Exception')), + new ObjectType('Throwable'), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new ObjectType('Exception')), + new ObjectType('Exception'), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(false, new ObjectType('Exception')), + new ObjectType('InvalidArgumentException'), + ], + MixedType::class, + 'mixed=implicit', // should be MixedType~Exception+InvalidArgumentException + ], + [ + [ + new NullType(), + new MixedType(false, new NullType()), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(), + new MixedType(false, new NullType()), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ), + new ObjectType('DateTime'), + ], + UnionType::class, + 'DateTime|T (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + new ObjectType('DateTime'), + ], + ObjectType::class, + 'DateTime', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + ], + TemplateType::class, + 'T of DateTime (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'U', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'T of DateTime (function a(), parameter)|U of DateTime (function a(), parameter)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new IntegerType(), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new StringType(), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new IntegerType(), + new StringType(), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new UnionType([new IntegerType(), new StringType()]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new IntegerType(), + new StringType(), + new FloatType(), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new UnionType([new IntegerType(), new StringType(), new FloatType()]), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new UnionType([new ConstantIntegerType(1), new ConstantIntegerType(2)]), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new UnionType([new ConstantIntegerType(1), new ConstantIntegerType(2), new FloatType()]), + ], + UnionType::class, + 'float|int|string', + ], + [ + [ + new StringType(), + new ClassStringType(), + ], + StringType::class, + 'string', + ], + [ + [ + new ClassStringType(), + new ConstantStringType(\stdClass::class), + ], + ClassStringType::class, + 'class-string', + ], + [ + [ + new ClassStringType(), + new ConstantStringType('Nonexistent'), + ], + UnionType::class, + '\'Nonexistent\'|class-string', + ], + [ + [ + new ClassStringType(), + new IntegerType(), + ], + UnionType::class, + 'class-string|int', + ], + [ + [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\Exception::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ClassStringType(), + ], + ClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new StringType(), + ], + StringType::class, + 'string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Throwable::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\stdClass::class)), + ], + UnionType::class, + 'class-string|class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\Exception::class), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Throwable::class)), + new ConstantStringType(\Exception::class), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + new ConstantStringType(\Exception::class), + ], + UnionType::class, + '\'Exception\'|class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\stdClass::class), + ], + UnionType::class, + '\'stdClass\'|class-string', + ], + [ + [ + new StringType(), + new IntersectionType([new StringType(), new CallableType()]), + ], + StringType::class, + 'string', + ], + [ + [ + new IntersectionType([new StringType(), new CallableType()]), + new ConstantStringType('test_function'), + ], + UnionType::class, + '\'test_function\'|(callable(): mixed&string)', + ], + [ + [ + new IntersectionType([new StringType(), new CallableType()]), + new IntegerType(), + ], + UnionType::class, + '(callable(): mixed&string)|int', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + IntegerRangeType::fromInterval(2, 5), + ], + IntegerRangeType::class, + 'int<1, 5>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 2), + IntegerRangeType::fromInterval(3, 5), + ], + IntegerRangeType::class, + 'int<1, 5>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + IntegerRangeType::fromInterval(7, 9), + ], + UnionType::class, + 'int<1, 3>|int<7, 9>', + ], + [ + [ + IntegerRangeType::fromInterval(7, 9), + IntegerRangeType::fromInterval(1, 3), + ], + UnionType::class, + 'int<1, 3>|int<7, 9>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new ConstantIntegerType(3), + ], + IntegerRangeType::class, + 'int<1, 3>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new ConstantIntegerType(4), + ], + IntegerRangeType::class, + 'int<1, 4>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new ConstantIntegerType(5), + ], + UnionType::class, + '5|int<1, 3>', + ], + [ + [ + new UnionType([ + IntegerRangeType::fromInterval(null, 1), + IntegerRangeType::fromInterval(3, null), + ]), + new ConstantIntegerType(2), + ], + IntegerType::class, + 'int', + ], + [ + [ + new MixedType(), + new MixedType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(true), + new MixedType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(true), + new MixedType(true), + ], + MixedType::class, + 'mixed=explicit', + ], + [ + [ + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + ], + GenericObjectType::class, + 'PHPStan\Type\Variance\Invariant', + ], + [ + [ + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + UnionType::class, + 'PHPStan\Type\Variance\Invariant|PHPStan\Type\Variance\Invariant', + ], + [ + [ + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + GenericObjectType::class, + 'PHPStan\Type\Variance\Covariant', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new ObjectWithoutClassType(), + ], + ObjectWithoutClassType::class, + 'object', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new ObjectType(\stdClass::class), + ], + UnionType::class, + 'stdClass|T of object (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new MixedType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'K', + null, + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'K (function a(), parameter)|T (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'K', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'K of object (function a(), parameter)|T of object (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType(\Exception::class), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'K', + new ObjectType(\stdClass::class), + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'K of stdClass (function a(), parameter)|T of Exception (function a(), parameter)', + ], + [ + [ + new ObjectType(\DateTimeImmutable::class), + new ObjectType(\DateTimeInterface::class, new ObjectType(\DateTimeImmutable::class)), + ], + ObjectType::class, + \DateTimeInterface::class, + ], + [ + [ + new StringType(), + new MixedType(false, new StringType()), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new StringType(), + ]), + ], + UnionType::class, + 'array()|array(string)', + ], + [ + [ + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new StringType(), + ], 1, [0]), + ], + UnionType::class, + 'array()|array(?0 => string)', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('c'), + new ConstantStringType('d'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + ], + UnionType::class, + 'array(\'a\' => int, \'b\' => int)|array(\'c\' => int, \'d\' => int)', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + ], [ + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + ], + ConstantArrayType::class, + 'array(\'a\' => int, ?\'b\' => int)', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + new ConstantArrayType([ + new ConstantStringType('b'), + new ConstantStringType('c'), + ], [ + new IntegerType(), + new IntegerType(), + ]), + ], + UnionType::class, + 'array(\'a\' => int, \'b\' => int)|array(\'b\' => int, \'c\' => int)', + ], + [ + [ + TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), + TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), + ], + IntersectionType::class, + 'string&hasOffset(int)', + ], + [ + [ + TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), + TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), + ], + IntersectionType::class, + '\'abc\'&hasOffset(int)', + ], + [ + [ + StaticTypeFactory::falsey(), + StaticTypeFactory::falsey(), + ], + UnionType::class, + '0|0.0|\'\'|\'0\'|array()|false|null', + ], + [ + [ + StaticTypeFactory::truthy(), + StaticTypeFactory::truthy(), + ], + MixedType::class, + 'mixed~0|0.0|\'\'|\'0\'|array()|false|null=implicit', + ], + [ + [ + StaticTypeFactory::falsey(), + StaticTypeFactory::truthy(), + ], + MixedType::class, + 'mixed=implicit', + ], + ]; + } - $this->assertSame( - $expectedTypeDescription, - $actualTypeDescription, - sprintf('union(%s)', implode(', ', array_map( - static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, - $types - ))) - ); + /** + * @dataProvider dataUnion + * @param \PHPStan\Type\Type[] $types + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testUnion( + array $types, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $actualType = TypeCombinator::union(...$types); + $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); + if ($actualType instanceof MixedType) { + if ($actualType->isExplicitMixed()) { + $actualTypeDescription .= '=explicit'; + } else { + $actualTypeDescription .= '=implicit'; + } + } - $this->assertInstanceOf($expectedTypeClass, $actualType); + $this->assertSame( + $expectedTypeDescription, + $actualTypeDescription, + sprintf('union(%s)', implode(', ', array_map( + static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, + $types + ))) + ); - $hasSubtraction = false; - foreach ($types as $type) { - if (!($type instanceof SubtractableType) || $type->getSubtractedType() === null) { - continue; - } + $this->assertInstanceOf($expectedTypeClass, $actualType); - $hasSubtraction = true; - } + $hasSubtraction = false; + foreach ($types as $type) { + if (!($type instanceof SubtractableType) || $type->getSubtractedType() === null) { + continue; + } - if ($hasSubtraction) { - return; - } - } + $hasSubtraction = true; + } - /** - * @dataProvider dataUnion - * @param \PHPStan\Type\Type[] $types - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testUnionInversed( - array $types, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $types = array_reverse($types); - $actualType = TypeCombinator::union(...$types); - $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); - if ($actualType instanceof MixedType) { - if ($actualType->isExplicitMixed()) { - $actualTypeDescription .= '=explicit'; - } else { - $actualTypeDescription .= '=implicit'; - } - } - $this->assertSame( - $expectedTypeDescription, - $actualTypeDescription, - sprintf('union(%s)', implode(', ', array_map( - static function (Type $type): string { - return $type->describe(VerbosityLevel::precise()); - }, - $types - ))) - ); - $this->assertInstanceOf($expectedTypeClass, $actualType); - } + if ($hasSubtraction) { + return; + } + } - public function dataIntersect(): array - { - return [ - [ - [ - new IterableType(new MixedType(), new StringType()), - new ObjectType('ArrayObject'), - ], - IntersectionType::class, - 'ArrayObject&iterable', - ], - [ - [ - new IterableType(new MixedType(), new StringType()), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IterableType(new MixedType(true), new StringType()), - new ObjectType('Iterator'), - ], - IntersectionType::class, - 'iterable&Iterator', - ], - [ - [ - new ObjectType('Iterator'), - new IterableType( - new MixedType(true), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('_'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ) - ), - ], - IntersectionType::class, - 'iterable&Iterator', - ], - [ - [ - new ObjectType('Foo'), - new StaticType('Foo'), - ], - StaticType::class, - 'static(Foo)', - ], - [ - [ - new VoidType(), - new MixedType(), - ], - VoidType::class, - 'void', - ], - [ - [ - new ObjectType('UnknownClass'), - new ObjectType('UnknownClass'), - ], - ObjectType::class, - 'UnknownClass', - ], - [ - [ - new UnionType([new ObjectType('UnknownClassA'), new ObjectType('UnknownClassB')]), - new UnionType([new ObjectType('UnknownClassA'), new ObjectType('UnknownClassB')]), - ], - UnionType::class, - 'UnknownClassA|UnknownClassB', - ], - [ - [ - new ConstantBooleanType(true), - new BooleanType(), - ], - ConstantBooleanType::class, - 'true', - ], - [ - [ - new ConstantBooleanType(false), - new BooleanType(), - ], - ConstantBooleanType::class, - 'false', - ], - [ - [ - StaticTypeFactory::truthy(), - new BooleanType(), - ], - ConstantBooleanType::class, - 'true', - ], - [ - [ - StaticTypeFactory::falsey(), - new BooleanType(), - ], - ConstantBooleanType::class, - 'false', - ], - [ - [ - StaticTypeFactory::falsey(), - StaticTypeFactory::truthy(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new StringType(), - new NeverType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ObjectType('Iterator'), - new ObjectType('Countable'), - new ObjectType('Traversable'), - ], - IntersectionType::class, - 'Countable&Iterator', - ], - [ - [ - new ObjectType('Iterator'), - new ObjectType('Traversable'), - new ObjectType('Countable'), - ], - IntersectionType::class, - 'Countable&Iterator', - ], - [ - [ - new ObjectType('Traversable'), - new ObjectType('Iterator'), - new ObjectType('Countable'), - ], - IntersectionType::class, - 'Countable&Iterator', - ], - [ - [ - new IterableType(new MixedType(), new MixedType()), - new IterableType(new MixedType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new ArrayType(new MixedType(), new MixedType()), - new IterableType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ArrayType(new IntegerType(), new MixedType()), - new IterableType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new ArrayType(new IntegerType(), new MixedType()), - new IterableType(new StringType(), new MixedType()), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ArrayType(new MixedType(), new IntegerType()), - new IterableType(new MixedType(), new StringType()), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ArrayType(new IntegerType(), new MixedType()), - new ArrayType(new MixedType(), new StringType()), - ], - ArrayType::class, - 'array', - ], - [ - [ - new IterableType(new IntegerType(), new MixedType()), - new IterableType(new MixedType(), new StringType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new MixedType(), - new IterableType(new MixedType(), new MixedType()), - ], - IterableType::class, - 'iterable', - ], - [ - [ - new IntegerType(), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - IntegerType::class, - 'int', - ], - [ - [ - new ConstantIntegerType(1), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - ConstantIntegerType::class, - '1', - ], - [ - [ - new ConstantStringType('foo'), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - ConstantStringType::class, - '\'foo\'', - ], - [ - [ - new StringType(), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - StringType::class, - 'string', - ], - [ - [ - new UnionType([new StringType(), new IntegerType()]), - new BenevolentUnionType([new IntegerType(), new StringType()]), - ], - UnionType::class, - 'int|string', - ], - [ - [ - new ObjectType(\Test\Foo::class), - new HasMethodType('__toString'), - ], - IntersectionType::class, - 'Test\Foo&hasMethod(__toString)', - ], - [ - [ - new ObjectType(\Test\ClassWithToString::class), - new HasMethodType('__toString'), - ], - ObjectType::class, - 'Test\ClassWithToString', - ], - [ - [ - new ObjectType(\CheckTypeFunctionCall\FinalClassWithMethodExists::class), - new HasMethodType('doBar'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ObjectWithoutClassType(), - new HasMethodType('__toString'), - ], - IntersectionType::class, - 'object&hasMethod(__toString)', - ], - [ - [ - new IntegerType(), - new HasMethodType('__toString'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasMethodType('__toString'), - ]), - new HasMethodType('__toString'), - ], - IntersectionType::class, - 'object&hasMethod(__toString)', - ], - [ - [ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasMethodType('foo'), - ]), - new HasMethodType('bar'), - ], - IntersectionType::class, - 'object&hasMethod(bar)&hasMethod(foo)', - ], - [ - [ - new UnionType([ - new ObjectType(\Test\Foo::class), - new ObjectType(\Test\FirstInterface::class), - ]), - new HasMethodType('__toString'), - ], - UnionType::class, - '(Test\FirstInterface&hasMethod(__toString))|(Test\Foo&hasMethod(__toString))', - ], - [ - [ - new ObjectType(\Test\Foo::class), - new HasPropertyType('fooProperty'), - ], - IntersectionType::class, - 'Test\Foo&hasProperty(fooProperty)', - ], - [ - [ - new ObjectType(\Test\ClassWithNullableProperty::class), - new HasPropertyType('foo'), - ], - ObjectType::class, - 'Test\ClassWithNullableProperty', - ], - [ - [ - new ObjectType(\CheckTypeFunctionCall\FinalClassWithPropertyExists::class), - new HasPropertyType('barProperty'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ObjectWithoutClassType(), - new HasPropertyType('fooProperty'), - ], - IntersectionType::class, - 'object&hasProperty(fooProperty)', - ], - [ - [ - new IntegerType(), - new HasPropertyType('fooProperty'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType('fooProperty'), - ]), - new HasPropertyType('fooProperty'), - ], - IntersectionType::class, - 'object&hasProperty(fooProperty)', - ], - [ - [ - new IntersectionType([ - new ObjectWithoutClassType(), - new HasPropertyType('foo'), - ]), - new HasPropertyType('bar'), - ], - IntersectionType::class, - 'object&hasProperty(bar)&hasProperty(foo)', - ], - [ - [ - new UnionType([ - new ObjectType(\Test\Foo::class), - new ObjectType(\Test\FirstInterface::class), - ]), - new HasPropertyType('fooProperty'), - ], - UnionType::class, - '(Test\FirstInterface&hasProperty(fooProperty))|(Test\Foo&hasProperty(fooProperty))', - ], - [ - [ - new ArrayType(new StringType(), new StringType()), - new HasOffsetType(new ConstantStringType('a')), - ], - IntersectionType::class, - 'array&hasOffset(\'a\')', - ], - [ - [ - new ArrayType(new StringType(), new StringType()), - new HasOffsetType(new ConstantStringType('a')), - new HasOffsetType(new ConstantStringType('a')), - ], - IntersectionType::class, - 'array&hasOffset(\'a\')', - ], - [ - [ - new ArrayType(new StringType(), new StringType()), - new HasOffsetType(new StringType()), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], - [ - [ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], - [ - [ - new ConstantArrayType( - [new ConstantStringType('a')], - [new ConstantStringType('foo')] - ), - new HasOffsetType(new ConstantStringType('a')), - ], - ConstantArrayType::class, - 'array(\'a\' => \'foo\')', - ], - [ - [ - new ConstantArrayType( - [new ConstantStringType('a')], - [new ConstantStringType('foo')] - ), - new HasOffsetType(new ConstantStringType('b')), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ClosureType([], new MixedType(), false), - new HasOffsetType(new ConstantStringType('a')), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - TypeCombinator::union( - new ConstantArrayType( - [new ConstantStringType('a')], - [new ConstantStringType('foo')] - ), - new ConstantArrayType( - [new ConstantStringType('b')], - [new ConstantStringType('foo')] - ) - ), - new HasOffsetType(new ConstantStringType('b')), - ], - ConstantArrayType::class, - 'array(\'b\' => \'foo\')', - ], - [ - [ - TypeCombinator::union( - new ConstantArrayType( - [new ConstantStringType('a')], - [new ConstantStringType('foo')] - ), - new ClosureType([], new MixedType(), false) - ), - new HasOffsetType(new ConstantStringType('a')), - ], - ConstantArrayType::class, - 'array(\'a\' => \'foo\')', - ], - [ - [ - new ClosureType([], new MixedType(), false), - new ObjectType(\Closure::class), - ], - ClosureType::class, - 'Closure(): mixed', - ], - [ - [ - new ClosureType([], new MixedType(), false), - new CallableType(), - ], - ClosureType::class, - 'Closure(): mixed', - ], - [ - [ - new ClosureType([], new MixedType(), false), - new ObjectWithoutClassType(), - ], - ClosureType::class, - 'Closure(): mixed', - ], - [ - [ - new UnionType([ - new ArrayType(new MixedType(), new StringType()), - new NullType(), - ]), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], - [ - [ - new ArrayType(new MixedType(), new MixedType()), - new NonEmptyArrayType(), - ], - IntersectionType::class, - 'array&nonEmpty', - ], - [ - [ - new StringType(), - new NonEmptyArrayType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new NonEmptyArrayType(), - ]), - new NonEmptyArrayType(), - ], - IntersectionType::class, - 'array&nonEmpty', - ], - [ - [ - TypeCombinator::union( - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new StringType(), - ]) - ), - new NonEmptyArrayType(), - ], - ConstantArrayType::class, - 'array(string)', - ], - [ - [ - new ConstantArrayType([], []), - new NonEmptyArrayType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - ]), - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('bar')), - ]), - ], - IntersectionType::class, - 'array&hasOffset(\'bar\')&hasOffset(\'foo\')', - ], - [ - [ - new StringType(), - new IntegerType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new MixedType(false, new StringType()), - new StringType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new MixedType(false, new StringType()), - new ConstantStringType('foo'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new MixedType(false, new StringType()), - new ConstantIntegerType(1), - ], - ConstantIntegerType::class, - '1', - ], - [ - [ - new MixedType(false, new StringType()), - new MixedType(false, new IntegerType()), - ], - MixedType::class, - 'mixed~int|string=implicit', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ), - new ObjectType('DateTime'), - ], - IntersectionType::class, - 'DateTime&T (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - new ObjectType('DateTime'), - ], - TemplateObjectType::class, - 'T of DateTime (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - ], - TemplateType::class, - 'T of DateTime (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'U', - new ObjectType('DateTime'), - TemplateTypeVariance::createInvariant() - ), - ], - IntersectionType::class, - 'T of DateTime (function a(), parameter)&U of DateTime (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - null, - TemplateTypeVariance::createInvariant() - ), - new MixedType(), - ], - TemplateType::class, - 'T (function a(), parameter)=explicit', - ], - [ - [ - new StringType(), - new ClassStringType(), - ], - ClassStringType::class, - 'class-string', - ], - [ - [ - new ClassStringType(), - new ConstantStringType(\stdClass::class), - ], - ConstantStringType::class, - '\'stdClass\'', - ], - [ - [ - new ClassStringType(), - new ConstantStringType('Nonexistent'), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ClassStringType(), - new IntegerType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ConstantStringType(\Exception::class), - new GenericClassStringType(new ObjectType(\Exception::class)), - ], - ConstantStringType::class, - '\'Exception\'', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ClassStringType(), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new StringType(), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Exception::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\Throwable::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - ], - GenericClassStringType::class, - 'class-string', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new GenericClassStringType(new ObjectType(\stdClass::class)), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\Exception::class), - ], - ConstantStringType::class, - '\'Exception\'', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Throwable::class)), - new ConstantStringType(\Exception::class), - ], - ConstantStringType::class, - '\'Exception\'', - ], - [ - [ - new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), - new ConstantStringType(\Exception::class), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new GenericClassStringType(new ObjectType(\Exception::class)), - new ConstantStringType(\stdClass::class), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - IntegerRangeType::fromInterval(2, 5), - ], - IntegerRangeType::class, - 'int<2, 3>', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - IntegerRangeType::fromInterval(3, 5), - ], - ConstantIntegerType::class, - '3', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - IntegerRangeType::fromInterval(7, 9), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new ConstantIntegerType(3), - ], - ConstantIntegerType::class, - '3', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new ConstantIntegerType(4), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - IntegerRangeType::fromInterval(1, 3), - new IntegerType(), - ], - IntegerRangeType::class, - 'int<1, 3>', - ], - [ - [ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(), new MixedType()), - ], - ObjectType::class, - 'Traversable', - ], - [ - [ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(), new MixedType()), - ], - ObjectType::class, - 'Traversable', - ], - [ - [ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(), new MixedType(true)), - ], - IntersectionType::class, - 'iterable&Traversable', - ], - [ - [ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(true), new MixedType()), - ], - IntersectionType::class, - 'iterable&Traversable', - ], - [ - [ - new ObjectType(\Traversable::class), - new IterableType(new MixedType(true), new MixedType(true)), - ], - IntersectionType::class, - 'iterable&Traversable', - ], - [ - [ - new MixedType(), - new MixedType(), - ], - MixedType::class, - 'mixed=implicit', - ], - [ - [ - new MixedType(true), - new MixedType(), - ], - MixedType::class, - 'mixed=explicit', - ], - [ - [ - new MixedType(true), - new MixedType(true), - ], - MixedType::class, - 'mixed=explicit', - ], - [ - [ - new GenericObjectType(Variance\Covariant::class, [ - new ObjectType(\DateTimeInterface::class), - ]), - new GenericObjectType(Variance\Covariant::class, [ - new ObjectType(\DateTime::class), - ]), - ], - GenericObjectType::class, - 'PHPStan\Type\Variance\Covariant', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new ObjectWithoutClassType(), - ], - TemplateObjectWithoutClassType::class, - 'T of object (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new ObjectType(\stdClass::class), - ], - IntersectionType::class, - 'stdClass&T of object (function a(), parameter)', - ], - [ - [ - TemplateTypeFactory::create( - TemplateTypeScope::createWithFunction('a'), - 'T', - new ObjectWithoutClassType(), - TemplateTypeVariance::createInvariant() - ), - new MixedType(), - ], - TemplateObjectWithoutClassType::class, - 'T of object (function a(), parameter)', - ], - [ - [ - new ConstantStringType('NonexistentClass'), - new ClassStringType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ConstantStringType(\stdClass::class), - new ClassStringType(), - ], - ConstantStringType::class, - '\'stdClass\'', - ], - [ - [ - new ObjectType(\DateTimeInterface::class), - new ObjectType(\Iterator::class), - ], - IntersectionType::class, - 'DateTimeInterface&Iterator', - ], - [ - [ - new ObjectType(\DateTimeInterface::class), - new GenericObjectType(\Iterator::class, [new MixedType(), new MixedType()]), - ], - IntersectionType::class, - 'DateTimeInterface&Iterator', - ], - [ - [ - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new IntegerType(), - new IntegerType(), - ], 2, [0]), - new HasOffsetType(new ConstantStringType('a')), - ], - ConstantArrayType::class, - 'array(\'a\' => int, \'b\' => int)', - ], - [ - [ - new StringType(), - new HasOffsetType(new IntegerType()), - ], - IntersectionType::class, - 'string&hasOffset(int)', - ], - [ - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new MixedType(), - ], - BenevolentUnionType::class, - '(int|string)', - ], - [ - [ - new ConstantStringType('abc'), - new AccessoryNumericStringType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new ConstantStringType('123'), - new AccessoryNumericStringType(), - ], - ConstantStringType::class, - '\'123\'', - ], - [ - [ - new StringType(), - new AccessoryNumericStringType(), - ], - IntersectionType::class, - 'string&numeric', - ], - [ - [ - new IntegerType(), - new AccessoryNumericStringType(), - ], - NeverType::class, - '*NEVER*', - ], - [ - [ - new IntersectionType([ - new ArrayType(new StringType(), new IntegerType()), - new NonEmptyArrayType(), - ]), - new NeverType(), - ], - NeverType::class, - '*NEVER*', - ], - ]; - } + /** + * @dataProvider dataUnion + * @param \PHPStan\Type\Type[] $types + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testUnionInversed( + array $types, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $types = array_reverse($types); + $actualType = TypeCombinator::union(...$types); + $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); + if ($actualType instanceof MixedType) { + if ($actualType->isExplicitMixed()) { + $actualTypeDescription .= '=explicit'; + } else { + $actualTypeDescription .= '=implicit'; + } + } + $this->assertSame( + $expectedTypeDescription, + $actualTypeDescription, + sprintf('union(%s)', implode(', ', array_map( + static function (Type $type): string { + return $type->describe(VerbosityLevel::precise()); + }, + $types + ))) + ); + $this->assertInstanceOf($expectedTypeClass, $actualType); + } - /** - * @dataProvider dataIntersect - * @param \PHPStan\Type\Type[] $types - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testIntersect( - array $types, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $actualType = TypeCombinator::intersect(...$types); - $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); - if ($actualType instanceof MixedType) { - if ($actualType->isExplicitMixed()) { - $actualTypeDescription .= '=explicit'; - } else { - $actualTypeDescription .= '=implicit'; - } - } - $this->assertSame($expectedTypeDescription, $actualTypeDescription); - $this->assertInstanceOf($expectedTypeClass, $actualType); - } + public function dataIntersect(): array + { + return [ + [ + [ + new IterableType(new MixedType(), new StringType()), + new ObjectType('ArrayObject'), + ], + IntersectionType::class, + 'ArrayObject&iterable', + ], + [ + [ + new IterableType(new MixedType(), new StringType()), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IterableType(new MixedType(true), new StringType()), + new ObjectType('Iterator'), + ], + IntersectionType::class, + 'iterable&Iterator', + ], + [ + [ + new ObjectType('Iterator'), + new IterableType( + new MixedType(true), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('_'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ) + ), + ], + IntersectionType::class, + 'iterable&Iterator', + ], + [ + [ + new ObjectType('Foo'), + new StaticType('Foo'), + ], + StaticType::class, + 'static(Foo)', + ], + [ + [ + new VoidType(), + new MixedType(), + ], + VoidType::class, + 'void', + ], + [ + [ + new ObjectType('UnknownClass'), + new ObjectType('UnknownClass'), + ], + ObjectType::class, + 'UnknownClass', + ], + [ + [ + new UnionType([new ObjectType('UnknownClassA'), new ObjectType('UnknownClassB')]), + new UnionType([new ObjectType('UnknownClassA'), new ObjectType('UnknownClassB')]), + ], + UnionType::class, + 'UnknownClassA|UnknownClassB', + ], + [ + [ + new ConstantBooleanType(true), + new BooleanType(), + ], + ConstantBooleanType::class, + 'true', + ], + [ + [ + new ConstantBooleanType(false), + new BooleanType(), + ], + ConstantBooleanType::class, + 'false', + ], + [ + [ + StaticTypeFactory::truthy(), + new BooleanType(), + ], + ConstantBooleanType::class, + 'true', + ], + [ + [ + StaticTypeFactory::falsey(), + new BooleanType(), + ], + ConstantBooleanType::class, + 'false', + ], + [ + [ + StaticTypeFactory::falsey(), + StaticTypeFactory::truthy(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new StringType(), + new NeverType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ObjectType('Iterator'), + new ObjectType('Countable'), + new ObjectType('Traversable'), + ], + IntersectionType::class, + 'Countable&Iterator', + ], + [ + [ + new ObjectType('Iterator'), + new ObjectType('Traversable'), + new ObjectType('Countable'), + ], + IntersectionType::class, + 'Countable&Iterator', + ], + [ + [ + new ObjectType('Traversable'), + new ObjectType('Iterator'), + new ObjectType('Countable'), + ], + IntersectionType::class, + 'Countable&Iterator', + ], + [ + [ + new IterableType(new MixedType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new ArrayType(new MixedType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new IterableType(new StringType(), new MixedType()), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ArrayType(new MixedType(), new IntegerType()), + new IterableType(new MixedType(), new StringType()), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IterableType(new IntegerType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new MixedType(), + new IterableType(new MixedType(), new MixedType()), + ], + IterableType::class, + 'iterable', + ], + [ + [ + new IntegerType(), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + IntegerType::class, + 'int', + ], + [ + [ + new ConstantIntegerType(1), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + ConstantIntegerType::class, + '1', + ], + [ + [ + new ConstantStringType('foo'), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + ConstantStringType::class, + '\'foo\'', + ], + [ + [ + new StringType(), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + StringType::class, + 'string', + ], + [ + [ + new UnionType([new StringType(), new IntegerType()]), + new BenevolentUnionType([new IntegerType(), new StringType()]), + ], + UnionType::class, + 'int|string', + ], + [ + [ + new ObjectType(\Test\Foo::class), + new HasMethodType('__toString'), + ], + IntersectionType::class, + 'Test\Foo&hasMethod(__toString)', + ], + [ + [ + new ObjectType(\Test\ClassWithToString::class), + new HasMethodType('__toString'), + ], + ObjectType::class, + 'Test\ClassWithToString', + ], + [ + [ + new ObjectType(\CheckTypeFunctionCall\FinalClassWithMethodExists::class), + new HasMethodType('doBar'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ObjectWithoutClassType(), + new HasMethodType('__toString'), + ], + IntersectionType::class, + 'object&hasMethod(__toString)', + ], + [ + [ + new IntegerType(), + new HasMethodType('__toString'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasMethodType('__toString'), + ]), + new HasMethodType('__toString'), + ], + IntersectionType::class, + 'object&hasMethod(__toString)', + ], + [ + [ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasMethodType('foo'), + ]), + new HasMethodType('bar'), + ], + IntersectionType::class, + 'object&hasMethod(bar)&hasMethod(foo)', + ], + [ + [ + new UnionType([ + new ObjectType(\Test\Foo::class), + new ObjectType(\Test\FirstInterface::class), + ]), + new HasMethodType('__toString'), + ], + UnionType::class, + '(Test\FirstInterface&hasMethod(__toString))|(Test\Foo&hasMethod(__toString))', + ], + [ + [ + new ObjectType(\Test\Foo::class), + new HasPropertyType('fooProperty'), + ], + IntersectionType::class, + 'Test\Foo&hasProperty(fooProperty)', + ], + [ + [ + new ObjectType(\Test\ClassWithNullableProperty::class), + new HasPropertyType('foo'), + ], + ObjectType::class, + 'Test\ClassWithNullableProperty', + ], + [ + [ + new ObjectType(\CheckTypeFunctionCall\FinalClassWithPropertyExists::class), + new HasPropertyType('barProperty'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ObjectWithoutClassType(), + new HasPropertyType('fooProperty'), + ], + IntersectionType::class, + 'object&hasProperty(fooProperty)', + ], + [ + [ + new IntegerType(), + new HasPropertyType('fooProperty'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType('fooProperty'), + ]), + new HasPropertyType('fooProperty'), + ], + IntersectionType::class, + 'object&hasProperty(fooProperty)', + ], + [ + [ + new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType('foo'), + ]), + new HasPropertyType('bar'), + ], + IntersectionType::class, + 'object&hasProperty(bar)&hasProperty(foo)', + ], + [ + [ + new UnionType([ + new ObjectType(\Test\Foo::class), + new ObjectType(\Test\FirstInterface::class), + ]), + new HasPropertyType('fooProperty'), + ], + UnionType::class, + '(Test\FirstInterface&hasProperty(fooProperty))|(Test\Foo&hasProperty(fooProperty))', + ], + [ + [ + new ArrayType(new StringType(), new StringType()), + new HasOffsetType(new ConstantStringType('a')), + ], + IntersectionType::class, + 'array&hasOffset(\'a\')', + ], + [ + [ + new ArrayType(new StringType(), new StringType()), + new HasOffsetType(new ConstantStringType('a')), + new HasOffsetType(new ConstantStringType('a')), + ], + IntersectionType::class, + 'array&hasOffset(\'a\')', + ], + [ + [ + new ArrayType(new StringType(), new StringType()), + new HasOffsetType(new StringType()), + new HasOffsetType(new StringType()), + ], + IntersectionType::class, + 'array&hasOffset(string)', + ], + [ + [ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new StringType()), + new HasOffsetType(new StringType()), + ], + IntersectionType::class, + 'array&hasOffset(string)', + ], + [ + [ + new ConstantArrayType( + [new ConstantStringType('a')], + [new ConstantStringType('foo')] + ), + new HasOffsetType(new ConstantStringType('a')), + ], + ConstantArrayType::class, + 'array(\'a\' => \'foo\')', + ], + [ + [ + new ConstantArrayType( + [new ConstantStringType('a')], + [new ConstantStringType('foo')] + ), + new HasOffsetType(new ConstantStringType('b')), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ClosureType([], new MixedType(), false), + new HasOffsetType(new ConstantStringType('a')), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + TypeCombinator::union( + new ConstantArrayType( + [new ConstantStringType('a')], + [new ConstantStringType('foo')] + ), + new ConstantArrayType( + [new ConstantStringType('b')], + [new ConstantStringType('foo')] + ) + ), + new HasOffsetType(new ConstantStringType('b')), + ], + ConstantArrayType::class, + 'array(\'b\' => \'foo\')', + ], + [ + [ + TypeCombinator::union( + new ConstantArrayType( + [new ConstantStringType('a')], + [new ConstantStringType('foo')] + ), + new ClosureType([], new MixedType(), false) + ), + new HasOffsetType(new ConstantStringType('a')), + ], + ConstantArrayType::class, + 'array(\'a\' => \'foo\')', + ], + [ + [ + new ClosureType([], new MixedType(), false), + new ObjectType(\Closure::class), + ], + ClosureType::class, + 'Closure(): mixed', + ], + [ + [ + new ClosureType([], new MixedType(), false), + new CallableType(), + ], + ClosureType::class, + 'Closure(): mixed', + ], + [ + [ + new ClosureType([], new MixedType(), false), + new ObjectWithoutClassType(), + ], + ClosureType::class, + 'Closure(): mixed', + ], + [ + [ + new UnionType([ + new ArrayType(new MixedType(), new StringType()), + new NullType(), + ]), + new HasOffsetType(new StringType()), + ], + IntersectionType::class, + 'array&hasOffset(string)', + ], + [ + [ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ], + IntersectionType::class, + 'array&nonEmpty', + ], + [ + [ + new StringType(), + new NonEmptyArrayType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + new NonEmptyArrayType(), + ], + IntersectionType::class, + 'array&nonEmpty', + ], + [ + [ + TypeCombinator::union( + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new StringType(), + ]) + ), + new NonEmptyArrayType(), + ], + ConstantArrayType::class, + 'array(string)', + ], + [ + [ + new ConstantArrayType([], []), + new NonEmptyArrayType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('bar')), + ]), + ], + IntersectionType::class, + 'array&hasOffset(\'bar\')&hasOffset(\'foo\')', + ], + [ + [ + new StringType(), + new IntegerType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new MixedType(false, new StringType()), + new StringType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new MixedType(false, new StringType()), + new ConstantStringType('foo'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new MixedType(false, new StringType()), + new ConstantIntegerType(1), + ], + ConstantIntegerType::class, + '1', + ], + [ + [ + new MixedType(false, new StringType()), + new MixedType(false, new IntegerType()), + ], + MixedType::class, + 'mixed~int|string=implicit', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ), + new ObjectType('DateTime'), + ], + IntersectionType::class, + 'DateTime&T (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + new ObjectType('DateTime'), + ], + TemplateObjectType::class, + 'T of DateTime (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + ], + TemplateType::class, + 'T of DateTime (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'U', + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() + ), + ], + IntersectionType::class, + 'T of DateTime (function a(), parameter)&U of DateTime (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ), + new MixedType(), + ], + TemplateType::class, + 'T (function a(), parameter)=explicit', + ], + [ + [ + new StringType(), + new ClassStringType(), + ], + ClassStringType::class, + 'class-string', + ], + [ + [ + new ClassStringType(), + new ConstantStringType(\stdClass::class), + ], + ConstantStringType::class, + '\'stdClass\'', + ], + [ + [ + new ClassStringType(), + new ConstantStringType('Nonexistent'), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ClassStringType(), + new IntegerType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ConstantStringType(\Exception::class), + new GenericClassStringType(new ObjectType(\Exception::class)), + ], + ConstantStringType::class, + '\'Exception\'', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ClassStringType(), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new StringType(), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Throwable::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + ], + GenericClassStringType::class, + 'class-string', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\stdClass::class)), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\Exception::class), + ], + ConstantStringType::class, + '\'Exception\'', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Throwable::class)), + new ConstantStringType(\Exception::class), + ], + ConstantStringType::class, + '\'Exception\'', + ], + [ + [ + new GenericClassStringType(new ObjectType(\InvalidArgumentException::class)), + new ConstantStringType(\Exception::class), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new ConstantStringType(\stdClass::class), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + IntegerRangeType::fromInterval(2, 5), + ], + IntegerRangeType::class, + 'int<2, 3>', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + IntegerRangeType::fromInterval(3, 5), + ], + ConstantIntegerType::class, + '3', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + IntegerRangeType::fromInterval(7, 9), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new ConstantIntegerType(3), + ], + ConstantIntegerType::class, + '3', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new ConstantIntegerType(4), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + IntegerRangeType::fromInterval(1, 3), + new IntegerType(), + ], + IntegerRangeType::class, + 'int<1, 3>', + ], + [ + [ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(), new MixedType()), + ], + ObjectType::class, + 'Traversable', + ], + [ + [ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(), new MixedType()), + ], + ObjectType::class, + 'Traversable', + ], + [ + [ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(), new MixedType(true)), + ], + IntersectionType::class, + 'iterable&Traversable', + ], + [ + [ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(true), new MixedType()), + ], + IntersectionType::class, + 'iterable&Traversable', + ], + [ + [ + new ObjectType(\Traversable::class), + new IterableType(new MixedType(true), new MixedType(true)), + ], + IntersectionType::class, + 'iterable&Traversable', + ], + [ + [ + new MixedType(), + new MixedType(), + ], + MixedType::class, + 'mixed=implicit', + ], + [ + [ + new MixedType(true), + new MixedType(), + ], + MixedType::class, + 'mixed=explicit', + ], + [ + [ + new MixedType(true), + new MixedType(true), + ], + MixedType::class, + 'mixed=explicit', + ], + [ + [ + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + GenericObjectType::class, + 'PHPStan\Type\Variance\Covariant', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new ObjectWithoutClassType(), + ], + TemplateObjectWithoutClassType::class, + 'T of object (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new ObjectType(\stdClass::class), + ], + IntersectionType::class, + 'stdClass&T of object (function a(), parameter)', + ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'T', + new ObjectWithoutClassType(), + TemplateTypeVariance::createInvariant() + ), + new MixedType(), + ], + TemplateObjectWithoutClassType::class, + 'T of object (function a(), parameter)', + ], + [ + [ + new ConstantStringType('NonexistentClass'), + new ClassStringType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ConstantStringType(\stdClass::class), + new ClassStringType(), + ], + ConstantStringType::class, + '\'stdClass\'', + ], + [ + [ + new ObjectType(\DateTimeInterface::class), + new ObjectType(\Iterator::class), + ], + IntersectionType::class, + 'DateTimeInterface&Iterator', + ], + [ + [ + new ObjectType(\DateTimeInterface::class), + new GenericObjectType(\Iterator::class, [new MixedType(), new MixedType()]), + ], + IntersectionType::class, + 'DateTimeInterface&Iterator', + ], + [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new IntegerType(), + ], 2, [0]), + new HasOffsetType(new ConstantStringType('a')), + ], + ConstantArrayType::class, + 'array(\'a\' => int, \'b\' => int)', + ], + [ + [ + new StringType(), + new HasOffsetType(new IntegerType()), + ], + IntersectionType::class, + 'string&hasOffset(int)', + ], + [ + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new MixedType(), + ], + BenevolentUnionType::class, + '(int|string)', + ], + [ + [ + new ConstantStringType('abc'), + new AccessoryNumericStringType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ConstantStringType('123'), + new AccessoryNumericStringType(), + ], + ConstantStringType::class, + '\'123\'', + ], + [ + [ + new StringType(), + new AccessoryNumericStringType(), + ], + IntersectionType::class, + 'string&numeric', + ], + [ + [ + new IntegerType(), + new AccessoryNumericStringType(), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new IntersectionType([ + new ArrayType(new StringType(), new IntegerType()), + new NonEmptyArrayType(), + ]), + new NeverType(), + ], + NeverType::class, + '*NEVER*', + ], + ]; + } - /** - * @dataProvider dataIntersect - * @param \PHPStan\Type\Type[] $types - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testIntersectInversed( - array $types, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $actualType = TypeCombinator::intersect(...array_reverse($types)); - $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); - if ($actualType instanceof MixedType) { - if ($actualType->isExplicitMixed()) { - $actualTypeDescription .= '=explicit'; - } else { - $actualTypeDescription .= '=implicit'; - } - } - $this->assertSame($expectedTypeDescription, $actualTypeDescription); - $this->assertInstanceOf($expectedTypeClass, $actualType); - } + /** + * @dataProvider dataIntersect + * @param \PHPStan\Type\Type[] $types + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testIntersect( + array $types, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $actualType = TypeCombinator::intersect(...$types); + $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); + if ($actualType instanceof MixedType) { + if ($actualType->isExplicitMixed()) { + $actualTypeDescription .= '=explicit'; + } else { + $actualTypeDescription .= '=implicit'; + } + } + $this->assertSame($expectedTypeDescription, $actualTypeDescription); + $this->assertInstanceOf($expectedTypeClass, $actualType); + } - public function dataRemove(): array - { - return [ - [ - new ConstantBooleanType(true), - new ConstantBooleanType(true), - NeverType::class, - '*NEVER*', - ], - [ - new UnionType([ - new IntegerType(), - new ConstantBooleanType(true), - ]), - new ConstantBooleanType(true), - IntegerType::class, - 'int', - ], - [ - new UnionType([ - new ObjectType('Foo'), - new ObjectType('Bar'), - ]), - new ObjectType('Foo'), - ObjectType::class, - 'Bar', - ], - [ - new UnionType([ - new ObjectType('Foo'), - new ObjectType('Bar'), - new ObjectType('Baz'), - ]), - new ObjectType('Foo'), - UnionType::class, - 'Bar|Baz', - ], - [ - new UnionType([ - new ArrayType(new MixedType(), new StringType()), - new ArrayType(new MixedType(), new IntegerType()), - new ObjectType('ArrayObject'), - ]), - new ArrayType(new MixedType(), new IntegerType()), - UnionType::class, - 'array|ArrayObject', - ], - [ - new ConstantBooleanType(true), - new ConstantBooleanType(false), - ConstantBooleanType::class, - 'true', - ], - [ - new ConstantBooleanType(false), - new ConstantBooleanType(true), - ConstantBooleanType::class, - 'false', - ], - [ - new ConstantBooleanType(true), - new BooleanType(), - NeverType::class, - '*NEVER*', - ], - [ - new ConstantBooleanType(false), - new BooleanType(), - NeverType::class, - '*NEVER*', - ], - [ - new BooleanType(), - new ConstantBooleanType(true), - ConstantBooleanType::class, - 'false', - ], - [ - new BooleanType(), - new ConstantBooleanType(false), - ConstantBooleanType::class, - 'true', - ], - [ - new BooleanType(), - new BooleanType(), - NeverType::class, - '*NEVER*', - ], - [ - StaticTypeFactory::falsey(), - StaticTypeFactory::falsey(), - NeverType::class, - '*NEVER*', - ], - [ - StaticTypeFactory::truthy(), - StaticTypeFactory::truthy(), - NeverType::class, - '*NEVER*', - ], - [ - StaticTypeFactory::truthy(), - StaticTypeFactory::falsey(), - MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array()|false|null', - ], - [ - StaticTypeFactory::falsey(), - StaticTypeFactory::truthy(), - UnionType::class, - '0|0.0|\'\'|\'0\'|array()|false|null', - ], - [ - new BooleanType(), - StaticTypeFactory::falsey(), - ConstantBooleanType::class, - 'true', - ], - [ - new BooleanType(), - StaticTypeFactory::truthy(), - ConstantBooleanType::class, - 'false', - ], - [ - new UnionType([ - new ConstantBooleanType(true), - new IntegerType(), - ]), - new BooleanType(), - IntegerType::class, - 'int', - ], - [ - new UnionType([ - new ConstantBooleanType(false), - new IntegerType(), - ]), - new BooleanType(), - IntegerType::class, - 'int', - ], - [ - new UnionType([ - new BooleanType(), - new IntegerType(), - ]), - new ConstantBooleanType(true), - UnionType::class, - 'int|false', - ], - [ - new UnionType([ - new BooleanType(), - new IntegerType(), - ]), - new ConstantBooleanType(false), - UnionType::class, - 'int|true', - ], - [ - new UnionType([ - new StringType(), - new IntegerType(), - new NullType(), - ]), - new UnionType([ - new NullType(), - new StringType(), - ]), - IntegerType::class, - 'int', - ], - [ - new IterableType(new MixedType(), new MixedType()), - new ArrayType(new MixedType(), new MixedType()), - ObjectType::class, - 'Traversable', - ], - [ - new IterableType(new MixedType(), new MixedType()), - new ObjectType(\Traversable::class), - ArrayType::class, - 'array', - ], - [ - new IterableType(new MixedType(), new MixedType()), - new ObjectType(\Iterator::class), - IterableType::class, - 'iterable', - ], - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new StringType(), - IntegerType::class, - 'int', - ], - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new IntegerType(), - StringType::class, - 'string', - ], - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new ConstantStringType('foo'), - UnionType::class, - 'int|string', - ], - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new ConstantIntegerType(1), - UnionType::class, - 'int|int<2, max>|string', - ], - [ - new BenevolentUnionType([new IntegerType(), new StringType()]), - new UnionType([new IntegerType(), new StringType()]), - NeverType::class, - '*NEVER*', - ], - [ - new ArrayType(new MixedType(), new MixedType()), - new ConstantArrayType([], []), - IntersectionType::class, - 'array&nonEmpty', - ], - [ - TypeCombinator::union( - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new StringType(), - ]) - ), - new ConstantArrayType([], []), - ConstantArrayType::class, - 'array(string)', - ], - [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new NonEmptyArrayType(), - ]), - new NonEmptyArrayType(), - NeverType::class, - '*NEVER*', - ], - [ - new ArrayType(new MixedType(), new MixedType()), - new NonEmptyArrayType(), - ConstantArrayType::class, - 'array()', - ], - [ - new ArrayType(new MixedType(), new MixedType()), - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new ConstantStringType('foo')), - ]), - ArrayType::class, - 'array', - ], - [ - new MixedType(), - new IntegerType(), - MixedType::class, - 'mixed~int', - ], - [ - new MixedType(false, new IntegerType()), - new IntegerType(), - MixedType::class, - 'mixed~int', - ], - [ - new MixedType(false, new IntegerType()), - new StringType(), - MixedType::class, - 'mixed~int|string', - ], - [ - new MixedType(false), - new MixedType(), - NeverType::class, - '*NEVER*', - ], - [ - new MixedType(false, new StringType()), - new MixedType(), - NeverType::class, - '*NEVER*', - ], - [ - new MixedType(false), - new MixedType(false, new StringType()), - StringType::class, - 'string', - ], - [ - new MixedType(false, new StringType()), - new NeverType(), - MixedType::class, - 'mixed~string', - ], - [ - new ObjectType('Exception'), - new ObjectType('InvalidArgumentException'), - ObjectType::class, - 'Exception~InvalidArgumentException', - ], - [ - new ObjectType('Exception', new ObjectType('InvalidArgumentException')), - new ObjectType('LengthException'), - ObjectType::class, - 'Exception~InvalidArgumentException|LengthException', - ], - [ - new ObjectType('Exception'), - new ObjectType('Throwable'), - NeverType::class, - '*NEVER*', - ], - [ - new ObjectType('Exception', new ObjectType('InvalidArgumentException')), - new ObjectType('InvalidArgumentException'), - ObjectType::class, - 'Exception~InvalidArgumentException', - ], - [ - IntegerRangeType::fromInterval(3, 7), - IntegerRangeType::fromInterval(2, 4), - IntegerRangeType::class, - 'int<5, 7>', - ], - [ - IntegerRangeType::fromInterval(3, 7), - IntegerRangeType::fromInterval(3, 4), - IntegerRangeType::class, - 'int<5, 7>', - ], - [ - IntegerRangeType::fromInterval(3, 7), - IntegerRangeType::fromInterval(5, 7), - IntegerRangeType::class, - 'int<3, 4>', - ], - [ - IntegerRangeType::fromInterval(3, 7), - new ConstantIntegerType(3), - IntegerRangeType::class, - 'int<4, 7>', - ], - [ - new IntegerType(), - IntegerRangeType::fromInterval(null, 7), - IntegerRangeType::class, - 'int<8, max>', - ], - [ - IntegerRangeType::fromInterval(0, 2), - IntegerRangeType::fromInterval(-1, 3), - NeverType::class, - '*NEVER*', - ], - [ - IntegerRangeType::fromInterval(0, 2), - IntegerRangeType::fromInterval(0, 3), - NeverType::class, - '*NEVER*', - ], - [ - IntegerRangeType::fromInterval(0, 2), - IntegerRangeType::fromInterval(-1, 2), - NeverType::class, - '*NEVER*', - ], - [ - IntegerRangeType::fromInterval(0, 2), - new IntegerType(), - NeverType::class, - '*NEVER*', - ], - [ - IntegerRangeType::fromInterval(null, 1), - IntegerRangeType::fromInterval(4, null), - IntegerRangeType::class, - 'int', - ], - [ - IntegerRangeType::fromInterval(1, null), - IntegerRangeType::fromInterval(null, -4), - IntegerRangeType::class, - 'int<1, max>', - ], - [ - new UnionType([ - IntegerRangeType::fromInterval(3, null), - IntegerRangeType::fromInterval(null, 1), - ]), - IntegerRangeType::fromInterval(4, null), - UnionType::class, - '3|int', - ], - [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new StringType(), - new StringType(), - ], 2), - new HasOffsetType(new ConstantIntegerType(1)), - NeverType::class, - '*NEVER*', - ], - [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new StringType(), - new StringType(), - ], 2, [1]), - new HasOffsetType(new ConstantIntegerType(1)), - ConstantArrayType::class, - 'array(string)', - ], - [ - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new StringType(), - new StringType(), - ], 2, [1]), - new HasOffsetType(new ConstantIntegerType(0)), - NeverType::class, - '*NEVER*', - ], - [ - new MixedType(), - new NeverType(), - MixedType::class, - 'mixed', - ], - [ - new ObjectWithoutClassType(), - new NeverType(), - ObjectWithoutClassType::class, - 'object', - ], - [ - new ObjectType(\stdClass::class), - new NeverType(), - ObjectType::class, - 'stdClass', - ], - ]; - } + /** + * @dataProvider dataIntersect + * @param \PHPStan\Type\Type[] $types + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testIntersectInversed( + array $types, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $actualType = TypeCombinator::intersect(...array_reverse($types)); + $actualTypeDescription = $actualType->describe(VerbosityLevel::precise()); + if ($actualType instanceof MixedType) { + if ($actualType->isExplicitMixed()) { + $actualTypeDescription .= '=explicit'; + } else { + $actualTypeDescription .= '=implicit'; + } + } + $this->assertSame($expectedTypeDescription, $actualTypeDescription); + $this->assertInstanceOf($expectedTypeClass, $actualType); + } - /** - * @dataProvider dataRemove - * @param \PHPStan\Type\Type $fromType - * @param \PHPStan\Type\Type $type - * @param class-string<\PHPStan\Type\Type> $expectedTypeClass - * @param string $expectedTypeDescription - */ - public function testRemove( - Type $fromType, - Type $type, - string $expectedTypeClass, - string $expectedTypeDescription - ): void - { - $result = TypeCombinator::remove($fromType, $type); - $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); - $this->assertInstanceOf($expectedTypeClass, $result); - } + public function dataRemove(): array + { + return [ + [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + NeverType::class, + '*NEVER*', + ], + [ + new UnionType([ + new IntegerType(), + new ConstantBooleanType(true), + ]), + new ConstantBooleanType(true), + IntegerType::class, + 'int', + ], + [ + new UnionType([ + new ObjectType('Foo'), + new ObjectType('Bar'), + ]), + new ObjectType('Foo'), + ObjectType::class, + 'Bar', + ], + [ + new UnionType([ + new ObjectType('Foo'), + new ObjectType('Bar'), + new ObjectType('Baz'), + ]), + new ObjectType('Foo'), + UnionType::class, + 'Bar|Baz', + ], + [ + new UnionType([ + new ArrayType(new MixedType(), new StringType()), + new ArrayType(new MixedType(), new IntegerType()), + new ObjectType('ArrayObject'), + ]), + new ArrayType(new MixedType(), new IntegerType()), + UnionType::class, + 'array|ArrayObject', + ], + [ + new ConstantBooleanType(true), + new ConstantBooleanType(false), + ConstantBooleanType::class, + 'true', + ], + [ + new ConstantBooleanType(false), + new ConstantBooleanType(true), + ConstantBooleanType::class, + 'false', + ], + [ + new ConstantBooleanType(true), + new BooleanType(), + NeverType::class, + '*NEVER*', + ], + [ + new ConstantBooleanType(false), + new BooleanType(), + NeverType::class, + '*NEVER*', + ], + [ + new BooleanType(), + new ConstantBooleanType(true), + ConstantBooleanType::class, + 'false', + ], + [ + new BooleanType(), + new ConstantBooleanType(false), + ConstantBooleanType::class, + 'true', + ], + [ + new BooleanType(), + new BooleanType(), + NeverType::class, + '*NEVER*', + ], + [ + StaticTypeFactory::falsey(), + StaticTypeFactory::falsey(), + NeverType::class, + '*NEVER*', + ], + [ + StaticTypeFactory::truthy(), + StaticTypeFactory::truthy(), + NeverType::class, + '*NEVER*', + ], + [ + StaticTypeFactory::truthy(), + StaticTypeFactory::falsey(), + MixedType::class, + 'mixed~0|0.0|\'\'|\'0\'|array()|false|null', + ], + [ + StaticTypeFactory::falsey(), + StaticTypeFactory::truthy(), + UnionType::class, + '0|0.0|\'\'|\'0\'|array()|false|null', + ], + [ + new BooleanType(), + StaticTypeFactory::falsey(), + ConstantBooleanType::class, + 'true', + ], + [ + new BooleanType(), + StaticTypeFactory::truthy(), + ConstantBooleanType::class, + 'false', + ], + [ + new UnionType([ + new ConstantBooleanType(true), + new IntegerType(), + ]), + new BooleanType(), + IntegerType::class, + 'int', + ], + [ + new UnionType([ + new ConstantBooleanType(false), + new IntegerType(), + ]), + new BooleanType(), + IntegerType::class, + 'int', + ], + [ + new UnionType([ + new BooleanType(), + new IntegerType(), + ]), + new ConstantBooleanType(true), + UnionType::class, + 'int|false', + ], + [ + new UnionType([ + new BooleanType(), + new IntegerType(), + ]), + new ConstantBooleanType(false), + UnionType::class, + 'int|true', + ], + [ + new UnionType([ + new StringType(), + new IntegerType(), + new NullType(), + ]), + new UnionType([ + new NullType(), + new StringType(), + ]), + IntegerType::class, + 'int', + ], + [ + new IterableType(new MixedType(), new MixedType()), + new ArrayType(new MixedType(), new MixedType()), + ObjectType::class, + 'Traversable', + ], + [ + new IterableType(new MixedType(), new MixedType()), + new ObjectType(\Traversable::class), + ArrayType::class, + 'array', + ], + [ + new IterableType(new MixedType(), new MixedType()), + new ObjectType(\Iterator::class), + IterableType::class, + 'iterable', + ], + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new StringType(), + IntegerType::class, + 'int', + ], + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new IntegerType(), + StringType::class, + 'string', + ], + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new ConstantStringType('foo'), + UnionType::class, + 'int|string', + ], + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new ConstantIntegerType(1), + UnionType::class, + 'int|int<2, max>|string', + ], + [ + new BenevolentUnionType([new IntegerType(), new StringType()]), + new UnionType([new IntegerType(), new StringType()]), + NeverType::class, + '*NEVER*', + ], + [ + new ArrayType(new MixedType(), new MixedType()), + new ConstantArrayType([], []), + IntersectionType::class, + 'array&nonEmpty', + ], + [ + TypeCombinator::union( + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new StringType(), + ]) + ), + new ConstantArrayType([], []), + ConstantArrayType::class, + 'array(string)', + ], + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + new NonEmptyArrayType(), + NeverType::class, + '*NEVER*', + ], + [ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ConstantArrayType::class, + 'array()', + ], + [ + new ArrayType(new MixedType(), new MixedType()), + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + ArrayType::class, + 'array', + ], + [ + new MixedType(), + new IntegerType(), + MixedType::class, + 'mixed~int', + ], + [ + new MixedType(false, new IntegerType()), + new IntegerType(), + MixedType::class, + 'mixed~int', + ], + [ + new MixedType(false, new IntegerType()), + new StringType(), + MixedType::class, + 'mixed~int|string', + ], + [ + new MixedType(false), + new MixedType(), + NeverType::class, + '*NEVER*', + ], + [ + new MixedType(false, new StringType()), + new MixedType(), + NeverType::class, + '*NEVER*', + ], + [ + new MixedType(false), + new MixedType(false, new StringType()), + StringType::class, + 'string', + ], + [ + new MixedType(false, new StringType()), + new NeverType(), + MixedType::class, + 'mixed~string', + ], + [ + new ObjectType('Exception'), + new ObjectType('InvalidArgumentException'), + ObjectType::class, + 'Exception~InvalidArgumentException', + ], + [ + new ObjectType('Exception', new ObjectType('InvalidArgumentException')), + new ObjectType('LengthException'), + ObjectType::class, + 'Exception~InvalidArgumentException|LengthException', + ], + [ + new ObjectType('Exception'), + new ObjectType('Throwable'), + NeverType::class, + '*NEVER*', + ], + [ + new ObjectType('Exception', new ObjectType('InvalidArgumentException')), + new ObjectType('InvalidArgumentException'), + ObjectType::class, + 'Exception~InvalidArgumentException', + ], + [ + IntegerRangeType::fromInterval(3, 7), + IntegerRangeType::fromInterval(2, 4), + IntegerRangeType::class, + 'int<5, 7>', + ], + [ + IntegerRangeType::fromInterval(3, 7), + IntegerRangeType::fromInterval(3, 4), + IntegerRangeType::class, + 'int<5, 7>', + ], + [ + IntegerRangeType::fromInterval(3, 7), + IntegerRangeType::fromInterval(5, 7), + IntegerRangeType::class, + 'int<3, 4>', + ], + [ + IntegerRangeType::fromInterval(3, 7), + new ConstantIntegerType(3), + IntegerRangeType::class, + 'int<4, 7>', + ], + [ + new IntegerType(), + IntegerRangeType::fromInterval(null, 7), + IntegerRangeType::class, + 'int<8, max>', + ], + [ + IntegerRangeType::fromInterval(0, 2), + IntegerRangeType::fromInterval(-1, 3), + NeverType::class, + '*NEVER*', + ], + [ + IntegerRangeType::fromInterval(0, 2), + IntegerRangeType::fromInterval(0, 3), + NeverType::class, + '*NEVER*', + ], + [ + IntegerRangeType::fromInterval(0, 2), + IntegerRangeType::fromInterval(-1, 2), + NeverType::class, + '*NEVER*', + ], + [ + IntegerRangeType::fromInterval(0, 2), + new IntegerType(), + NeverType::class, + '*NEVER*', + ], + [ + IntegerRangeType::fromInterval(null, 1), + IntegerRangeType::fromInterval(4, null), + IntegerRangeType::class, + 'int', + ], + [ + IntegerRangeType::fromInterval(1, null), + IntegerRangeType::fromInterval(null, -4), + IntegerRangeType::class, + 'int<1, max>', + ], + [ + new UnionType([ + IntegerRangeType::fromInterval(3, null), + IntegerRangeType::fromInterval(null, 1), + ]), + IntegerRangeType::fromInterval(4, null), + UnionType::class, + '3|int', + ], + [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new StringType(), + new StringType(), + ], 2), + new HasOffsetType(new ConstantIntegerType(1)), + NeverType::class, + '*NEVER*', + ], + [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new StringType(), + new StringType(), + ], 2, [1]), + new HasOffsetType(new ConstantIntegerType(1)), + ConstantArrayType::class, + 'array(string)', + ], + [ + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new StringType(), + new StringType(), + ], 2, [1]), + new HasOffsetType(new ConstantIntegerType(0)), + NeverType::class, + '*NEVER*', + ], + [ + new MixedType(), + new NeverType(), + MixedType::class, + 'mixed', + ], + [ + new ObjectWithoutClassType(), + new NeverType(), + ObjectWithoutClassType::class, + 'object', + ], + [ + new ObjectType(\stdClass::class), + new NeverType(), + ObjectType::class, + 'stdClass', + ], + ]; + } - public function testSpecificUnionConstantArray(): void - { - $arrays = []; - for ($i = 0; $i < 5; $i++) { - $array = new ConstantArrayType([], []); - for ($j = 0; $j < 5; $j++) { - $arrays[] = $array = $array->setOffsetValueType(new ConstantIntegerType($j), new StringType()); - if ($i !== $j) { - continue; - } + /** + * @dataProvider dataRemove + * @param \PHPStan\Type\Type $fromType + * @param \PHPStan\Type\Type $type + * @param class-string<\PHPStan\Type\Type> $expectedTypeClass + * @param string $expectedTypeDescription + */ + public function testRemove( + Type $fromType, + Type $type, + string $expectedTypeClass, + string $expectedTypeDescription + ): void { + $result = TypeCombinator::remove($fromType, $type); + $this->assertSame($expectedTypeDescription, $result->describe(VerbosityLevel::precise())); + $this->assertInstanceOf($expectedTypeClass, $result); + } - $arrays[] = $array = $array->setOffsetValueType(new ConstantStringType('test'), new StringType()); - } - } - $resultType = TypeCombinator::union(...$arrays); - $this->assertInstanceOf(ConstantArrayType::class, $resultType); - $this->assertSame('array(0 => string, ?\'test\' => string, ?1 => string, ?2 => string, ?3 => string, ?4 => string)', $resultType->describe(VerbosityLevel::precise())); - } + public function testSpecificUnionConstantArray(): void + { + $arrays = []; + for ($i = 0; $i < 5; $i++) { + $array = new ConstantArrayType([], []); + for ($j = 0; $j < 5; $j++) { + $arrays[] = $array = $array->setOffsetValueType(new ConstantIntegerType($j), new StringType()); + if ($i !== $j) { + continue; + } + $arrays[] = $array = $array->setOffsetValueType(new ConstantStringType('test'), new StringType()); + } + } + $resultType = TypeCombinator::union(...$arrays); + $this->assertInstanceOf(ConstantArrayType::class, $resultType); + $this->assertSame('array(0 => string, ?\'test\' => string, ?1 => string, ?2 => string, ?3 => string, ?4 => string)', $resultType->describe(VerbosityLevel::precise())); + } } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 1d8894733e..ac02f730a8 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -1,4 +1,6 @@ -isCallable(); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataSelfCompare(): \Iterator - { - $broker = $this->createBroker(); - - $integerType = new IntegerType(); - $stringType = new StringType(); - $mixedType = new MixedType(); - $constantStringType = new ConstantStringType('foo'); - $constantIntegerType = new ConstantIntegerType(42); - $templateTypeScope = TemplateTypeScope::createWithClass('Foo'); - - $mixedParam = new NativeParameterReflection('foo', false, $mixedType, PassedByReference::createNo(), false, null); - $integerParam = new NativeParameterReflection('n', false, $integerType, PassedByReference::createNo(), false, null); - - yield [new AccessoryNumericStringType()]; - yield [new ArrayType($integerType, $stringType)]; - yield [new ArrayType($stringType, $mixedType)]; - yield [new BenevolentUnionType([$integerType, $stringType])]; - yield [new BooleanType()]; - yield [new CallableType()]; - yield [new CallableType([$mixedParam, $integerParam], $stringType, false)]; - yield [new ClassStringType()]; - yield [new ClosureType([$mixedParam, $integerParam], $stringType, false)]; - yield [new ConstantArrayType([$constantStringType, $constantIntegerType], [$mixedType, $stringType], 10, [1])]; - yield [new ConstantBooleanType(true)]; - yield [new ConstantFloatType(3.14)]; - yield [$constantIntegerType]; - yield [$constantStringType]; - yield [new ErrorType()]; - yield [new FloatType()]; - yield [new GenericClassStringType(new ObjectType(\Exception::class))]; - yield [new GenericObjectType('Foo', [new ObjectType('DateTime')])]; - yield [new HasMethodType('Foo')]; - yield [new HasOffsetType($constantStringType)]; - yield [new HasPropertyType('foo')]; - yield [IntegerRangeType::fromInterval(3, 10)]; - yield [$integerType]; - yield [new IntersectionType([new HasMethodType('Foo'), new HasPropertyType('bar')])]; - yield [new IterableType($integerType, $stringType)]; - yield [$mixedType]; - yield [new NeverType()]; - yield [new NonEmptyArrayType()]; - yield [new NonexistentParentClassType()]; - yield [new NullType()]; - yield [new ObjectType('Foo')]; - yield [new ObjectWithoutClassType(new ObjectType('Foo'))]; - yield [new ResourceType()]; - yield [new StaticType('Foo')]; - yield [new StrictMixedType()]; - yield [new StringAlwaysAcceptingObjectWithToStringType()]; - yield [$stringType]; - yield [TemplateTypeFactory::create($templateTypeScope, 'T', null, TemplateTypeVariance::createInvariant())]; - yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectType('Foo'), TemplateTypeVariance::createInvariant())]; - yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectWithoutClassType(), TemplateTypeVariance::createInvariant())]; - yield [new ThisType($broker->getClass('Foo'))]; - yield [new UnionType([$integerType, $stringType])]; - yield [new VoidType()]; - } - - /** - * @dataProvider dataSelfCompare - * - * @param Type $type - */ - public function testSelfCompare(Type $type): void - { - $description = $type->describe(VerbosityLevel::precise()); - $this->assertTrue( - $type->equals($type), - sprintf('%s -> equals(itself)', $description) - ); - $this->assertEquals( - 'Yes', - $type->isSuperTypeOf($type)->describe(), - sprintf('%s -> isSuperTypeOf(itself)', $description) - ); - $this->assertInstanceOf( - get_class($type), - TypeCombinator::union($type, $type), - sprintf('%s -> union with itself is same type', $description) - ); - } - - public function dataIsSuperTypeOf(): \Iterator - { - $unionTypeA = new UnionType([ - new IntegerType(), - new StringType(), - ]); - - yield [ - $unionTypeA, - $unionTypeA->getTypes()[0], - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - $unionTypeA->getTypes()[1], - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - $unionTypeA, - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new StringType(), new CallableType()]), - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - new MixedType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new CallableType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new UnionType([new IntegerType(), new FloatType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new UnionType([new CallableType(), new FloatType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new MixedType(), new CallableType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new FloatType(), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new UnionType([new ConstantBooleanType(true), new FloatType()]), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new IterableType(new MixedType(), new MixedType()), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), - TrinaryLogic::createNo(), - ]; - - $unionTypeB = new UnionType([ - new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('DatePeriod')), - ]), - new ArrayType(new MixedType(), new ObjectType('DatePeriod')), - ]); - - yield [ - $unionTypeB, - $unionTypeB->getTypes()[0], - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - $unionTypeB->getTypes()[1], - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - $unionTypeB, - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - new MixedType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new ObjectType('ArrayObject'), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new IterableType(new MixedType(), new ObjectType('DatePeriod')), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new IterableType(new MixedType(), new MixedType()), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new StringType(), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeB, - new IntegerType(), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeB, - new ObjectType('Foo'), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeB, - new IterableType(new MixedType(), new ObjectType('DateTime')), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeB, - new CallableType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new IntersectionType([new MixedType(), new CallableType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new IntersectionType([new StringType(), new CallableType()]), - TrinaryLogic::createNo(), - ]; - } - - /** - * @dataProvider dataIsSuperTypeOf - * @param UnionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSuperTypeOf(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSuperTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - public function dataIsSubTypeOf(): \Iterator - { - $unionTypeA = new UnionType([ - new IntegerType(), - new StringType(), - ]); - - yield [ - $unionTypeA, - $unionTypeA, - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - new UnionType(array_merge($unionTypeA->getTypes(), [new ResourceType()])), - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - new MixedType(), - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeA, - $unionTypeA->getTypes()[0], - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - $unionTypeA->getTypes()[1], - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new CallableType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new UnionType([new IntegerType(), new FloatType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new UnionType([new CallableType(), new FloatType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new StringType(), new CallableType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new MixedType(), new CallableType()]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeA, - new FloatType(), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new UnionType([new ConstantBooleanType(true), new FloatType()]), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new IterableType(new MixedType(), new MixedType()), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeA, - new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), - TrinaryLogic::createNo(), - ]; - - $unionTypeB = new UnionType([ - new IntersectionType([ - new ObjectType('ArrayObject'), - new IterableType(new MixedType(), new ObjectType('Item')), - new CallableType(), - ]), - new ArrayType(new MixedType(), new ObjectType('Item')), - ]); - - yield [ - $unionTypeB, - $unionTypeB, - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - new UnionType(array_merge($unionTypeB->getTypes(), [new StringType()])), - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - new MixedType(), - TrinaryLogic::createYes(), - ]; - - yield [ - $unionTypeB, - $unionTypeB->getTypes()[0], - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - $unionTypeB->getTypes()[1], - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new ObjectType('ArrayObject'), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new CallableType(), - TrinaryLogic::createMaybe(), - ]; - - yield [ - $unionTypeB, - new FloatType(), - TrinaryLogic::createNo(), - ]; - - yield [ - $unionTypeB, - new ObjectType('Foo'), - TrinaryLogic::createNo(), - ]; - } - - /** - * @dataProvider dataIsSubTypeOf - * @param UnionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOf(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $type->isSubTypeOf($otherType); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) - ); - } - - /** - * @dataProvider dataIsSubTypeOf - * @param UnionType $type - * @param Type $otherType - * @param TrinaryLogic $expectedResult - */ - public function testIsSubTypeOfInversed(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void - { - $actualResult = $otherType->isSuperTypeOf($type); - $this->assertSame( - $expectedResult->describe(), - $actualResult->describe(), - sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) - ); - } - - public function dataDescribe(): array - { - return [ - [ - new UnionType([new IntegerType(), new StringType()]), - 'int|string', - 'int|string', - ], - [ - new UnionType([new IntegerType(), new StringType(), new NullType()]), - 'int|string|null', - 'int|string|null', - ], - [ - new UnionType([ - new ConstantStringType('1aaa'), - new ConstantStringType('11aaa'), - new ConstantStringType('2aaa'), - new ConstantStringType('10aaa'), - new ConstantIntegerType(2), - new ConstantIntegerType(1), - new ConstantIntegerType(10), - new ConstantFloatType(2.2), - new NullType(), - new ConstantStringType('10'), - new ObjectType(\stdClass::class), - new ConstantBooleanType(true), - new ConstantStringType('foo'), - new ConstantStringType('2'), - new ConstantStringType('1'), - ]), - "1|2|2.2|10|'1'|'10'|'10aaa'|'11aaa'|'1aaa'|'2'|'2aaa'|'foo'|stdClass|true|null", - 'float|int|stdClass|string|true|null', - ], - [ - TypeCombinator::union( - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new StringType(), - new BooleanType(), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new IntegerType(), - new FloatType(), - ]), - new ConstantStringType('aaa') - ), - '\'aaa\'|array(\'a\' => int|string, \'b\' => bool|float)', - 'array|string', - ], - [ - TypeCombinator::union( - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new StringType(), - new BooleanType(), - ]), - new ConstantArrayType([ - new ConstantStringType('b'), - new ConstantStringType('c'), - ], [ - new IntegerType(), - new FloatType(), - ]), - new ConstantStringType('aaa') - ), - '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'b\' => int, \'c\' => float)', - 'array|string', - ], - [ - TypeCombinator::union( - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - ], [ - new StringType(), - new BooleanType(), - ]), - new ConstantArrayType([ - new ConstantStringType('c'), - new ConstantStringType('d'), - ], [ - new IntegerType(), - new FloatType(), - ]), - new ConstantStringType('aaa') - ), - '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'c\' => int, \'d\' => float)', - 'array|string', - ], - [ - TypeCombinator::union( - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new StringType(), - ]), - new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ], [ - new IntegerType(), - new BooleanType(), - new FloatType(), - ]) - ), - 'array(0 => int|string, ?1 => bool, ?2 => float)', - 'array', - ], - [ - TypeCombinator::union( - new ConstantArrayType([], []), - new ConstantArrayType([ - new ConstantStringType('foooo'), - ], [ - new ConstantStringType('barrr'), - ]) - ), - 'array()|array(\'foooo\' => \'barrr\')', - 'array', - ], - [ - TypeCombinator::union( - new IntegerType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]) - ), - 'int|(string&numeric)', - 'int|string', - ], - ]; - } - - /** - * @dataProvider dataDescribe - * @param Type $type - * @param string $expectedValueDescription - * @param string $expectedTypeOnlyDescription - */ - public function testDescribe( - Type $type, - string $expectedValueDescription, - string $expectedTypeOnlyDescription - ): void - { - $this->assertSame($expectedValueDescription, $type->describe(VerbosityLevel::precise())); - $this->assertSame($expectedTypeOnlyDescription, $type->describe(VerbosityLevel::typeOnly())); - } - - public function dataAccepts(): array - { - return [ - [ - new UnionType([new CallableType(), new NullType()]), - new ClosureType([], new StringType(), false), - TrinaryLogic::createYes(), - ], - [ - new UnionType([new CallableType(), new NullType()]), - new UnionType([new ClosureType([], new StringType(), false), new BooleanType()]), - TrinaryLogic::createMaybe(), - ], - [ - new UnionType([new CallableType(), new NullType()]), - new BooleanType(), - TrinaryLogic::createNo(), - ], - [ - new UnionType([ - new CallableType([ - new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), - ], new BooleanType(), false), - new NullType(), - ]), - new CallableType(), - TrinaryLogic::createYes(), - ], - [ - new UnionType([ - new CallableType([ - new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), - ], new BooleanType(), false), - new NullType(), - ]), - new BooleanType(), - TrinaryLogic::createNo(), - ], - [ - new UnionType([ - new ClosureType([ - new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), - ], new BooleanType(), false), - new NullType(), - ]), - new CallableType(), - TrinaryLogic::createMaybe(), - ], - [ - new UnionType([ - new ClosureType([ - new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), - new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), - ], new BooleanType(), false), - new NullType(), - ]), - new ClosureType([], new MixedType(), false), - TrinaryLogic::createYes(), - ], - ]; - } - - /** - * @dataProvider dataAccepts - * @param UnionType $type - * @param Type $acceptedType - * @param TrinaryLogic $expectedResult - */ - public function testAccepts( - UnionType $type, - Type $acceptedType, - TrinaryLogic $expectedResult - ): void - { - $this->assertSame( - $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), - sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) - ); - } - - public function dataHasMethod(): array - { - return [ - [ - new UnionType([new ObjectType(\DateTimeImmutable::class), new IntegerType()]), - 'format', - TrinaryLogic::createMaybe(), - ], - [ - new UnionType([new ObjectType(\DateTimeImmutable::class), new ObjectType(\DateTime::class)]), - 'format', - TrinaryLogic::createYes(), - ], - [ - new UnionType([new FloatType(), new IntegerType()]), - 'format', - TrinaryLogic::createNo(), - ], - [ - new UnionType([new ObjectType(\DateTimeImmutable::class), new NullType()]), - 'format', - TrinaryLogic::createMaybe(), - ], - ]; - } - - /** - * @dataProvider dataHasMethod - * @param UnionType $type - * @param string $methodName - * @param TrinaryLogic $expectedResult - */ - public function testHasMethod( - UnionType $type, - string $methodName, - TrinaryLogic $expectedResult - ): void - { - $this->assertSame($expectedResult->describe(), $type->hasMethod($methodName)->describe()); - } - - public function testSorting(): void - { - $types = [ - new ConstantBooleanType(false), - new ConstantBooleanType(true), - new ConstantIntegerType(-1), - new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantFloatType(-1.0), - new ConstantFloatType(0.0), - new ConstantFloatType(1.0), - new ConstantStringType(''), - new ConstantStringType('a'), - new ConstantStringType('b'), - new ConstantArrayType([], []), - new ConstantArrayType([new ConstantStringType('')], [new ConstantStringType('')]), - new IntegerType(), - IntegerRangeType::fromInterval(10, 20), - IntegerRangeType::fromInterval(30, 40), - new FloatType(), - new StringType(), - new ClassStringType(), - new MixedType(), - ]; - - $type1 = new UnionType($types); - $type2 = new UnionType(array_reverse($types)); - - $this->assertTrue( - $type1->equals($type2), - 'UnionType sorting always produces the same order' - ); - } - + public function dataIsCallable(): array + { + return [ + [ + TypeCombinator::union( + new ConstantArrayType( + [new ConstantIntegerType(0), new ConstantIntegerType(1)], + [new ConstantStringType('Closure'), new ConstantStringType('bind')] + ), + new ConstantStringType('array_push') + ), + TrinaryLogic::createYes(), + ], + [ + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new ObjectType('Closure'), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new StringType(), + new IntegerType(), + ]), + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataIsCallable + * @param UnionType $type + * @param TrinaryLogic $expectedResult + */ + public function testIsCallable(UnionType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isCallable(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isCallable()', $type->describe(VerbosityLevel::precise())) + ); + } + + public function dataSelfCompare(): \Iterator + { + $broker = $this->createBroker(); + + $integerType = new IntegerType(); + $stringType = new StringType(); + $mixedType = new MixedType(); + $constantStringType = new ConstantStringType('foo'); + $constantIntegerType = new ConstantIntegerType(42); + $templateTypeScope = TemplateTypeScope::createWithClass('Foo'); + + $mixedParam = new NativeParameterReflection('foo', false, $mixedType, PassedByReference::createNo(), false, null); + $integerParam = new NativeParameterReflection('n', false, $integerType, PassedByReference::createNo(), false, null); + + yield [new AccessoryNumericStringType()]; + yield [new ArrayType($integerType, $stringType)]; + yield [new ArrayType($stringType, $mixedType)]; + yield [new BenevolentUnionType([$integerType, $stringType])]; + yield [new BooleanType()]; + yield [new CallableType()]; + yield [new CallableType([$mixedParam, $integerParam], $stringType, false)]; + yield [new ClassStringType()]; + yield [new ClosureType([$mixedParam, $integerParam], $stringType, false)]; + yield [new ConstantArrayType([$constantStringType, $constantIntegerType], [$mixedType, $stringType], 10, [1])]; + yield [new ConstantBooleanType(true)]; + yield [new ConstantFloatType(3.14)]; + yield [$constantIntegerType]; + yield [$constantStringType]; + yield [new ErrorType()]; + yield [new FloatType()]; + yield [new GenericClassStringType(new ObjectType(\Exception::class))]; + yield [new GenericObjectType('Foo', [new ObjectType('DateTime')])]; + yield [new HasMethodType('Foo')]; + yield [new HasOffsetType($constantStringType)]; + yield [new HasPropertyType('foo')]; + yield [IntegerRangeType::fromInterval(3, 10)]; + yield [$integerType]; + yield [new IntersectionType([new HasMethodType('Foo'), new HasPropertyType('bar')])]; + yield [new IterableType($integerType, $stringType)]; + yield [$mixedType]; + yield [new NeverType()]; + yield [new NonEmptyArrayType()]; + yield [new NonexistentParentClassType()]; + yield [new NullType()]; + yield [new ObjectType('Foo')]; + yield [new ObjectWithoutClassType(new ObjectType('Foo'))]; + yield [new ResourceType()]; + yield [new StaticType('Foo')]; + yield [new StrictMixedType()]; + yield [new StringAlwaysAcceptingObjectWithToStringType()]; + yield [$stringType]; + yield [TemplateTypeFactory::create($templateTypeScope, 'T', null, TemplateTypeVariance::createInvariant())]; + yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectType('Foo'), TemplateTypeVariance::createInvariant())]; + yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectWithoutClassType(), TemplateTypeVariance::createInvariant())]; + yield [new ThisType($broker->getClass('Foo'))]; + yield [new UnionType([$integerType, $stringType])]; + yield [new VoidType()]; + } + + /** + * @dataProvider dataSelfCompare + * + * @param Type $type + */ + public function testSelfCompare(Type $type): void + { + $description = $type->describe(VerbosityLevel::precise()); + $this->assertTrue( + $type->equals($type), + sprintf('%s -> equals(itself)', $description) + ); + $this->assertEquals( + 'Yes', + $type->isSuperTypeOf($type)->describe(), + sprintf('%s -> isSuperTypeOf(itself)', $description) + ); + $this->assertInstanceOf( + get_class($type), + TypeCombinator::union($type, $type), + sprintf('%s -> union with itself is same type', $description) + ); + } + + public function dataIsSuperTypeOf(): \Iterator + { + $unionTypeA = new UnionType([ + new IntegerType(), + new StringType(), + ]); + + yield [ + $unionTypeA, + $unionTypeA->getTypes()[0], + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + $unionTypeA->getTypes()[1], + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + $unionTypeA, + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new StringType(), new CallableType()]), + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + new MixedType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new CallableType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new UnionType([new IntegerType(), new FloatType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new UnionType([new CallableType(), new FloatType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new MixedType(), new CallableType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new FloatType(), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new UnionType([new ConstantBooleanType(true), new FloatType()]), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new IterableType(new MixedType(), new MixedType()), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), + TrinaryLogic::createNo(), + ]; + + $unionTypeB = new UnionType([ + new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('DatePeriod')), + ]), + new ArrayType(new MixedType(), new ObjectType('DatePeriod')), + ]); + + yield [ + $unionTypeB, + $unionTypeB->getTypes()[0], + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + $unionTypeB->getTypes()[1], + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + $unionTypeB, + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + new MixedType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new ObjectType('ArrayObject'), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new IterableType(new MixedType(), new ObjectType('DatePeriod')), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new IterableType(new MixedType(), new MixedType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new StringType(), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeB, + new IntegerType(), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeB, + new ObjectType('Foo'), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeB, + new IterableType(new MixedType(), new ObjectType('DateTime')), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeB, + new CallableType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new IntersectionType([new MixedType(), new CallableType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new IntersectionType([new StringType(), new CallableType()]), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataIsSuperTypeOf + * @param UnionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSuperTypeOf(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSuperTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + public function dataIsSubTypeOf(): \Iterator + { + $unionTypeA = new UnionType([ + new IntegerType(), + new StringType(), + ]); + + yield [ + $unionTypeA, + $unionTypeA, + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + new UnionType(array_merge($unionTypeA->getTypes(), [new ResourceType()])), + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + new MixedType(), + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeA, + $unionTypeA->getTypes()[0], + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + $unionTypeA->getTypes()[1], + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new CallableType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new UnionType([new IntegerType(), new FloatType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new UnionType([new CallableType(), new FloatType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new StringType(), new CallableType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new MixedType(), new CallableType()]), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeA, + new FloatType(), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new UnionType([new ConstantBooleanType(true), new FloatType()]), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new IterableType(new MixedType(), new MixedType()), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeA, + new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), + TrinaryLogic::createNo(), + ]; + + $unionTypeB = new UnionType([ + new IntersectionType([ + new ObjectType('ArrayObject'), + new IterableType(new MixedType(), new ObjectType('Item')), + new CallableType(), + ]), + new ArrayType(new MixedType(), new ObjectType('Item')), + ]); + + yield [ + $unionTypeB, + $unionTypeB, + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + new UnionType(array_merge($unionTypeB->getTypes(), [new StringType()])), + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + new MixedType(), + TrinaryLogic::createYes(), + ]; + + yield [ + $unionTypeB, + $unionTypeB->getTypes()[0], + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + $unionTypeB->getTypes()[1], + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new ObjectType('ArrayObject'), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new CallableType(), + TrinaryLogic::createMaybe(), + ]; + + yield [ + $unionTypeB, + new FloatType(), + TrinaryLogic::createNo(), + ]; + + yield [ + $unionTypeB, + new ObjectType('Foo'), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataIsSubTypeOf + * @param UnionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOf(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isSubTypeOf($otherType); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSubTypeOf(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + + /** + * @dataProvider dataIsSubTypeOf + * @param UnionType $type + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testIsSubTypeOfInversed(UnionType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $otherType->isSuperTypeOf($type); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isSuperTypeOf(%s)', $otherType->describe(VerbosityLevel::precise()), $type->describe(VerbosityLevel::precise())) + ); + } + + public function dataDescribe(): array + { + return [ + [ + new UnionType([new IntegerType(), new StringType()]), + 'int|string', + 'int|string', + ], + [ + new UnionType([new IntegerType(), new StringType(), new NullType()]), + 'int|string|null', + 'int|string|null', + ], + [ + new UnionType([ + new ConstantStringType('1aaa'), + new ConstantStringType('11aaa'), + new ConstantStringType('2aaa'), + new ConstantStringType('10aaa'), + new ConstantIntegerType(2), + new ConstantIntegerType(1), + new ConstantIntegerType(10), + new ConstantFloatType(2.2), + new NullType(), + new ConstantStringType('10'), + new ObjectType(\stdClass::class), + new ConstantBooleanType(true), + new ConstantStringType('foo'), + new ConstantStringType('2'), + new ConstantStringType('1'), + ]), + "1|2|2.2|10|'1'|'10'|'10aaa'|'11aaa'|'1aaa'|'2'|'2aaa'|'foo'|stdClass|true|null", + 'float|int|stdClass|string|true|null', + ], + [ + TypeCombinator::union( + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new StringType(), + new BooleanType(), + ]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new FloatType(), + ]), + new ConstantStringType('aaa') + ), + '\'aaa\'|array(\'a\' => int|string, \'b\' => bool|float)', + 'array|string', + ], + [ + TypeCombinator::union( + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new StringType(), + new BooleanType(), + ]), + new ConstantArrayType([ + new ConstantStringType('b'), + new ConstantStringType('c'), + ], [ + new IntegerType(), + new FloatType(), + ]), + new ConstantStringType('aaa') + ), + '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'b\' => int, \'c\' => float)', + 'array|string', + ], + [ + TypeCombinator::union( + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new StringType(), + new BooleanType(), + ]), + new ConstantArrayType([ + new ConstantStringType('c'), + new ConstantStringType('d'), + ], [ + new IntegerType(), + new FloatType(), + ]), + new ConstantStringType('aaa') + ), + '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'c\' => int, \'d\' => float)', + 'array|string', + ], + [ + TypeCombinator::union( + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new StringType(), + ]), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ], [ + new IntegerType(), + new BooleanType(), + new FloatType(), + ]) + ), + 'array(0 => int|string, ?1 => bool, ?2 => float)', + 'array', + ], + [ + TypeCombinator::union( + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantStringType('foooo'), + ], [ + new ConstantStringType('barrr'), + ]) + ), + 'array()|array(\'foooo\' => \'barrr\')', + 'array', + ], + [ + TypeCombinator::union( + new IntegerType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]) + ), + 'int|(string&numeric)', + 'int|string', + ], + ]; + } + + /** + * @dataProvider dataDescribe + * @param Type $type + * @param string $expectedValueDescription + * @param string $expectedTypeOnlyDescription + */ + public function testDescribe( + Type $type, + string $expectedValueDescription, + string $expectedTypeOnlyDescription + ): void { + $this->assertSame($expectedValueDescription, $type->describe(VerbosityLevel::precise())); + $this->assertSame($expectedTypeOnlyDescription, $type->describe(VerbosityLevel::typeOnly())); + } + + public function dataAccepts(): array + { + return [ + [ + new UnionType([new CallableType(), new NullType()]), + new ClosureType([], new StringType(), false), + TrinaryLogic::createYes(), + ], + [ + new UnionType([new CallableType(), new NullType()]), + new UnionType([new ClosureType([], new StringType(), false), new BooleanType()]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([new CallableType(), new NullType()]), + new BooleanType(), + TrinaryLogic::createNo(), + ], + [ + new UnionType([ + new CallableType([ + new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), + ], new BooleanType(), false), + new NullType(), + ]), + new CallableType(), + TrinaryLogic::createYes(), + ], + [ + new UnionType([ + new CallableType([ + new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), + ], new BooleanType(), false), + new NullType(), + ]), + new BooleanType(), + TrinaryLogic::createNo(), + ], + [ + new UnionType([ + new ClosureType([ + new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), + ], new BooleanType(), false), + new NullType(), + ]), + new CallableType(), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new ClosureType([ + new NativeParameterReflection('a', false, new IntegerType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new StringType(), PassedByReference::createNo(), false, null), + new NativeParameterReflection('a', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null), + ], new BooleanType(), false), + new NullType(), + ]), + new ClosureType([], new MixedType(), false), + TrinaryLogic::createYes(), + ], + ]; + } + + /** + * @dataProvider dataAccepts + * @param UnionType $type + * @param Type $acceptedType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts( + UnionType $type, + Type $acceptedType, + TrinaryLogic $expectedResult + ): void { + $this->assertSame( + $expectedResult->describe(), + $type->accepts($acceptedType, true)->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())) + ); + } + + public function dataHasMethod(): array + { + return [ + [ + new UnionType([new ObjectType(\DateTimeImmutable::class), new IntegerType()]), + 'format', + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([new ObjectType(\DateTimeImmutable::class), new ObjectType(\DateTime::class)]), + 'format', + TrinaryLogic::createYes(), + ], + [ + new UnionType([new FloatType(), new IntegerType()]), + 'format', + TrinaryLogic::createNo(), + ], + [ + new UnionType([new ObjectType(\DateTimeImmutable::class), new NullType()]), + 'format', + TrinaryLogic::createMaybe(), + ], + ]; + } + + /** + * @dataProvider dataHasMethod + * @param UnionType $type + * @param string $methodName + * @param TrinaryLogic $expectedResult + */ + public function testHasMethod( + UnionType $type, + string $methodName, + TrinaryLogic $expectedResult + ): void { + $this->assertSame($expectedResult->describe(), $type->hasMethod($methodName)->describe()); + } + + public function testSorting(): void + { + $types = [ + new ConstantBooleanType(false), + new ConstantBooleanType(true), + new ConstantIntegerType(-1), + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantFloatType(-1.0), + new ConstantFloatType(0.0), + new ConstantFloatType(1.0), + new ConstantStringType(''), + new ConstantStringType('a'), + new ConstantStringType('b'), + new ConstantArrayType([], []), + new ConstantArrayType([new ConstantStringType('')], [new ConstantStringType('')]), + new IntegerType(), + IntegerRangeType::fromInterval(10, 20), + IntegerRangeType::fromInterval(30, 40), + new FloatType(), + new StringType(), + new ClassStringType(), + new MixedType(), + ]; + + $type1 = new UnionType($types); + $type2 = new UnionType(array_reverse($types)); + + $this->assertTrue( + $type1->equals($type2), + 'UnionType sorting always produces the same order' + ); + } } diff --git a/tests/PHPStan/Type/data/DoctrineIntersectionTypeIsSupertypeOfCollection.php b/tests/PHPStan/Type/data/DoctrineIntersectionTypeIsSupertypeOfCollection.php index 47ab77a225..cd3bad1c1e 100644 --- a/tests/PHPStan/Type/data/DoctrineIntersectionTypeIsSupertypeOfCollection.php +++ b/tests/PHPStan/Type/data/DoctrineIntersectionTypeIsSupertypeOfCollection.php @@ -14,5 +14,4 @@ */ interface Collection extends Countable, IteratorAggregate, ArrayAccess { - } diff --git a/tests/PHPStan/Type/data/TestIntersectionTypeIsSupertypeOfCollection.php b/tests/PHPStan/Type/data/TestIntersectionTypeIsSupertypeOfCollection.php index 0bc220e675..c7e39f58d3 100644 --- a/tests/PHPStan/Type/data/TestIntersectionTypeIsSupertypeOfCollection.php +++ b/tests/PHPStan/Type/data/TestIntersectionTypeIsSupertypeOfCollection.php @@ -14,5 +14,4 @@ */ interface Collection extends Countable, IteratorAggregate, ArrayAccess { - } diff --git a/tests/PHPStan/Type/data/annotations.php b/tests/PHPStan/Type/data/annotations.php index 0732fd31c4..9486aa885b 100644 --- a/tests/PHPStan/Type/data/annotations.php +++ b/tests/PHPStan/Type/data/annotations.php @@ -1,4 +1,6 @@ - | Foo */ - public function getIterator(); + /** @return iterable | Foo */ + public function getIterator(); } diff --git a/tests/PHPStan/Type/data/dependent-phpdocs.php b/tests/PHPStan/Type/data/dependent-phpdocs.php index df181a3608..1f9db1d288 100644 --- a/tests/PHPStan/Type/data/dependent-phpdocs.php +++ b/tests/PHPStan/Type/data/dependent-phpdocs.php @@ -1,12 +1,14 @@ -&1', escapeshellarg(__DIR__ . '/PHP-Parser')), $outputLines, $exitCode); - if ($exitCode === 0) { - return; - } - - $this->fail(implode("\n", $outputLines)); - } - - public function testResultCache(): void - { - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $lexerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php'; - $lexerCode = FileReader::read($lexerPath); - $originalLexerCode = $lexerCode; - - $lexerCode = str_replace('@param string $code', '', $lexerCode); - $lexerCode = str_replace('public function startLexing($code', 'public function startLexing(\\PhpParser\\Node\\Expr\\MethodCall $code', $lexerCode); - file_put_contents($lexerPath, $lexerCode); - - $errorHandlerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/ErrorHandler.php'; - $errorHandlerContents = FileReader::read($errorHandlerPath); - $errorHandlerContents .= "\n\n"; - file_put_contents($errorHandlerPath, $errorHandlerContents); - - $bootstrapPath = __DIR__ . '/PHP-Parser/lib/bootstrap.php'; - $originalBootstrapContents = FileReader::read($bootstrapPath); - file_put_contents($bootstrapPath, "\n\n echo ['foo'];", FILE_APPEND); - - $this->runPhpstanWithErrors(); - $this->runPhpstanWithErrors(); - - file_put_contents($lexerPath, $originalLexerCode); - - unlink($bootstrapPath); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_3.php'); - - file_put_contents($bootstrapPath, $originalBootstrapContents); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - } - - private function runPhpstanWithErrors(): void - { - $result = $this->runPhpstan(1); - $this->assertSame(3, $result['totals']['file_errors']); - $this->assertSame(0, $result['totals']['errors']); - - $fileHelper = new FileHelper(__DIR__); - - $this->assertSame('Parameter #1 $source of function token_get_all expects string, PhpParser\Node\Expr\MethodCall given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php')]['messages'][0]['message']); - $this->assertSame('Parameter #1 $code of method PhpParser\Lexer::startLexing() expects PhpParser\Node\Expr\MethodCall, string given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/ParserAbstract.php')]['messages'][0]['message']); - $this->assertSame('Parameter #1 (array(\'foo\')) of echo cannot be converted to string.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/bootstrap.php')]['messages'][0]['message']); - $this->assertResultCache(__DIR__ . '/resultCache_2.php'); - } - - public function testResultCacheDeleteFile(): void - { - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $serializerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Serializer.php'; - $serializerCode = FileReader::read($serializerPath); - $originalSerializerCode = $serializerCode; - unlink($serializerPath); - - $fileHelper = new FileHelper(__DIR__); - - $result = $this->runPhpstan(1); - $this->assertSame(4, $result['totals']['file_errors'], Json::encode($result)); - $this->assertSame(0, $result['totals']['errors'], Json::encode($result)); - - $message = $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][0]['message']; - $this->assertStringContainsString('Ignored error pattern #^Argument of an invalid type PhpParser\\\\Node supplied for foreach, only iterables are supported\\.$# in path', $message); - $this->assertStringContainsString('was not matched in reported errors.', $message); - $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][1]['message']); - $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][2]['message']); - $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][3]['message']); - - file_put_contents($serializerPath, $originalSerializerCode); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - } - - /** - * @param int $expectedExitCode - * @return mixed[] - */ - private function runPhpstan(int $expectedExitCode): array - { - exec(sprintf( - '%s %s analyse -c %s -l 5 --no-progress --error-format json lib 2>&1', - escapeshellarg(PHP_BINARY), - escapeshellarg(__DIR__ . '/../../bin/phpstan'), - escapeshellarg(__DIR__ . '/phpstan.neon') - ), $outputLines, $exitCode); - $output = implode("\n", $outputLines); - - try { - $json = Json::decode($output, Json::FORCE_ARRAY); - } catch (\Nette\Utils\JsonException $e) { - $this->fail(sprintf('%s: %s', $e->getMessage(), $output)); - } - - if ($exitCode !== $expectedExitCode) { - $this->fail($output); - } - - return $json; - } - - /** - * @param mixed[] $resultCache - * @return mixed[] - */ - private function transformResultCache(array $resultCache): array - { - $new = []; - foreach ($resultCache['dependencies'] as $file => $data) { - $files = array_map(function (string $file): string { - return $this->relativizePath($file); - }, $data['dependentFiles']); - sort($files); - $new[$this->relativizePath($file)] = $files; - } - - ksort($new); - - return $new; - } - - private function relativizePath(string $path): string - { - $path = str_replace('\\', '/', $path); - $helper = new SimpleRelativePathHelper(str_replace('\\', '/', __DIR__ . '/PHP-Parser')); - return $helper->getRelativePath($path); - } - - private function assertResultCache(string $expectedCachePath): void - { - $resultCachePath = __DIR__ . '/tmp/resultCache.php'; - $resultCache = $this->transformResultCache(require $resultCachePath); - $expectedResultCachePath = require $expectedCachePath; - $this->assertSame($expectedResultCachePath, $resultCache); - } - + public function setUp(): void + { + chdir(__DIR__ . '/PHP-Parser'); + + if (DIRECTORY_SEPARATOR !== '\\') { + return; + } + + $baselinePath = __DIR__ . '/baseline.neon'; + $baselineContents = FileReader::read($baselinePath); + $baselineContents = str_replace('offset 88', 'offset 91', $baselineContents); + file_put_contents($baselinePath, $baselineContents); + } + + public function tearDown(): void + { + exec(sprintf('git -C %s reset --hard 2>&1', escapeshellarg(__DIR__ . '/PHP-Parser')), $outputLines, $exitCode); + if ($exitCode === 0) { + return; + } + + $this->fail(implode("\n", $outputLines)); + } + + public function testResultCache(): void + { + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_1.php'); + + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_1.php'); + + $lexerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php'; + $lexerCode = FileReader::read($lexerPath); + $originalLexerCode = $lexerCode; + + $lexerCode = str_replace('@param string $code', '', $lexerCode); + $lexerCode = str_replace('public function startLexing($code', 'public function startLexing(\\PhpParser\\Node\\Expr\\MethodCall $code', $lexerCode); + file_put_contents($lexerPath, $lexerCode); + + $errorHandlerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/ErrorHandler.php'; + $errorHandlerContents = FileReader::read($errorHandlerPath); + $errorHandlerContents .= "\n\n"; + file_put_contents($errorHandlerPath, $errorHandlerContents); + + $bootstrapPath = __DIR__ . '/PHP-Parser/lib/bootstrap.php'; + $originalBootstrapContents = FileReader::read($bootstrapPath); + file_put_contents($bootstrapPath, "\n\n echo ['foo'];", FILE_APPEND); + + $this->runPhpstanWithErrors(); + $this->runPhpstanWithErrors(); + + file_put_contents($lexerPath, $originalLexerCode); + + unlink($bootstrapPath); + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_3.php'); + + file_put_contents($bootstrapPath, $originalBootstrapContents); + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_1.php'); + } + + private function runPhpstanWithErrors(): void + { + $result = $this->runPhpstan(1); + $this->assertSame(3, $result['totals']['file_errors']); + $this->assertSame(0, $result['totals']['errors']); + + $fileHelper = new FileHelper(__DIR__); + + $this->assertSame('Parameter #1 $source of function token_get_all expects string, PhpParser\Node\Expr\MethodCall given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php')]['messages'][0]['message']); + $this->assertSame('Parameter #1 $code of method PhpParser\Lexer::startLexing() expects PhpParser\Node\Expr\MethodCall, string given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/ParserAbstract.php')]['messages'][0]['message']); + $this->assertSame('Parameter #1 (array(\'foo\')) of echo cannot be converted to string.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/bootstrap.php')]['messages'][0]['message']); + $this->assertResultCache(__DIR__ . '/resultCache_2.php'); + } + + public function testResultCacheDeleteFile(): void + { + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_1.php'); + + $serializerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Serializer.php'; + $serializerCode = FileReader::read($serializerPath); + $originalSerializerCode = $serializerCode; + unlink($serializerPath); + + $fileHelper = new FileHelper(__DIR__); + + $result = $this->runPhpstan(1); + $this->assertSame(4, $result['totals']['file_errors'], Json::encode($result)); + $this->assertSame(0, $result['totals']['errors'], Json::encode($result)); + + $message = $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][0]['message']; + $this->assertStringContainsString('Ignored error pattern #^Argument of an invalid type PhpParser\\\\Node supplied for foreach, only iterables are supported\\.$# in path', $message); + $this->assertStringContainsString('was not matched in reported errors.', $message); + $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][1]['message']); + $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][2]['message']); + $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][3]['message']); + + file_put_contents($serializerPath, $originalSerializerCode); + $this->runPhpstan(0); + $this->assertResultCache(__DIR__ . '/resultCache_1.php'); + } + + /** + * @param int $expectedExitCode + * @return mixed[] + */ + private function runPhpstan(int $expectedExitCode): array + { + exec(sprintf( + '%s %s analyse -c %s -l 5 --no-progress --error-format json lib 2>&1', + escapeshellarg(PHP_BINARY), + escapeshellarg(__DIR__ . '/../../bin/phpstan'), + escapeshellarg(__DIR__ . '/phpstan.neon') + ), $outputLines, $exitCode); + $output = implode("\n", $outputLines); + + try { + $json = Json::decode($output, Json::FORCE_ARRAY); + } catch (\Nette\Utils\JsonException $e) { + $this->fail(sprintf('%s: %s', $e->getMessage(), $output)); + } + + if ($exitCode !== $expectedExitCode) { + $this->fail($output); + } + + return $json; + } + + /** + * @param mixed[] $resultCache + * @return mixed[] + */ + private function transformResultCache(array $resultCache): array + { + $new = []; + foreach ($resultCache['dependencies'] as $file => $data) { + $files = array_map(function (string $file): string { + return $this->relativizePath($file); + }, $data['dependentFiles']); + sort($files); + $new[$this->relativizePath($file)] = $files; + } + + ksort($new); + + return $new; + } + + private function relativizePath(string $path): string + { + $path = str_replace('\\', '/', $path); + $helper = new SimpleRelativePathHelper(str_replace('\\', '/', __DIR__ . '/PHP-Parser')); + return $helper->getRelativePath($path); + } + + private function assertResultCache(string $expectedCachePath): void + { + $resultCachePath = __DIR__ . '/tmp/resultCache.php'; + $resultCache = $this->transformResultCache(require $resultCachePath); + $expectedResultCachePath = require $expectedCachePath; + $this->assertSame($expectedResultCachePath, $resultCache); + } } diff --git a/tests/e2e/anon-class/Granularity.php b/tests/e2e/anon-class/Granularity.php index 9762130417..fc68cbf812 100644 --- a/tests/e2e/anon-class/Granularity.php +++ b/tests/e2e/anon-class/Granularity.php @@ -1,16 +1,16 @@ - SOAP_1_2]); - $soap = new MySoapClient2('some.wsdl', ['soap_version' => SOAP_1_2]); - $soap = new MySoapClient3('some.wsdl', ['soap_version' => SOAP_1_2]); + $soap = new MySoapClient('some.wsdl', ['soap_version' => SOAP_1_2]); + $soap = new MySoapClient2('some.wsdl', ['soap_version' => SOAP_1_2]); + $soap = new MySoapClient3('some.wsdl', ['soap_version' => SOAP_1_2]); }; class MySoapHeader extends \SoapHeader { - - public function __construct(string $username, string $password) - { - parent::SoapHeader($username, $password); - } - + public function __construct(string $username, string $password) + { + parent::SoapHeader($username, $password); + } } function () { - $header = new MySoapHeader('user', 'passw0rd'); + $header = new MySoapHeader('user', 'passw0rd'); }; function (\SoapFault $fault) { - echo $fault->faultcode; - echo $fault->faultstring; + echo $fault->faultcode; + echo $fault->faultstring; }; diff --git a/tests/e2e/data/timecop.php b/tests/e2e/data/timecop.php index 3a0ee354a1..88afc1eeae 100644 --- a/tests/e2e/data/timecop.php +++ b/tests/e2e/data/timecop.php @@ -1,4 +1,6 @@ -bar = $bar; - } - - public static function create(): self - { - return new self(new DateTimeImmutable()); - } + public function __construct(DateTimeImmutable $bar) + { + $this->bar = $bar; + } + public static function create(): self + { + return new self(new DateTimeImmutable()); + } } diff --git a/tests/e2e/resultCache_1.php b/tests/e2e/resultCache_1.php index 8aabcb449b..31482bd77f 100644 --- a/tests/e2e/resultCache_1.php +++ b/tests/e2e/resultCache_1.php @@ -1,10 +1,12 @@ - - array ( + + array( 0 => 'lib/bootstrap.php', ), - 'lib/PhpParser/Builder.php' => - array ( + 'lib/PhpParser/Builder.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -19,12 +21,12 @@ 11 => 'lib/PhpParser/BuilderAbstract.php', 12 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Class_.php' => - array ( + 'lib/PhpParser/Builder/Class_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Declaration.php' => - array ( + 'lib/PhpParser/Builder/Declaration.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -34,47 +36,47 @@ 6 => 'lib/PhpParser/Builder/Trait_.php', 7 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/FunctionLike.php' => - array ( + 'lib/PhpParser/Builder/FunctionLike.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Function_.php' => - array ( + 'lib/PhpParser/Builder/Function_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Interface_.php' => - array ( + 'lib/PhpParser/Builder/Interface_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Method.php' => - array ( + 'lib/PhpParser/Builder/Method.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Namespace_.php' => - array ( + 'lib/PhpParser/Builder/Namespace_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Param.php' => - array ( + 'lib/PhpParser/Builder/Param.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Property.php' => - array ( + 'lib/PhpParser/Builder/Property.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Trait_.php' => - array ( + 'lib/PhpParser/Builder/Trait_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Use_.php' => - array ( + 'lib/PhpParser/Builder/Use_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/BuilderAbstract.php' => - array ( + 'lib/PhpParser/BuilderAbstract.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -88,11 +90,11 @@ 10 => 'lib/PhpParser/Builder/Use_.php', 11 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/BuilderFactory.php' => - array ( + 'lib/PhpParser/BuilderFactory.php' => + array( ), - 'lib/PhpParser/Comment.php' => - array ( + 'lib/PhpParser/Comment.php' => + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -104,8 +106,8 @@ 8 => 'lib/PhpParser/PrettyPrinterAbstract.php', 9 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Comment/Doc.php' => - array ( + 'lib/PhpParser/Comment/Doc.php' => + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -115,8 +117,8 @@ 6 => 'lib/PhpParser/NodeAbstract.php', 7 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Error.php' => - array ( + 'lib/PhpParser/Error.php' => + array( 0 => 'lib/PhpParser/ErrorHandler.php', 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', @@ -130,8 +132,8 @@ 10 => 'lib/PhpParser/Parser/Php7.php', 11 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/ErrorHandler.php' => - array ( + 'lib/PhpParser/ErrorHandler.php' => + array( 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', 2 => 'lib/PhpParser/Lexer.php', @@ -141,30 +143,30 @@ 6 => 'lib/PhpParser/Parser/Multiple.php', 7 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( + 'lib/PhpParser/ErrorHandler/Collecting.php' => + array( ), - 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( + 'lib/PhpParser/ErrorHandler/Throwing.php' => + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Multiple.php', 3 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/Lexer.php' => - array ( + 'lib/PhpParser/Lexer.php' => + array( 0 => 'lib/PhpParser/Lexer/Emulative.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Lexer/Emulative.php' => - array ( + 'lib/PhpParser/Lexer/Emulative.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Node.php' => - array ( + 'lib/PhpParser/Node.php' => + array( 0 => 'lib/PhpParser/Builder.php', 1 => 'lib/PhpParser/Builder/Class_.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -353,8 +355,8 @@ 185 => 'lib/PhpParser/PrettyPrinterAbstract.php', 186 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Node/Arg.php' => - array ( + 'lib/PhpParser/Node/Arg.php' => + array( 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', 2 => 'lib/PhpParser/Node/Expr/New_.php', @@ -363,8 +365,8 @@ 5 => 'lib/PhpParser/Parser/Php7.php', 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Const_.php' => - array ( + 'lib/PhpParser/Node/Const_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', 1 => 'lib/PhpParser/Node/Stmt/Const_.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', @@ -372,8 +374,8 @@ 4 => 'lib/PhpParser/Parser/Php7.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr.php' => - array ( + 'lib/PhpParser/Node/Expr.php' => + array( 0 => 'lib/PhpParser/Builder/Param.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -509,14 +511,14 @@ 132 => 'lib/PhpParser/PrettyPrinter/Standard.php', 133 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( + 'lib/PhpParser/Node/Expr/ArrayItem.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Expr/Array_.php', 2 => 'lib/PhpParser/Node/Expr/List_.php', @@ -524,21 +526,21 @@ 4 => 'lib/PhpParser/Parser/Php7.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Array_.php' => - array ( + 'lib/PhpParser/Node/Expr/Array_.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Assign.php' => - array ( + 'lib/PhpParser/Node/Expr/Assign.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp.php' => + array( 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', @@ -555,86 +557,86 @@ 13 => 'lib/PhpParser/Parser/Php7.php', 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignRef.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp.php' => + array( 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', @@ -666,182 +668,182 @@ 28 => 'lib/PhpParser/Parser/Php7.php', 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( + 'lib/PhpParser/Node/Expr/BitwiseNot.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( + 'lib/PhpParser/Node/Expr/BooleanNot.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast.php' => + array( 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', @@ -853,254 +855,254 @@ 8 => 'lib/PhpParser/Parser/Php7.php', 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Array_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Double.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Int_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Object_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/String_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( + 'lib/PhpParser/Node/Expr/Clone_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Closure.php' => - array ( + 'lib/PhpParser/Node/Expr/Closure.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( + 'lib/PhpParser/Node/Expr/ClosureUse.php' => + array( 0 => 'lib/PhpParser/Node/Expr/Closure.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ConstFetch.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( + 'lib/PhpParser/Node/Expr/Empty_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Error.php' => - array ( + 'lib/PhpParser/Node/Expr/Error.php' => + array( 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( + 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( + 'lib/PhpParser/Node/Expr/Eval_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( + 'lib/PhpParser/Node/Expr/Exit_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( + 'lib/PhpParser/Node/Expr/FuncCall.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Include_.php' => - array ( + 'lib/PhpParser/Node/Expr/Include_.php' => + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( + 'lib/PhpParser/Node/Expr/Instanceof_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( + 'lib/PhpParser/Node/Expr/Isset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/List_.php' => - array ( + 'lib/PhpParser/Node/Expr/List_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( + 'lib/PhpParser/Node/Expr/MethodCall.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/New_.php' => - array ( + 'lib/PhpParser/Node/Expr/New_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( + 'lib/PhpParser/Node/Expr/PostDec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( + 'lib/PhpParser/Node/Expr/PostInc.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( + 'lib/PhpParser/Node/Expr/PreDec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( + 'lib/PhpParser/Node/Expr/PreInc.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Print_.php' => - array ( + 'lib/PhpParser/Node/Expr/Print_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/PropertyFetch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( + 'lib/PhpParser/Node/Expr/ShellExec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( + 'lib/PhpParser/Node/Expr/StaticCall.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( + 'lib/PhpParser/Node/Expr/Ternary.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( + 'lib/PhpParser/Node/Expr/UnaryMinus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( + 'lib/PhpParser/Node/Expr/UnaryPlus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Variable.php' => - array ( + 'lib/PhpParser/Node/Expr/Variable.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( + 'lib/PhpParser/Node/Expr/YieldFrom.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( + 'lib/PhpParser/Node/Expr/Yield_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/FunctionLike.php' => - array ( + 'lib/PhpParser/Node/FunctionLike.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Trait_.php', @@ -1114,8 +1116,8 @@ 10 => 'lib/PhpParser/ParserAbstract.php', 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Name.php' => - array ( + 'lib/PhpParser/Node/Name.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1157,23 +1159,23 @@ 38 => 'lib/PhpParser/PrettyPrinter/Standard.php', 39 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( + 'lib/PhpParser/Node/Name/FullyQualified.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Name/Relative.php' => - array ( + 'lib/PhpParser/Node/Name/Relative.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/NullableType.php' => - array ( + 'lib/PhpParser/Node/NullableType.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1188,8 +1190,8 @@ 11 => 'lib/PhpParser/Parser/Php7.php', 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Param.php' => - array ( + 'lib/PhpParser/Node/Param.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Param.php', 2 => 'lib/PhpParser/Node/Expr/Closure.php', @@ -1202,8 +1204,8 @@ 9 => 'lib/PhpParser/ParserAbstract.php', 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar.php' => - array ( + 'lib/PhpParser/Node/Scalar.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', @@ -1224,33 +1226,33 @@ 17 => 'lib/PhpParser/ParserAbstract.php', 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( + 'lib/PhpParser/Node/Scalar/DNumber.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( + 'lib/PhpParser/Node/Scalar/Encapsed.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( + 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( + 'lib/PhpParser/Node/Scalar/LNumber.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/ParserAbstract.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst.php' => + array( 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', @@ -1263,64 +1265,64 @@ 9 => 'lib/PhpParser/Parser/Php7.php', 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/String_.php' => - array ( + 'lib/PhpParser/Node/Scalar/String_.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt.php' => - array ( + 'lib/PhpParser/Node/Stmt.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Interface_.php', @@ -1388,21 +1390,21 @@ 64 => 'lib/PhpParser/PrettyPrinter/Standard.php', 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Break_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Case_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Catch_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1410,15 +1412,15 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassConst.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassLike.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Interface_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1439,8 +1441,8 @@ 17 => 'lib/PhpParser/ParserAbstract.php', 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassMethod.php' => + array( 0 => 'lib/PhpParser/Builder/Method.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', @@ -1450,8 +1452,8 @@ 6 => 'lib/PhpParser/ParserAbstract.php', 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Class_.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Property.php', @@ -1467,130 +1469,130 @@ 12 => 'lib/PhpParser/ParserAbstract.php', 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Const_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Continue_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( + 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Declare_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Do_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Echo_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( + 'lib/PhpParser/Node/Stmt/ElseIf_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Else_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Finally_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/For_.php' => - array ( + 'lib/PhpParser/Node/Stmt/For_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Foreach_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Function_.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Global_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Goto_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/GroupUse.php' => + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( + 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/If_.php' => - array ( + 'lib/PhpParser/Node/Stmt/If_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( + 'lib/PhpParser/Node/Stmt/InlineHTML.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Interface_.php' => + array( 0 => 'lib/PhpParser/Builder/Interface_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1598,14 +1600,14 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Label.php' => - array ( + 'lib/PhpParser/Node/Stmt/Label.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Namespace_.php' => + array( 0 => 'lib/PhpParser/Builder/Namespace_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1614,16 +1616,16 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( + 'lib/PhpParser/Node/Stmt/Nop.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Property.php' => - array ( + 'lib/PhpParser/Node/Stmt/Property.php' => + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1631,55 +1633,55 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( + 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Node/Stmt/Property.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Return_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( + 'lib/PhpParser/Node/Stmt/StaticVar.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Static_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Static_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Switch_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Throw_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUse.php' => + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', @@ -1688,42 +1690,42 @@ 5 => 'lib/PhpParser/Parser/Php7.php', 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Trait_.php' => + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( + 'lib/PhpParser/Node/Stmt/TryCatch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Unset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/UseUse.php' => + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', 2 => 'lib/PhpParser/Node/Stmt/Use_.php', @@ -1734,8 +1736,8 @@ 7 => 'lib/PhpParser/ParserAbstract.php', 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Use_.php' => + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/BuilderFactory.php', 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', @@ -1746,14 +1748,14 @@ 7 => 'lib/PhpParser/Parser/Php7.php', 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/While_.php' => - array ( + 'lib/PhpParser/Node/Stmt/While_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/NodeAbstract.php' => - array ( + 'lib/PhpParser/NodeAbstract.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1933,86 +1935,86 @@ 176 => 'lib/PhpParser/PrettyPrinter/Standard.php', 177 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/NodeDumper.php' => - array ( + 'lib/PhpParser/NodeDumper.php' => + array( ), - 'lib/PhpParser/NodeTraverser.php' => - array ( + 'lib/PhpParser/NodeTraverser.php' => + array( ), - 'lib/PhpParser/NodeTraverserInterface.php' => - array ( + 'lib/PhpParser/NodeTraverserInterface.php' => + array( 0 => 'lib/PhpParser/NodeTraverser.php', ), - 'lib/PhpParser/NodeVisitor.php' => - array ( + 'lib/PhpParser/NodeVisitor.php' => + array( 0 => 'lib/PhpParser/NodeTraverser.php', 1 => 'lib/PhpParser/NodeTraverserInterface.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 3 => 'lib/PhpParser/NodeVisitorAbstract.php', ), - 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( + 'lib/PhpParser/NodeVisitor/NameResolver.php' => + array( ), - 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( + 'lib/PhpParser/NodeVisitorAbstract.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', ), - 'lib/PhpParser/Parser.php' => - array ( + 'lib/PhpParser/Parser.php' => + array( 0 => 'lib/PhpParser/Parser/Multiple.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Multiple.php' => - array ( + 'lib/PhpParser/Parser/Multiple.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Php5.php' => - array ( + 'lib/PhpParser/Parser/Php5.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Php7.php' => - array ( + 'lib/PhpParser/Parser/Php7.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Tokens.php' => - array ( + 'lib/PhpParser/Parser/Tokens.php' => + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/Lexer/Emulative.php', ), - 'lib/PhpParser/ParserAbstract.php' => - array ( + 'lib/PhpParser/ParserAbstract.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/ParserFactory.php' => - array ( + 'lib/PhpParser/ParserFactory.php' => + array( ), - 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( + 'lib/PhpParser/PrettyPrinter/Standard.php' => + array( ), - 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( + 'lib/PhpParser/PrettyPrinterAbstract.php' => + array( 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Serializer.php' => - array ( + 'lib/PhpParser/Serializer.php' => + array( 0 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Serializer/XML.php' => - array ( + 'lib/PhpParser/Serializer/XML.php' => + array( ), - 'lib/PhpParser/Unserializer.php' => - array ( + 'lib/PhpParser/Unserializer.php' => + array( 0 => 'lib/PhpParser/Unserializer/XML.php', ), - 'lib/PhpParser/Unserializer/XML.php' => - array ( + 'lib/PhpParser/Unserializer/XML.php' => + array( ), - 'lib/bootstrap.php' => - array ( + 'lib/bootstrap.php' => + array( ), -); \ No newline at end of file +); diff --git a/tests/e2e/resultCache_2.php b/tests/e2e/resultCache_2.php index 5befe26310..5d47717d1e 100644 --- a/tests/e2e/resultCache_2.php +++ b/tests/e2e/resultCache_2.php @@ -1,10 +1,12 @@ - - array ( + + array( 0 => 'lib/bootstrap.php', ), - 'lib/PhpParser/Builder.php' => - array ( + 'lib/PhpParser/Builder.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -19,12 +21,12 @@ 11 => 'lib/PhpParser/BuilderAbstract.php', 12 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Class_.php' => - array ( + 'lib/PhpParser/Builder/Class_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Declaration.php' => - array ( + 'lib/PhpParser/Builder/Declaration.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -34,47 +36,47 @@ 6 => 'lib/PhpParser/Builder/Trait_.php', 7 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/FunctionLike.php' => - array ( + 'lib/PhpParser/Builder/FunctionLike.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Function_.php' => - array ( + 'lib/PhpParser/Builder/Function_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Interface_.php' => - array ( + 'lib/PhpParser/Builder/Interface_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Method.php' => - array ( + 'lib/PhpParser/Builder/Method.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Namespace_.php' => - array ( + 'lib/PhpParser/Builder/Namespace_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Param.php' => - array ( + 'lib/PhpParser/Builder/Param.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Property.php' => - array ( + 'lib/PhpParser/Builder/Property.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Trait_.php' => - array ( + 'lib/PhpParser/Builder/Trait_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/Builder/Use_.php' => - array ( + 'lib/PhpParser/Builder/Use_.php' => + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/BuilderAbstract.php' => - array ( + 'lib/PhpParser/BuilderAbstract.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -88,11 +90,11 @@ 10 => 'lib/PhpParser/Builder/Use_.php', 11 => 'lib/PhpParser/BuilderFactory.php', ), - 'lib/PhpParser/BuilderFactory.php' => - array ( + 'lib/PhpParser/BuilderFactory.php' => + array( ), - 'lib/PhpParser/Comment.php' => - array ( + 'lib/PhpParser/Comment.php' => + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -104,8 +106,8 @@ 8 => 'lib/PhpParser/PrettyPrinterAbstract.php', 9 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Comment/Doc.php' => - array ( + 'lib/PhpParser/Comment/Doc.php' => + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -115,8 +117,8 @@ 6 => 'lib/PhpParser/NodeAbstract.php', 7 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Error.php' => - array ( + 'lib/PhpParser/Error.php' => + array( 0 => 'lib/PhpParser/ErrorHandler.php', 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', @@ -130,8 +132,8 @@ 10 => 'lib/PhpParser/Parser/Php7.php', 11 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/ErrorHandler.php' => - array ( + 'lib/PhpParser/ErrorHandler.php' => + array( 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', 2 => 'lib/PhpParser/Lexer.php', @@ -141,30 +143,30 @@ 6 => 'lib/PhpParser/Parser/Multiple.php', 7 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( + 'lib/PhpParser/ErrorHandler/Collecting.php' => + array( ), - 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( + 'lib/PhpParser/ErrorHandler/Throwing.php' => + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Multiple.php', 3 => 'lib/PhpParser/ParserAbstract.php', ), - 'lib/PhpParser/Lexer.php' => - array ( + 'lib/PhpParser/Lexer.php' => + array( 0 => 'lib/PhpParser/Lexer/Emulative.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Lexer/Emulative.php' => - array ( + 'lib/PhpParser/Lexer/Emulative.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Node.php' => - array ( + 'lib/PhpParser/Node.php' => + array( 0 => 'lib/PhpParser/Builder.php', 1 => 'lib/PhpParser/Builder/Class_.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -354,8 +356,8 @@ 186 => 'lib/PhpParser/PrettyPrinterAbstract.php', 187 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Node/Arg.php' => - array ( + 'lib/PhpParser/Node/Arg.php' => + array( 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', 2 => 'lib/PhpParser/Node/Expr/New_.php', @@ -364,8 +366,8 @@ 5 => 'lib/PhpParser/Parser/Php7.php', 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Const_.php' => - array ( + 'lib/PhpParser/Node/Const_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', 1 => 'lib/PhpParser/Node/Stmt/Const_.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', @@ -373,8 +375,8 @@ 4 => 'lib/PhpParser/Parser/Php7.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr.php' => - array ( + 'lib/PhpParser/Node/Expr.php' => + array( 0 => 'lib/PhpParser/Builder/Param.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -511,14 +513,14 @@ 133 => 'lib/PhpParser/PrettyPrinter/Standard.php', 134 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( + 'lib/PhpParser/Node/Expr/ArrayItem.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Expr/Array_.php', 2 => 'lib/PhpParser/Node/Expr/List_.php', @@ -526,21 +528,21 @@ 4 => 'lib/PhpParser/Parser/Php7.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Array_.php' => - array ( + 'lib/PhpParser/Node/Expr/Array_.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Assign.php' => - array ( + 'lib/PhpParser/Node/Expr/Assign.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp.php' => + array( 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', @@ -557,86 +559,86 @@ 13 => 'lib/PhpParser/Parser/Php7.php', 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( + 'lib/PhpParser/Node/Expr/AssignRef.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp.php' => + array( 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', @@ -668,182 +670,182 @@ 28 => 'lib/PhpParser/Parser/Php7.php', 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( + 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( + 'lib/PhpParser/Node/Expr/BitwiseNot.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( + 'lib/PhpParser/Node/Expr/BooleanNot.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast.php' => + array( 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', @@ -855,255 +857,255 @@ 8 => 'lib/PhpParser/Parser/Php7.php', 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Array_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Double.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Int_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Object_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/String_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( + 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( + 'lib/PhpParser/Node/Expr/Clone_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Closure.php' => - array ( + 'lib/PhpParser/Node/Expr/Closure.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( + 'lib/PhpParser/Node/Expr/ClosureUse.php' => + array( 0 => 'lib/PhpParser/Node/Expr/Closure.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/ConstFetch.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( + 'lib/PhpParser/Node/Expr/Empty_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Error.php' => - array ( + 'lib/PhpParser/Node/Expr/Error.php' => + array( 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( + 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( + 'lib/PhpParser/Node/Expr/Eval_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( + 'lib/PhpParser/Node/Expr/Exit_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( + 'lib/PhpParser/Node/Expr/FuncCall.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Include_.php' => - array ( + 'lib/PhpParser/Node/Expr/Include_.php' => + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( + 'lib/PhpParser/Node/Expr/Instanceof_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( + 'lib/PhpParser/Node/Expr/Isset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/List_.php' => - array ( + 'lib/PhpParser/Node/Expr/List_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( + 'lib/PhpParser/Node/Expr/MethodCall.php' => + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/New_.php' => - array ( + 'lib/PhpParser/Node/Expr/New_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( + 'lib/PhpParser/Node/Expr/PostDec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( + 'lib/PhpParser/Node/Expr/PostInc.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( + 'lib/PhpParser/Node/Expr/PreDec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( + 'lib/PhpParser/Node/Expr/PreInc.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Print_.php' => - array ( + 'lib/PhpParser/Node/Expr/Print_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/PropertyFetch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( + 'lib/PhpParser/Node/Expr/ShellExec.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( + 'lib/PhpParser/Node/Expr/StaticCall.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( + 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( + 'lib/PhpParser/Node/Expr/Ternary.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( + 'lib/PhpParser/Node/Expr/UnaryMinus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( + 'lib/PhpParser/Node/Expr/UnaryPlus.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Variable.php' => - array ( + 'lib/PhpParser/Node/Expr/Variable.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( + 'lib/PhpParser/Node/Expr/YieldFrom.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( + 'lib/PhpParser/Node/Expr/Yield_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/FunctionLike.php' => - array ( + 'lib/PhpParser/Node/FunctionLike.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Trait_.php', @@ -1117,8 +1119,8 @@ 10 => 'lib/PhpParser/ParserAbstract.php', 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Name.php' => - array ( + 'lib/PhpParser/Node/Name.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1160,23 +1162,23 @@ 38 => 'lib/PhpParser/PrettyPrinter/Standard.php', 39 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( + 'lib/PhpParser/Node/Name/FullyQualified.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Name/Relative.php' => - array ( + 'lib/PhpParser/Node/Name/Relative.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/NullableType.php' => - array ( + 'lib/PhpParser/Node/NullableType.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1191,8 +1193,8 @@ 11 => 'lib/PhpParser/Parser/Php7.php', 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Param.php' => - array ( + 'lib/PhpParser/Node/Param.php' => + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Param.php', 2 => 'lib/PhpParser/Node/Expr/Closure.php', @@ -1205,8 +1207,8 @@ 9 => 'lib/PhpParser/ParserAbstract.php', 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar.php' => - array ( + 'lib/PhpParser/Node/Scalar.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', @@ -1227,33 +1229,33 @@ 17 => 'lib/PhpParser/ParserAbstract.php', 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( + 'lib/PhpParser/Node/Scalar/DNumber.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( + 'lib/PhpParser/Node/Scalar/Encapsed.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( + 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( + 'lib/PhpParser/Node/Scalar/LNumber.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/ParserAbstract.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst.php' => + array( 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', @@ -1266,64 +1268,64 @@ 9 => 'lib/PhpParser/Parser/Php7.php', 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( + 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Scalar/String_.php' => - array ( + 'lib/PhpParser/Node/Scalar/String_.php' => + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt.php' => - array ( + 'lib/PhpParser/Node/Stmt.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Interface_.php', @@ -1391,21 +1393,21 @@ 64 => 'lib/PhpParser/PrettyPrinter/Standard.php', 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Break_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Case_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Catch_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1413,15 +1415,15 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassConst.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassLike.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Interface_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1442,8 +1444,8 @@ 17 => 'lib/PhpParser/ParserAbstract.php', 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( + 'lib/PhpParser/Node/Stmt/ClassMethod.php' => + array( 0 => 'lib/PhpParser/Builder/Method.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', @@ -1453,8 +1455,8 @@ 6 => 'lib/PhpParser/ParserAbstract.php', 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Class_.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Property.php', @@ -1470,130 +1472,130 @@ 12 => 'lib/PhpParser/ParserAbstract.php', 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Const_.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Continue_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( + 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Declare_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Do_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Echo_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( + 'lib/PhpParser/Node/Stmt/ElseIf_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Else_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Finally_.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/For_.php' => - array ( + 'lib/PhpParser/Node/Stmt/For_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Foreach_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Function_.php' => + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Global_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Goto_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/GroupUse.php' => + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( + 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/If_.php' => - array ( + 'lib/PhpParser/Node/Stmt/If_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( + 'lib/PhpParser/Node/Stmt/InlineHTML.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Interface_.php' => + array( 0 => 'lib/PhpParser/Builder/Interface_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1601,14 +1603,14 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Label.php' => - array ( + 'lib/PhpParser/Node/Stmt/Label.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Namespace_.php' => + array( 0 => 'lib/PhpParser/Builder/Namespace_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1617,16 +1619,16 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( + 'lib/PhpParser/Node/Stmt/Nop.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/Node/Stmt/Property.php' => - array ( + 'lib/PhpParser/Node/Stmt/Property.php' => + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1634,55 +1636,55 @@ 4 => 'lib/PhpParser/ParserAbstract.php', 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( + 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Node/Stmt/Property.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Return_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( + 'lib/PhpParser/Node/Stmt/StaticVar.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/Static_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Static_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Switch_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Throw_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUse.php' => + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => + array( 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', @@ -1691,42 +1693,42 @@ 5 => 'lib/PhpParser/Parser/Php7.php', 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( + 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Trait_.php' => + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', 3 => 'lib/PhpParser/Parser/Php7.php', 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( + 'lib/PhpParser/Node/Stmt/TryCatch.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Unset_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( + 'lib/PhpParser/Node/Stmt/UseUse.php' => + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', 2 => 'lib/PhpParser/Node/Stmt/Use_.php', @@ -1737,8 +1739,8 @@ 7 => 'lib/PhpParser/ParserAbstract.php', 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( + 'lib/PhpParser/Node/Stmt/Use_.php' => + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/BuilderFactory.php', 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', @@ -1749,14 +1751,14 @@ 7 => 'lib/PhpParser/Parser/Php7.php', 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Node/Stmt/While_.php' => - array ( + 'lib/PhpParser/Node/Stmt/While_.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/NodeAbstract.php' => - array ( + 'lib/PhpParser/NodeAbstract.php' => + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1937,86 +1939,86 @@ 177 => 'lib/PhpParser/PrettyPrinter/Standard.php', 178 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), - 'lib/PhpParser/NodeDumper.php' => - array ( + 'lib/PhpParser/NodeDumper.php' => + array( ), - 'lib/PhpParser/NodeTraverser.php' => - array ( + 'lib/PhpParser/NodeTraverser.php' => + array( ), - 'lib/PhpParser/NodeTraverserInterface.php' => - array ( + 'lib/PhpParser/NodeTraverserInterface.php' => + array( 0 => 'lib/PhpParser/NodeTraverser.php', ), - 'lib/PhpParser/NodeVisitor.php' => - array ( + 'lib/PhpParser/NodeVisitor.php' => + array( 0 => 'lib/PhpParser/NodeTraverser.php', 1 => 'lib/PhpParser/NodeTraverserInterface.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 3 => 'lib/PhpParser/NodeVisitorAbstract.php', ), - 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( + 'lib/PhpParser/NodeVisitor/NameResolver.php' => + array( ), - 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( + 'lib/PhpParser/NodeVisitorAbstract.php' => + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', ), - 'lib/PhpParser/Parser.php' => - array ( + 'lib/PhpParser/Parser.php' => + array( 0 => 'lib/PhpParser/Parser/Multiple.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/ParserAbstract.php', 4 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Multiple.php' => - array ( + 'lib/PhpParser/Parser/Multiple.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Php5.php' => - array ( + 'lib/PhpParser/Parser/Php5.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Php7.php' => - array ( + 'lib/PhpParser/Parser/Php7.php' => + array( 0 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/Parser/Tokens.php' => - array ( + 'lib/PhpParser/Parser/Tokens.php' => + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/Lexer/Emulative.php', ), - 'lib/PhpParser/ParserAbstract.php' => - array ( + 'lib/PhpParser/ParserAbstract.php' => + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserFactory.php', ), - 'lib/PhpParser/ParserFactory.php' => - array ( + 'lib/PhpParser/ParserFactory.php' => + array( ), - 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( + 'lib/PhpParser/PrettyPrinter/Standard.php' => + array( ), - 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( + 'lib/PhpParser/PrettyPrinterAbstract.php' => + array( 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), - 'lib/PhpParser/Serializer.php' => - array ( + 'lib/PhpParser/Serializer.php' => + array( 0 => 'lib/PhpParser/Serializer/XML.php', ), - 'lib/PhpParser/Serializer/XML.php' => - array ( + 'lib/PhpParser/Serializer/XML.php' => + array( ), - 'lib/PhpParser/Unserializer.php' => - array ( + 'lib/PhpParser/Unserializer.php' => + array( 0 => 'lib/PhpParser/Unserializer/XML.php', ), - 'lib/PhpParser/Unserializer/XML.php' => - array ( + 'lib/PhpParser/Unserializer/XML.php' => + array( ), - 'lib/bootstrap.php' => - array ( + 'lib/bootstrap.php' => + array( ), -); \ No newline at end of file +); diff --git a/tests/e2e/resultCache_3.php b/tests/e2e/resultCache_3.php index ccd09a149b..de0d87d3e7 100644 --- a/tests/e2e/resultCache_3.php +++ b/tests/e2e/resultCache_3.php @@ -1,10 +1,12 @@ - - array ( + array( 0 => 'lib/bootstrap.php', ), 'lib/PhpParser/Builder.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -20,11 +22,11 @@ 12 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Class_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Declaration.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -35,46 +37,46 @@ 7 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/FunctionLike.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Function_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Interface_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Method.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Namespace_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Param.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Property.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Trait_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/Builder/Use_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/BuilderAbstract.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Declaration.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -89,10 +91,10 @@ 11 => 'lib/PhpParser/BuilderFactory.php', ), 'lib/PhpParser/BuilderFactory.php' => - array ( + array( ), 'lib/PhpParser/Comment.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -105,7 +107,7 @@ 9 => 'lib/PhpParser/Serializer/XML.php', ), 'lib/PhpParser/Comment/Doc.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Declaration.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -116,7 +118,7 @@ 7 => 'lib/PhpParser/Serializer/XML.php', ), 'lib/PhpParser/Error.php' => - array ( + array( 0 => 'lib/PhpParser/ErrorHandler.php', 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', @@ -131,7 +133,7 @@ 11 => 'lib/PhpParser/ParserAbstract.php', ), 'lib/PhpParser/ErrorHandler.php' => - array ( + array( 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', 2 => 'lib/PhpParser/Lexer.php', @@ -142,17 +144,17 @@ 7 => 'lib/PhpParser/ParserAbstract.php', ), 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( + array( ), 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Multiple.php', 3 => 'lib/PhpParser/ParserAbstract.php', ), 'lib/PhpParser/Lexer.php' => - array ( + array( 0 => 'lib/PhpParser/Lexer/Emulative.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', @@ -160,11 +162,11 @@ 4 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Lexer/Emulative.php' => - array ( + array( 0 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Node.php' => - array ( + array( 0 => 'lib/PhpParser/Builder.php', 1 => 'lib/PhpParser/Builder/Class_.php', 2 => 'lib/PhpParser/Builder/FunctionLike.php', @@ -354,7 +356,7 @@ 186 => 'lib/PhpParser/Serializer/XML.php', ), 'lib/PhpParser/Node/Arg.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', 2 => 'lib/PhpParser/Node/Expr/New_.php', @@ -364,7 +366,7 @@ 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Const_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', 1 => 'lib/PhpParser/Node/Stmt/Const_.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', @@ -373,7 +375,7 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Param.php', 1 => 'lib/PhpParser/Builder/Property.php', 2 => 'lib/PhpParser/BuilderAbstract.php', @@ -510,13 +512,13 @@ 133 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Expr/Array_.php', 2 => 'lib/PhpParser/Node/Expr/List_.php', @@ -525,20 +527,20 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Array_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Assign.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', @@ -556,85 +558,85 @@ 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', @@ -667,181 +669,181 @@ 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', @@ -854,76 +856,76 @@ 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Closure.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/Closure.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -931,176 +933,176 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Error.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Include_.php' => - array ( + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/List_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/New_.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Print_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Variable.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/FunctionLike.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Trait_.php', @@ -1115,7 +1117,7 @@ 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Name.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1158,7 +1160,7 @@ 39 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1166,14 +1168,14 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Name/Relative.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/NullableType.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1189,7 +1191,7 @@ 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Param.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/FunctionLike.php', 1 => 'lib/PhpParser/Builder/Param.php', 2 => 'lib/PhpParser/Node/Expr/Closure.php', @@ -1203,7 +1205,7 @@ 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', @@ -1225,32 +1227,32 @@ 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/ParserAbstract.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', @@ -1264,55 +1266,55 @@ 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Scalar/String_.php' => - array ( + array( 0 => 'lib/PhpParser/BuilderAbstract.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', @@ -1320,7 +1322,7 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Function_.php', 2 => 'lib/PhpParser/Builder/Interface_.php', @@ -1389,20 +1391,20 @@ 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1411,14 +1413,14 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Interface_.php', 2 => 'lib/PhpParser/Builder/Method.php', @@ -1440,7 +1442,7 @@ 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Method.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', @@ -1451,7 +1453,7 @@ 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/Method.php', 2 => 'lib/PhpParser/Builder/Property.php', @@ -1468,60 +1470,60 @@ 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/If_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', @@ -1529,19 +1531,19 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/For_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Function_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1549,19 +1551,19 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( + array( 0 => 'lib/PhpParser/NodeDumper.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1569,20 +1571,20 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/If_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', @@ -1590,7 +1592,7 @@ 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Interface_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1599,13 +1601,13 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Label.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Namespace_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1615,7 +1617,7 @@ 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', @@ -1623,7 +1625,7 @@ 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/Node/Stmt/Property.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Builder/Trait_.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1632,7 +1634,7 @@ 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Property.php', 1 => 'lib/PhpParser/Node/Stmt/Property.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1640,38 +1642,38 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/Static_.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1679,7 +1681,7 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( + array( 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', @@ -1689,20 +1691,20 @@ 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Trait_.php', 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 2 => 'lib/PhpParser/Parser/Php5.php', @@ -1710,20 +1712,20 @@ 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserAbstract.php', 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', 2 => 'lib/PhpParser/Node/Stmt/Use_.php', @@ -1735,7 +1737,7 @@ 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Use_.php', 1 => 'lib/PhpParser/BuilderFactory.php', 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', @@ -1747,13 +1749,13 @@ 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Node/Stmt/While_.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/NodeAbstract.php' => - array ( + array( 0 => 'lib/PhpParser/Builder/Class_.php', 1 => 'lib/PhpParser/Builder/FunctionLike.php', 2 => 'lib/PhpParser/Builder/Function_.php', @@ -1934,31 +1936,31 @@ 177 => 'lib/PhpParser/PrettyPrinterAbstract.php', ), 'lib/PhpParser/NodeDumper.php' => - array ( + array( ), 'lib/PhpParser/NodeTraverser.php' => - array ( + array( ), 'lib/PhpParser/NodeTraverserInterface.php' => - array ( + array( 0 => 'lib/PhpParser/NodeTraverser.php', ), 'lib/PhpParser/NodeVisitor.php' => - array ( + array( 0 => 'lib/PhpParser/NodeTraverser.php', 1 => 'lib/PhpParser/NodeTraverserInterface.php', 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', 3 => 'lib/PhpParser/NodeVisitorAbstract.php', ), 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( + array( ), 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( + array( 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', ), 'lib/PhpParser/Parser.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Multiple.php', 1 => 'lib/PhpParser/Parser/Php5.php', 2 => 'lib/PhpParser/Parser/Php7.php', @@ -1966,50 +1968,50 @@ 4 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Parser/Multiple.php' => - array ( + array( 0 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Parser/Php5.php' => - array ( + array( 0 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Parser/Php7.php' => - array ( + array( 0 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/Parser/Tokens.php' => - array ( + array( 0 => 'lib/PhpParser/Lexer.php', 1 => 'lib/PhpParser/Lexer/Emulative.php', ), 'lib/PhpParser/ParserAbstract.php' => - array ( + array( 0 => 'lib/PhpParser/Parser/Php5.php', 1 => 'lib/PhpParser/Parser/Php7.php', 2 => 'lib/PhpParser/ParserFactory.php', ), 'lib/PhpParser/ParserFactory.php' => - array ( + array( ), 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( + array( ), 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( + array( 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', ), 'lib/PhpParser/Serializer.php' => - array ( + array( 0 => 'lib/PhpParser/Serializer/XML.php', ), 'lib/PhpParser/Serializer/XML.php' => - array ( + array( ), 'lib/PhpParser/Unserializer.php' => - array ( + array( 0 => 'lib/PhpParser/Unserializer/XML.php', ), 'lib/PhpParser/Unserializer/XML.php' => - array ( + array( ), ); diff --git a/tests/notAutoloaded/Bar.php b/tests/notAutoloaded/Bar.php index 5d39c7ca9e..e35ea9827b 100644 --- a/tests/notAutoloaded/Bar.php +++ b/tests/notAutoloaded/Bar.php @@ -1,8 +1,9 @@ -fooProperty = 'test'; + /** @var string */ + private $fooProperty; - return $this->fooProperty; - } + public function doFoo(): string + { + $this->fooProperty = 'test'; + return $this->fooProperty; + } } diff --git a/tests/notAutoloaded/functionFoo.php b/tests/notAutoloaded/functionFoo.php index 5f181ed79f..39a5aa4faa 100644 --- a/tests/notAutoloaded/functionFoo.php +++ b/tests/notAutoloaded/functionFoo.php @@ -1,8 +1,10 @@ - $collection - */ - public function doFoo(Collection $collection): void - { - $collection->partition(function ($key, $value): bool { - return true; - }); - } - + /** + * @param \RecursiveTemplateProblem\Collection $collection + */ + public function doFoo(Collection $collection): void + { + $collection->partition(function ($key, $value): bool { + return true; + }); + } } interface InterfaceWithStubPhpDoc { - - /** - * @return int - */ - public function doFoo(); - + /** + * @return int + */ + public function doFoo(); } class ClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - } class AnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc { - } interface InterfaceWithStubPhpDoc2 { - - /** - * @return int - */ - public function doFoo(); - + /** + * @return int + */ + public function doFoo(); } class YetAnotherFoo { - - /** - * @param int $i - * @return string - */ - public function doFoo($i) - { - return ''; - } - + /** + * @param int $i + * @return string + */ + public function doFoo($i) + { + return ''; + } } class YetYetAnotherFoo { - - /** - * @param int $i - * @return string - */ - public function doFoo($i) - { - return ''; - } - + /** + * @param int $i + * @return string + */ + public function doFoo($i) + { + return ''; + } } diff --git a/tests/phpstan-bootstrap.php b/tests/phpstan-bootstrap.php index 8360247df4..5df3514cce 100644 --- a/tests/phpstan-bootstrap.php +++ b/tests/phpstan-bootstrap.php @@ -1,4 +1,6 @@ -