From 069b55e627f5c7eefc80ec61983899b6bda5775a Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 3 Jun 2024 09:29:23 +0100 Subject: [PATCH] Do not export test files - Use gitattributes to not export test files - Updates dependencies to not export the test files --- .gitattributes | 13 + bin/update.php | 12 + composer.json | 4 +- .../Inference/Walker/TestAssertWalker.php | 7 +- src/Tests/Assert/TrinaryAssert.php | 23 - src/Tests/Benchmarks/AnalyserBench.php | 13 - src/Tests/Benchmarks/BaseBenchCase.php | 68 - src/Tests/Benchmarks/DiagnosticsBench.php | 53 - src/Tests/Benchmarks/Examples/MethodClass.php | 21 - .../Benchmarks/Examples/PropertyClass.php | 10 - .../Benchmarks/PhpUnitReflectClassBench.php | 53 - src/Tests/Benchmarks/ReflectMethodBench.php | 47 - src/Tests/Benchmarks/ReflectPropertyBench.php | 40 - src/Tests/Benchmarks/ReflectionStubsBench.php | 35 - .../Benchmarks/SelfReflectClassBench.php | 35 - src/Tests/Benchmarks/YiiBench.php | 26 - .../diagnostics/lots_of_missing_methods.test | 1179 ---- .../lots_of_new_generic_objects.test | 66 - .../diagnostics/lots_of_new_objects.test | 62 - .../fixtures/diagnostics/method_chain.test | 6 - .../fixtures/diagnostics/phpstan.test | 4977 ----------------- .../fixtures/yii/ActiveRecord.php.test | 799 --- .../yii/ActiveRecordInterface.php.test | 472 -- .../fixtures/yii/Arrayable.php.test | 93 - .../fixtures/yii/ArrayableTrait.php.test | 247 - .../fixtures/yii/BaseActiveRecord.php.test | 1755 ------ .../fixtures/yii/BaseObject.php.test | 295 - .../fixtures/yii/Component.php.test | 766 --- .../Benchmarks/fixtures/yii/Model.php.test | 1052 ---- .../Benchmarks/fixtures/yii/Record.php.test | 21 - .../yii/StaticInstanceInterface.php.test | 31 - .../fixtures/yii/StaticInstanceTrait.php.test | 41 - src/Tests/Inference/SelfTest.php | 40 - .../anonymous_function/as_closure.test | 3 - .../as_closure_with_args.test | 3 - .../array-creation-expression/test.test | 9 - .../Inference/arrow_function/as_closure.test | 3 - .../arrow_function/as_closure_with_args.test | 3 - .../Inference/arrow_function/parameter.test | 3 - .../Inference/arrow_function/parameter2.test | 8 - .../Inference/arrow_function/parameter3.test | 9 - src/Tests/Inference/assignment/array1.test | 22 - src/Tests/Inference/assignment/array_2.test | 13 - src/Tests/Inference/assignment/array_add.test | 6 - .../assignment/array_add_in_foreach.test | 25 - .../Inference/assignment/list_assignment.test | 10 - .../assignment/list_desconstruct_nested.test | 10 - .../Inference/assignment/replacement.test | 20 - .../assignment/ternary_expression.test | 10 - .../Inference/assignment/unknown_key.test | 10 - .../binary-expression/arithmetic.test | 22 - .../binary-expression/array-union.test | 3 - .../Inference/binary-expression/bitwise.test | 30 - .../binary-expression/compare.scalar.test | 44 - .../Inference/binary-expression/concat.test | 9 - .../Inference/binary-expression/logical.test | 32 - .../Inference/binary-expression/type.test | 19 - .../call-expression/invoke-gh-1686.test | 32 - .../Inference/call-expression/invoke.test | 13 - .../Inference/call-expression/invoke2.test | 30 - .../type-from-invoked-callable.test | 4 - src/Tests/Inference/cast/cast.test | 5 - .../Inference/catch-clause/exceptions.test | 5 - src/Tests/Inference/catch-clause/union.test | 6 - .../combination/function_params.test | 7 - .../combination/inline_assertion.test | 6 - .../combination/intersect_interface.test | 18 - .../combination/narrow_abstract.test | 14 - .../param_with_multiple_types.test | 15 - src/Tests/Inference/combination/property.test | 31 - src/Tests/Inference/combination/union.test | 8 - .../Inference/combination/union_narrow.test | 16 - .../union_narrow_mulitple_ancestors.test | 21 - src/Tests/Inference/constant/constant.test | 6 - .../constant/constant_namespaced.test | 9 - .../constant_namespaced_imported.test | 11 - .../Inference/enum/backed_enum_case.test | 9 - src/Tests/Inference/enum/custom_member.test | 18 - src/Tests/Inference/enum/enum_case.test | 48 - src/Tests/Inference/enum/enum_trait.test | 20 - src/Tests/Inference/enum/gh-2220.test | 16 - .../foreach/assigns_type_to_item.test | 9 - .../foreach/assigns_type_to_key.test | 9 - .../foreach/generic_iterator_aggregate.test | 16 - ...neric_iterator_aggregate_then_foreach.test | 21 - src/Tests/Inference/foreach/gh-1708.test | 6 - .../Inference/foreach/list_deconstruct.test | 10 - .../Inference/foreach/list_deconstruct_1.test | 19 - src/Tests/Inference/foreach/literal_keys.test | 9 - .../Inference/foreach/literal_values.test | 9 - .../foreach/literal_values_removes_dupes.test | 9 - src/Tests/Inference/foreach/namespaced.test | 10 - .../foreach/preserve_types_after_break.test | 34 - .../Inference/foreach/with_docblock.test | 13 - .../function_intersection_docblock-param.test | 9 - .../function_intersection_param.test | 6 - src/Tests/Inference/function/array_map.test | 15 - src/Tests/Inference/function/array_merge.test | 25 - src/Tests/Inference/function/array_pop.test | 10 - src/Tests/Inference/function/array_shift.test | 13 - src/Tests/Inference/function/array_sum.test | 6 - .../Inference/function/assert.properties.test | 10 - src/Tests/Inference/function/assert.test | 34 - .../Inference/function/assert_not_object.test | 7 - .../Inference/function/assert_not_string.test | 7 - .../Inference/function/assert_object.test | 7 - .../Inference/function/assert_string.test | 8 - .../assert_variable_and_not_is_string.test | 9 - src/Tests/Inference/function/in_array.test | 28 - src/Tests/Inference/function/is_callable.test | 5 - src/Tests/Inference/function/is_float.test | 4 - src/Tests/Inference/function/is_int.test | 5 - src/Tests/Inference/function/is_null.test | 4 - src/Tests/Inference/function/is_string.test | 4 - .../Inference/function/iterator_to_array.test | 6 - .../iterator_to_array_from_generic.test | 18 - src/Tests/Inference/function/namespaced.test | 8 - src/Tests/Inference/function/reset.test | 10 - src/Tests/Inference/general/narrowing.test | 48 - src/Tests/Inference/generator/yield.test | 51 - .../Inference/generics/array_access1.test | 7 - .../array_access_resolve_method_type1.test | 11 - ...lass-string-generic-decared-interface.test | 24 - .../class-string-generic-nested-return.test | 20 - .../generics/class-string-generic-union.test | 19 - .../generics/class-string-generic.test | 20 - .../Inference/generics/class_extend1.test | 24 - .../Inference/generics/class_extend2.test | 39 - .../generics/class_implements_multiple1.test | 41 - .../generics/class_implements_single1.test | 37 - .../generics/class_template_extends1.test | 39 - .../generics/class_template_implements1.test | 37 - .../generics/constructor-array_arg.test | 26 - .../generics/constructor-generic-arg.test | 29 - .../constructor-param-and-extend.test | 41 - .../generics/constructor-params.test | 27 - src/Tests/Inference/generics/generator_1.test | 17 - src/Tests/Inference/generics/generator_2.test | 17 - .../generics/generator_yield_from_1.test | 15 - .../Inference/generics/generic_with_this.test | 29 - .../Inference/generics/gh-1530-example.test | 49 - src/Tests/Inference/generics/gh-1771.test | 31 - src/Tests/Inference/generics/gh-1800.test | 52 - src/Tests/Inference/generics/gh-1875.test | 28 - .../Inference/generics/gh-2295-test.test | 49 - src/Tests/Inference/generics/interface.test | 19 - src/Tests/Inference/generics/iterable.test | 7 - src/Tests/Inference/generics/iterator1.test | 14 - src/Tests/Inference/generics/iterator2.test | 14 - .../generics/iterator_aggregate1.test | 14 - .../generics/iterator_aggregate2.test | 14 - .../Inference/generics/method_generic.test | 19 - .../method_generic_class-string-2nd-arg.test | 21 - ...hod_generic_class-string-union_return.test | 24 - .../generics/method_returns_collection.test | 27 - .../generics/method_returns_collection2.test | 31 - .../method_returns_templated_generic.test | 37 - .../generics/nullable_template_param.test | 21 - src/Tests/Inference/generics/parameter.test | 26 - .../phpactor_reflection_collection.test | 45 - .../generics/phpactor_reflection_of_type.test | 45 - .../generics/type_from_template_in_class.test | 17 - .../Inference/global/global_keyword.test | 8 - src/Tests/Inference/if-statement/and.test | 5 - src/Tests/Inference/if-statement/bang.test | 7 - .../Inference/if-statement/bangbang.test | 8 - src/Tests/Inference/if-statement/die.test | 15 - src/Tests/Inference/if-statement/else.test | 12 - .../Inference/if-statement/else_assign.test | 9 - src/Tests/Inference/if-statement/elseif.test | 14 - .../Inference/if-statement/elseifdie.test | 14 - src/Tests/Inference/if-statement/false.test | 8 - src/Tests/Inference/if-statement/if_or.test | 5 - .../Inference/if-statement/instanceof.test | 6 - .../if-statement/instanceof_removes_null.test | 8 - .../instanceof_removes_scalar.test | 8 - .../is_not_string_and_not_instanceof.test | 10 - .../if-statement/multile_nested.test | 17 - .../if-statement/multiple_statements.test | 25 - .../multiple_statements_open_branches.test | 18 - .../multiple_statements_with_class.test | 23 - .../Inference/if-statement/namespace.test | 9 - src/Tests/Inference/if-statement/no_vars.test | 8 - .../if-statement/non-terminating-branch.test | 11 - .../Inference/if-statement/nullable.test | 11 - .../Inference/if-statement/property.test | 14 - .../if-statement/property_negated.test | 16 - .../if-statement/remove_null_type1.test | 10 - .../if-statement/remove_null_type2.test | 10 - .../if-statement/type_after_break.test | 10 - .../if-statement/type_after_continue.test | 17 - .../if-statement/type_after_exception.test | 7 - .../if-statement/type_after_return.test | 13 - .../Inference/if-statement/union_and.test | 8 - .../if-statement/union_and_else.test | 12 - .../Inference/if-statement/union_or.test | 8 - .../Inference/if-statement/union_or_else.test | 25 - .../variable_introduced_in_branch.test | 15 - .../Inference/invalid-ast/missing-paren.test | 7 - .../Inference/invalid-ast/missing-token.test | 0 .../member-access/access-from-union.test | 32 - .../class-constant-glob-array-shape.test | 16 - .../class-constant-glob-self.test | 18 - .../Inference/member-access/nested_trait.test | 22 - .../null-coalesce/null-coalesce_null.test | 7 - .../null-coalesce/null-coalesce_nullable.test | 9 - .../Inference/postfix-update/decrement.test | 4 - .../Inference/postfix-update/increment.test | 4 - .../function-fallback-to-global.test | 11 - .../qualified-name/function-in-namespace.test | 12 - .../qualified-name/function-no-namespace.test | 9 - .../reflection/circular-dependency-trait.test | 19 - .../circular-dependency_interface.test | 11 - .../circular-dependency_parent.test | 11 - src/Tests/Inference/reflection/gh-2207.test | 7 - .../Inference/reflection/mixin_class.test | 28 - .../Inference/reflection/mixin_generic.test | 21 - .../reflection/mixin_properties.test | 16 - .../Inference/reflection/mixin_recursive.test | 26 - .../Inference/reflection/mixin_static.test | 23 - .../Inference/reflection/multiple_mixins.test | 37 - .../promoted_property_with_params.test | 17 - .../reflection/virtial_static_method.test | 10 - .../Inference/require_and_include/foo.php | 7 - .../require_and_include/include.test | 5 - .../require_and_include/require.test | 4 - .../resolver/call_expression_closure.test | 9 - .../resolver/call_expression_closure2.test | 11 - .../return-statement/class_method.test | 8 - .../return-statement/missing_return_type.test | 10 - .../return-statement/multiple_return.test | 13 - .../Inference/return-statement/no_return.test | 9 - .../array_shape_access.test | 18 - .../ternary_expression/for_missing.test | 3 - .../use_if_branch_if_truthy.test | 7 - src/Tests/Inference/type/arrayshape.test | 12 - .../Inference/type/arrayshape_multiline.test | 14 - .../type/arrayshape_multiline_optional.test | 16 - src/Tests/Inference/type/callable.test | 10 - .../type/class-string-new-no-type.test | 6 - .../Inference/type/class-string-new.test | 6 - .../type/class-string-static-call.test | 15 - src/Tests/Inference/type/class-string.test | 5 - src/Tests/Inference/type/closure.test | 8 - .../type/conditional-type-nested.test | 17 - .../type/conditional-type-on-function.test | 19 - .../Inference/type/conditional-type.test | 28 - .../Inference/type/conditional-type2.test | 17 - .../Inference/type/conditional-type3.test | 17 - src/Tests/Inference/type/gh-1707.test | 8 - src/Tests/Inference/type/int-range.test | 17 - src/Tests/Inference/type/list.test | 17 - src/Tests/Inference/type/never.test | 16 - src/Tests/Inference/type/parenthesized.test | 14 - .../Inference/type/parenthesized_closure.test | 11 - .../Inference/type/self_context_trait.test | 13 - src/Tests/Inference/type/static.test | 6 - src/Tests/Inference/type/static_context.test | 8 - src/Tests/Inference/type/string-literal.test | 2 - .../type/union_from_relative_docblock.test | 11 - src/Tests/Inference/type/variadic.test | 5 - .../Inference/variable/braced_expression.test | 7 - src/Tests/Inference/variable/pass-by-ref.test | 10 - .../Inference/virtual_member/method.test | 38 - .../Inference/virtual_member/method2.test | 41 - .../method_and_property_with_same_name.test | 10 - .../Inference/virtual_member/property.test | 11 - .../virtual_member/trait_method1.test | 16 - .../virtual-method-returns-static.test | 18 - .../virtual-method-returns-this.test | 18 - .../ComposerSourceCodeLocatorTest.php | 18 - .../Phpactor/ClassToFileSourceLocatorTest.php | 39 - .../ReflectionClassCollectionTest.php | 58 - .../ReflectionMethodCollectionTest.php | 63 - .../ReflectionParameterCollectionTest.php | 43 - .../Reflection/ReflectionArgumentTest.php | 153 - .../Reflection/ReflectionClassTest.php | 1193 ---- .../Reflection/ReflectionConstantTest.php | 198 - .../Reflection/ReflectionEnumTest.php | 155 - .../Reflection/ReflectionFunctionTest.php | 169 - .../Reflection/ReflectionInterfaceTest.php | 234 - .../Reflection/ReflectionMethodCallTest.php | 134 - .../Reflection/ReflectionMethodTest.php | 827 --- .../Reflection/ReflectionParameterTest.php | 236 - .../ReflectionPromotedPropertyTest.php | 105 - .../Reflection/ReflectionPropertyTest.php | 406 -- .../Reflection/ReflectionScopeTest.php | 68 - .../Reflection/ReflectionTraitTest.php | 155 - .../TraitImport/TraitImportsTest.php | 90 - .../DeclaredMemberTypeResolverTest.php | 44 - .../Integration/Core/ClassReflectorTest.php | 80 - .../Core/ConstantReflectorTest.php | 81 - .../Core/FunctionReflectorTest.php | 82 - .../Core/Inference/FrameBuilderTest.php | 63 - .../FrameWalker/AssertWalkerTest.php | 65 - .../FrameWalker/AssignmentWalkerTest.php | 321 -- .../FrameWalker/FunctionLikeWalkerTest.php | 220 - .../FrameWalker/IncludeWalkerTest.php | 93 - .../FrameWalker/ReturnTypeWalkerTest.php | 74 - .../FrameWalker/VariableWalkerTest.php | 174 - .../Core/Inference/FrameWalkerTestCase.php | 45 - .../Inference/NodeContextResolverTest.php | 1212 ---- .../StubSourceLocatorTest.php | 63 - .../Integration/Core/SourceReflectorTest.php | 86 - .../Core/Util/OriginalMethodResolverTest.php | 105 - src/Tests/Integration/IntegrationTestCase.php | 67 - src/Tests/Smoke/smoke_test.php | 97 - .../Composer/ComposerSourceLocatorTest.php | 18 - .../DocblockParserFactoryTest.php | 427 -- .../Parser/CachedParserTest.php | 28 - .../ReflectionClassCollectionTest.php | 46 - src/Tests/Unit/Core/Cache/TtlCacheTest.php | 63 - src/Tests/Unit/Core/CacheForDocumentTest.php | 28 - src/Tests/Unit/Core/ClassNameTest.php | 51 - src/Tests/Unit/Core/DefaultValueTest.php | 27 - .../Unit/Core/DocBlock/PlainDocblockTest.php | 47 - .../Core/Inference/AssignmentstTestCase.php | 98 - src/Tests/Unit/Core/Inference/FrameTest.php | 22 - .../Core/Inference/LocalAssignmentsTest.php | 14 - .../Unit/Core/Inference/NodeReflectorTest.php | 27 - .../Unit/Core/Inference/ProblemsTest.php | 30 - .../Unit/Core/Inference/SymbolFactoryTest.php | 66 - .../Core/Inference/TypeAssertionsTest.php | 110 - .../Core/Inference/TypeCombinatorTest.php | 212 - src/Tests/Unit/Core/NameImportsTest.php | 86 - src/Tests/Unit/Core/NameTest.php | 27 - src/Tests/Unit/Core/PositionTest.php | 19 - .../ChainReflectionMemberCollectionTest.php | 260 - ...ogeneousReflectionMemberCollectionTest.php | 117 - .../MemonizedClassReflectorTest.php | 97 - .../ContextualSourceCodeReflectorTest.php | 54 - .../ChainSourceLocatorTest.php | 97 - ...iveReflectionFunctionSourceLocatorTest.php | 44 - .../StringSourceLocatorTest.php | 19 - .../TemporarySourceLocatorTest.php | 60 - src/Tests/Unit/Core/TemplateMapTest.php | 18 - .../Unit/Core/Type/AggregateTypeTest.php | 202 - .../Unit/Core/Type/ArrayLiteralTypeTest.php | 57 - .../Unit/Core/Type/ArrayShapeTypeTest.php | 44 - src/Tests/Unit/Core/Type/ArrayTypeTest.php | 39 - src/Tests/Unit/Core/Type/CallableTypeTest.php | 35 - src/Tests/Unit/Core/Type/ClosureTypeTest.php | 29 - .../Unit/Core/Type/GenericClassTypeTest.php | 45 - .../Unit/Core/Type/IntersectionTypeTest.php | 55 - src/Tests/Unit/Core/Type/NumericTypeTest.php | 21 - .../Unit/Core/Type/ReflectedClassTypeTest.php | 105 - .../Unit/Core/Type/StringLiteralTypeTest.php | 23 - src/Tests/Unit/Core/Type/UnionTypeTest.php | 63 - src/Tests/Unit/Core/TypeFactoryTest.php | 222 - .../VirtualReflectionMemberTestCase.php | 99 - .../Virtual/VirtualReflectionMethodTest.php | 85 - .../VirtualReflectionParameterTest.php | 81 - src/Tests/Unit/ReflectorBuilderTest.php | 117 - src/Tests/Unit/TypeUtilTest.php | 165 - .../88f1dd30b180fd8d7e17547833be97ef.map | 1 - src/Tests/Workspace/one.php | 1 - src/Tests/Workspace/test.php | 1 - .../ComposerSourceCodeLocatorTest.php | 2 +- .../Phpactor/ClassToFileSourceLocatorTest.php | 2 +- .../Composer/ComposerSourceLocatorTest.php | 2 +- 360 files changed, 32 insertions(+), 27094 deletions(-) create mode 100644 .gitattributes create mode 100755 bin/update.php delete mode 100644 src/Tests/Assert/TrinaryAssert.php delete mode 100644 src/Tests/Benchmarks/AnalyserBench.php delete mode 100644 src/Tests/Benchmarks/BaseBenchCase.php delete mode 100644 src/Tests/Benchmarks/DiagnosticsBench.php delete mode 100644 src/Tests/Benchmarks/Examples/MethodClass.php delete mode 100644 src/Tests/Benchmarks/Examples/PropertyClass.php delete mode 100644 src/Tests/Benchmarks/PhpUnitReflectClassBench.php delete mode 100644 src/Tests/Benchmarks/ReflectMethodBench.php delete mode 100644 src/Tests/Benchmarks/ReflectPropertyBench.php delete mode 100644 src/Tests/Benchmarks/ReflectionStubsBench.php delete mode 100644 src/Tests/Benchmarks/SelfReflectClassBench.php delete mode 100644 src/Tests/Benchmarks/YiiBench.php delete mode 100644 src/Tests/Benchmarks/fixtures/diagnostics/lots_of_missing_methods.test delete mode 100644 src/Tests/Benchmarks/fixtures/diagnostics/lots_of_new_generic_objects.test delete mode 100644 src/Tests/Benchmarks/fixtures/diagnostics/lots_of_new_objects.test delete mode 100644 src/Tests/Benchmarks/fixtures/diagnostics/method_chain.test delete mode 100644 src/Tests/Benchmarks/fixtures/diagnostics/phpstan.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/ActiveRecord.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/ActiveRecordInterface.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/Arrayable.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/ArrayableTrait.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/BaseActiveRecord.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/BaseObject.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/Component.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/Model.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/Record.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/StaticInstanceInterface.php.test delete mode 100644 src/Tests/Benchmarks/fixtures/yii/StaticInstanceTrait.php.test delete mode 100644 src/Tests/Inference/SelfTest.php delete mode 100644 src/Tests/Inference/anonymous_function/as_closure.test delete mode 100644 src/Tests/Inference/anonymous_function/as_closure_with_args.test delete mode 100644 src/Tests/Inference/array-creation-expression/test.test delete mode 100644 src/Tests/Inference/arrow_function/as_closure.test delete mode 100644 src/Tests/Inference/arrow_function/as_closure_with_args.test delete mode 100644 src/Tests/Inference/arrow_function/parameter.test delete mode 100644 src/Tests/Inference/arrow_function/parameter2.test delete mode 100644 src/Tests/Inference/arrow_function/parameter3.test delete mode 100644 src/Tests/Inference/assignment/array1.test delete mode 100644 src/Tests/Inference/assignment/array_2.test delete mode 100644 src/Tests/Inference/assignment/array_add.test delete mode 100644 src/Tests/Inference/assignment/array_add_in_foreach.test delete mode 100644 src/Tests/Inference/assignment/list_assignment.test delete mode 100644 src/Tests/Inference/assignment/list_desconstruct_nested.test delete mode 100644 src/Tests/Inference/assignment/replacement.test delete mode 100644 src/Tests/Inference/assignment/ternary_expression.test delete mode 100644 src/Tests/Inference/assignment/unknown_key.test delete mode 100644 src/Tests/Inference/binary-expression/arithmetic.test delete mode 100644 src/Tests/Inference/binary-expression/array-union.test delete mode 100644 src/Tests/Inference/binary-expression/bitwise.test delete mode 100644 src/Tests/Inference/binary-expression/compare.scalar.test delete mode 100644 src/Tests/Inference/binary-expression/concat.test delete mode 100644 src/Tests/Inference/binary-expression/logical.test delete mode 100644 src/Tests/Inference/binary-expression/type.test delete mode 100644 src/Tests/Inference/call-expression/invoke-gh-1686.test delete mode 100644 src/Tests/Inference/call-expression/invoke.test delete mode 100644 src/Tests/Inference/call-expression/invoke2.test delete mode 100644 src/Tests/Inference/call-expression/type-from-invoked-callable.test delete mode 100644 src/Tests/Inference/cast/cast.test delete mode 100644 src/Tests/Inference/catch-clause/exceptions.test delete mode 100644 src/Tests/Inference/catch-clause/union.test delete mode 100644 src/Tests/Inference/combination/function_params.test delete mode 100644 src/Tests/Inference/combination/inline_assertion.test delete mode 100644 src/Tests/Inference/combination/intersect_interface.test delete mode 100644 src/Tests/Inference/combination/narrow_abstract.test delete mode 100644 src/Tests/Inference/combination/param_with_multiple_types.test delete mode 100644 src/Tests/Inference/combination/property.test delete mode 100644 src/Tests/Inference/combination/union.test delete mode 100644 src/Tests/Inference/combination/union_narrow.test delete mode 100644 src/Tests/Inference/combination/union_narrow_mulitple_ancestors.test delete mode 100644 src/Tests/Inference/constant/constant.test delete mode 100644 src/Tests/Inference/constant/constant_namespaced.test delete mode 100644 src/Tests/Inference/constant/constant_namespaced_imported.test delete mode 100644 src/Tests/Inference/enum/backed_enum_case.test delete mode 100644 src/Tests/Inference/enum/custom_member.test delete mode 100644 src/Tests/Inference/enum/enum_case.test delete mode 100644 src/Tests/Inference/enum/enum_trait.test delete mode 100644 src/Tests/Inference/enum/gh-2220.test delete mode 100644 src/Tests/Inference/foreach/assigns_type_to_item.test delete mode 100644 src/Tests/Inference/foreach/assigns_type_to_key.test delete mode 100644 src/Tests/Inference/foreach/generic_iterator_aggregate.test delete mode 100644 src/Tests/Inference/foreach/generic_iterator_aggregate_then_foreach.test delete mode 100644 src/Tests/Inference/foreach/gh-1708.test delete mode 100644 src/Tests/Inference/foreach/list_deconstruct.test delete mode 100644 src/Tests/Inference/foreach/list_deconstruct_1.test delete mode 100644 src/Tests/Inference/foreach/literal_keys.test delete mode 100644 src/Tests/Inference/foreach/literal_values.test delete mode 100644 src/Tests/Inference/foreach/literal_values_removes_dupes.test delete mode 100644 src/Tests/Inference/foreach/namespaced.test delete mode 100644 src/Tests/Inference/foreach/preserve_types_after_break.test delete mode 100644 src/Tests/Inference/foreach/with_docblock.test delete mode 100644 src/Tests/Inference/function-like/function_intersection_docblock-param.test delete mode 100644 src/Tests/Inference/function-like/function_intersection_param.test delete mode 100644 src/Tests/Inference/function/array_map.test delete mode 100644 src/Tests/Inference/function/array_merge.test delete mode 100644 src/Tests/Inference/function/array_pop.test delete mode 100644 src/Tests/Inference/function/array_shift.test delete mode 100644 src/Tests/Inference/function/array_sum.test delete mode 100644 src/Tests/Inference/function/assert.properties.test delete mode 100644 src/Tests/Inference/function/assert.test delete mode 100644 src/Tests/Inference/function/assert_not_object.test delete mode 100644 src/Tests/Inference/function/assert_not_string.test delete mode 100644 src/Tests/Inference/function/assert_object.test delete mode 100644 src/Tests/Inference/function/assert_string.test delete mode 100644 src/Tests/Inference/function/assert_variable_and_not_is_string.test delete mode 100644 src/Tests/Inference/function/in_array.test delete mode 100644 src/Tests/Inference/function/is_callable.test delete mode 100644 src/Tests/Inference/function/is_float.test delete mode 100644 src/Tests/Inference/function/is_int.test delete mode 100644 src/Tests/Inference/function/is_null.test delete mode 100644 src/Tests/Inference/function/is_string.test delete mode 100644 src/Tests/Inference/function/iterator_to_array.test delete mode 100644 src/Tests/Inference/function/iterator_to_array_from_generic.test delete mode 100644 src/Tests/Inference/function/namespaced.test delete mode 100644 src/Tests/Inference/function/reset.test delete mode 100644 src/Tests/Inference/general/narrowing.test delete mode 100644 src/Tests/Inference/generator/yield.test delete mode 100644 src/Tests/Inference/generics/array_access1.test delete mode 100644 src/Tests/Inference/generics/array_access_resolve_method_type1.test delete mode 100644 src/Tests/Inference/generics/class-string-generic-decared-interface.test delete mode 100644 src/Tests/Inference/generics/class-string-generic-nested-return.test delete mode 100644 src/Tests/Inference/generics/class-string-generic-union.test delete mode 100644 src/Tests/Inference/generics/class-string-generic.test delete mode 100644 src/Tests/Inference/generics/class_extend1.test delete mode 100644 src/Tests/Inference/generics/class_extend2.test delete mode 100644 src/Tests/Inference/generics/class_implements_multiple1.test delete mode 100644 src/Tests/Inference/generics/class_implements_single1.test delete mode 100644 src/Tests/Inference/generics/class_template_extends1.test delete mode 100644 src/Tests/Inference/generics/class_template_implements1.test delete mode 100644 src/Tests/Inference/generics/constructor-array_arg.test delete mode 100644 src/Tests/Inference/generics/constructor-generic-arg.test delete mode 100644 src/Tests/Inference/generics/constructor-param-and-extend.test delete mode 100644 src/Tests/Inference/generics/constructor-params.test delete mode 100644 src/Tests/Inference/generics/generator_1.test delete mode 100644 src/Tests/Inference/generics/generator_2.test delete mode 100644 src/Tests/Inference/generics/generator_yield_from_1.test delete mode 100644 src/Tests/Inference/generics/generic_with_this.test delete mode 100644 src/Tests/Inference/generics/gh-1530-example.test delete mode 100644 src/Tests/Inference/generics/gh-1771.test delete mode 100644 src/Tests/Inference/generics/gh-1800.test delete mode 100644 src/Tests/Inference/generics/gh-1875.test delete mode 100644 src/Tests/Inference/generics/gh-2295-test.test delete mode 100644 src/Tests/Inference/generics/interface.test delete mode 100644 src/Tests/Inference/generics/iterable.test delete mode 100644 src/Tests/Inference/generics/iterator1.test delete mode 100644 src/Tests/Inference/generics/iterator2.test delete mode 100644 src/Tests/Inference/generics/iterator_aggregate1.test delete mode 100644 src/Tests/Inference/generics/iterator_aggregate2.test delete mode 100644 src/Tests/Inference/generics/method_generic.test delete mode 100644 src/Tests/Inference/generics/method_generic_class-string-2nd-arg.test delete mode 100644 src/Tests/Inference/generics/method_generic_class-string-union_return.test delete mode 100644 src/Tests/Inference/generics/method_returns_collection.test delete mode 100644 src/Tests/Inference/generics/method_returns_collection2.test delete mode 100644 src/Tests/Inference/generics/method_returns_templated_generic.test delete mode 100644 src/Tests/Inference/generics/nullable_template_param.test delete mode 100644 src/Tests/Inference/generics/parameter.test delete mode 100644 src/Tests/Inference/generics/phpactor_reflection_collection.test delete mode 100644 src/Tests/Inference/generics/phpactor_reflection_of_type.test delete mode 100644 src/Tests/Inference/generics/type_from_template_in_class.test delete mode 100644 src/Tests/Inference/global/global_keyword.test delete mode 100644 src/Tests/Inference/if-statement/and.test delete mode 100644 src/Tests/Inference/if-statement/bang.test delete mode 100644 src/Tests/Inference/if-statement/bangbang.test delete mode 100644 src/Tests/Inference/if-statement/die.test delete mode 100644 src/Tests/Inference/if-statement/else.test delete mode 100644 src/Tests/Inference/if-statement/else_assign.test delete mode 100644 src/Tests/Inference/if-statement/elseif.test delete mode 100644 src/Tests/Inference/if-statement/elseifdie.test delete mode 100644 src/Tests/Inference/if-statement/false.test delete mode 100644 src/Tests/Inference/if-statement/if_or.test delete mode 100644 src/Tests/Inference/if-statement/instanceof.test delete mode 100644 src/Tests/Inference/if-statement/instanceof_removes_null.test delete mode 100644 src/Tests/Inference/if-statement/instanceof_removes_scalar.test delete mode 100644 src/Tests/Inference/if-statement/is_not_string_and_not_instanceof.test delete mode 100644 src/Tests/Inference/if-statement/multile_nested.test delete mode 100644 src/Tests/Inference/if-statement/multiple_statements.test delete mode 100644 src/Tests/Inference/if-statement/multiple_statements_open_branches.test delete mode 100644 src/Tests/Inference/if-statement/multiple_statements_with_class.test delete mode 100644 src/Tests/Inference/if-statement/namespace.test delete mode 100644 src/Tests/Inference/if-statement/no_vars.test delete mode 100644 src/Tests/Inference/if-statement/non-terminating-branch.test delete mode 100644 src/Tests/Inference/if-statement/nullable.test delete mode 100644 src/Tests/Inference/if-statement/property.test delete mode 100644 src/Tests/Inference/if-statement/property_negated.test delete mode 100644 src/Tests/Inference/if-statement/remove_null_type1.test delete mode 100644 src/Tests/Inference/if-statement/remove_null_type2.test delete mode 100644 src/Tests/Inference/if-statement/type_after_break.test delete mode 100644 src/Tests/Inference/if-statement/type_after_continue.test delete mode 100644 src/Tests/Inference/if-statement/type_after_exception.test delete mode 100644 src/Tests/Inference/if-statement/type_after_return.test delete mode 100644 src/Tests/Inference/if-statement/union_and.test delete mode 100644 src/Tests/Inference/if-statement/union_and_else.test delete mode 100644 src/Tests/Inference/if-statement/union_or.test delete mode 100644 src/Tests/Inference/if-statement/union_or_else.test delete mode 100644 src/Tests/Inference/if-statement/variable_introduced_in_branch.test delete mode 100644 src/Tests/Inference/invalid-ast/missing-paren.test delete mode 100644 src/Tests/Inference/invalid-ast/missing-token.test delete mode 100644 src/Tests/Inference/member-access/access-from-union.test delete mode 100644 src/Tests/Inference/member-access/class-constant-glob-array-shape.test delete mode 100644 src/Tests/Inference/member-access/class-constant-glob-self.test delete mode 100644 src/Tests/Inference/member-access/nested_trait.test delete mode 100644 src/Tests/Inference/null-coalesce/null-coalesce_null.test delete mode 100644 src/Tests/Inference/null-coalesce/null-coalesce_nullable.test delete mode 100644 src/Tests/Inference/postfix-update/decrement.test delete mode 100644 src/Tests/Inference/postfix-update/increment.test delete mode 100644 src/Tests/Inference/qualified-name/function-fallback-to-global.test delete mode 100644 src/Tests/Inference/qualified-name/function-in-namespace.test delete mode 100644 src/Tests/Inference/qualified-name/function-no-namespace.test delete mode 100644 src/Tests/Inference/reflection/circular-dependency-trait.test delete mode 100644 src/Tests/Inference/reflection/circular-dependency_interface.test delete mode 100644 src/Tests/Inference/reflection/circular-dependency_parent.test delete mode 100644 src/Tests/Inference/reflection/gh-2207.test delete mode 100644 src/Tests/Inference/reflection/mixin_class.test delete mode 100644 src/Tests/Inference/reflection/mixin_generic.test delete mode 100644 src/Tests/Inference/reflection/mixin_properties.test delete mode 100644 src/Tests/Inference/reflection/mixin_recursive.test delete mode 100644 src/Tests/Inference/reflection/mixin_static.test delete mode 100644 src/Tests/Inference/reflection/multiple_mixins.test delete mode 100644 src/Tests/Inference/reflection/promoted_property_with_params.test delete mode 100644 src/Tests/Inference/reflection/virtial_static_method.test delete mode 100644 src/Tests/Inference/require_and_include/foo.php delete mode 100644 src/Tests/Inference/require_and_include/include.test delete mode 100644 src/Tests/Inference/require_and_include/require.test delete mode 100644 src/Tests/Inference/resolver/call_expression_closure.test delete mode 100644 src/Tests/Inference/resolver/call_expression_closure2.test delete mode 100644 src/Tests/Inference/return-statement/class_method.test delete mode 100644 src/Tests/Inference/return-statement/missing_return_type.test delete mode 100644 src/Tests/Inference/return-statement/multiple_return.test delete mode 100644 src/Tests/Inference/return-statement/no_return.test delete mode 100644 src/Tests/Inference/subscript-expression/array_shape_access.test delete mode 100644 src/Tests/Inference/ternary_expression/for_missing.test delete mode 100644 src/Tests/Inference/ternary_expression/use_if_branch_if_truthy.test delete mode 100644 src/Tests/Inference/type/arrayshape.test delete mode 100644 src/Tests/Inference/type/arrayshape_multiline.test delete mode 100644 src/Tests/Inference/type/arrayshape_multiline_optional.test delete mode 100644 src/Tests/Inference/type/callable.test delete mode 100644 src/Tests/Inference/type/class-string-new-no-type.test delete mode 100644 src/Tests/Inference/type/class-string-new.test delete mode 100644 src/Tests/Inference/type/class-string-static-call.test delete mode 100644 src/Tests/Inference/type/class-string.test delete mode 100644 src/Tests/Inference/type/closure.test delete mode 100644 src/Tests/Inference/type/conditional-type-nested.test delete mode 100644 src/Tests/Inference/type/conditional-type-on-function.test delete mode 100644 src/Tests/Inference/type/conditional-type.test delete mode 100644 src/Tests/Inference/type/conditional-type2.test delete mode 100644 src/Tests/Inference/type/conditional-type3.test delete mode 100644 src/Tests/Inference/type/gh-1707.test delete mode 100644 src/Tests/Inference/type/int-range.test delete mode 100644 src/Tests/Inference/type/list.test delete mode 100644 src/Tests/Inference/type/never.test delete mode 100644 src/Tests/Inference/type/parenthesized.test delete mode 100644 src/Tests/Inference/type/parenthesized_closure.test delete mode 100644 src/Tests/Inference/type/self_context_trait.test delete mode 100644 src/Tests/Inference/type/static.test delete mode 100644 src/Tests/Inference/type/static_context.test delete mode 100644 src/Tests/Inference/type/string-literal.test delete mode 100644 src/Tests/Inference/type/union_from_relative_docblock.test delete mode 100644 src/Tests/Inference/type/variadic.test delete mode 100644 src/Tests/Inference/variable/braced_expression.test delete mode 100644 src/Tests/Inference/variable/pass-by-ref.test delete mode 100644 src/Tests/Inference/virtual_member/method.test delete mode 100644 src/Tests/Inference/virtual_member/method2.test delete mode 100644 src/Tests/Inference/virtual_member/method_and_property_with_same_name.test delete mode 100644 src/Tests/Inference/virtual_member/property.test delete mode 100644 src/Tests/Inference/virtual_member/trait_method1.test delete mode 100644 src/Tests/Inference/virtual_member/virtual-method-returns-static.test delete mode 100644 src/Tests/Inference/virtual_member/virtual-method-returns-this.test delete mode 100644 src/Tests/Integration/Bridge/Composer/ComposerSourceCodeLocatorTest.php delete mode 100644 src/Tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionMethodCollectionTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionParameterCollectionTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionArgumentTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionClassTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionConstantTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionFunctionTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionInterfaceTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodCallTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionParameterTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPromotedPropertyTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPropertyTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionScopeTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionTraitTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/TraitImport/TraitImportsTest.php delete mode 100644 src/Tests/Integration/Bridge/TolerantParser/Reflection/TypeResolver/DeclaredMemberTypeResolverTest.php delete mode 100644 src/Tests/Integration/Core/ClassReflectorTest.php delete mode 100644 src/Tests/Integration/Core/ConstantReflectorTest.php delete mode 100644 src/Tests/Integration/Core/FunctionReflectorTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameBuilderTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/AssertWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/AssignmentWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/FunctionLikeWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/IncludeWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/ReturnTypeWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalker/VariableWalkerTest.php delete mode 100644 src/Tests/Integration/Core/Inference/FrameWalkerTestCase.php delete mode 100644 src/Tests/Integration/Core/Inference/NodeContextResolverTest.php delete mode 100644 src/Tests/Integration/Core/SourceCodeLocator/StubSourceLocatorTest.php delete mode 100644 src/Tests/Integration/Core/SourceReflectorTest.php delete mode 100644 src/Tests/Integration/Core/Util/OriginalMethodResolverTest.php delete mode 100644 src/Tests/Integration/IntegrationTestCase.php delete mode 100755 src/Tests/Smoke/smoke_test.php delete mode 100644 src/Tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php delete mode 100644 src/Tests/Unit/Bridge/Phpactor/DocblockParser/DocblockParserFactoryTest.php delete mode 100644 src/Tests/Unit/Bridge/TolerantParser/Parser/CachedParserTest.php delete mode 100644 src/Tests/Unit/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php delete mode 100644 src/Tests/Unit/Core/Cache/TtlCacheTest.php delete mode 100644 src/Tests/Unit/Core/CacheForDocumentTest.php delete mode 100644 src/Tests/Unit/Core/ClassNameTest.php delete mode 100644 src/Tests/Unit/Core/DefaultValueTest.php delete mode 100644 src/Tests/Unit/Core/DocBlock/PlainDocblockTest.php delete mode 100644 src/Tests/Unit/Core/Inference/AssignmentstTestCase.php delete mode 100644 src/Tests/Unit/Core/Inference/FrameTest.php delete mode 100644 src/Tests/Unit/Core/Inference/LocalAssignmentsTest.php delete mode 100644 src/Tests/Unit/Core/Inference/NodeReflectorTest.php delete mode 100644 src/Tests/Unit/Core/Inference/ProblemsTest.php delete mode 100644 src/Tests/Unit/Core/Inference/SymbolFactoryTest.php delete mode 100644 src/Tests/Unit/Core/Inference/TypeAssertionsTest.php delete mode 100644 src/Tests/Unit/Core/Inference/TypeCombinatorTest.php delete mode 100644 src/Tests/Unit/Core/NameImportsTest.php delete mode 100644 src/Tests/Unit/Core/NameTest.php delete mode 100644 src/Tests/Unit/Core/PositionTest.php delete mode 100644 src/Tests/Unit/Core/Reflection/Collection/ChainReflectionMemberCollectionTest.php delete mode 100644 src/Tests/Unit/Core/Reflection/Collection/HomogeneousReflectionMemberCollectionTest.php delete mode 100644 src/Tests/Unit/Core/Reflector/ClassReflector/MemonizedClassReflectorTest.php delete mode 100644 src/Tests/Unit/Core/Reflector/SourceCode/ContextualSourceCodeReflectorTest.php delete mode 100644 src/Tests/Unit/Core/SourceCodeLocator/ChainSourceLocatorTest.php delete mode 100644 src/Tests/Unit/Core/SourceCodeLocator/NativeReflectionFunctionSourceLocatorTest.php delete mode 100644 src/Tests/Unit/Core/SourceCodeLocator/StringSourceLocatorTest.php delete mode 100644 src/Tests/Unit/Core/SourceCodeLocator/TemporarySourceLocatorTest.php delete mode 100644 src/Tests/Unit/Core/TemplateMapTest.php delete mode 100644 src/Tests/Unit/Core/Type/AggregateTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/ArrayLiteralTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/ArrayShapeTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/ArrayTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/CallableTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/ClosureTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/GenericClassTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/IntersectionTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/NumericTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/ReflectedClassTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/StringLiteralTypeTest.php delete mode 100644 src/Tests/Unit/Core/Type/UnionTypeTest.php delete mode 100644 src/Tests/Unit/Core/TypeFactoryTest.php delete mode 100644 src/Tests/Unit/Core/Virtual/VirtualReflectionMemberTestCase.php delete mode 100644 src/Tests/Unit/Core/Virtual/VirtualReflectionMethodTest.php delete mode 100644 src/Tests/Unit/Core/Virtual/VirtualReflectionParameterTest.php delete mode 100644 src/Tests/Unit/ReflectorBuilderTest.php delete mode 100644 src/Tests/Unit/TypeUtilTest.php delete mode 100644 src/Tests/Workspace/88f1dd30b180fd8d7e17547833be97ef.map delete mode 100644 src/Tests/Workspace/one.php delete mode 100644 src/Tests/Workspace/test.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ed1821b63 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +# Handle line endings automatically for files detected as text and leave all +# files detected as binary untouched. +* text=auto + +# Files and directories with the attribute export-ignore won’t be added to +# archive files. See http://git-scm.com/docs/gitattributes for details. +.gitattributes export-ignore +.gitignore export-ignore +/*.neon export-ignore +/.github export-ignore +/.php-cs-fixer.dist.php export-ignore +/phpunit.xml.dist export-ignore +/tests/ export-ignore diff --git a/bin/update.php b/bin/update.php new file mode 100755 index 000000000..8edf3e6d6 --- /dev/null +++ b/bin/update.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php +remove('src'); +$fs->remove('tests'); +$fs->mirror('../phpactor/lib/WorseReflection', 'src'); +$fs->mirror('src/Tests', 'tests'); diff --git a/composer.json b/composer.json index dda805485..b0f42fd3e 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ "phpactor/tolerant-php-parser": "dev-main", "amphp/amp": "^2.6", - "phpactor/text-document": "^2.0.0", - "phpactor/docblock-parser": "^0.1.0", + "phpactor/text-document": "^2.1.0", + "phpactor/docblock-parser": "^0.2.0", "psr/log": "^1.0" }, "require-dev": { diff --git a/src/Core/Inference/Walker/TestAssertWalker.php b/src/Core/Inference/Walker/TestAssertWalker.php index 5ef1f72a5..ddca59c18 100644 --- a/src/Core/Inference/Walker/TestAssertWalker.php +++ b/src/Core/Inference/Walker/TestAssertWalker.php @@ -7,7 +7,7 @@ use Microsoft\PhpParser\Node\Expression\ArgumentExpression; use Microsoft\PhpParser\Node\Expression\CallExpression; use PHPUnit\Framework\TestCase; -use Phpactor\Extension\LanguageServerBridge\Converter\PositionConverter; +use Phpactor\TextDocument\ByteOffset; use Phpactor\WorseReflection\Bridge\TolerantParser\TextDocument\NodeToTextDocumentConverter; use Phpactor\WorseReflection\Core\Inference\Frame; use Phpactor\WorseReflection\Core\Inference\FrameResolver; @@ -196,19 +196,16 @@ private function resolveArgs(?ArgumentExpressionList $argList, FrameResolver $re private function assertTypeIs(Node $node, Type $actualType, Type $expectedType, ?NodeContext $message = null): void { $message = isset($message) ? TypeUtil::valueOrNull($message->type()) : null; - $position = PositionConverter::intByteOffsetToPosition($node->getStartPosition(), $node->getFileContents()); if ($actualType->__toString() === TypeUtil::valueOrNull($expectedType)) { $this->testCase->addToAssertionCount(1); return; } $this->testCase->fail(sprintf( - "%s: \n\n %s\n\nis:\n\n %s\n\non offset %s line %s char %s", + "%s: \n\n %s\n\nis:\n\n %s\n\non offset %s", $message ?: 'Failed asserting that:', $actualType->__toString(), trim($expectedType->__toString(), '"'), $node->getStartPosition(), - $position->line + 1, - $position->character + 1, )); } } diff --git a/src/Tests/Assert/TrinaryAssert.php b/src/Tests/Assert/TrinaryAssert.php deleted file mode 100644 index a2a3292a9..000000000 --- a/src/Tests/Assert/TrinaryAssert.php +++ /dev/null @@ -1,23 +0,0 @@ -getReflector()->reflectOffset(TextDocumentBuilder::fromUri(__DIR__ . '/../../../../vendor/phpactor/tolerant-php-parser/src/Parser.php')->build(), 183744); - } -} diff --git a/src/Tests/Benchmarks/BaseBenchCase.php b/src/Tests/Benchmarks/BaseBenchCase.php deleted file mode 100644 index 386040669..000000000 --- a/src/Tests/Benchmarks/BaseBenchCase.php +++ /dev/null @@ -1,68 +0,0 @@ -workspace(); - $workspace->reset(); - $stubLocator = new StubSourceLocator( - ReflectorBuilder::create()->build(), - __DIR__ . '/../../../../vendor/jetbrains/phpstorm-stubs', - __DIR__ . '/../Cache', - ); - - $builder = ReflectorBuilder::create(); - foreach ($this->diagnosticProviders as $provider) { - $builder->addDiagnosticProvider($provider); - } - $this->reflector = $builder - ->addLocator($composerLocator) - ->addLocator($stubLocator) - ->enableCache() - ->cacheLifetime(5) - ->enableContextualSourceLocation() - ->build(); - } - - public function loadFixture(string $name): void - { - foreach ((array)glob(sprintf('%s/%s/%s/*.php.test', __DIR__, 'fixtures', $name)) as $path) { - $this->workspace()->put( - substr(basename((string)$path), 0, -5), - (string)file_get_contents((string)$path) - ); - } - } - - public function getReflector(): Reflector - { - return $this->reflector; - } - - private function workspace(): Workspace - { - return new Workspace(__DIR__ . '/../Workspace'); - } -} diff --git a/src/Tests/Benchmarks/DiagnosticsBench.php b/src/Tests/Benchmarks/DiagnosticsBench.php deleted file mode 100644 index eba5d2b57..000000000 --- a/src/Tests/Benchmarks/DiagnosticsBench.php +++ /dev/null @@ -1,53 +0,0 @@ -reflector = ReflectorBuilder::create() - ->addDiagnosticProvider(new MissingMemberProvider()) - ->build(); - } - - /** - * @BeforeMethods({"init"}) - * @ParamProviders({"providePaths"}) - * @param array{path:string} $params - */ - public function benchDiagnostics(array $params): void - { - $diagnostics = wait($this->reflector->diagnostics( - TextDocumentBuilder::fromUri($params['path'])->build() - )); - } - - /** - * @return Generator - */ - public function providePaths(): Generator - { - foreach ((new GlobIterator(__DIR__ . '/fixtures/diagnostics/*.test')) as $info) { - assert($info instanceof SplFileInfo); - yield $info->getFilename() => [ - 'path' => $info->getRealPath() - ]; - } - } -} diff --git a/src/Tests/Benchmarks/Examples/MethodClass.php b/src/Tests/Benchmarks/Examples/MethodClass.php deleted file mode 100644 index 80a339c0b..000000000 --- a/src/Tests/Benchmarks/Examples/MethodClass.php +++ /dev/null @@ -1,21 +0,0 @@ -getReflector()->reflectClassLike(ClassName::fromString(TestCase::class)); - } - - /** - * @Subject() - * @OutputTimeUnit("milliseconds", precision=2) - * @Assert("mode(variant.time.avg) <= mode(baseline.time.avg) +/- 10%") - */ - public function test_case_methods_and_properties(): void - { - $class = $this->getReflector()->reflectClassLike(ClassName::fromString(TestCase::class)); - - foreach ($class->methods() as $method) { - foreach ($method->parameters() as $parameter) { - $method->type(); - } - } - } - - /** - * @Subject() - * @Revs(1) - * @OutputTimeUnit("milliseconds", precision=2) - * @Assert("mode(variant.time.avg) <= mode(baseline.time.avg) +/- 10%") - */ - public function test_case_method_frames(): void - { - $class = $this->getReflector()->reflectClassLike(ClassName::fromString(TestCase::class)); - - foreach ($class->methods() as $method) { - $method->frame(); - } - } -} diff --git a/src/Tests/Benchmarks/ReflectMethodBench.php b/src/Tests/Benchmarks/ReflectMethodBench.php deleted file mode 100644 index 92a589c71..000000000 --- a/src/Tests/Benchmarks/ReflectMethodBench.php +++ /dev/null @@ -1,47 +0,0 @@ -class = $this->getReflector()->reflectClassLike(ClassName::fromString(MethodClass::class)); - } - - /** - * @Subject() - */ - public function method(): void - { - $this->class->methods()->get('methodNoReturnType'); - } - - /** - * @Subject() - */ - public function method_return_type(): void - { - $this->class->methods()->get('methodWithReturnType')->returnType(); - } - - /** - * @Subject() - */ - public function method_inferred_return_type(): void - { - $this->class->methods()->get('methodWithDocblockReturnType')->type(); - } -} diff --git a/src/Tests/Benchmarks/ReflectPropertyBench.php b/src/Tests/Benchmarks/ReflectPropertyBench.php deleted file mode 100644 index 1ddb6a15e..000000000 --- a/src/Tests/Benchmarks/ReflectPropertyBench.php +++ /dev/null @@ -1,40 +0,0 @@ -class = $this->getReflector()->reflectClassLike(ClassName::fromString(PropertyClass::class)); - } - - /** - * @Subject() - */ - public function property(): void - { - $this->class->properties()->get('noType'); - } - - /** - * @Subject() - */ - public function property_return_type(): void - { - $this->class->properties()->get('withType')->inferredType(); - } -} diff --git a/src/Tests/Benchmarks/ReflectionStubsBench.php b/src/Tests/Benchmarks/ReflectionStubsBench.php deleted file mode 100644 index ff3476e82..000000000 --- a/src/Tests/Benchmarks/ReflectionStubsBench.php +++ /dev/null @@ -1,35 +0,0 @@ -reflector = $this->getReflector(); - } - - /** - * @Subject() - */ - public function test_classes_and_methods(): void - { - $classes = $this->reflector->reflectClassesIn(TextDocumentBuilder::fromUri(__DIR__ . '/../../../../vendor/jetbrains/phpstorm-stubs/Reflection/Reflection.php')->build()); - - foreach ($classes as $class) { - foreach ($class->methods() as $method) { - } - } - } -} diff --git a/src/Tests/Benchmarks/SelfReflectClassBench.php b/src/Tests/Benchmarks/SelfReflectClassBench.php deleted file mode 100644 index 58ef74d3f..000000000 --- a/src/Tests/Benchmarks/SelfReflectClassBench.php +++ /dev/null @@ -1,35 +0,0 @@ -getReflector()->reflectClassLike(ClassName::fromString(self::class)); - - foreach ($class->methods() as $method) { - foreach ($method->parameters() as $parameter) { - $method->inferredType(); - } - } - } - - public function benchFrames(): void - { - $class = $this->getReflector()->reflectClassLike(ClassName::fromString(self::class)); - - foreach ($class->methods() as $method) { - $method->frame(); - } - } -} diff --git a/src/Tests/Benchmarks/YiiBench.php b/src/Tests/Benchmarks/YiiBench.php deleted file mode 100644 index 55fdd5460..000000000 --- a/src/Tests/Benchmarks/YiiBench.php +++ /dev/null @@ -1,26 +0,0 @@ -loadFixture('yii'); - } - - /** - * @BeforeMethods({"setUp", "install"}) - */ - public function benchMembers(): void - { - $reflection = $this->getReflector()->reflectClass('Phpactor\WorseReflection\Tests\Workspace\Record'); - foreach ($reflection->members() as $method) { - $method->inferredType(); - } - } -} diff --git a/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_missing_methods.test b/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_missing_methods.test deleted file mode 100644 index 6cc8d3d0a..000000000 --- a/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_missing_methods.test +++ /dev/null @@ -1,1179 +0,0 @@ -expectException(\Phpactor\WorseReflection\Core\Exception\ClassNotFound::class); - $this->createReflector('')->reflectClassLike(ClassName::fromString('Foobar')); - } - - /** - * @dataProvider provideReflectionClass - */ - public function testReflectClass(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideReflectionClass(): Generator - { - yield 'It reflects an empty class' => [ - <<<'EOT' - assertEquals('Foobar', (string) $class->name()->short()); - $this->assertInstanceOf(ReflectionClass::class, $class); - $this->assertFalse($class->isInterface()); - }, - ]; - - yield 'It reflects a class which extends another' => [ - <<<'EOT' - assertEquals('Foobar', (string) $class->name()->short()); - $this->assertEquals('Barfoo', (string) $class->parent()->name()->short()); - }, - ]; - - yield 'It reflects class constants' => [ - <<<'EOT' - assertCount(3, $class->constants()); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('FOOBAR')); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('EEEBAR')); - }, - ]; - - yield 'It can provide the name of its last member' => [ - <<<'EOT' - assertEquals('bar', $class->properties()->last()->name()); - }, - ]; - - yield 'It can provide the name of its first member' => [ - <<<'EOT' - assertEquals('foo', $class->properties()->first()->name()); - }, - ]; - - yield 'It can provide its position' => [ - <<<'EOT' - assertEquals(7, $class->position()->start()); - }, - ]; - - yield 'It can provide the position of its member declarations' => [ - <<<'EOT' - assertEquals(20, $class->memberListPosition()->start()); - }, - ]; - - yield 'It provides list of its interfaces' => [ - <<<'EOT' - assertEquals(1, $class->interfaces()->count()); - $this->assertEquals('InterfaceOne', $class->interfaces()->first()->name()); - }, - ]; - - yield 'It list of interfaces includes interfaces from parent classes' => [ - <<<'EOT' - assertEquals(1, $class->interfaces()->count()); - $this->assertEquals('InterfaceOne', $class->interfaces()->first()->name()); - }, - ]; - - yield 'It provides list of its traits' => [ - <<<'EOT' - assertEquals(2, $class->traits()->count()); - $this->assertEquals('TraitNUMBERone', $class->traits()->get('TraitNUMBERone')->name()); - $this->assertEquals('TraitNUMBERtwo', $class->traits()->get('TraitNUMBERtwo')->name()); - }, - ]; - - yield 'Traits are inherited from parent classes (?)' => [ - <<<'EOT' - assertEquals(1, $class->traits()->count()); - $this->assertEquals('TraitNUMBERone', $class->traits()->first()->name()); - }, - ]; - - yield 'Get methods includes trait methods' => [ - <<<'EOT' - assertEquals(3, $class->methods()->count()); - $this->assertTrue($class->methods()->has('traitMethod1')); - $this->assertTrue($class->methods()->has('traitMethod2')); - }, - ]; - - yield 'Tolerates not found traits' => [ - <<<'EOT' - assertEquals(1, $class->methods()->count()); - }, - ]; - - yield 'Get methods includes aliased trait methods' => [ - <<<'EOT' - assertEquals(4, $class->methods()->count()); - $this->assertTrue($class->methods()->has('one')); - $this->assertTrue($class->methods()->has('two')); - $this->assertTrue($class->methods()->has('three')); - $this->assertTrue($class->methods()->has('four')); - $this->assertEquals(Visibility::private(), $class->methods()->get('two')->visibility()); - $this->assertEquals(Visibility::protected(), $class->methods()->get('three')->visibility()); - $this->assertFalse($class->methods()->belongingTo(ClassName::fromString(Class2::class))->has('two')); - $this->assertEquals('TraitOne', $class->methods()->get('two')->declaringClass()->name()->short()); - }, - ]; - - yield 'Get methods includes namespaced aliased trait methods' => [ - <<<'EOT' - assertEquals(3, $class->methods()->count()); - $this->assertTrue($class->methods()->has('one')); - $this->assertTrue($class->methods()->has('three')); - }, - ]; - - yield 'Get properties includes trait properties' => [ - <<<'EOT' - assertEquals(1, $class->properties()->count()); - $this->assertEquals('prop1', $class->properties()->first()->name()); - }, - ]; - - yield 'Get methods at offset' => [ - <<<'EOT' - assertEquals(1, $class->methods()->atOffset(27)->count()); - }, - ]; - - yield 'Get properties includes trait methods' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - }, - ]; - - yield 'Get properties for belonging to' => [ - <<<'EOT' - assertCount(1, $class->properties()->belongingTo(ClassName::fromString('Class1'))); - $this->assertCount(0, $class->properties()->belongingTo(ClassName::fromString('Class2'))); - }, - ]; - - - yield 'If it extends an interface, then ignore' => [ - <<<'EOT' - assertEquals(0, $class->methods()->count()); - }, - ]; - - - yield 'isInstanceOf returns false when it is not an instance of' => [ - <<<'EOT' - assertFalse($class->isInstanceOf(ClassName::fromString('Foobar'))); - }, - ]; - - yield 'isInstanceOf returns true for itself' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('Class2'))); - }, - ]; - - yield 'isInstanceOf returns true when it is not an instance of an interface' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeInterface'))); - }, - ]; - - yield 'isInstanceOf returns true when a class implements the interface and has a parent' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeInterface'))); - }, - ]; - - yield 'isInstanceOf returns true for a parent class' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeParent'))); - }, - ]; - - yield 'Returns source code' => [ - <<<'EOT' - assertStringContainsString('class Class2', (string) $class->sourceCode()); - }, - ]; - - yield 'Returns imported classes' => [ - <<<'EOT' - assertEquals(NameImports::fromNames([ - 'Barfoo' => Name::fromString('Foobar\\Barfoo'), - 'Carzatz' => Name::fromString('Barfoo\\Foobaz'), - ]), $class->scope()->nameImports()); - }, - ]; - - yield 'Inherits constants from interface' => [ - <<<'EOT' - assertCount(1, $class->constants()); - $this->assertEquals('SOME_CONSTANT', $class->constants()->get('SOME_CONSTANT')->name()); - }, - ]; - - yield 'Returns all members' => [ - <<<'EOT' - assertCount(3, $class->members()); - $this->assertTrue($class->members()->has('FOOBAR')); - $this->assertTrue($class->members()->has('foobar')); - $this->assertTrue($class->members()->has('foo')); - }, - ]; - - yield 'Incomplete extends' => [ - <<<'EOT' - assertNull($class->parent()); - $this->assertEquals('Class1', $class->name()->short()); - }, - ]; - - yield 'Does not infinite loop with self-referencing class on get interfaces' => [ - <<<'EOT' - assertCount(0, $class->interfaces()); - }, - ]; - - yield 'Says if class is abstract' => [ - <<<'EOT' - assertTrue($class->isAbstract()); - }, - ]; - - yield 'Says if class is not abstract' => [ - <<<'EOT' - assertFalse($class->isAbstract()); - }, - ]; - - yield 'Says if class is final' => [ - <<<'EOT' - assertTrue($class->isFinal()); - }, - ]; - - yield 'Says if class is deprecated' => [ - <<<'EOT' - assertTrue($class->deprecation()->isDefined()); - }, - ]; - } - - /** - * @dataProvider provideVirtualMethods - */ - public function testVirtualMethods(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideVirtualMethods() - { - yield 'virtual methods' => [ - <<<'EOT' - assertEquals(2, $class->methods()->count()); - $this->assertEquals('foobar', $class->methods()->first()->name()); - } - ]; - - yield 'virtual methods merge onto existing ones' => [ - <<<'EOT' - assertCount(1, $class->methods()); - - // originally this returned the declared type - $this->assertEquals( - 'Foobar', - $class->methods()->first()->type()->__toString(), - ); - $this->assertEquals( - 'Foobar', - $class->methods()->first()->inferredType()->__toString(), - ); - }, - ]; - - yield 'virtual methods are inherited' => [ - <<<'EOT' - assertCount(2, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from interface' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from multiple layers of interfaces' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from parent class which implements interface' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - $this->assertEquals( - 'ParentInterface', - $class->methods()->get('foobar')->declaringClass()->name()->__toString() - ); - }, - ]; - - yield 'virtual method types can be relative' => [ - 'assertEquals( - 'Bosh\Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual method types can be absolute' => [ - 'assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods of child classes override those of parents' => [ - <<<'EOT' - assertCount(2, $class->methods()); - $this->assertEquals( - 'Barfoo', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are extracted from traits' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals('Foobar', $class->methods()->first()->inferredType()->__toString()); - }, - ]; - - yield 'virtual methods are extracted from traits of a parent class' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals('Foobar', $class->methods()->first()->inferredType()->__toString()); - }, - ]; - } - - /** - * @dataProvider provideVirtualProperties - */ - public function testVirtualProperties(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideVirtualProperties() - { - yield 'virtual properties' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - } - ]; - - yield 'invalid properties' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - } - ]; - - yield 'multiple types' => [ - <<<'EOT' - assertEquals(1, $class->properties()->count()); - self::assertInstanceOf(UnionType::class, $class->properties()->first()->type()); - self::assertEquals('string|int', $class->properties()->first()->type()); - } - ]; - - yield 'virtual properties are extracted from traits' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - $this->assertEquals('Foobar', $class->properties()->first()->inferredType()->__toString()); - $this->assertEquals('barfoo', $class->properties()->last()->name()); - $this->assertEquals('Barfoo', $class->properties()->last()->inferredType()->__toString()); - } - ]; - - yield 'virtual properties are extracted from traits of a parent class' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - $this->assertEquals('Foobar', $class->properties()->first()->inferredType()->__toString()); - $this->assertEquals('barfoo', $class->properties()->last()->name()); - $this->assertEquals('Barfoo', $class->properties()->last()->inferredType()->__toString()); - } - ]; - } -} - - -wrAssertDiagnostics(108, ['MissingMethod']); - diff --git a/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_new_generic_objects.test b/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_new_generic_objects.test deleted file mode 100644 index 829464a40..000000000 --- a/src/Tests/Benchmarks/fixtures/diagnostics/lots_of_new_generic_objects.test +++ /dev/null @@ -1,66 +0,0 @@ -bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang()->bang(->bang()->bang()->bang(->bang()->bang(); diff --git a/src/Tests/Benchmarks/fixtures/diagnostics/phpstan.test b/src/Tests/Benchmarks/fixtures/diagnostics/phpstan.test deleted file mode 100644 index 5a2deb423..000000000 --- a/src/Tests/Benchmarks/fixtures/diagnostics/phpstan.test +++ /dev/null @@ -1,4977 +0,0 @@ - */ - private array $truthyScopes = []; - - /** @var array */ - private array $falseyScopes = []; - - private ?string $namespace; - - private ?self $scopeOutOfFirstLevelStatement = null; - - private ?self $scopeWithPromotedNativeTypes = null; - - /** - * @param array $expressionTypes - * @param array $conditionalExpressions - * @param list $inClosureBindScopeClasses - * @param array $currentlyAssignedExpressions - * @param array $currentlyAllowedUndefinedExpressions - * @param array $nativeExpressionTypes - * @param array $inFunctionCallsStack - */ - public function __construct( - private InternalScopeFactory $scopeFactory, - private ReflectionProvider $reflectionProvider, - private InitializerExprTypeResolver $initializerExprTypeResolver, - private DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry, - private ExprPrinter $exprPrinter, - private TypeSpecifier $typeSpecifier, - private PropertyReflectionFinder $propertyReflectionFinder, - private Parser $parser, - private NodeScopeResolver $nodeScopeResolver, - private ConstantResolver $constantResolver, - private ScopeContext $context, - private PhpVersion $phpVersion, - private bool $declareStrictTypes = false, - private FunctionReflection|ExtendedMethodReflection|null $function = null, - ?string $namespace = null, - private array $expressionTypes = [], - private array $nativeExpressionTypes = [], - private array $conditionalExpressions = [], - private array $inClosureBindScopeClasses = [], - private ?ParametersAcceptor $anonymousFunctionReflection = null, - private bool $inFirstLevelStatement = true, - private array $currentlyAssignedExpressions = [], - private array $currentlyAllowedUndefinedExpressions = [], - private array $inFunctionCallsStack = [], - private bool $afterExtractCall = false, - private ?Scope $parentScope = null, - private bool $nativeTypesPromoted = false, - private bool $explicitMixedInUnknownGenericNew = false, - private bool $explicitMixedForGlobalVariables = false, - ) - { - if ($namespace === '') { - $namespace = null; - } - - $this->namespace = $namespace; - } - - /** @api */ - public function getFile(): string - { - return $this->context->getFile(); - } - - /** @api */ - public function getFileDescription(): string - { - if ($this->context->getTraitReflection() === null) { - return $this->getFile(); - } - - /** @var ClassReflection $classReflection */ - $classReflection = $this->context->getClassReflection(); - - $className = $classReflection->getDisplayName(); - if (!$classReflection->isAnonymous()) { - $className = sprintf('class %s', $className); - } - - $traitReflection = $this->context->getTraitReflection(); - if ($traitReflection->getFileName() === null) { - throw new ShouldNotHappenException(); - } - - return sprintf( - '%s (in context of %s)', - $traitReflection->getFileName(), - $className, - ); - } - - /** @api */ - public function isDeclareStrictTypes(): bool - { - return $this->declareStrictTypes; - } - - public function enterDeclareStrictTypes(): self - { - return $this->scopeFactory->create( - $this->context, - true, - null, - null, - $this->expressionTypes, - $this->nativeExpressionTypes, - ); - - } - - /** @api */ - public function isInClass(): bool - { - return $this->context->getClassReflection() !== null; - } - - /** @api */ - public function isInTrait(): bool - { - return $this->context->getTraitReflection() !== null; - } - - /** @api */ - public function getClassReflection(): ?ClassReflection - { - return $this->context->getClassReflection(); - } - - /** @api */ - public function getTraitReflection(): ?ClassReflection - { - return $this->context->getTraitReflection(); - } - - /** - * @api - * @return FunctionReflection|ExtendedMethodReflection|null - */ - public function getFunction() - { - return $this->function; - } - - /** @api */ - public function getFunctionName(): ?string - { - return $this->function !== null ? $this->function->getName() : null; - } - - /** @api */ - public function getNamespace(): ?string - { - return $this->namespace; - } - - /** @api */ - public function getParentScope(): ?Scope - { - return $this->parentScope; - } - - /** @api */ - 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->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - [], - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - true, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function afterClearstatcacheCall(): self - { - $expressionTypes = $this->expressionTypes; - foreach (array_keys($expressionTypes) 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_writeable', - 'is_readable', - 'is_executable', - 'is_file', - 'is_dir', - 'is_link', - 'filectime', - 'fileatime', - 'filemtime', - 'fileinode', - 'filegroup', - 'fileowner', - 'filesize', - 'filetype', - 'fileperms', - ] as $functionName) { - if (!str_starts_with($exprString, $functionName . '(') && !str_starts_with($exprString, '\\' . $functionName . '(')) { - continue; - } - - unset($expressionTypes[$exprString]); - continue 2; - } - } - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function afterOpenSslCall(string $openSslFunctionName): self - { - $expressionTypes = $this->expressionTypes; - - if (in_array($openSslFunctionName, [ - 'openssl_cipher_iv_length', - 'openssl_cms_decrypt', - 'openssl_cms_encrypt', - 'openssl_cms_read', - 'openssl_cms_sign', - 'openssl_cms_verify', - 'openssl_csr_export_to_file', - 'openssl_csr_export', - 'openssl_csr_get_public_key', - 'openssl_csr_get_subject', - 'openssl_csr_new', - 'openssl_csr_sign', - 'openssl_decrypt', - 'openssl_dh_compute_key', - 'openssl_digest', - 'openssl_encrypt', - 'openssl_get_curve_names', - 'openssl_get_privatekey', - 'openssl_get_publickey', - 'openssl_open', - 'openssl_pbkdf2', - 'openssl_pkcs12_export_to_file', - 'openssl_pkcs12_export', - 'openssl_pkcs12_read', - 'openssl_pkcs7_decrypt', - 'openssl_pkcs7_encrypt', - 'openssl_pkcs7_read', - 'openssl_pkcs7_sign', - 'openssl_pkcs7_verify', - 'openssl_pkey_derive', - 'openssl_pkey_export_to_file', - 'openssl_pkey_export', - 'openssl_pkey_get_private', - 'openssl_pkey_get_public', - 'openssl_pkey_new', - 'openssl_private_decrypt', - 'openssl_private_encrypt', - 'openssl_public_decrypt', - 'openssl_public_encrypt', - 'openssl_random_pseudo_bytes', - 'openssl_seal', - 'openssl_sign', - 'openssl_spki_export_challenge', - 'openssl_spki_export', - 'openssl_spki_new', - 'openssl_spki_verify', - 'openssl_verify', - 'openssl_x509_checkpurpose', - 'openssl_x509_export_to_file', - 'openssl_x509_export', - 'openssl_x509_fingerprint', - 'openssl_x509_read', - 'openssl_x509_verify', - ], true)) { - unset($expressionTypes['\openssl_error_string()']); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - /** @api */ - public function hasVariableType(string $variableName): TrinaryLogic - { - if ($this->isGlobalVariable($variableName)) { - return TrinaryLogic::createYes(); - } - - $varExprString = '$' . $variableName; - if (!isset($this->expressionTypes[$varExprString])) { - if ($this->canAnyVariableExist()) { - return TrinaryLogic::createMaybe(); - } - - return TrinaryLogic::createNo(); - } - - return $this->expressionTypes[$varExprString]->getCertainty(); - } - - /** @api */ - public function getVariableType(string $variableName): Type - { - if ($this->hasVariableType($variableName)->maybe()) { - if ($variableName === 'argc') { - return IntegerRangeType::fromInterval(1, null); - } - if ($variableName === 'argv') { - return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( - new ArrayType(new IntegerType(), new StringType()), - new NonEmptyArrayType(), - )); - } - if ($this->canAnyVariableExist()) { - return new MixedType(); - } - } - - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables)); - } - - if ($this->hasVariableType($variableName)->no()) { - throw new UndefinedVariableException($this, $variableName); - } - - $varExprString = '$' . $variableName; - if (!array_key_exists($varExprString, $this->expressionTypes)) { - return new MixedType(); - } - - return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType()); - } - - /** - * @api - * @return array - */ - public function getDefinedVariables(): array - { - $variables = []; - foreach ($this->expressionTypes as $exprString => $holder) { - if (!$holder->getExpr() instanceof Variable) { - continue; - } - if (!$holder->getCertainty()->yes()) { - continue; - } - - $variables[] = substr($exprString, 1); - } - - return $variables; - } - - private function isGlobalVariable(string $variableName): bool - { - return in_array($variableName, [ - 'GLOBALS', - '_SERVER', - '_GET', - '_POST', - '_FILES', - '_COOKIE', - '_SESSION', - '_REQUEST', - '_ENV', - ], true); - } - - /** @api */ - public function hasConstant(Name $name): bool - { - $isCompilerHaltOffset = $name->toString() === '__COMPILER_HALT_OFFSET__'; - if ($isCompilerHaltOffset) { - return $this->fileHasCompilerHaltStatementCalls(); - } - - if (!$name->isFullyQualified() && $this->getNamespace() !== null) { - if ($this->hasExpressionType(new ConstFetch(new FullyQualified([$this->getNamespace(), $name->toString()])))->yes()) { - return true; - } - } - if ($this->hasExpressionType(new ConstFetch(new FullyQualified($name->toString())))->yes()) { - return true; - } - - return $this->reflectionProvider->hasConstant($name, $this); - } - - private function fileHasCompilerHaltStatementCalls(): bool - { - $nodes = $this->parser->parseFile($this->getFile()); - foreach ($nodes as $node) { - if ($node instanceof Node\Stmt\HaltCompiler) { - return true; - } - } - - return false; - } - - /** @api */ - public function isInAnonymousFunction(): bool - { - return $this->anonymousFunctionReflection !== null; - } - - /** @api */ - public function getAnonymousFunctionReflection(): ?ParametersAcceptor - { - return $this->anonymousFunctionReflection; - } - - /** @api */ - public function getAnonymousFunctionReturnType(): ?Type - { - if ($this->anonymousFunctionReflection === null) { - return null; - } - - return $this->anonymousFunctionReflection->getReturnType(); - } - - /** @api */ - public function getType(Expr $node): Type - { - if ($node instanceof GetIterableKeyTypeExpr) { - return $this->getType($node->getExpr())->getIterableKeyType(); - } - if ($node instanceof GetIterableValueTypeExpr) { - return $this->getType($node->getExpr())->getIterableValueType(); - } - if ($node instanceof GetOffsetValueTypeExpr) { - return $this->getType($node->getVar())->getOffsetValueType($this->getType($node->getDim())); - } - if ($node instanceof SetOffsetValueTypeExpr) { - return $this->getType($node->getVar())->setOffsetValueType( - $node->getDim() !== null ? $this->getType($node->getDim()) : null, - $this->getType($node->getValue()), - ); - } - if ($node instanceof TypeExpr) { - return $node->getExprType(); - } - - if ($node instanceof OriginalPropertyTypeExpr) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->getPropertyFetch(), $this); - if ($propertyReflection === null) { - return new ErrorType(); - } - - return $propertyReflection->getReadableType(); - } - - $key = $this->getNodeKey($node); - - if (!array_key_exists($key, $this->resolvedTypes)) { - $this->resolvedTypes[$key] = TypeUtils::resolveLateResolvableTypes($this->resolveType($key, $node)); - } - return $this->resolvedTypes[$key]; - } - - private function getNodeKey(Expr $node): string - { - $key = $this->exprPrinter->printExpr($node); - - if ( - $node instanceof Node\FunctionLike - && $node->hasAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME) - && $node->hasAttribute('startFilePos') - ) { - $key .= '/*' . $node->getAttribute('startFilePos') . '*/'; - } - - return $key; - } - - private function resolveType(string $exprString, Expr $node): Type - { - if ($node instanceof Expr\Exit_ || $node instanceof Expr\Throw_) { - return new NeverType(true); - } - - if (!$node instanceof Variable && $this->hasExpressionType($node)->yes()) { - return $this->expressionTypes[$exprString]->getType(); - } - - 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) { - if ( - $node->left instanceof Variable - && is_string($node->left->name) - && $node->right instanceof Variable - && is_string($node->right->name) - && $node->left->name === $node->right->name - ) { - return new ConstantBooleanType(true); - } - - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - - return $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType); - } - - if ($node instanceof Expr\BinaryOp\NotEqual) { - return $this->getType(new Expr\BooleanNot(new BinaryOp\Equal($node->left, $node->right))); - } - - if ($node instanceof Expr\Empty_) { - $result = $this->issetCheck($node->expr, static function (Type $type): ?bool { - $isNull = (new NullType())->isSuperTypeOf($type); - $isFalsey = (new ConstantBooleanType(false))->isSuperTypeOf($type->toBoolean()); - if ($isNull->maybe()) { - return null; - } - if ($isFalsey->maybe()) { - return null; - } - - if ($isNull->yes()) { - if ($isFalsey->yes()) { - return false; - } - if ($isFalsey->no()) { - return true; - } - - return false; - } - - return !$isFalsey->yes(); - }); - if ($result === null) { - return new BooleanType(); - } - - return new ConstantBooleanType(!$result); - } - - if ($node instanceof Node\Expr\BooleanNot) { - $exprBooleanType = $this->getType($node->expr)->toBoolean(); - if ($exprBooleanType instanceof ConstantBooleanType) { - return new ConstantBooleanType(!$exprBooleanType->getValue()); - } - - return new BooleanType(); - } - - if ($node instanceof Node\Expr\BitwiseNot) { - return $this->initializerExprTypeResolver->getBitwiseNotType($node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ( - $node instanceof Node\Expr\BinaryOp\BooleanAnd - || $node instanceof Node\Expr\BinaryOp\LogicalAnd - ) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - if ( - $leftBooleanType->isFalse()->yes() - ) { - return new ConstantBooleanType(false); - } - - $rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); - - if ( - $rightBooleanType->isFalse()->yes() - ) { - return new ConstantBooleanType(false); - } - - if ( - $leftBooleanType->isTrue()->yes() - && $rightBooleanType->isTrue()->yes() - ) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - if ( - $node instanceof Node\Expr\BinaryOp\BooleanOr - || $node instanceof Node\Expr\BinaryOp\LogicalOr - ) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - if ( - $leftBooleanType->isTrue()->yes() - ) { - return new ConstantBooleanType(true); - } - - $rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); - - if ( - $rightBooleanType->isTrue()->yes() - ) { - return new ConstantBooleanType(true); - } - - if ( - $leftBooleanType->isFalse()->yes() - && $rightBooleanType->isFalse()->yes() - ) { - return new ConstantBooleanType(false); - } - - return new BooleanType(); - } - - if ($node instanceof Node\Expr\BinaryOp\LogicalXor) { - $leftBooleanType = $this->getType($node->left)->toBoolean(); - $rightBooleanType = $this->getType($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 ( - $node->left instanceof Variable - && is_string($node->left->name) - && $node->right instanceof Variable - && is_string($node->right->name) - && $node->left->name === $node->right->name - ) { - return new ConstantBooleanType(true); - } - - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - - if ( - ( - $node->left instanceof Node\Expr\PropertyFetch - || $node->left instanceof Node\Expr\StaticPropertyFetch - ) - && $rightType->isNull()->yes() - && !$this->hasPropertyNativeType($node->left) - ) { - return new BooleanType(); - } - - if ( - ( - $node->right instanceof Node\Expr\PropertyFetch - || $node->right instanceof Node\Expr\StaticPropertyFetch - ) - && $leftType->isNull()->yes() - && !$this->hasPropertyNativeType($node->right) - ) { - return new BooleanType(); - } - - return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType); - } - - if ($node instanceof Expr\BinaryOp\NotIdentical) { - return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($node->left, $node->right))); - } - - if ($node instanceof Expr\Instanceof_) { - $expressionType = $this->getType($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->getObjectClassNames() !== []) { - $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) { - return $this->initializerExprTypeResolver->getUnaryMinusType($node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\BinaryOp\Concat) { - return $this->initializerExprTypeResolver->getConcatType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Concat) { - return $this->initializerExprTypeResolver->getConcatType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\BitwiseAnd) { - return $this->initializerExprTypeResolver->getBitwiseAndType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\BitwiseAnd) { - return $this->initializerExprTypeResolver->getBitwiseAndType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\BitwiseOr) { - return $this->initializerExprTypeResolver->getBitwiseOrType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\BitwiseOr) { - return $this->initializerExprTypeResolver->getBitwiseOrType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\BitwiseXor) { - return $this->initializerExprTypeResolver->getBitwiseXorType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\BitwiseXor) { - return $this->initializerExprTypeResolver->getBitwiseXorType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\BinaryOp\Spaceship) { - return $this->initializerExprTypeResolver->getSpaceshipType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Div) { - return $this->initializerExprTypeResolver->getDivType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Div) { - return $this->initializerExprTypeResolver->getDivType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Mod) { - return $this->initializerExprTypeResolver->getModType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Mod) { - return $this->initializerExprTypeResolver->getModType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Plus) { - return $this->initializerExprTypeResolver->getPlusType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Plus) { - return $this->initializerExprTypeResolver->getPlusType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Minus) { - return $this->initializerExprTypeResolver->getMinusType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Minus) { - return $this->initializerExprTypeResolver->getMinusType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Mul) { - return $this->initializerExprTypeResolver->getMulType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Mul) { - return $this->initializerExprTypeResolver->getMulType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\Pow) { - return $this->initializerExprTypeResolver->getPowType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\Pow) { - return $this->initializerExprTypeResolver->getPowType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\ShiftLeft) { - return $this->initializerExprTypeResolver->getShiftLeftType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\ShiftLeft) { - return $this->initializerExprTypeResolver->getShiftLeftType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof BinaryOp\ShiftRight) { - return $this->initializerExprTypeResolver->getShiftRightType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\AssignOp\ShiftRight) { - return $this->initializerExprTypeResolver->getShiftRightType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr)); - } - - if ($node instanceof Expr\Clone_) { - return $this->getType($node->expr); - } - - if ($node instanceof LNumber) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof String_) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof Node\Scalar\Encapsed) { - $resultType = null; - - foreach ($node->parts as $part) { - $partType = $part instanceof EncapsedStringPart - ? new ConstantStringType($part->value) - : $this->getType($part)->toString(); - if ($resultType === null) { - $resultType = $partType; - - continue; - } - - $resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType); - if (count($resultType->getConstantStrings()) === 0) { - return $resultType; - } - } - - return $resultType ?? new ConstantStringType(''); - } elseif ($node instanceof DNumber) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) { - if ($node instanceof FuncCall) { - if ($node->name instanceof Name) { - if ($this->reflectionProvider->hasFunction($node->name, $this)) { - return $this->createFirstClassCallable( - $this->reflectionProvider->getFunction($node->name, $this)->getVariants(), - ); - } - - return new ObjectType(Closure::class); - } - - $callableType = $this->getType($node->name); - if (!$callableType->isCallable()->yes()) { - return new ObjectType(Closure::class); - } - - return $this->createFirstClassCallable( - $callableType->getCallableParametersAcceptors($this), - ); - } - - if ($node instanceof MethodCall) { - if (!$node->name instanceof Node\Identifier) { - return new ObjectType(Closure::class); - } - - $varType = $this->getType($node->var); - $method = $this->getMethodReflection($varType, $node->name->toString()); - if ($method === null) { - return new ObjectType(Closure::class); - } - - return $this->createFirstClassCallable($method->getVariants()); - } - - if ($node instanceof Expr\StaticCall) { - if (!$node->class instanceof Name) { - return new ObjectType(Closure::class); - } - - $classType = $this->resolveTypeByName($node->class); - if (!$node->name instanceof Node\Identifier) { - return new ObjectType(Closure::class); - } - - $methodName = $node->name->toString(); - if (!$classType->hasMethod($methodName)->yes()) { - return new ObjectType(Closure::class); - } - - return $this->createFirstClassCallable($classType->getMethod($methodName, $this)->getVariants()); - } - - if ($node instanceof New_) { - return new ErrorType(); - } - - throw new ShouldNotHappenException(); - } 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 ShouldNotHappenException(); - } - $parameters[] = new NativeParameterReflection( - $param->var->name, - $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex, - $this->getFunctionType($param->type, $this->isParameterValueNullable($param), false), - $param->byRef - ? PassedByReference::createCreatesNewVariable() - : PassedByReference::createNo(), - $param->variadic, - $param->default !== null ? $this->getType($param->default) : null, - ); - } - - $callableParameters = null; - $arrayMapArgs = $node->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME); - if ($arrayMapArgs !== null) { - $callableParameters = []; - foreach ($arrayMapArgs as $funcCallArg) { - $callableParameters[] = new DummyParameter('item', $this->getType($funcCallArg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null); - } - } - - if ($node instanceof Expr\ArrowFunction) { - $arrowScope = $this->enterArrowFunctionWithoutReflection($node, $callableParameters); - - if ($node->expr instanceof Expr\Yield_ || $node->expr instanceof Expr\YieldFrom) { - $yieldNode = $node->expr; - - if ($yieldNode instanceof Expr\Yield_) { - if ($yieldNode->key === null) { - $keyType = new IntegerType(); - } else { - $keyType = $arrowScope->getType($yieldNode->key); - } - - if ($yieldNode->value === null) { - $valueType = new NullType(); - } else { - $valueType = $arrowScope->getType($yieldNode->value); - } - } else { - $yieldFromType = $arrowScope->getType($yieldNode->expr); - $keyType = $yieldFromType->getIterableKeyType(); - $valueType = $yieldFromType->getIterableValueType(); - } - - $returnType = new GenericObjectType(Generator::class, [ - $keyType, - $valueType, - new MixedType(), - new VoidType(), - ]); - } else { - $returnType = $arrowScope->getType($node->expr); - if ($node->returnType !== null) { - $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); - } - } - } else { - $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]; - }, StatementContext::createTopLevel()); - - $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 $exprType->getObjectTypeOrClassStringObjectType(); - - } elseif ($node instanceof Array_) { - return $this->initializerExprTypeResolver->getArrayType($node, fn (Expr $expr): Type => $this->getType($expr)); - } 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 Node\Expr\Cast\String_) { - return $this->getType($node->expr)->toString(); - } elseif ($node instanceof Node\Expr\Cast\Array_) { - return $this->getType($node->expr)->toArray(); - } elseif ($node instanceof Node\Scalar\MagicConst) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof Object_) { - $castToObject = static function (Type $type): Type { - if ($type->isObject()->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 = $varType->getConstantScalarValues(); - $stringType = new StringType(); - if (count($varScalars) > 0) { - $newTypes = []; - - foreach ($varScalars as $varValue) { - if ($node instanceof Expr\PreInc) { - ++$varValue; - } else { - --$varValue; - } - - $newTypes[] = $this->getTypeFromValue($varValue); - } - return TypeCombinator::union(...$newTypes); - } elseif ($varType->isString()->yes()) { - if ($varType->isLiteralString()->yes()) { - return new IntersectionType([$stringType, new AccessoryLiteralStringType()]); - } - return $stringType; - } - - if ($node instanceof Expr\PreInc) { - return $this->getType(new BinaryOp\Plus($node->var, new LNumber(1))); - } - - return $this->getType(new BinaryOp\Minus($node->var, new LNumber(1))); - } elseif ($node instanceof Expr\Yield_) { - $functionReflection = $this->getFunction(); - if ($functionReflection === null) { - return new MixedType(); - } - - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - $generatorSendType = $returnType->getTemplateType(Generator::class, 'TSend'); - if ($generatorSendType instanceof ErrorType) { - return new MixedType(); - } - - return $generatorSendType; - } elseif ($node instanceof Expr\YieldFrom) { - $yieldFromType = $this->getType($node->expr); - $generatorReturnType = $yieldFromType->getTemplateType(Generator::class, 'TReturn'); - if ($generatorReturnType instanceof ErrorType) { - 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 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); - } - - $filteringExprType = $matchScope->getType($filteringExpr); - - if (!(new ConstantBooleanType(false))->isSuperTypeOf($filteringExprType)->yes()) { - $truthyScope = $matchScope->filterByTruthyValue($filteringExpr); - $types[] = $truthyScope->getType($arm->body); - } - - $matchScope = $matchScope->filterByFalseyValue($filteringExpr); - } - - return TypeCombinator::union(...$types); - } - - if ($node instanceof Expr\Isset_) { - $issetResult = true; - foreach ($node->vars as $var) { - $result = $this->issetCheck($var, static function (Type $type): ?bool { - $isNull = (new NullType())->isSuperTypeOf($type); - if ($isNull->maybe()) { - return null; - } - - return !$isNull->yes(); - }); - if ($result !== null) { - if (!$result) { - return new ConstantBooleanType($result); - } - - continue; - } - - $issetResult = $result; - } - - if ($issetResult === null) { - return new BooleanType(); - } - - return new ConstantBooleanType($issetResult); - } - - if ($node instanceof Expr\AssignOp\Coalesce) { - return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes())); - } - - if ($node instanceof Expr\BinaryOp\Coalesce) { - $leftType = $this->getType($node->left); - - $result = $this->issetCheck($node->left, static function (Type $type): ?bool { - $isNull = (new NullType())->isSuperTypeOf($type); - if ($isNull->maybe()) { - return null; - } - - return !$isNull->yes(); - }); - - if ($result !== null && $result !== false) { - return TypeCombinator::removeNull($leftType); - } - - $rightType = $this->filterByFalseyValue( - new BinaryOp\NotIdentical($node->left, new ConstFetch(new Name('null'))), - )->getType($node->right); - - if ($result === null) { - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType, - ); - } - - return $rightType; - } - - if ($node instanceof ConstFetch) { - $constName = (string) $node->name; - $loweredConstName = strtolower($constName); - if ($loweredConstName === 'true') { - return new ConstantBooleanType(true); - } elseif ($loweredConstName === 'false') { - return new ConstantBooleanType(false); - } elseif ($loweredConstName === 'null') { - return new NullType(); - } - - $namespacedName = null; - if (!$node->name->isFullyQualified() && $this->getNamespace() !== null) { - $namespacedName = new FullyQualified([$this->getNamespace(), $node->name->toString()]); - } - $globalName = new FullyQualified($node->name->toString()); - - foreach ([$namespacedName, $globalName] as $name) { - if ($name === null) { - continue; - } - $constFetch = new ConstFetch($name); - if ($this->hasExpressionType($constFetch)->yes()) { - return $this->constantResolver->resolveConstantType( - $name->toString(), - $this->expressionTypes[$this->getNodeKey($constFetch)]->getType(), - ); - } - } - - $constantType = $this->constantResolver->resolveConstant($node->name, $this); - if ($constantType !== null) { - return $constantType; - } - - return new ErrorType(); - } elseif ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Identifier) { - if ($this->hasExpressionType($node)->yes()) { - return $this->expressionTypes[$exprString]->getType(); - } - return $this->initializerExprTypeResolver->getClassConstFetchTypeByReflection( - $node->class, - $node->name->name, - $this->isInClass() ? $this->getClassReflection() : null, - fn (Expr $expr): Type => $this->getType($expr), - ); - } - - if ($node instanceof Expr\Ternary) { - if ($node->if === null) { - $conditionType = $this->getType($node->cond); - $booleanConditionType = $conditionType->toBoolean(); - if ($booleanConditionType->isTrue()->yes()) { - return $this->filterByTruthyValue($node->cond)->getType($node->cond); - } - - if ($booleanConditionType->isFalse()->yes()) { - return $this->filterByFalseyValue($node->cond)->getType($node->else); - } - - return TypeCombinator::union( - TypeCombinator::removeFalsey($this->filterByTruthyValue($node->cond)->getType($node->cond)), - $this->filterByFalseyValue($node->cond)->getType($node->else), - ); - } - - $booleanConditionType = $this->getType($node->cond)->toBoolean(); - if ($booleanConditionType->isTrue()->yes()) { - return $this->filterByTruthyValue($node->cond)->getType($node->if); - } - - if ($booleanConditionType->isFalse()->yes()) { - 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) { - if ($this->nativeTypesPromoted) { - $typeCallback = function () use ($node): Type { - $methodReflection = $this->getMethodReflection( - $this->getNativeType($node->var), - $node->name->name, - ); - if ($methodReflection === null) { - return new ErrorType(); - } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); - } - - $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) { - if ($this->nativeTypesPromoted) { - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - } else { - $staticMethodCalledOnType = $this->getNativeType($node->class); - } - $methodReflection = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflection === null) { - return new ErrorType(); - } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); - }; - - $callType = $typeCallback(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $callType); - } - - return $callType; - } - - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - } else { - $staticMethodCalledOnType = $this->getType($node->class)->getObjectTypeOrClassStringObjectType(); - } - - $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) { - if ($this->nativeTypesPromoted) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); - if ($propertyReflection === null) { - return new ErrorType(); - } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); - } - - return $this->getNullsafeShortCircuitingType($node->var, $nativeType); - } - - $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 - ) { - if ($this->nativeTypesPromoted) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); - if ($propertyReflection === null) { - return new ErrorType(); - } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); - } - - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $nativeType); - } - - return $nativeType; - } - - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); - } else { - $staticPropertyFetchedOnType = $this->getType($node->class)->getObjectTypeOrClassStringObjectType(); - } - - $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->getArgs(), - $calledOnType->getCallableParametersAcceptors($this), - )->getReturnType(); - } - - if (!$this->reflectionProvider->hasFunction($node->name, $this)) { - return new ErrorType(); - } - - $functionReflection = $this->reflectionProvider->getFunction($node->name, $this); - if ($this->nativeTypesPromoted) { - return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType(); - } - - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $this, - $node->getArgs(), - $functionReflection->getVariants(), - ); - $normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); - if ($normalizedNode !== null) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicFunctionReturnTypeExtensions() as $dynamicFunctionReturnTypeExtension) { - if (!$dynamicFunctionReturnTypeExtension->isFunctionSupported($functionReflection)) { - continue; - } - - $resolvedType = $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall( - $functionReflection, - $normalizedNode, - $this, - ); - if ($resolvedType !== null) { - return $resolvedType; - } - } - } - - return $parametersAcceptor->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; - } - - /** - * @param callable(Type): ?bool $typeCallback - */ - public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = null): ?bool - { - // mirrored in PHPStan\Rules\IssetCheck - if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { - $hasVariable = $this->hasVariableType($expr->name); - if ($hasVariable->maybe()) { - return null; - } - - if ($result === null) { - if ($hasVariable->yes()) { - if ($expr->name === '_SESSION') { - return null; - } - - return $typeCallback($this->getVariableType($expr->name)); - } - - return false; - } - - return $result; - } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - $type = $this->getType($expr->var); - $dimType = $this->getType($expr->dim); - $hasOffsetValue = $type->hasOffsetValueType($dimType); - if (!$type->isOffsetAccessible()->yes()) { - return $result ?? $this->issetCheckUndefined($expr->var); - } - - if ($hasOffsetValue->no()) { - return false; - } - - // 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()) { - $result = $typeCallback($type->getOffsetValueType($dimType)); - - if ($result !== null) { - return $this->issetCheck($expr->var, $typeCallback, $result); - } - } - - // Has offset, it is nullable - return null; - - } elseif ($expr instanceof Node\Expr\PropertyFetch || $expr instanceof Node\Expr\StaticPropertyFetch) { - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this); - - if ($propertyReflection === null) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); - } - - if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); - } - - return null; - } - - if (!$propertyReflection->isNative()) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); - } - - if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); - } - - return null; - } - - $nativeType = $propertyReflection->getNativeType(); - if (!$nativeType instanceof MixedType) { - if (!$this->hasExpressionType($expr)->yes()) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); - } - - if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); - } - - return null; - } - } - - if ($result !== null) { - return $result; - } - - $result = $typeCallback($propertyReflection->getWritableType()); - if ($result !== null) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheck($expr->var, $typeCallback, $result); - } - - if ($expr->class instanceof Expr) { - return $this->issetCheck($expr->class, $typeCallback, $result); - } - } - - return $result; - } - - if ($result !== null) { - return $result; - } - - return $typeCallback($this->getType($expr)); - } - - private function issetCheckUndefined(Expr $expr): ?bool - { - if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { - $hasVariable = $this->hasVariableType($expr->name); - if (!$hasVariable->no()) { - return null; - } - - return false; - } - - if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - $type = $this->getType($expr->var); - $dimType = $this->getType($expr->dim); - $hasOffsetValue = $type->hasOffsetValueType($dimType); - if (!$type->isOffsetAccessible()->yes()) { - return $this->issetCheckUndefined($expr->var); - } - - if (!$hasOffsetValue->no()) { - return $this->issetCheckUndefined($expr->var); - } - - return false; - } - - if ($expr instanceof Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); - } - - if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); - } - - return null; - } - - /** - * @param ParametersAcceptor[] $variants - */ - private function createFirstClassCallable(array $variants): Type - { - $closureTypes = []; - foreach ($variants as $variant) { - $returnType = $variant->getReturnType(); - if ($variant instanceof ParametersAcceptorWithPhpDocs) { - $returnType = $this->nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType; - } - $parameters = $variant->getParameters(); - $closureTypes[] = new ClosureType( - $parameters, - $returnType, - $variant->isVariadic(), - $variant->getTemplateTypeMap(), - $variant->getResolvedTemplateTypeMap(), - ); - } - - return TypeCombinator::union(...$closureTypes); - } - - /** @api */ - public function getNativeType(Expr $expr): Type - { - return $this->promoteNativeTypes()->getType($expr); - } - - /** - * @api - * @deprecated Use getNativeType() - */ - public function doNotTreatPhpDocTypesAsCertain(): Scope - { - return $this->promoteNativeTypes(); - } - - private function promoteNativeTypes(): self - { - if ($this->nativeTypesPromoted) { - return $this; - } - - if ($this->scopeWithPromotedNativeTypes !== null) { - return $this->scopeWithPromotedNativeTypes; - } - - return $this->scopeWithPromotedNativeTypes = $this->scopeFactory->create( - $this->context, - $this->declareStrictTypes, - $this->function, - $this->namespace, - $this->nativeExpressionTypes, - [], - [], - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - true, - ); - } - - /** - * @param Node\Expr\PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch - */ - 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; - } - - /** @api */ - protected function getTypeFromArrayDimFetch( - Expr\ArrayDimFetch $arrayDimFetch, - Type $offsetType, - Type $offsetAccessibleType, - ): Type - { - if ($arrayDimFetch->dim === null) { - throw new ShouldNotHappenException(); - } - - if (!$offsetAccessibleType->isArray()->yes() && (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 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() !== null) { - return $currentClassReflection->getParentClass()->getName(); - } - return null; - case 'static': - return null; - } - - return $originalClass; - } - - /** @api */ - public function resolveName(Name $name): string - { - $originalClass = (string) $name; - if ($this->isInClass()) { - if (in_array(strtolower($originalClass), [ - 'self', - 'static', - ], true)) { - if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) { - return $this->inClosureBindScopeClasses[0]; - } - return $this->getClassReflection()->getName(); - } elseif ($originalClass === 'parent') { - $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== null) { - return $currentClassReflection->getParentClass()->getName(); - } - } - } - - return $originalClass; - } - - /** @api */ - public function resolveTypeByName(Name $name): TypeWithClassName - { - if ($name->toLowerString() === 'static' && $this->isInClass()) { - if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) { - if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0])) { - return new StaticType($this->reflectionProvider->getClass($this->inClosureBindScopeClasses[0])); - } - } - - return new StaticType($this->getClassReflection()); - } - - $originalClass = $this->resolveName($name); - if ($this->isInClass()) { - if ($this->inClosureBindScopeClasses === [$originalClass]) { - if ($this->reflectionProvider->hasClass($originalClass)) { - return new ThisType($this->reflectionProvider->getClass($originalClass)); - } - return new ObjectType($originalClass); - } - - $thisType = new ThisType($this->getClassReflection()); - $ancestor = $thisType->getAncestorWithClassName($originalClass); - if ($ancestor !== null) { - return $ancestor; - } - } - - return new ObjectType($originalClass); - } - - /** - * @api - * @param mixed $value - */ - public function getTypeFromValue($value): Type - { - return ConstantTypeHelper::getTypeFromValue($value); - } - - /** - * @api - * @deprecated use hasExpressionType instead - */ - public function isSpecified(Expr $node): bool - { - return !$node instanceof Variable && $this->hasExpressionType($node)->yes(); - } - - /** @api */ - public function hasExpressionType(Expr $node): TrinaryLogic - { - $exprString = $this->getNodeKey($node); - if (!isset($this->expressionTypes[$exprString])) { - return TrinaryLogic::createNo(); - } - return $this->expressionTypes[$exprString]->getCertainty(); - } - - /** - * @param MethodReflection|FunctionReflection $reflection - */ - public function pushInFunctionCall($reflection): self - { - $stack = $this->inFunctionCallsStack; - $stack[] = $reflection; - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $stack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - public function popInFunctionCall(): self - { - $stack = $this->inFunctionCallsStack; - array_pop($stack); - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $stack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - /** @api */ - 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(); - } - - /** @api */ - public function isInFunctionExists(string $functionName): bool - { - $expr = new FuncCall(new FullyQualified('function_exists'), [ - new Arg(new String_(ltrim($functionName, '\\'))), - ]); - - return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes(); - } - - /** @api */ - public function enterClass(ClassReflection $classReflection): self - { - $thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection)); - $constantTypes = $this->getConstantTypes(); - $constantTypes['$this'] = $thisHolder; - $nativeConstantTypes = $this->getNativeConstantTypes(); - $nativeConstantTypes['$this'] = $thisHolder; - - return $this->scopeFactory->create( - $this->context->enterClass($classReflection), - $this->isDeclareStrictTypes(), - null, - $this->getNamespace(), - $constantTypes, - $nativeConstantTypes, - [], - [], - null, - true, - [], - [], - [], - false, - $classReflection->isAnonymous() ? $this : null, - ); - } - - public function enterTrait(ClassReflection $traitReflection): self - { - $namespace = null; - $traitName = $traitReflection->getName(); - $traitNameParts = explode('\\', $traitName); - if (count($traitNameParts) > 1) { - $namespace = implode('\\', array_slice($traitNameParts, 0, -1)); - } - return $this->scopeFactory->create( - $this->context->enterTrait($traitReflection), - $this->isDeclareStrictTypes(), - $this->getFunction(), - $namespace, - $this->expressionTypes, - $this->nativeExpressionTypes, - [], - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - ); - } - - /** - * @api - * @param Type[] $phpDocParameterTypes - * @param Type[] $parameterOutTypes - */ - 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, - bool $acceptsNamedArguments = true, - ?Assertions $asserts = null, - ?Type $selfOutType = null, - ?string $phpDocComment = null, - array $parameterOutTypes = [], - ): self - { - if (!$this->isInClass()) { - throw new ShouldNotHappenException(); - } - - return $this->enterFunctionLike( - new PhpMethodFromParserNodeReflection( - $this->getClassReflection(), - $classMethod, - $this->getFile(), - $templateTypeMap, - $this->getRealParameterTypes($classMethod), - array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes), - $this->getRealParameterDefaultValues($classMethod), - $this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)), - $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, - $throwType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $isPure, - $acceptsNamedArguments, - $asserts ?? Assertions::createEmpty(), - $selfOutType, - $phpDocComment, - array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes), - ), - !$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); - }); - } - - /** - * @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 ShouldNotHappenException(); - } - $realParameterTypes[$parameter->var->name] = $this->getFunctionType( - $parameter->type, - $this->isParameterValueNullable($parameter), - false, - ); - } - - return $realParameterTypes; - } - - /** - * @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 ShouldNotHappenException(); - } - $realParameterDefaultValues[$parameter->var->name] = $this->getType($parameter->default); - } - - return $realParameterDefaultValues; - } - - /** - * @api - * @param Type[] $phpDocParameterTypes - * @param Type[] $parameterOutTypes - */ - 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, - bool $acceptsNamedArguments = true, - ?Assertions $asserts = null, - ?string $phpDocComment = null, - array $parameterOutTypes = [], - ): self - { - return $this->enterFunctionLike( - new PhpFunctionFromParserNodeReflection( - $function, - $this->getFile(), - $templateTypeMap, - $this->getRealParameterTypes($function), - array_map(static fn (Type $type): Type => 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, - $acceptsNamedArguments, - $asserts ?? Assertions::createEmpty(), - $phpDocComment, - array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes), - ), - false, - ); - } - - private function enterFunctionLike( - PhpFunctionFromParserNodeReflection $functionReflection, - bool $preserveThis, - ): self - { - $expressionTypes = []; - $nativeExpressionTypes = []; - foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) { - $parameterType = $parameter->getType(); - $paramExprString = '$' . $parameter->getName(); - if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { - $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType); - } else { - $parameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $parameterType)); - } - } - $parameterNode = new Variable($parameter->getName()); - $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $parameterType); - - $nativeParameterType = $parameter->getNativeType(); - if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { - $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType); - } else { - $nativeParameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $nativeParameterType)); - } - } - $nativeExpressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $nativeParameterType); - } - - if ($preserveThis && array_key_exists('$this', $this->expressionTypes)) { - $expressionTypes['$this'] = $this->expressionTypes['$this']; - } - if ($preserveThis && array_key_exists('$this', $this->nativeExpressionTypes)) { - $nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this']; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $functionReflection, - $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), - array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes), - ); - } - - /** @api */ - public function enterNamespace(string $namespaceName): self - { - return $this->scopeFactory->create( - $this->context->beginFile(), - $this->isDeclareStrictTypes(), - null, - $namespaceName, - ); - } - - /** - * @param list $scopeClasses - */ - public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $scopeClasses): self - { - $expressionTypes = $this->expressionTypes; - if ($thisType !== null) { - $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType); - } else { - unset($expressionTypes['$this']); - } - - $nativeExpressionTypes = $this->nativeExpressionTypes; - if ($nativeThisType !== null) { - $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType); - } else { - unset($nativeExpressionTypes['$this']); - } - - if ($scopeClasses === ['static'] && $this->isInClass()) { - $scopeClasses = [$this->getClassReflection()->getName()]; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $this->conditionalExpressions, - $scopeClasses, - $this->anonymousFunctionReflection, - ); - } - - public function restoreOriginalScopeAfterClosureBind(self $originalScope): self - { - $expressionTypes = $this->expressionTypes; - if (isset($originalScope->expressionTypes['$this'])) { - $expressionTypes['$this'] = $originalScope->expressionTypes['$this']; - } else { - unset($expressionTypes['$this']); - } - - $nativeExpressionTypes = $this->nativeExpressionTypes; - if (isset($originalScope->nativeExpressionTypes['$this'])) { - $nativeExpressionTypes['$this'] = $originalScope->nativeExpressionTypes['$this']; - } else { - unset($nativeExpressionTypes['$this']); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $this->conditionalExpressions, - $originalScope->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - ); - } - - public function enterClosureCall(Type $thisType, Type $nativeThisType): self - { - $expressionTypes = $this->expressionTypes; - $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType); - - $nativeExpressionTypes = $this->nativeExpressionTypes; - $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $this->conditionalExpressions, - $thisType->getObjectClassNames(), - $this->anonymousFunctionReflection, - ); - } - - /** @api */ - public function isInClosureBind(): bool - { - return $this->inClosureBindScopeClasses !== []; - } - - /** - * @api - * @param ParameterReflection[]|null $callableParameters - */ - public function enterAnonymousFunction( - Expr\Closure $closure, - ?array $callableParameters = null, - ): self - { - $anonymousFunctionReflection = $this->getType($closure); - if (!$anonymousFunctionReflection instanceof ClosureType) { - throw new ShouldNotHappenException(); - } - - $scope = $this->enterAnonymousFunctionWithoutReflection($closure, $callableParameters); - - return $this->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->getFunction(), - $scope->getNamespace(), - $scope->expressionTypes, - $scope->nativeExpressionTypes, - [], - $scope->inClosureBindScopeClasses, - $anonymousFunctionReflection, - true, - [], - [], - [], - false, - $this, - $this->nativeTypesPromoted, - ); - } - - /** - * @param ParameterReflection[]|null $callableParameters - */ - private function enterAnonymousFunctionWithoutReflection( - Expr\Closure $closure, - ?array $callableParameters = null, - ): self - { - $expressionTypes = []; - $nativeTypes = []; - foreach ($closure->params as $i => $parameter) { - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { - throw new ShouldNotHappenException(); - } - $paramExprString = sprintf('$%s', $parameter->var->name); - $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()); - } - } - $holder = ExpressionTypeHolder::createYes($parameter->var, $parameterType); - $expressionTypes[$paramExprString] = $holder; - $nativeTypes[$paramExprString] = $holder; - } - - $nonRefVariableNames = []; - foreach ($closure->uses as $use) { - if (!is_string($use->var->name)) { - throw new ShouldNotHappenException(); - } - $variableName = $use->var->name; - $paramExprString = '$' . $use->var->name; - if ($use->byRef) { - $holder = ExpressionTypeHolder::createYes($use->var, new MixedType()); - $expressionTypes[$paramExprString] = $holder; - $nativeTypes[$paramExprString] = $holder; - continue; - } - $nonRefVariableNames[$variableName] = true; - if ($this->hasVariableType($variableName)->no()) { - $variableType = new ErrorType(); - $variableNativeType = new ErrorType(); - } else { - $variableType = $this->getVariableType($variableName); - $variableNativeType = $this->getNativeType($use->var); - } - $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableType); - $nativeTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableNativeType); - } - - foreach ($this->invalidateStaticExpressions($this->expressionTypes) as $exprString => $typeHolder) { - $expr = $typeHolder->getExpr(); - if ($expr instanceof Variable) { - continue; - } - $variables = (new NodeFinder())->findInstanceOf([$expr], Variable::class); - if ($variables === [] && !$this->expressionTypeIsUnchangeable($typeHolder)) { - continue; - } - foreach ($variables as $variable) { - if (!$variable instanceof Variable) { - continue 2; - } - if (!is_string($variable->name)) { - continue 2; - } - if (!array_key_exists($variable->name, $nonRefVariableNames)) { - continue 2; - } - } - - $expressionTypes[$exprString] = $typeHolder; - } - - if ($this->hasVariableType('this')->yes() && !$closure->static) { - $node = new Variable('this'); - $expressionTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getType($node)); - $nativeTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getNativeType($node)); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), - array_merge($this->getNativeConstantTypes(), $nativeTypes), - [], - $this->inClosureBindScopeClasses, - new TrivialParametersAcceptor(), - true, - [], - [], - [], - false, - $this, - $this->nativeTypesPromoted, - ); - } - - private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool - { - $expr = $typeHolder->getExpr(); - $type = $typeHolder->getType(); - - return $expr instanceof FuncCall - && !$expr->isFirstClassCallable() - && $expr->name instanceof FullyQualified - && $expr->name->toLowerString() === 'function_exists' - && isset($expr->getArgs()[0]) - && count($this->getType($expr->getArgs()[0]->value)->getConstantStrings()) === 1 - && (new ConstantBooleanType(true))->isSuperTypeOf($type)->yes(); - } - - /** - * @param array $expressionTypes - * @return array - */ - private function invalidateStaticExpressions(array $expressionTypes): array - { - $filteredExpressionTypes = []; - $nodeFinder = new NodeFinder(); - foreach ($expressionTypes as $exprString => $expressionType) { - $staticExpression = $nodeFinder->findFirst( - [$expressionType->getExpr()], - static fn ($node) => $node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch, - ); - if ($staticExpression !== null) { - continue; - } - $filteredExpressionTypes[$exprString] = $expressionType; - } - return $filteredExpressionTypes; - } - - /** - * @api - * @param ParameterReflection[]|null $callableParameters - */ - public function enterArrowFunction(Expr\ArrowFunction $arrowFunction, ?array $callableParameters = null): self - { - $anonymousFunctionReflection = $this->getType($arrowFunction); - if (!$anonymousFunctionReflection instanceof ClosureType) { - throw new ShouldNotHappenException(); - } - - $scope = $this->enterArrowFunctionWithoutReflection($arrowFunction, $callableParameters); - - return $this->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->getFunction(), - $scope->getNamespace(), - $scope->expressionTypes, - $scope->nativeExpressionTypes, - $scope->conditionalExpressions, - $scope->inClosureBindScopeClasses, - $anonymousFunctionReflection, - true, - [], - [], - [], - $scope->afterExtractCall, - $scope->parentScope, - $this->nativeTypesPromoted, - ); - } - - /** - * @param ParameterReflection[]|null $callableParameters - */ - private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction, ?array $callableParameters): self - { - $arrowFunctionScope = $this; - foreach ($arrowFunction->params as $i => $parameter) { - if ($parameter->type === null) { - $parameterType = new MixedType(); - } else { - $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 ShouldNotHappenException(); - } - $arrowFunctionScope = $arrowFunctionScope->assignVariable($parameter->var->name, $parameterType, $parameterType); - } - - if ($arrowFunction->static) { - $arrowFunctionScope = $arrowFunctionScope->invalidateExpression(new Variable('this')); - } - - return $this->scopeFactory->create( - $arrowFunctionScope->context, - $this->isDeclareStrictTypes(), - $arrowFunctionScope->getFunction(), - $arrowFunctionScope->getNamespace(), - $this->invalidateStaticExpressions($arrowFunctionScope->expressionTypes), - $arrowFunctionScope->nativeExpressionTypes, - $arrowFunctionScope->conditionalExpressions, - $arrowFunctionScope->inClosureBindScopeClasses, - null, - true, - [], - [], - [], - $arrowFunctionScope->afterExtractCall, - $arrowFunctionScope->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function isParameterValueNullable(Node\Param $parameter): bool - { - if ($parameter->default instanceof ConstFetch) { - return strtolower((string) $parameter->default->name) === 'null'; - } - - return false; - } - - /** - * @api - * @param Node\Name|Node\Identifier|Node\ComplexType|null $type - */ - public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type - { - if ($isNullable) { - return TypeCombinator::addNull( - $this->getFunctionType($type, false, $isVariadic), - ); - } - if ($isVariadic) { - if ($this->phpVersion->supportsNamedArguments()) { - return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType( - $type, - false, - false, - )); - } - - return AccessoryArrayListType::intersectWith(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() !== null) { - return new ObjectType($this->getClassReflection()->getParentClass()->getName()); - } - - return new NonexistentParentClassType(); - } - } - - return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection() : 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(), $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(), $nativeIterateeType->getIterableKeyType()); - - if ($iterateeType->isArray()->yes()) { - $scope = $scope->assignExpression( - new Expr\ArrayDimFetch($iteratee, new Variable($keyName)), - $iterateeType->getIterableValueType(), - $nativeIterateeType->getIterableValueType(), - ); - } - - return $scope; - } - - /** - * @deprecated Use enterCatchType - * @param Node\Name[] $classes - */ - public function enterCatch(array $classes, ?string $variableName): self - { - $type = TypeCombinator::union(...array_map(static fn (Node\Name $class): ObjectType => new ObjectType((string) $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)), - TypeCombinator::intersect($catchType, new ObjectType(Throwable::class)), - ); - } - - public function enterExpressionAssign(Expr $expr): self - { - $exprString = $this->getNodeKey($expr); - $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; - $currentlyAssignedExpressions[$exprString] = true; - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - public function exitExpressionAssign(Expr $expr): self - { - $exprString = $this->getNodeKey($expr); - $currentlyAssignedExpressions = $this->currentlyAssignedExpressions; - unset($currentlyAssignedExpressions[$exprString]); - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - /** @api */ - public function isInExpressionAssign(Expr $expr): bool - { - $exprString = $this->getNodeKey($expr); - return array_key_exists($exprString, $this->currentlyAssignedExpressions); - } - - public function setAllowedUndefinedExpression(Expr $expr): self - { - if ($this->phpVersion->deprecatesDynamicProperties() && $expr instanceof Expr\StaticPropertyFetch) { - return $this; - } - - $exprString = $this->getNodeKey($expr); - $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions; - $currentlyAllowedUndefinedExpressions[$exprString] = true; - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - public function unsetAllowedUndefinedExpression(Expr $expr): self - { - $exprString = $this->getNodeKey($expr); - $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions; - unset($currentlyAllowedUndefinedExpressions[$exprString]); - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->isInFirstLevelStatement(), - $this->currentlyAssignedExpressions, - $currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - - return $scope; - } - - /** @api */ - public function isUndefinedExpressionAllowed(Expr $expr): bool - { - $exprString = $this->getNodeKey($expr); - return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions); - } - - public function assignVariable(string $variableName, Type $type, Type $nativeType, ?TrinaryLogic $certainty = null): self - { - $node = new Variable($variableName); - $scope = $this->assignExpression($node, $type, $nativeType); - if ($certainty !== null) { - if ($certainty->no()) { - throw new ShouldNotHappenException(); - } elseif (!$certainty->yes()) { - $exprString = '$' . $variableName; - $scope->expressionTypes[$exprString] = new ExpressionTypeHolder($node, $type, $certainty); - $scope->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($node, $nativeType, $certainty); - } - } - - return $scope; - } - - public function unsetExpression(Expr $expr): self - { - $scope = $this; - if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - $exprVarType = $scope->getType($expr->var); - $dimType = $scope->getType($expr->dim); - $unsetType = $exprVarType->unsetOffset($dimType); - $exprVarNativeType = $scope->getNativeType($expr->var); - $dimNativeType = $scope->getNativeType($expr->dim); - $unsetNativeType = $exprVarNativeType->unsetOffset($dimNativeType); - $scope = $scope->assignExpression($expr->var, $unsetType, $unsetNativeType)->invalidateExpression( - new FuncCall(new FullyQualified('count'), [new Arg($expr->var)]), - )->invalidateExpression( - new FuncCall(new FullyQualified('sizeof'), [new Arg($expr->var)]), - )->invalidateExpression( - new FuncCall(new Name('count'), [new Arg($expr->var)]), - )->invalidateExpression( - new FuncCall(new Name('sizeof'), [new Arg($expr->var)]), - ); - - if ($expr->var instanceof Expr\ArrayDimFetch && $expr->var->dim !== null) { - $scope = $scope->assignExpression( - $expr->var->var, - $this->getType($expr->var->var)->setOffsetValueType( - $scope->getType($expr->var->dim), - $scope->getType($expr->var), - ), - $this->getNativeType($expr->var->var)->setOffsetValueType( - $scope->getNativeType($expr->var->dim), - $scope->getNativeType($expr->var), - ), - ); - } - } - - return $scope->invalidateExpression($expr); - } - - public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType): self - { - if ($expr instanceof ConstFetch) { - $loweredConstName = strtolower($expr->name->toString()); - if (in_array($loweredConstName, ['true', 'false', 'null'], true)) { - return $this; - } - } - - if ($expr instanceof FuncCall && $expr->name instanceof Name && $type->isFalse()->yes()) { - $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this); - if ($functionName !== null && in_array(strtolower($functionName), [ - 'is_dir', - 'is_file', - 'file_exists', - ], true)) { - return $this; - } - } - - $scope = $this; - if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - $dimType = $scope->getType($expr->dim)->toArrayKey(); - if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { - $exprVarType = $scope->getType($expr->var); - if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) { - $types = [ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ]; - if ($dimType instanceof ConstantIntegerType) { - $types[] = new StringType(); - } - $scope = $scope->specifyExpressionType( - $expr->var, - TypeCombinator::intersect( - TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)), - new HasOffsetValueType($dimType, $type), - ), - $scope->getNativeType($expr->var), - ); - } - } - } - - $exprString = $this->getNodeKey($expr); - $expressionTypes = $scope->expressionTypes; - $expressionTypes[$exprString] = ExpressionTypeHolder::createYes($expr, $type); - $nativeTypes = $scope->nativeExpressionTypes; - $nativeTypes[$exprString] = ExpressionTypeHolder::createYes($expr, $nativeType); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function assignExpression(Expr $expr, Type $type, ?Type $nativeType = null): self - { - if ($nativeType === null) { - $nativeType = new MixedType(); - } - $scope = $this; - if ($expr instanceof PropertyFetch) { - $scope = $this->invalidateExpression($expr) - ->invalidateMethodsOnExpression($expr->var); - } elseif ($expr instanceof Expr\StaticPropertyFetch) { - $scope = $this->invalidateExpression($expr); - } elseif ($expr instanceof Variable) { - $scope = $this->invalidateExpression($expr); - } - - return $scope->specifyExpressionType($expr, $type, $nativeType); - } - - public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self - { - $expressionTypes = $this->expressionTypes; - $nativeExpressionTypes = $this->nativeExpressionTypes; - $invalidated = false; - $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); - - foreach ($expressionTypes as $exprString => $exprTypeHolder) { - $exprExpr = $exprTypeHolder->getExpr(); - if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $requireMoreCharacters)) { - continue; - } - - unset($expressionTypes[$exprString]); - unset($nativeExpressionTypes[$exprString]); - $invalidated = true; - } - - $newConditionalExpressions = []; - foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { - if (count($holders) === 0) { - continue; - } - if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $holders[array_key_first($holders)]->getTypeHolder()->getExpr())) { - $invalidated = true; - continue; - } - foreach ($holders as $holder) { - $conditionalTypeHolders = $holder->getConditionExpressionTypeHolders(); - foreach ($conditionalTypeHolders as $conditionalTypeHolder) { - if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr())) { - $invalidated = true; - continue 3; - } - } - } - $newConditionalExpressions[$conditionalExprString] = $holders; - } - - if (!$invalidated) { - return $this; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $newConditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, bool $requireMoreCharacters = false): bool - { - if ($requireMoreCharacters && $exprStringToInvalidate === $this->getNodeKey($expr)) { - return false; - } - - // Variables will not contain traversable expressions. skip the NodeFinder overhead - if ($expr instanceof Variable && is_string($expr->name)) { - return $exprStringToInvalidate === $this->getNodeKey($expr); - } - - $nodeFinder = new NodeFinder(); - $expressionToInvalidateClass = get_class($exprToInvalidate); - $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool { - if (!$node instanceof $expressionToInvalidateClass) { - return false; - } - - $nodeString = $this->getNodeKey($node); - - return $nodeString === $exprStringToInvalidate; - }); - - if ($found === null) { - return false; - } - - if ($this->phpVersion->supportsReadOnlyProperties() && $expr instanceof PropertyFetch && $expr->name instanceof Node\Identifier && $requireMoreCharacters) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this); - if ($propertyReflection !== null) { - $nativePropertyReflection = $propertyReflection->getNativeReflection(); - if ($nativePropertyReflection !== null && $nativePropertyReflection->isReadOnly()) { - return false; - } - } - } - - return true; - } - - private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self - { - $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); - $expressionTypes = $this->expressionTypes; - $nativeExpressionTypes = $this->nativeExpressionTypes; - $invalidated = false; - $nodeFinder = new NodeFinder(); - foreach ($expressionTypes as $exprString => $exprTypeHolder) { - $expr = $exprTypeHolder->getExpr(); - $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($exprStringToInvalidate): bool { - if (!$node instanceof MethodCall) { - return false; - } - - return $this->getNodeKey($node->var) === $exprStringToInvalidate; - }); - if ($found === null) { - continue; - } - - unset($expressionTypes[$exprString]); - unset($nativeExpressionTypes[$exprString]); - $invalidated = true; - } - - if (!$invalidated) { - return $this; - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - private function addTypeToExpression(Expr $expr, Type $type): self - { - $originalExprType = $this->getType($expr); - $nativeType = $this->getNativeType($expr); - - if ($originalExprType->equals($nativeType)) { - $newType = TypeCombinator::intersect($type, $originalExprType); - return $this->specifyExpressionType($expr, $newType, $newType); - } - - return $this->specifyExpressionType( - $expr, - TypeCombinator::intersect($type, $originalExprType), - TypeCombinator::intersect($type, $nativeType), - ); - } - - public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self - { - $exprType = $this->getType($expr); - if ( - $exprType instanceof NeverType || - $typeToRemove instanceof NeverType - ) { - return $this; - } - return $this->specifyExpressionType( - $expr, - TypeCombinator::remove($exprType, $typeToRemove), - TypeCombinator::remove($this->getNativeType($expr), $typeToRemove), - ); - } - - /** - * @api - * @return MutatingScope - */ - public function filterByTruthyValue(Expr $expr): Scope - { - $exprString = $this->getNodeKey($expr); - if (array_key_exists($exprString, $this->truthyScopes)) { - return $this->truthyScopes[$exprString]; - } - - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createTruthy()); - $scope = $this->filterBySpecifiedTypes($specifiedTypes); - $this->truthyScopes[$exprString] = $scope; - - return $scope; - } - - /** - * @api - * @return MutatingScope - */ - public function filterByFalseyValue(Expr $expr): Scope - { - $exprString = $this->getNodeKey($expr); - if (array_key_exists($exprString, $this->falseyScopes)) { - return $this->falseyScopes[$exprString]; - } - - $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createFalsey()); - $scope = $this->filterBySpecifiedTypes($specifiedTypes); - $this->falseyScopes[$exprString] = $scope; - - return $scope; - } - - public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self - { - $typeSpecifications = []; - foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) { - if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) { - continue; - } - $typeSpecifications[] = [ - 'sure' => true, - 'exprString' => $exprString, - 'expr' => $expr, - 'type' => $type, - ]; - } - foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) { - continue; - } - $typeSpecifications[] = [ - 'sure' => false, - 'exprString' => $exprString, - 'expr' => $expr, - 'type' => $type, - ]; - } - - usort($typeSpecifications, static function (array $a, array $b): int { - $length = strlen($a['exprString']) - strlen($b['exprString']); - if ($length !== 0) { - return $length; - } - - return $b['sure'] - $a['sure']; // @phpstan-ignore-line - }); - - $scope = $this; - $specifiedExpressions = []; - foreach ($typeSpecifications as $typeSpecification) { - $expr = $typeSpecification['expr']; - $type = $typeSpecification['type']; - if ($typeSpecification['sure']) { - if ($specifiedTypes->shouldOverwrite()) { - $scope = $scope->assignExpression($expr, $type, $type); - } else { - $scope = $scope->addTypeToExpression($expr, $type); - } - } else { - $scope = $scope->removeTypeFromExpression($expr, $type); - } - $specifiedExpressions[$this->getNodeKey($expr)] = ExpressionTypeHolder::createYes($expr, $scope->getType($expr)); - } - - foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) { - foreach ($conditionalExpressions as $conditionalExpression) { - foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { - if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) { - continue 2; - } - } - - if ($conditionalExpression->getTypeHolder()->getCertainty()->no()) { - unset($scope->expressionTypes[$conditionalExprString]); - } else { - $scope->expressionTypes[$conditionalExprString] = array_key_exists($conditionalExprString, $scope->expressionTypes) - ? new ExpressionTypeHolder( - $scope->expressionTypes[$conditionalExprString]->getExpr(), - TypeCombinator::intersect($scope->expressionTypes[$conditionalExprString]->getType(), $conditionalExpression->getTypeHolder()->getType()), - TrinaryLogic::maxMin($scope->expressionTypes[$conditionalExprString]->getCertainty(), $conditionalExpression->getTypeHolder()->getCertainty()), - ) - : $conditionalExpression->getTypeHolder(); - $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder(); - } - } - } - - return $scope->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->getFunction(), - $scope->getNamespace(), - $scope->expressionTypes, - $scope->nativeExpressionTypes, - array_merge($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions), - $scope->inClosureBindScopeClasses, - $scope->anonymousFunctionReflection, - $scope->inFirstLevelStatement, - $scope->currentlyAssignedExpressions, - $scope->currentlyAllowedUndefinedExpressions, - $scope->inFunctionCallsStack, - $scope->afterExtractCall, - $scope->parentScope, - $scope->nativeTypesPromoted, - ); - } - - /** - * @param ConditionalExpressionHolder[] $conditionalExpressionHolders - */ - public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): self - { - $conditionalExpressions = $this->conditionalExpressions; - $conditionalExpressions[$exprString] = $conditionalExpressionHolders; - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function exitFirstLevelStatements(): self - { - if (!$this->inFirstLevelStatement) { - return $this; - } - - if ($this->scopeOutOfFirstLevelStatement !== null) { - return $this->scopeOutOfFirstLevelStatement; - } - - $scope = $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->expressionTypes, - $this->nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - false, - $this->currentlyAssignedExpressions, - $this->currentlyAllowedUndefinedExpressions, - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - $scope->resolvedTypes = $this->resolvedTypes; - $scope->truthyScopes = $this->truthyScopes; - $scope->falseyScopes = $this->falseyScopes; - $this->scopeOutOfFirstLevelStatement = $scope; - - return $scope; - } - - /** @api */ - public function isInFirstLevelStatement(): bool - { - return $this->inFirstLevelStatement; - } - - public function mergeWith(?self $otherScope): self - { - if ($otherScope === null) { - return $this; - } - $ourExpressionTypes = $this->expressionTypes; - $theirExpressionTypes = $otherScope->expressionTypes; - - $mergedExpressionTypes = $this->mergeVariableHolders($ourExpressionTypes, $theirExpressionTypes); - $conditionalExpressions = $this->intersectConditionalExpressions($otherScope->conditionalExpressions); - $conditionalExpressions = $this->createConditionalExpressions( - $conditionalExpressions, - $ourExpressionTypes, - $theirExpressionTypes, - $mergedExpressionTypes, - ); - $conditionalExpressions = $this->createConditionalExpressions( - $conditionalExpressions, - $theirExpressionTypes, - $ourExpressionTypes, - $mergedExpressionTypes, - ); - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $mergedExpressionTypes, - $this->mergeVariableHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes), - $conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - [], - $this->afterExtractCall && $otherScope->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - /** - * @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 $ourExpressionTypes - * @param array $theirExpressionTypes - * @param array $mergedExpressionTypes - * @return array - */ - private function createConditionalExpressions( - array $conditionalExpressions, - array $ourExpressionTypes, - array $theirExpressionTypes, - array $mergedExpressionTypes, - ): array - { - $newVariableTypes = $ourExpressionTypes; - foreach ($theirExpressionTypes as $exprString => $holder) { - if (!array_key_exists($exprString, $mergedExpressionTypes)) { - continue; - } - - if (!$mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) { - continue; - } - - unset($newVariableTypes[$exprString]); - } - - $typeGuards = []; - foreach ($newVariableTypes as $exprString => $holder) { - if (!$holder->getCertainty()->yes()) { - continue; - } - if (!array_key_exists($exprString, $mergedExpressionTypes)) { - continue; - } - if ($mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) { - continue; - } - - $typeGuards[$exprString] = $holder; - } - - if (count($typeGuards) === 0) { - return $conditionalExpressions; - } - - foreach ($newVariableTypes as $exprString => $holder) { - if ( - array_key_exists($exprString, $mergedExpressionTypes) - && $mergedExpressionTypes[$exprString]->equals($holder) - ) { - continue; - } - - $variableTypeGuards = $typeGuards; - unset($variableTypeGuards[$exprString]); - - if (count($variableTypeGuards) === 0) { - continue; - } - - $conditionalExpression = new ConditionalExpressionHolder($variableTypeGuards, $holder); - $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression; - } - - foreach ($mergedExpressionTypes as $exprString => $mergedExprTypeHolder) { - if (array_key_exists($exprString, $ourExpressionTypes)) { - continue; - } - - $conditionalExpression = new ConditionalExpressionHolder($typeGuards, new ExpressionTypeHolder($mergedExprTypeHolder->getExpr(), new ErrorType(), TrinaryLogic::createNo())); - $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression; - } - - return $conditionalExpressions; - } - - /** - * @param array $ourVariableTypeHolders - * @param array $theirVariableTypeHolders - * @return array - */ - private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array - { - $intersectedVariableTypeHolders = []; - foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) { - if (isset($theirVariableTypeHolders[$exprString])) { - $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder->and($theirVariableTypeHolders[$exprString]); - } else { - $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - } - } - - foreach ($theirVariableTypeHolders as $exprString => $variableTypeHolder) { - if (isset($intersectedVariableTypeHolders[$exprString])) { - continue; - } - - $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - } - - return $intersectedVariableTypeHolders; - } - - public function processFinallyScope(self $finallyScope, self $originalFinallyScope): self - { - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $this->processFinallyScopeVariableTypeHolders( - $this->expressionTypes, - $finallyScope->expressionTypes, - $originalFinallyScope->expressionTypes, - ), - $this->processFinallyScopeVariableTypeHolders( - $this->nativeExpressionTypes, - $finallyScope->nativeExpressionTypes, - $originalFinallyScope->nativeExpressionTypes, - ), - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - /** - * @param array $ourVariableTypeHolders - * @param array $finallyVariableTypeHolders - * @param array $originalVariableTypeHolders - * @return array - */ - private function processFinallyScopeVariableTypeHolders( - array $ourVariableTypeHolders, - array $finallyVariableTypeHolders, - array $originalVariableTypeHolders, - ): array - { - foreach ($finallyVariableTypeHolders as $exprString => $variableTypeHolder) { - if ( - isset($originalVariableTypeHolders[$exprString]) - && !$originalVariableTypeHolders[$exprString]->getType()->equals($variableTypeHolder->getType()) - ) { - $ourVariableTypeHolders[$exprString] = $variableTypeHolder; - continue; - } - - if (isset($originalVariableTypeHolders[$exprString])) { - continue; - } - - $ourVariableTypeHolders[$exprString] = $variableTypeHolder; - } - - return $ourVariableTypeHolders; - } - - /** - * @param Expr\ClosureUse[] $byRefUses - */ - public function processClosureScope( - self $closureScope, - ?self $prevScope, - array $byRefUses, - ): self - { - $nativeExpressionTypes = $this->nativeExpressionTypes; - $expressionTypes = $this->expressionTypes; - if (count($byRefUses) === 0) { - return $this; - } - - foreach ($byRefUses as $use) { - if (!is_string($use->var->name)) { - throw new ShouldNotHappenException(); - } - - $variableName = $use->var->name; - $variableExprString = '$' . $variableName; - - if (!$closureScope->hasVariableType($variableName)->yes()) { - $holder = ExpressionTypeHolder::createYes($use->var, new NullType()); - $expressionTypes[$variableExprString] = $holder; - $nativeExpressionTypes[$variableExprString] = $holder; - 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, 0); - } - } - - $expressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType); - $nativeExpressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeExpressionTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - $this->inFunctionCallsStack, - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope): self - { - $expressionTypes = $this->expressionTypes; - foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) { - if (!isset($expressionTypes[$variableExprString])) { - $expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - continue; - } - - $expressionTypes[$variableExprString] = new ExpressionTypeHolder( - $variableTypeHolder->getExpr(), - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()), - ); - } - $nativeTypes = $this->nativeExpressionTypes; - foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) { - if (!isset($nativeTypes[$variableExprString])) { - $nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - continue; - } - - $nativeTypes[$variableExprString] = new ExpressionTypeHolder( - $variableTypeHolder->getExpr(), - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()), - ); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - public function generalizeWith(self $otherScope): self - { - $variableTypeHolders = $this->generalizeVariableTypeHolders( - $this->expressionTypes, - $otherScope->expressionTypes, - ); - $nativeTypes = $this->generalizeVariableTypeHolders( - $this->nativeExpressionTypes, - $otherScope->nativeExpressionTypes, - ); - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $variableTypeHolders, - $nativeTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - - /** - * @param array $variableTypeHolders - * @param array $otherVariableTypeHolders - * @return array - */ - private function generalizeVariableTypeHolders( - array $variableTypeHolders, - array $otherVariableTypeHolders, - ): array - { - foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { - if (!isset($otherVariableTypeHolders[$variableExprString])) { - continue; - } - - $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( - $variableTypeHolder->getExpr(), - self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0), - $variableTypeHolder->getCertainty(), - ); - } - - return $variableTypeHolders; - } - - private static function generalizeType(Type $a, Type $b, int $depth): 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' => []]; - $integerRanges = ['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->isConstantArray()->yes()) { - $constantArrays[$key][] = $type; - continue; - } - if ($type->isArray()->yes()) { - $generalArrays[$key][] = $type; - continue; - } - if ($type instanceof IntegerRangeType) { - $integerRanges[$key][] = $type; - continue; - } - - $otherTypes[] = $type; - } - } - - $resultTypes = []; - foreach ([ - $constantFloats, - $constantBooleans, - $constantStrings, - ] as $constantTypes) { - if (count($constantTypes['a']) === 0) { - if (count($constantTypes['b']) > 0) { - $resultTypes[] = TypeCombinator::union(...$constantTypes['b']); - } - continue; - } elseif (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[] = TypeCombinator::union(...$constantTypes['a'], ...$constantTypes['b'])->generalize(GeneralizePrecision::moreSpecific()); - } - - 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), - $depth + 1, - ), - !$constantArraysA->hasOffsetValueType($keyType)->and($constantArraysB->hasOffsetValueType($keyType))->negate()->no(), - ); - } - - $resultTypes[] = $resultArrayBuilder->getArray(); - } else { - $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)), - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)), - ); - if ($constantArraysA->isIterableAtLeastOnce()->yes() && $constantArraysB->isIterableAtLeastOnce()->yes()) { - $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); - } - if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) { - $resultType = AccessoryArrayListType::intersectWith($resultType); - } - $resultTypes[] = $resultType; - } - } - } elseif (count($constantArrays['b']) > 0) { - $resultTypes[] = TypeCombinator::union(...$constantArrays['b']); - } - - 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(); - if ( - $aValueType->isArray()->yes() - && $aValueType->isConstantArray()->no() - && $bValueType->isArray()->yes() - && $bValueType->isConstantArray()->no() - ) { - $aDepth = self::getArrayDepth($aValueType) + $depth; - $bDepth = self::getArrayDepth($bValueType) + $depth; - if ( - ($aDepth > 2 || $bDepth > 2) - && abs($aDepth - $bDepth) > 0 - ) { - $aValueType = new MixedType(); - $bValueType = new MixedType(); - } - } - - $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)), - TypeCombinator::union(self::generalizeType($aValueType, $bValueType, $depth + 1)), - ); - if ($generalArraysA->isIterableAtLeastOnce()->yes() && $generalArraysB->isIterableAtLeastOnce()->yes()) { - $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); - } - if ($generalArraysA->isList()->yes() && $generalArraysB->isList()->yes()) { - $resultType = AccessoryArrayListType::intersectWith($resultType); - } - if ($generalArraysA->isOversizedArray()->yes() && $generalArraysB->isOversizedArray()->yes()) { - $resultType = TypeCombinator::intersect($resultType, new OversizedArrayType()); - } - $resultTypes[] = $resultType; - } - } elseif (count($generalArrays['b']) > 0) { - $resultTypes[] = TypeCombinator::union(...$generalArrays['b']); - } - - if (count($constantIntegers['a']) > 0) { - if (count($constantIntegers['b']) === 0) { - $resultTypes[] = TypeCombinator::union(...$constantIntegers['a']); - } else { - $constantIntegersA = TypeCombinator::union(...$constantIntegers['a']); - $constantIntegersB = TypeCombinator::union(...$constantIntegers['b']); - - if ($constantIntegersA->equals($constantIntegersB)) { - $resultTypes[] = $constantIntegersA; - } else { - $min = null; - $max = null; - foreach ($constantIntegers['a'] as $int) { - if ($min === null || $int->getValue() < $min) { - $min = $int->getValue(); - } - if ($max !== null && $int->getValue() <= $max) { - continue; - } - - $max = $int->getValue(); - } - - $gotGreater = false; - $gotSmaller = false; - foreach ($constantIntegers['b'] as $int) { - if ($int->getValue() > $max) { - $gotGreater = true; - } - if ($int->getValue() >= $min) { - continue; - } - - $gotSmaller = true; - } - - if ($gotGreater && $gotSmaller) { - $resultTypes[] = new IntegerType(); - } elseif ($gotGreater) { - $resultTypes[] = IntegerRangeType::fromInterval($min, null); - } elseif ($gotSmaller) { - $resultTypes[] = IntegerRangeType::fromInterval(null, $max); - } else { - $resultTypes[] = TypeCombinator::union($constantIntegersA, $constantIntegersB); - } - } - } - } elseif (count($constantIntegers['b']) > 0) { - $resultTypes[] = TypeCombinator::union(...$constantIntegers['b']); - } - - if (count($integerRanges['a']) > 0) { - if (count($integerRanges['b']) === 0) { - $resultTypes[] = TypeCombinator::union(...$integerRanges['a']); - } else { - $integerRangesA = TypeCombinator::union(...$integerRanges['a']); - $integerRangesB = TypeCombinator::union(...$integerRanges['b']); - - if ($integerRangesA->equals($integerRangesB)) { - $resultTypes[] = $integerRangesA; - } else { - $min = null; - $max = null; - foreach ($integerRanges['a'] as $range) { - if ($range->getMin() === null) { - $rangeMin = PHP_INT_MIN; - } else { - $rangeMin = $range->getMin(); - } - if ($range->getMax() === null) { - $rangeMax = PHP_INT_MAX; - } else { - $rangeMax = $range->getMax(); - } - - if ($min === null || $rangeMin < $min) { - $min = $rangeMin; - } - if ($max !== null && $rangeMax <= $max) { - continue; - } - - $max = $rangeMax; - } - - $gotGreater = false; - $gotSmaller = false; - foreach ($integerRanges['b'] as $range) { - if ($range->getMin() === null) { - $rangeMin = PHP_INT_MIN; - } else { - $rangeMin = $range->getMin(); - } - if ($range->getMax() === null) { - $rangeMax = PHP_INT_MAX; - } else { - $rangeMax = $range->getMax(); - } - - if ($rangeMax > $max) { - $gotGreater = true; - } - if ($rangeMin >= $min) { - continue; - } - - $gotSmaller = true; - } - - if ($min === PHP_INT_MIN) { - $min = null; - } - if ($max === PHP_INT_MAX) { - $max = null; - } - - if ($gotGreater && $gotSmaller) { - $resultTypes[] = new IntegerType(); - } elseif ($gotGreater) { - $resultTypes[] = IntegerRangeType::fromInterval($min, null); - } elseif ($gotSmaller) { - $resultTypes[] = IntegerRangeType::fromInterval(null, $max); - } else { - $resultTypes[] = TypeCombinator::union($integerRangesA, $integerRangesB); - } - } - } - } elseif (count($integerRanges['b']) > 0) { - $resultTypes[] = TypeCombinator::union(...$integerRanges['b']); - } - - $accessoryTypes = array_map( - static fn (Type $type): Type => $type->generalize(GeneralizePrecision::moreSpecific()), - TypeUtils::getAccessoryTypes($a), - ); - - return TypeCombinator::intersect( - TypeCombinator::union(...$resultTypes, ...$otherTypes), - ...$accessoryTypes, - ); - } - - private static function getArrayDepth(Type $type): int - { - $depth = 0; - $arrays = TypeUtils::getAnyArrays($type); - while (count($arrays) > 0) { - $temp = $type->getIterableValueType(); - $type = $temp; - $arrays = TypeUtils::getAnyArrays($type); - $depth++; - } - - return $depth; - } - - public function equals(self $otherScope): bool - { - if (!$this->context->equals($otherScope->context)) { - return false; - } - - if (!$this->compareVariableTypeHolders($this->expressionTypes, $otherScope->expressionTypes)) { - return false; - } - return $this->compareVariableTypeHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes); - } - - /** - * @param array $variableTypeHolders - * @param array $otherVariableTypeHolders - */ - private function compareVariableTypeHolders(array $variableTypeHolders, array $otherVariableTypeHolders): bool - { - if (count($variableTypeHolders) !== count($otherVariableTypeHolders)) { - return false; - } - foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { - if (!isset($otherVariableTypeHolders[$variableExprString])) { - return false; - } - - if (!$variableTypeHolder->getCertainty()->equals($otherVariableTypeHolders[$variableExprString]->getCertainty())) { - return false; - } - - if (!$variableTypeHolder->getType()->equals($otherVariableTypeHolders[$variableExprString]->getType())) { - return false; - } - - unset($otherVariableTypeHolders[$variableExprString]); - } - - return true; - } - - /** @api */ - public function canAccessProperty(PropertyReflection $propertyReflection): bool - { - return $this->canAccessClassMember($propertyReflection); - } - - /** @api */ - public function canCallMethod(MethodReflection $methodReflection): bool - { - if ($this->canAccessClassMember($methodReflection)) { - return true; - } - - return $this->canAccessClassMember($methodReflection->getPrototype()); - } - - /** @api */ - public function canAccessConstant(ConstantReflection $constantReflection): bool - { - return $this->canAccessClassMember($constantReflection); - } - - private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool - { - if ($classMemberReflection->isPublic()) { - return true; - } - - $classReflectionName = $classMemberReflection->getDeclaringClass()->getName(); - $canAccessClassMember = static function (ClassReflection $classReflection) use ($classMemberReflection, $classReflectionName) { - if ($classMemberReflection->isPrivate()) { - return $classReflection->getName() === $classReflectionName; - } - - // protected - - if ( - $classReflection->getName() === $classReflectionName - || $classReflection->isSubclassOf($classReflectionName) - ) { - return true; - } - - return $classMemberReflection->getDeclaringClass()->isSubclassOf($classReflection->getName()); - }; - - foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { - if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) { - continue; - } - - if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) { - return true; - } - } - - if ($this->isInClass()) { - return $canAccessClassMember($this->getClassReflection()); - } - - return false; - } - - /** - * @return string[] - */ - public function debug(): array - { - $descriptions = []; - foreach ($this->expressionTypes as $name => $variableTypeHolder) { - $key = sprintf('%s (%s)', $name, $variableTypeHolder->getCertainty()->describe()); - $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise()); - } - foreach ($this->nativeExpressionTypes as $exprString => $nativeTypeHolder) { - $key = sprintf('native %s', $exprString); - $descriptions[$key] = $nativeTypeHolder->getType()->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->getArgs(), - ); - - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $this, - $methodCall->getArgs(), - $constructorMethod->getVariants(), - ); - $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); - - if ($normalizedMethodCall !== null) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) { - continue; - } - - $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( - $constructorMethod, - $normalizedMethodCall, - $this, - ); - if ($resolvedType === null) { - continue; - } - - $resolvedTypes[] = $resolvedType; - } - } - - 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; - } - - $assignedToProperty = $node->getAttribute(NewAssignedToPropertyVisitor::ATTRIBUTE_NAME); - if ($assignedToProperty !== null) { - $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 = TypeCombinator::removeNull($this->getType($assignedToProperty)); - 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->getArgs(), - $constructorMethod->getVariants(), - ); - - if ($this->explicitMixedInUnknownGenericNew) { - return new GenericObjectType( - $resolvedClassName, - $classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()), - ); - } - - $resolvedPhpDoc = $classReflection->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return $objectType; - } - - $list = []; - $typeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); - foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $templateType = $typeMap->getType($tag->getName()); - if ($templateType !== null) { - $list[] = $templateType; - continue; - } - $bound = $tag->getBound(); - if ($bound instanceof MixedType && $bound->isExplicitMixed()) { - $bound = new MixedType(false); - } - $list[] = $bound; - } - - return new GenericObjectType( - $resolvedClassName, - $list, - ); - } - - private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type - { - 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; - } - - /** @api */ - public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection - { - $type = $this->filterTypeWithMethod($typeWithMethod, $methodName); - if ($type === null) { - return null; - } - - return $type->getMethod($methodName, $this); - } - - /** - * @param MethodCall|Node\Expr\StaticCall $methodCall - */ - private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type - { - $typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName); - if ($typeWithMethod === null) { - return null; - } - - $methodReflection = $typeWithMethod->getMethod($methodName, $this); - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( - $this, - $methodCall->getArgs(), - $methodReflection->getVariants(), - ); - if ($methodCall instanceof MethodCall) { - $normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall); - } else { - $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); - } - if ($normalizedMethodCall === null) { - return $parametersAcceptor->getReturnType(); - } - - $resolvedTypes = []; - foreach ($typeWithMethod->getObjectClassNames() as $className) { - if ($normalizedMethodCall instanceof MethodCall) { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) { - if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) { - continue; - } - - $resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $this); - if ($resolvedType === null) { - continue; - } - - $resolvedTypes[] = $resolvedType; - } - } else { - foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { - if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) { - continue; - } - - $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( - $methodReflection, - $normalizedMethodCall, - $this, - ); - if ($resolvedType === null) { - continue; - } - - $resolvedTypes[] = $resolvedType; - } - } - } - - if (count($resolvedTypes) > 0) { - return TypeCombinator::union(...$resolvedTypes); - } - - return $parametersAcceptor->getReturnType(); - } - - /** @api */ - 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 PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch - */ - 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(); - } - - public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ConstantReflection - { - if ($typeWithConstant instanceof UnionType) { - $newTypes = []; - foreach ($typeWithConstant->getTypes() as $innerType) { - if (!$innerType->hasConstant($constantName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithConstant = TypeCombinator::union(...$newTypes); - } - if (!$typeWithConstant->hasConstant($constantName)->yes()) { - return null; - } - - return $typeWithConstant->getConstant($constantName); - } - - /** - * @return array - */ - private function getConstantTypes(): array - { - $constantTypes = []; - foreach ($this->expressionTypes as $exprString => $typeHolder) { - $expr = $typeHolder->getExpr(); - if (!$expr instanceof ConstFetch) { - continue; - } - $constantTypes[$exprString] = $typeHolder; - } - return $constantTypes; - } - - /** - * @return array - */ - private function getNativeConstantTypes(): array - { - $constantTypes = []; - foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) { - $expr = $typeHolder->getExpr(); - if (!$expr instanceof ConstFetch) { - continue; - } - $constantTypes[$exprString] = $typeHolder; - } - return $constantTypes; - } - -} diff --git a/src/Tests/Benchmarks/fixtures/yii/ActiveRecord.php.test b/src/Tests/Benchmarks/fixtures/yii/ActiveRecord.php.test deleted file mode 100644 index 0dbef6196..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/ActiveRecord.php.test +++ /dev/null @@ -1,799 +0,0 @@ -name`. - * In this example, Active Record is providing an object-oriented interface for accessing data stored in the database. - * But Active Record provides much more functionality than this. - * - * To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and - * implement the `tableName` method: - * - * ```php - * Tip: You may also use the [Gii code generator](guide:start-gii) to generate ActiveRecord classes from your - * > database tables. - * - * Class instances are obtained in one of two ways: - * - * * Using the `new` operator to create a new, empty object - * * Using a method to fetch an existing record (or records) from the database - * - * Below is an example showing some typical usage of ActiveRecord: - * - * ```php - * $user = new User(); - * $user->name = 'Qiang'; - * $user->save(); // a new row is inserted into user table - * - * // the following will retrieve the user 'CeBe' from the database - * $user = User::find()->where(['name' => 'CeBe'])->one(); - * - * // this will get related records from orders table when relation is defined - * $orders = $user->orders; - * ``` - * - * For more details and usage information on ActiveRecord, see the [guide article on ActiveRecord](guide:db-active-record). - * - * @method ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info - * @method ActiveQuery hasOne($class, array $link) see [[BaseActiveRecord::hasOne()]] for more info - * - * @author Qiang Xue - * @author Carsten Brandt - * @since 2.0 - */ -class ActiveRecord extends BaseActiveRecord -{ - /** - * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_INSERT = 0x01; - /** - * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_UPDATE = 0x02; - /** - * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. - */ - const OP_DELETE = 0x04; - /** - * All three operations: insert, update, delete. - * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE. - */ - const OP_ALL = 0x07; - - - /** - * Loads default values from database table schema. - * - * You may call this method to load default values after creating a new instance: - * - * ```php - * // class Customer extends \yii\db\ActiveRecord - * $customer = new Customer(); - * $customer->loadDefaultValues(); - * ``` - * - * @param bool $skipIfSet whether existing value should be preserved. - * This will only set defaults for attributes that are `null`. - * @return $this the model instance itself. - */ - public function loadDefaultValues($skipIfSet = true) - { - foreach (static::getTableSchema()->columns as $column) { - if ($column->defaultValue !== null && (!$skipIfSet || $this->{$column->name} === null)) { - $this->{$column->name} = $column->defaultValue; - } - } - - return $this; - } - - /** - * Returns the database connection used by this AR class. - * By default, the "db" application component is used as the database connection. - * You may override this method if you want to use a different database connection. - * @return Connection the database connection used by this AR class. - */ - public static function getDb() - { - return Yii::$app->getDb(); - } - - /** - * Creates an [[ActiveQuery]] instance with a given SQL statement. - * - * Note that because the SQL statement is already specified, calling additional - * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]] - * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is - * still fine. - * - * Below is an example: - * - * ```php - * $customers = Customer::findBySql('SELECT * FROM customer')->all(); - * ``` - * - * @param string $sql the SQL statement to be executed - * @param array $params parameters to be bound to the SQL statement during execution. - * @return ActiveQuery the newly created [[ActiveQuery]] instance - */ - public static function findBySql($sql, $params = []) - { - $query = static::find(); - $query->sql = $sql; - - return $query->params($params); - } - - /** - * Finds ActiveRecord instance(s) by the given condition. - * This method is internally called by [[findOne()]] and [[findAll()]]. - * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. - * @throws InvalidConfigException if there is no primary key defined. - * @internal - */ - protected static function findByCondition($condition) - { - $query = static::find(); - - if (!ArrayHelper::isAssociative($condition) && !$condition instanceof ExpressionInterface) { - // query by primary key - $primaryKey = static::primaryKey(); - if (isset($primaryKey[0])) { - $pk = $primaryKey[0]; - if (!empty($query->join) || !empty($query->joinWith)) { - $pk = static::tableName() . '.' . $pk; - } - // if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values - $condition = [$pk => is_array($condition) ? array_values($condition) : $condition]; - } else { - throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.'); - } - } elseif (is_array($condition)) { - $aliases = static::filterValidAliases($query); - $condition = static::filterCondition($condition, $aliases); - } - - return $query->andWhere($condition); - } - - /** - * Returns table aliases which are not the same as the name of the tables. - * - * @param Query $query - * @return array - * @throws InvalidConfigException - * @since 2.0.17 - * @internal - */ - protected static function filterValidAliases(Query $query) - { - $tables = $query->getTablesUsedInFrom(); - - $aliases = array_diff(array_keys($tables), $tables); - - return array_map(function ($alias) { - return preg_replace('/{{([\w]+)}}/', '$1', $alias); - }, array_values($aliases)); - } - - /** - * Filters array condition before it is assiged to a Query filter. - * - * This method will ensure that an array condition only filters on existing table columns. - * - * @param array $condition condition to filter. - * @param array $aliases - * @return array filtered condition. - * @throws InvalidArgumentException in case array contains unsafe values. - * @throws InvalidConfigException - * @since 2.0.15 - * @internal - */ - protected static function filterCondition(array $condition, array $aliases = []) - { - $result = []; - $db = static::getDb(); - $columnNames = static::filterValidColumnNames($db, $aliases); - - foreach ($condition as $key => $value) { - if (is_string($key) && !in_array($db->quoteSql($key), $columnNames, true)) { - throw new InvalidArgumentException('Key "' . $key . '" is not a column name and can not be used as a filter'); - } - $result[$key] = is_array($value) ? array_values($value) : $value; - } - - return $result; - } - - /** - * Valid column names are table column names or column names prefixed with table name or table alias - * - * @param Connection $db - * @param array $aliases - * @return array - * @throws InvalidConfigException - * @since 2.0.17 - * @internal - */ - protected static function filterValidColumnNames($db, array $aliases) - { - $columnNames = []; - $tableName = static::tableName(); - $quotedTableName = $db->quoteTableName($tableName); - - foreach (static::getTableSchema()->getColumnNames() as $columnName) { - $columnNames[] = $columnName; - $columnNames[] = $db->quoteColumnName($columnName); - $columnNames[] = "$tableName.$columnName"; - $columnNames[] = $db->quoteSql("$quotedTableName.[[$columnName]]"); - foreach ($aliases as $tableAlias) { - $columnNames[] = "$tableAlias.$columnName"; - $quotedTableAlias = $db->quoteTableName($tableAlias); - $columnNames[] = $db->quoteSql("$quotedTableAlias.[[$columnName]]"); - } - } - - return $columnNames; - } - - /** - * {@inheritdoc} - */ - public function refresh() - { - $query = static::find(); - $tableName = key($query->getTablesUsedInFrom()); - $pk = []; - // disambiguate column names in case ActiveQuery adds a JOIN - foreach ($this->getPrimaryKey(true) as $key => $value) { - $pk[$tableName . '.' . $key] = $value; - } - $query->where($pk); - - /* @var $record BaseActiveRecord */ - $record = $query->one(); - return $this->refreshInternal($record); - } - - /** - * Updates the whole table using the provided attribute values and conditions. - * - * For example, to change the status to be 1 for all customers whose status is 2: - * - * ```php - * Customer::updateAll(['status' => 1], 'status = 2'); - * ``` - * - * > Warning: If you do not specify any condition, this method will update **all** rows in the table. - * - * Note that this method will not trigger any events. If you need [[EVENT_BEFORE_UPDATE]] or - * [[EVENT_AFTER_UPDATE]] to be triggered, you need to [[find()|find]] the models first and then - * call [[update()]] on each of them. For example an equivalent of the example above would be: - * - * ```php - * $models = Customer::find()->where('status = 2')->all(); - * foreach ($models as $model) { - * $model->status = 1; - * $model->update(false); // skipping validation as no user input is involved - * } - * ``` - * - * For a large set of models you might consider using [[ActiveQuery::each()]] to keep memory usage within limits. - * - * @param array $attributes attribute values (name-value pairs) to be saved into the table - * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return int the number of rows updated - */ - public static function updateAll($attributes, $condition = '', $params = []) - { - $command = static::getDb()->createCommand(); - $command->update(static::tableName(), $attributes, $condition, $params); - - return $command->execute(); - } - - /** - * Updates the whole table using the provided counter changes and conditions. - * - * For example, to increment all customers' age by 1, - * - * ```php - * Customer::updateAllCounters(['age' => 1]); - * ``` - * - * Note that this method will not trigger any events. - * - * @param array $counters the counters to be updated (attribute name => increment value). - * Use negative values if you want to decrement the counters. - * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method. - * @return int the number of rows updated - */ - public static function updateAllCounters($counters, $condition = '', $params = []) - { - $n = 0; - foreach ($counters as $name => $value) { - $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]); - $n++; - } - $command = static::getDb()->createCommand(); - $command->update(static::tableName(), $counters, $condition, $params); - - return $command->execute(); - } - - /** - * Deletes rows in the table using the provided conditions. - * - * For example, to delete all customers whose status is 3: - * - * ```php - * Customer::deleteAll('status = 3'); - * ``` - * - * > Warning: If you do not specify any condition, this method will delete **all** rows in the table. - * - * Note that this method will not trigger any events. If you need [[EVENT_BEFORE_DELETE]] or - * [[EVENT_AFTER_DELETE]] to be triggered, you need to [[find()|find]] the models first and then - * call [[delete()]] on each of them. For example an equivalent of the example above would be: - * - * ```php - * $models = Customer::find()->where('status = 3')->all(); - * foreach ($models as $model) { - * $model->delete(); - * } - * ``` - * - * For a large set of models you might consider using [[ActiveQuery::each()]] to keep memory usage within limits. - * - * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. - * @return int the number of rows deleted - */ - public static function deleteAll($condition = null, $params = []) - { - $command = static::getDb()->createCommand(); - $command->delete(static::tableName(), $condition, $params); - - return $command->execute(); - } - - /** - * {@inheritdoc} - * @return ActiveQuery the newly created [[ActiveQuery]] instance. - */ - public static function find() - { - return Yii::createObject(ActiveQuery::className(), [get_called_class()]); - } - - /** - * Declares the name of the database table associated with this AR class. - * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]] - * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is `tbl_`, - * `Customer` becomes `tbl_customer`, and `OrderItem` becomes `tbl_order_item`. You may override this method - * if the table is not named after this convention. - * @return string the table name - */ - public static function tableName() - { - return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}'; - } - - /** - * Returns the schema information of the DB table associated with this AR class. - * @return TableSchema the schema information of the DB table associated with this AR class. - * @throws InvalidConfigException if the table for the AR class does not exist. - */ - public static function getTableSchema() - { - $tableSchema = static::getDb() - ->getSchema() - ->getTableSchema(static::tableName()); - - if ($tableSchema === null) { - throw new InvalidConfigException('The table does not exist: ' . static::tableName()); - } - - return $tableSchema; - } - - /** - * Returns the primary key name(s) for this AR class. - * The default implementation will return the primary key(s) as declared - * in the DB table that is associated with this AR class. - * - * If the DB table does not declare any primary key, you should override - * this method to return the attributes that you want to use as primary keys - * for this AR class. - * - * Note that an array should be returned even for a table with single primary key. - * - * @return string[] the primary keys of the associated database table. - */ - public static function primaryKey() - { - return static::getTableSchema()->primaryKey; - } - - /** - * Returns the list of all attribute names of the model. - * The default implementation will return all column names of the table associated with this AR class. - * @return array list of attribute names. - */ - public function attributes() - { - return array_keys(static::getTableSchema()->columns); - } - - /** - * Declares which DB operations should be performed within a transaction in different scenarios. - * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], - * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively. - * By default, these methods are NOT enclosed in a DB transaction. - * - * In some scenarios, to ensure data consistency, you may want to enclose some or all of them - * in transactions. You can do so by overriding this method and returning the operations - * that need to be transactional. For example, - * - * ```php - * return [ - * 'admin' => self::OP_INSERT, - * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, - * // the above is equivalent to the following: - * // 'api' => self::OP_ALL, - * - * ]; - * ``` - * - * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) - * should be done in a transaction; and in the "api" scenario, all the operations should be done - * in a transaction. - * - * @return array the declarations of transactional operations. The array keys are scenarios names, - * and the array values are the corresponding transaction operations. - */ - public function transactions() - { - return []; - } - - /** - * {@inheritdoc} - */ - public static function populateRecord($record, $row) - { - $columns = static::getTableSchema()->columns; - foreach ($row as $name => $value) { - if (isset($columns[$name])) { - $row[$name] = $columns[$name]->phpTypecast($value); - } - } - parent::populateRecord($record, $row); - } - - /** - * Inserts a row into the associated database table using the attribute values of this record. - * - * This method performs the following steps in order: - * - * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]] - * returns `false`, the rest of the steps will be skipped; - * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation - * failed, the rest of the steps will be skipped; - * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, - * the rest of the steps will be skipped; - * 4. insert the record into database. If this fails, it will skip the rest of the steps; - * 5. call [[afterSave()]]; - * - * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_INSERT]], and [[EVENT_AFTER_INSERT]] - * will be raised by the corresponding methods. - * - * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database. - * - * If the table's primary key is auto-incremental and is `null` during insertion, - * it will be populated with the actual value after insertion. - * - * For example, to insert a customer record: - * - * ```php - * $customer = new Customer; - * $customer->name = $name; - * $customer->email = $email; - * $customer->insert(); - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributes list of attributes that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return bool whether the attributes are valid and the record is inserted successfully. - * @throws \Exception|\Throwable in case insert failed. - */ - public function insert($runValidation = true, $attributes = null) - { - if ($runValidation && !$this->validate($attributes)) { - Yii::info('Model not inserted due to validation error.', __METHOD__); - return false; - } - - if (!$this->isTransactional(self::OP_INSERT)) { - return $this->insertInternal($attributes); - } - - $transaction = static::getDb()->beginTransaction(); - try { - $result = $this->insertInternal($attributes); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - - return $result; - } catch (\Exception $e) { - $transaction->rollBack(); - throw $e; - } catch (\Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - - /** - * Inserts an ActiveRecord into DB without considering transaction. - * @param array $attributes list of attributes that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return bool whether the record is inserted successfully. - */ - protected function insertInternal($attributes = null) - { - if (!$this->beforeSave(true)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (($primaryKeys = static::getDb()->schema->insert(static::tableName(), $values)) === false) { - return false; - } - foreach ($primaryKeys as $name => $value) { - $id = static::getTableSchema()->columns[$name]->phpTypecast($value); - $this->setAttribute($name, $id); - $values[$name] = $id; - } - - $changedAttributes = array_fill_keys(array_keys($values), null); - $this->setOldAttributes($values); - $this->afterSave(true, $changedAttributes); - - return true; - } - - /** - * Saves the changes to this active record into the associated database table. - * - * This method performs the following steps in order: - * - * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]] - * returns `false`, the rest of the steps will be skipped; - * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation - * failed, the rest of the steps will be skipped; - * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, - * the rest of the steps will be skipped; - * 4. save the record into database. If this fails, it will skip the rest of the steps; - * 5. call [[afterSave()]]; - * - * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]] - * will be raised by the corresponding methods. - * - * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. - * - * For example, to update a customer record: - * - * ```php - * $customer = Customer::findOne($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->update(); - * ``` - * - * Note that it is possible the update does not affect any row in the table. - * In this case, this method will return 0. For this reason, you should use the following - * code to check if update() is successful or not: - * - * ```php - * if ($customer->update() !== false) { - * // update successful - * } else { - * // update failed - * } - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributeNames list of attributes that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return int|false the number of rows affected, or false if validation fails - * or [[beforeSave()]] stops the updating process. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being updated is outdated. - * @throws \Exception|\Throwable in case update failed. - */ - public function update($runValidation = true, $attributeNames = null) - { - if ($runValidation && !$this->validate($attributeNames)) { - Yii::info('Model not updated due to validation error.', __METHOD__); - return false; - } - - if (!$this->isTransactional(self::OP_UPDATE)) { - return $this->updateInternal($attributeNames); - } - - $transaction = static::getDb()->beginTransaction(); - try { - $result = $this->updateInternal($attributeNames); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - - return $result; - } catch (\Exception $e) { - $transaction->rollBack(); - throw $e; - } catch (\Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - - /** - * Deletes the table row corresponding to this active record. - * - * This method performs the following steps in order: - * - * 1. call [[beforeDelete()]]. If the method returns `false`, it will skip the - * rest of the steps; - * 2. delete the record from the database; - * 3. call [[afterDelete()]]. - * - * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] - * will be raised by the corresponding methods. - * - * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason. - * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being deleted is outdated. - * @throws \Exception|\Throwable in case delete failed. - */ - public function delete() - { - if (!$this->isTransactional(self::OP_DELETE)) { - return $this->deleteInternal(); - } - - $transaction = static::getDb()->beginTransaction(); - try { - $result = $this->deleteInternal(); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - - return $result; - } catch (\Exception $e) { - $transaction->rollBack(); - throw $e; - } catch (\Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - - /** - * Deletes an ActiveRecord without considering transaction. - * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason. - * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. - * @throws StaleObjectException - */ - protected function deleteInternal() - { - if (!$this->beforeDelete()) { - return false; - } - - // we do not check the return value of deleteAll() because it's possible - // the record is already deleted in the database and thus the method will return 0 - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - $condition[$lock] = $this->$lock; - } - $result = static::deleteAll($condition); - if ($lock !== null && !$result) { - throw new StaleObjectException('The object being deleted is outdated.'); - } - $this->setOldAttributes(null); - $this->afterDelete(); - - return $result; - } - - /** - * Returns a value indicating whether the given active record is the same as the current one. - * The comparison is made by comparing the table names and the primary key values of the two active records. - * If one of the records [[isNewRecord|is new]] they are also considered not equal. - * @param ActiveRecord $record record to compare to - * @return bool whether the two active records refer to the same row in the same database table. - */ - public function equals($record) - { - if ($this->isNewRecord || $record->isNewRecord) { - return false; - } - - return static::tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); - } - - /** - * Returns a value indicating whether the specified operation is transactional in the current [[$scenario]]. - * @param int $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. - * @return bool whether the specified operation is transactional in the current [[scenario]]. - */ - public function isTransactional($operation) - { - $scenario = $this->getScenario(); - $transactions = $this->transactions(); - - return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); - } -} diff --git a/src/Tests/Benchmarks/fixtures/yii/ActiveRecordInterface.php.test b/src/Tests/Benchmarks/fixtures/yii/ActiveRecordInterface.php.test deleted file mode 100644 index 846b40c1e..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/ActiveRecordInterface.php.test +++ /dev/null @@ -1,472 +0,0 @@ - - * @author Carsten Brandt - * @since 2.0 - */ -interface ActiveRecordInterface extends StaticInstanceInterface -{ - /** - * Returns the primary key **name(s)** for this AR class. - * - * Note that an array should be returned even when the record only has a single primary key. - * - * For the primary key **value** see [[getPrimaryKey()]] instead. - * - * @return string[] the primary key name(s) for this AR class. - */ - public static function primaryKey(); - - /** - * Returns the list of all attribute names of the record. - * @return array list of attribute names. - */ - public function attributes(); - - /** - * Returns the named attribute value. - * If this record is the result of a query and the attribute is not loaded, - * `null` will be returned. - * @param string $name the attribute name - * @return mixed the attribute value. `null` if the attribute is not set or does not exist. - * @see hasAttribute() - */ - public function getAttribute($name); - - /** - * Sets the named attribute value. - * @param string $name the attribute name. - * @param mixed $value the attribute value. - * @see hasAttribute() - */ - public function setAttribute($name, $value); - - /** - * Returns a value indicating whether the record has an attribute with the specified name. - * @param string $name the name of the attribute - * @return bool whether the record has an attribute with the specified name. - */ - public function hasAttribute($name); - - /** - * Returns the primary key value(s). - * @param bool $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with attribute names as keys and attribute values as values. - * Note that for composite primary keys, an array will always be returned regardless of this parameter value. - * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key - * is composite or `$asArray` is true. A string is returned otherwise (`null` will be returned if - * the key value is `null`). - */ - public function getPrimaryKey($asArray = false); - - /** - * Returns the old primary key value(s). - * This refers to the primary key value that is populated into the record - * after executing a find method (e.g. find(), findOne()). - * The value remains unchanged even if the primary key attribute is manually assigned with a different value. - * @param bool $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with column name as key and column value as value. - * If this is `false` (default), a scalar value will be returned for non-composite primary key. - * @property mixed The old primary key value. An array (column name => column value) is - * returned if the primary key is composite. A string is returned otherwise (`null` will be - * returned if the key value is `null`). - * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key - * is composite or `$asArray` is true. A string is returned otherwise (`null` will be returned if - * the key value is `null`). - */ - public function getOldPrimaryKey($asArray = false); - - /** - * Returns a value indicating whether the given set of attributes represents the primary key for this model. - * @param array $keys the set of attributes to check - * @return bool whether the given set of attributes represents the primary key for this model - */ - public static function isPrimaryKey($keys); - - /** - * Creates an [[ActiveQueryInterface]] instance for query purpose. - * - * The returned [[ActiveQueryInterface]] instance can be further customized by calling - * methods defined in [[ActiveQueryInterface]] before `one()` or `all()` is called to return - * populated ActiveRecord instances. For example, - * - * ```php - * // find the customer whose ID is 1 - * $customer = Customer::find()->where(['id' => 1])->one(); - * - * // find all active customers and order them by their age: - * $customers = Customer::find() - * ->where(['status' => 1]) - * ->orderBy('age') - * ->all(); - * ``` - * - * This method is also called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to - * create a relational query. - * - * You may override this method to return a customized query. For example, - * - * ```php - * class Customer extends ActiveRecord - * { - * public static function find() - * { - * // use CustomerQuery instead of the default ActiveQuery - * return new CustomerQuery(get_called_class()); - * } - * } - * ``` - * - * The following code shows how to apply a default condition for all queries: - * - * ```php - * class Customer extends ActiveRecord - * { - * public static function find() - * { - * return parent::find()->where(['deleted' => false]); - * } - * } - * - * // Use andWhere()/orWhere() to apply the default condition - * // SELECT FROM customer WHERE `deleted`=:deleted AND age>30 - * $customers = Customer::find()->andWhere('age>30')->all(); - * - * // Use where() to ignore the default condition - * // SELECT FROM customer WHERE age>30 - * $customers = Customer::find()->where('age>30')->all(); - * - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface]] instance. - */ - public static function find(); - - /** - * Returns a single active record model instance by a primary key or an array of column values. - * - * The method accepts: - * - * - a scalar value (integer or string): query by a single primary key value and return the - * corresponding record (or `null` if not found). - * - a non-associative array: query by a list of primary key values and return the - * first record (or `null` if not found). - * - an associative array of name-value pairs: query by a set of attribute values and return a single record - * matching all of them (or `null` if not found). Note that `['id' => 1, 2]` is treated as a non-associative array. - * Column names are limited to current records table columns for SQL DBMS, or filtered otherwise to be limited to simple filter conditions. - * - a yii\db\Expression: The expression will be used directly. (@since 2.0.37) - * - * That this method will automatically call the `one()` method and return an [[ActiveRecordInterface|ActiveRecord]] - * instance. - * - * > Note: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. - * > If you need to specify more complex conditions, use [[find()]] in combination with [[ActiveQuery::where()|where()]] instead. - * - * See the following code for usage examples: - * - * ```php - * // find a single customer whose primary key value is 10 - * $customer = Customer::findOne(10); - * - * // the above code is equivalent to: - * $customer = Customer::find()->where(['id' => 10])->one(); - * - * // find the customers whose primary key value is 10, 11 or 12. - * $customers = Customer::findOne([10, 11, 12]); - * - * // the above code is equivalent to: - * $customers = Customer::find()->where(['id' => [10, 11, 12]])->one(); - * - * // find the first customer whose age is 30 and whose status is 1 - * $customer = Customer::findOne(['age' => 30, 'status' => 1]); - * - * // the above code is equivalent to: - * $customer = Customer::find()->where(['age' => 30, 'status' => 1])->one(); - * ``` - * - * If you need to pass user input to this method, make sure the input value is scalar or in case of - * array condition, make sure the array structure can not be changed from the outside: - * - * ```php - * // yii\web\Controller ensures that $id is scalar - * public function actionView($id) - * { - * $model = Post::findOne($id); - * // ... - * } - * - * // explicitly specifying the colum to search, passing a scalar or array here will always result in finding a single record - * $model = Post::findOne(['id' => Yii::$app->request->get('id')]); - * - * // do NOT use the following code! it is possible to inject an array condition to filter by arbitrary column values! - * $model = Post::findOne(Yii::$app->request->get('id')); - * ``` - * - * @param mixed $condition primary key value or a set of column values - * @return static|null ActiveRecord instance matching the condition, or `null` if nothing matches. - */ - public static function findOne($condition); - - /** - * Returns a list of active record models that match the specified primary key value(s) or a set of column values. - * - * The method accepts: - * - * - a scalar value (integer or string): query by a single primary key value and return an array containing the - * corresponding record (or an empty array if not found). - * - a non-associative array: query by a list of primary key values and return the - * corresponding records (or an empty array if none was found). - * Note that an empty condition will result in an empty result as it will be interpreted as a search for - * primary keys and not an empty `WHERE` condition. - * - an associative array of name-value pairs: query by a set of attribute values and return an array of records - * matching all of them (or an empty array if none was found). Note that `['id' => 1, 2]` is treated as - * a non-associative array. - * Column names are limited to current records table columns for SQL DBMS, or filtered otherwise to be limted to simple filter conditions. - * - a yii\db\Expression: The expression will be used directly. (@since 2.0.37) - * - * This method will automatically call the `all()` method and return an array of [[ActiveRecordInterface|ActiveRecord]] - * instances. - * - * > Note: As this is a short-hand method only, using more complex conditions, like ['!=', 'id', 1] will not work. - * > If you need to specify more complex conditions, use [[find()]] in combination with [[ActiveQuery::where()|where()]] instead. - * - * See the following code for usage examples: - * - * ```php - * // find the customers whose primary key value is 10 - * $customers = Customer::findAll(10); - * - * // the above code is equivalent to: - * $customers = Customer::find()->where(['id' => 10])->all(); - * - * // find the customers whose primary key value is 10, 11 or 12. - * $customers = Customer::findAll([10, 11, 12]); - * - * // the above code is equivalent to: - * $customers = Customer::find()->where(['id' => [10, 11, 12]])->all(); - * - * // find customers whose age is 30 and whose status is 1 - * $customers = Customer::findAll(['age' => 30, 'status' => 1]); - * - * // the above code is equivalent to: - * $customers = Customer::find()->where(['age' => 30, 'status' => 1])->all(); - * ``` - * - * If you need to pass user input to this method, make sure the input value is scalar or in case of - * array condition, make sure the array structure can not be changed from the outside: - * - * ```php - * // yii\web\Controller ensures that $id is scalar - * public function actionView($id) - * { - * $model = Post::findOne($id); - * // ... - * } - * - * // explicitly specifying the colum to search, passing a scalar or array here will always result in finding a single record - * $model = Post::findOne(['id' => Yii::$app->request->get('id')]); - * - * // do NOT use the following code! it is possible to inject an array condition to filter by arbitrary column values! - * $model = Post::findOne(Yii::$app->request->get('id')); - * ``` - * - * @param mixed $condition primary key value or a set of column values - * @return array an array of ActiveRecord instance, or an empty array if nothing matches. - */ - public static function findAll($condition); - - /** - * Updates records using the provided attribute values and conditions. - * - * For example, to change the status to be 1 for all customers whose status is 2: - * - * ```php - * Customer::updateAll(['status' => 1], ['status' => '2']); - * ``` - * - * @param array $attributes attribute values (name-value pairs) to be saved for the record. - * Unlike [[update()]] these are not going to be validated. - * @param array $condition the condition that matches the records that should get updated. - * Please refer to [[QueryInterface::where()]] on how to specify this parameter. - * An empty condition will match all records. - * @return int the number of rows updated - */ - public static function updateAll($attributes, $condition = null); - - /** - * Deletes records using the provided conditions. - * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. - * - * For example, to delete all customers whose status is 3: - * - * ```php - * Customer::deleteAll([status = 3]); - * ``` - * - * @param array $condition the condition that matches the records that should get deleted. - * Please refer to [[QueryInterface::where()]] on how to specify this parameter. - * An empty condition will match all records. - * @return int the number of rows deleted - */ - public static function deleteAll($condition = null); - - /** - * Saves the current record. - * - * This method will call [[insert()]] when [[getIsNewRecord()|isNewRecord]] is true, or [[update()]] - * when [[getIsNewRecord()|isNewRecord]] is false. - * - * For example, to save a customer record: - * - * ```php - * $customer = new Customer; // or $customer = Customer::findOne($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->save(); - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributeNames list of attribute names that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return bool whether the saving succeeded (i.e. no validation errors occurred). - */ - public function save($runValidation = true, $attributeNames = null); - - /** - * Inserts the record into the database using the attribute values of this record. - * - * Usage example: - * - * ```php - * $customer = new Customer; - * $customer->name = $name; - * $customer->email = $email; - * $customer->insert(); - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributes list of attributes that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return bool whether the attributes are valid and the record is inserted successfully. - */ - public function insert($runValidation = true, $attributes = null); - - /** - * Saves the changes to this active record into the database. - * - * Usage example: - * - * ```php - * $customer = Customer::findOne($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->update(); - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributeNames list of attributes that need to be saved. Defaults to `null`, - * meaning all attributes that are loaded from DB will be saved. - * @return int|bool the number of rows affected, or `false` if validation fails - * or updating process is stopped for other reasons. - * Note that it is possible that the number of rows affected is 0, even though the - * update execution is successful. - */ - public function update($runValidation = true, $attributeNames = null); - - /** - * Deletes the record from the database. - * - * @return int|bool the number of rows deleted, or `false` if the deletion is unsuccessful for some reason. - * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful. - */ - public function delete(); - - /** - * Returns a value indicating whether the current record is new (not saved in the database). - * @return bool whether the record is new and should be inserted when calling [[save()]]. - */ - public function getIsNewRecord(); - - /** - * Returns a value indicating whether the given active record is the same as the current one. - * Two [[getIsNewRecord()|new]] records are considered to be not equal. - * @param static $record record to compare to - * @return bool whether the two active records refer to the same row in the same database table. - */ - public function equals($record); - - /** - * Returns the relation object with the specified name. - * A relation is defined by a getter method which returns an object implementing the [[ActiveQueryInterface]] - * (normally this would be a relational [[ActiveQuery]] object). - * It can be declared in either the ActiveRecord class itself or one of its behaviors. - * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive). - * @param bool $throwException whether to throw exception if the relation does not exist. - * @return ActiveQueryInterface the relational query object - */ - public function getRelation($name, $throwException = true); - - /** - * Populates the named relation with the related records. - * Note that this method does not check if the relation exists or not. - * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive). - * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation. - * @since 2.0.8 - */ - public function populateRelation($name, $records); - - /** - * Establishes the relationship between two records. - * - * The relationship is established by setting the foreign key value(s) in one record - * to be the corresponding primary key value(s) in the other record. - * The record with the foreign key will be saved into database without performing validation. - * - * If the relationship involves a junction table, a new row will be inserted into the - * junction table which contains the primary key values from both records. - * - * This method requires that the primary key value is not `null`. - * - * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method. - * @param static $model the record to be linked with the current one. - * @param array $extraColumns additional column values to be saved into the junction table. - * This parameter is only meaningful for a relationship involving a junction table - * (i.e., a relation set with [[ActiveQueryInterface::via()]]). - */ - public function link($name, $model, $extraColumns = []); - - /** - * Destroys the relationship between two records. - * - * The record with the foreign key of the relationship will be deleted if `$delete` is true. - * Otherwise, the foreign key will be set `null` and the record will be saved without validation. - * - * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method. - * @param static $model the model to be unlinked from the current one. - * @param bool $delete whether to delete the model that contains the foreign key. - * If false, the model's foreign key will be set `null` and saved. - * If true, the model containing the foreign key will be deleted. - */ - public function unlink($name, $model, $delete = false); - - /** - * Returns the connection used by this AR class. - * @return mixed the database connection used by this AR class. - */ - public static function getDb(); -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/Arrayable.php.test b/src/Tests/Benchmarks/fixtures/yii/Arrayable.php.test deleted file mode 100644 index f2146f38b..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/Arrayable.php.test +++ /dev/null @@ -1,93 +0,0 @@ - - * @since 2.0 - */ -interface Arrayable -{ - /** - * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. - * - * A field is a named element in the returned array by [[toArray()]]. - * - * This method should return an array of field names or field definitions. - * If the former, the field name will be treated as an object property name whose value will be used - * as the field value. If the latter, the array key should be the field name while the array value should be - * the corresponding field definition which can be either an object property name or a PHP callable - * returning the corresponding field value. The signature of the callable should be: - * - * ```php - * function ($model, $field) { - * // return field value - * } - * ``` - * - * For example, the following code declares four fields: - * - * - `email`: the field name is the same as the property name `email`; - * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their - * values are obtained from the `first_name` and `last_name` properties; - * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` - * and `last_name`. - * - * ```php - * return [ - * 'email', - * 'firstName' => 'first_name', - * 'lastName' => 'last_name', - * 'fullName' => function ($model) { - * return $model->first_name . ' ' . $model->last_name; - * }, - * ]; - * ``` - * - * @return array the list of field names or field definitions. - * @see toArray() - */ - public function fields(); - - /** - * Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]]. - * - * This method is similar to [[fields()]] except that the list of fields declared - * by this method are not returned by default by [[toArray()]]. Only when a field in the list - * is explicitly requested, will it be included in the result of [[toArray()]]. - * - * @return array the list of expandable field names or field definitions. Please refer - * to [[fields()]] on the format of the return value. - * @see toArray() - * @see fields() - */ - public function extraFields(); - - /** - * Converts the object into an array. - * - * @param array $fields the fields that the output array should contain. Fields not specified - * in [[fields()]] will be ignored. If this parameter is empty, all fields as specified in [[fields()]] will be returned. - * @param array $expand the additional fields that the output array should contain. - * Fields not specified in [[extraFields()]] will be ignored. If this parameter is empty, no extra fields - * will be returned. - * @param bool $recursive whether to recursively return array representation of embedded objects. - * @return array the array representation of the object - */ - public function toArray(array $fields = [], array $expand = [], $recursive = true); -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/ArrayableTrait.php.test b/src/Tests/Benchmarks/fixtures/yii/ArrayableTrait.php.test deleted file mode 100644 index 93b7175b8..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/ArrayableTrait.php.test +++ /dev/null @@ -1,247 +0,0 @@ - - * @since 2.0 - */ -trait ArrayableTrait -{ - /** - * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. - * - * A field is a named element in the returned array by [[toArray()]]. - * - * This method should return an array of field names or field definitions. - * If the former, the field name will be treated as an object property name whose value will be used - * as the field value. If the latter, the array key should be the field name while the array value should be - * the corresponding field definition which can be either an object property name or a PHP callable - * returning the corresponding field value. The signature of the callable should be: - * - * ```php - * function ($model, $field) { - * // return field value - * } - * ``` - * - * For example, the following code declares four fields: - * - * - `email`: the field name is the same as the property name `email`; - * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their - * values are obtained from the `first_name` and `last_name` properties; - * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` - * and `last_name`. - * - * ```php - * return [ - * 'email', - * 'firstName' => 'first_name', - * 'lastName' => 'last_name', - * 'fullName' => function () { - * return $this->first_name . ' ' . $this->last_name; - * }, - * ]; - * ``` - * - * In this method, you may also want to return different lists of fields based on some context - * information. For example, depending on the privilege of the current application user, - * you may return different sets of visible fields or filter out some fields. - * - * The default implementation of this method returns the public object member variables indexed by themselves. - * - * @return array the list of field names or field definitions. - * @see toArray() - */ - public function fields() - { - $fields = array_keys(Yii::getObjectVars($this)); - return array_combine($fields, $fields); - } - - /** - * Returns the list of fields that can be expanded further and returned by [[toArray()]]. - * - * This method is similar to [[fields()]] except that the list of fields returned - * by this method are not returned by default by [[toArray()]]. Only when field names - * to be expanded are explicitly specified when calling [[toArray()]], will their values - * be exported. - * - * The default implementation returns an empty array. - * - * You may override this method to return a list of expandable fields based on some context information - * (e.g. the current application user). - * - * @return array the list of expandable field names or field definitions. Please refer - * to [[fields()]] on the format of the return value. - * @see toArray() - * @see fields() - */ - public function extraFields() - { - return []; - } - - /** - * Converts the model into an array. - * - * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]]. - * It will then turn the model into an array with these fields. If `$recursive` is true, - * any embedded objects will also be converted into arrays. - * When embeded objects are [[Arrayable]], their respective nested fields will be extracted and passed to [[toArray()]]. - * - * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element - * which refers to a list of links as specified by the interface. - * - * @param array $fields the fields being requested. - * If empty or if it contains '*', all fields as specified by [[fields()]] will be returned. - * Fields can be nested, separated with dots (.). e.g.: item.field.sub-field - * `$recursive` must be true for nested fields to be extracted. If `$recursive` is false, only the root fields will be extracted. - * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]] - * will be considered. - * Expand can also be nested, separated with dots (.). e.g.: item.expand1.expand2 - * `$recursive` must be true for nested expands to be extracted. If `$recursive` is false, only the root expands will be extracted. - * @param bool $recursive whether to recursively return array representation of embedded objects. - * @return array the array representation of the object - */ - public function toArray(array $fields = [], array $expand = [], $recursive = true) - { - $data = []; - foreach ($this->resolveFields($fields, $expand) as $field => $definition) { - $attribute = is_string($definition) ? $this->$definition : $definition($this, $field); - - if ($recursive) { - $nestedFields = $this->extractFieldsFor($fields, $field); - $nestedExpand = $this->extractFieldsFor($expand, $field); - if ($attribute instanceof JsonSerializable) { - $attribute = $attribute->jsonSerialize(); - } elseif ($attribute instanceof Arrayable) { - $attribute = $attribute->toArray($nestedFields, $nestedExpand); - } elseif (is_array($attribute)) { - $attribute = array_map( - function ($item) use ($nestedFields, $nestedExpand) { - if ($item instanceof JsonSerializable) { - return $item->jsonSerialize(); - } elseif ($item instanceof Arrayable) { - return $item->toArray($nestedFields, $nestedExpand); - } - return $item; - }, - $attribute - ); - } - } - $data[$field] = $attribute; - } - - if ($this instanceof Linkable) { - $data['_links'] = Link::serialize($this->getLinks()); - } - - return $recursive ? ArrayHelper::toArray($data) : $data; - } - - /** - * Extracts the root field names from nested fields. - * Nested fields are separated with dots (.). e.g: "item.id" - * The previous example would extract "item". - * - * @param array $fields The fields requested for extraction - * @return array root fields extracted from the given nested fields - * @since 2.0.14 - */ - protected function extractRootFields(array $fields) - { - $result = []; - - foreach ($fields as $field) { - $result[] = current(explode('.', $field, 2)); - } - - if (in_array('*', $result, true)) { - $result = []; - } - - return array_unique($result); - } - - /** - * Extract nested fields from a fields collection for a given root field - * Nested fields are separated with dots (.). e.g: "item.id" - * The previous example would extract "id". - * - * @param array $fields The fields requested for extraction - * @param string $rootField The root field for which we want to extract the nested fields - * @return array nested fields extracted for the given field - * @since 2.0.14 - */ - protected function extractFieldsFor(array $fields, $rootField) - { - $result = []; - - foreach ($fields as $field) { - if (0 === strpos($field, "{$rootField}.")) { - $result[] = preg_replace('/^' . preg_quote($rootField, '/') . '\./i', '', $field); - } - } - - return array_unique($result); - } - - /** - * Determines which fields can be returned by [[toArray()]]. - * This method will first extract the root fields from the given fields. - * Then it will check the requested root fields against those declared in [[fields()]] and [[extraFields()]] - * to determine which fields can be returned. - * @param array $fields the fields being requested for exporting - * @param array $expand the additional fields being requested for exporting - * @return array the list of fields to be exported. The array keys are the field names, and the array values - * are the corresponding object property names or PHP callables returning the field values. - */ - protected function resolveFields(array $fields, array $expand) - { - $fields = $this->extractRootFields($fields); - $expand = $this->extractRootFields($expand); - $result = []; - - foreach ($this->fields() as $field => $definition) { - if (is_int($field)) { - $field = $definition; - } - if (empty($fields) || in_array($field, $fields, true)) { - $result[$field] = $definition; - } - } - - if (empty($expand)) { - return $result; - } - - foreach ($this->extraFields() as $field => $definition) { - if (is_int($field)) { - $field = $definition; - } - if (in_array($field, $expand, true)) { - $result[$field] = $definition; - } - } - - return $result; - } -} diff --git a/src/Tests/Benchmarks/fixtures/yii/BaseActiveRecord.php.test b/src/Tests/Benchmarks/fixtures/yii/BaseActiveRecord.php.test deleted file mode 100644 index 536762be8..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/BaseActiveRecord.php.test +++ /dev/null @@ -1,1755 +0,0 @@ - column value) is - * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key - * value is null). This property is read-only. - * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if - * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null). - * This property is read-only. - * @property array $relatedRecords An array of related records indexed by relation names. This property is - * read-only. - * - * @author Qiang Xue - * @author Carsten Brandt - * @since 2.0 - */ -abstract class BaseActiveRecord extends Model implements ActiveRecordInterface -{ - /** - * @event Event an event that is triggered when the record is initialized via [[init()]]. - */ - const EVENT_INIT = 'init'; - /** - * @event Event an event that is triggered after the record is created and populated with query result. - */ - const EVENT_AFTER_FIND = 'afterFind'; - /** - * @event ModelEvent an event that is triggered before inserting a record. - * You may set [[ModelEvent::isValid]] to be `false` to stop the insertion. - */ - const EVENT_BEFORE_INSERT = 'beforeInsert'; - /** - * @event AfterSaveEvent an event that is triggered after a record is inserted. - */ - const EVENT_AFTER_INSERT = 'afterInsert'; - /** - * @event ModelEvent an event that is triggered before updating a record. - * You may set [[ModelEvent::isValid]] to be `false` to stop the update. - */ - const EVENT_BEFORE_UPDATE = 'beforeUpdate'; - /** - * @event AfterSaveEvent an event that is triggered after a record is updated. - */ - const EVENT_AFTER_UPDATE = 'afterUpdate'; - /** - * @event ModelEvent an event that is triggered before deleting a record. - * You may set [[ModelEvent::isValid]] to be `false` to stop the deletion. - */ - const EVENT_BEFORE_DELETE = 'beforeDelete'; - /** - * @event Event an event that is triggered after a record is deleted. - */ - const EVENT_AFTER_DELETE = 'afterDelete'; - /** - * @event Event an event that is triggered after a record is refreshed. - * @since 2.0.8 - */ - const EVENT_AFTER_REFRESH = 'afterRefresh'; - - /** - * @var array attribute values indexed by attribute names - */ - private $_attributes = []; - /** - * @var array|null old attribute values indexed by attribute names. - * This is `null` if the record [[isNewRecord|is new]]. - */ - private $_oldAttributes; - /** - * @var array related models indexed by the relation names - */ - private $_related = []; - /** - * @var array relation names indexed by their link attributes - */ - private $_relationsDependencies = []; - - - /** - * {@inheritdoc} - * @return static|null ActiveRecord instance matching the condition, or `null` if nothing matches. - */ - public static function findOne($condition) - { - return static::findByCondition($condition)->one(); - } - - /** - * {@inheritdoc} - * @return static[] an array of ActiveRecord instances, or an empty array if nothing matches. - */ - public static function findAll($condition) - { - return static::findByCondition($condition)->all(); - } - - /** - * Finds ActiveRecord instance(s) by the given condition. - * This method is internally called by [[findOne()]] and [[findAll()]]. - * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. - * @throws InvalidConfigException if there is no primary key defined - * @internal - */ - protected static function findByCondition($condition) - { - $query = static::find(); - - if (!ArrayHelper::isAssociative($condition) && !$condition instanceof ExpressionInterface) { - // query by primary key - $primaryKey = static::primaryKey(); - if (isset($primaryKey[0])) { - // if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values - $condition = [$primaryKey[0] => is_array($condition) ? array_values($condition) : $condition]; - } else { - throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.'); - } - } - - return $query->andWhere($condition); - } - - /** - * Updates the whole table using the provided attribute values and conditions. - * - * For example, to change the status to be 1 for all customers whose status is 2: - * - * ```php - * Customer::updateAll(['status' => 1], 'status = 2'); - * ``` - * - * @param array $attributes attribute values (name-value pairs) to be saved into the table - * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @return int the number of rows updated - * @throws NotSupportedException if not overridden - */ - public static function updateAll($attributes, $condition = '') - { - throw new NotSupportedException(__METHOD__ . ' is not supported.'); - } - - /** - * Updates the whole table using the provided counter changes and conditions. - * - * For example, to increment all customers' age by 1, - * - * ```php - * Customer::updateAllCounters(['age' => 1]); - * ``` - * - * @param array $counters the counters to be updated (attribute name => increment value). - * Use negative values if you want to decrement the counters. - * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @return int the number of rows updated - * @throws NotSupportedException if not overrided - */ - public static function updateAllCounters($counters, $condition = '') - { - throw new NotSupportedException(__METHOD__ . ' is not supported.'); - } - - /** - * Deletes rows in the table using the provided conditions. - * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. - * - * For example, to delete all customers whose status is 3: - * - * ```php - * Customer::deleteAll('status = 3'); - * ``` - * - * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @return int the number of rows deleted - * @throws NotSupportedException if not overridden. - */ - public static function deleteAll($condition = null) - { - throw new NotSupportedException(__METHOD__ . ' is not supported.'); - } - - /** - * Returns the name of the column that stores the lock version for implementing optimistic locking. - * - * Optimistic locking allows multiple users to access the same record for edits and avoids - * potential conflicts. In case when a user attempts to save the record upon some staled data - * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown, - * and the update or deletion is skipped. - * - * Optimistic locking is only supported by [[update()]] and [[delete()]]. - * - * To use Optimistic locking: - * - * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. - * Override this method to return the name of this column. - * 2. Ensure the version value is submitted and loaded to your model before any update or delete. - * Or add [[\yii\behaviors\OptimisticLockBehavior|OptimisticLockBehavior]] to your model - * class in order to automate the process. - * 3. In the Web form that collects the user input, add a hidden field that stores - * the lock version of the recording being updated. - * 4. In the controller action that does the data updating, try to catch the [[StaleObjectException]] - * and implement necessary business logic (e.g. merging the changes, prompting stated data) - * to resolve the conflict. - * - * @return string the column name that stores the lock version of a table row. - * If `null` is returned (default implemented), optimistic locking will not be supported. - */ - public function optimisticLock() - { - return null; - } - - /** - * {@inheritdoc} - */ - public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) - { - if (parent::canGetProperty($name, $checkVars, $checkBehaviors)) { - return true; - } - - try { - return $this->hasAttribute($name); - } catch (\Exception $e) { - // `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used - return false; - } - } - - /** - * {@inheritdoc} - */ - public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) - { - if (parent::canSetProperty($name, $checkVars, $checkBehaviors)) { - return true; - } - - try { - return $this->hasAttribute($name); - } catch (\Exception $e) { - // `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used - return false; - } - } - - /** - * PHP getter magic method. - * This method is overridden so that attributes and related objects can be accessed like properties. - * - * @param string $name property name - * @throws InvalidArgumentException if relation name is wrong - * @return mixed property value - * @see getAttribute() - */ - public function __get($name) - { - if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { - return $this->_attributes[$name]; - } - - if ($this->hasAttribute($name)) { - return null; - } - - if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { - return $this->_related[$name]; - } - $value = parent::__get($name); - if ($value instanceof ActiveQueryInterface) { - $this->setRelationDependencies($name, $value); - return $this->_related[$name] = $value->findFor($name, $this); - } - - return $value; - } - - /** - * PHP setter magic method. - * This method is overridden so that AR attributes can be accessed like properties. - * @param string $name property name - * @param mixed $value property value - */ - public function __set($name, $value) - { - if ($this->hasAttribute($name)) { - if ( - !empty($this->_relationsDependencies[$name]) - && (!array_key_exists($name, $this->_attributes) || $this->_attributes[$name] !== $value) - ) { - $this->resetDependentRelations($name); - } - $this->_attributes[$name] = $value; - } else { - parent::__set($name, $value); - } - } - - /** - * Checks if a property value is null. - * This method overrides the parent implementation by checking if the named attribute is `null` or not. - * @param string $name the property name or the event name - * @return bool whether the property value is null - */ - public function __isset($name) - { - try { - return $this->__get($name) !== null; - } catch (\Throwable $t) { - return false; - } catch (\Exception $e) { - return false; - } - } - - /** - * Sets a component property to be null. - * This method overrides the parent implementation by clearing - * the specified attribute value. - * @param string $name the property name or the event name - */ - public function __unset($name) - { - if ($this->hasAttribute($name)) { - unset($this->_attributes[$name]); - if (!empty($this->_relationsDependencies[$name])) { - $this->resetDependentRelations($name); - } - } elseif (array_key_exists($name, $this->_related)) { - unset($this->_related[$name]); - } elseif ($this->getRelation($name, false) === null) { - parent::__unset($name); - } - } - - /** - * Declares a `has-one` relation. - * The declaration is returned in terms of a relational [[ActiveQuery]] instance - * through which the related record can be queried and retrieved back. - * - * A `has-one` relation means that there is at most one related record matching - * the criteria set by this relation, e.g., a customer has one country. - * - * For example, to declare the `country` relation for `Customer` class, we can write - * the following code in the `Customer` class: - * - * ```php - * public function getCountry() - * { - * return $this->hasOne(Country::className(), ['id' => 'country_id']); - * } - * ``` - * - * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name - * in the related class `Country`, while the 'country_id' value refers to an attribute name - * in the current AR class. - * - * Call methods declared in [[ActiveQuery]] to further customize the relation. - * - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the attributes of the record associated with the `$class` model, while the values of the - * array refer to the corresponding attributes in **this** AR class. - * @return ActiveQueryInterface the relational query object. - */ - public function hasOne($class, $link) - { - return $this->createRelationQuery($class, $link, false); - } - - /** - * Declares a `has-many` relation. - * The declaration is returned in terms of a relational [[ActiveQuery]] instance - * through which the related record can be queried and retrieved back. - * - * A `has-many` relation means that there are multiple related records matching - * the criteria set by this relation, e.g., a customer has many orders. - * - * For example, to declare the `orders` relation for `Customer` class, we can write - * the following code in the `Customer` class: - * - * ```php - * public function getOrders() - * { - * return $this->hasMany(Order::className(), ['customer_id' => 'id']); - * } - * ``` - * - * Note that in the above, the 'customer_id' key in the `$link` parameter refers to - * an attribute name in the related class `Order`, while the 'id' value refers to - * an attribute name in the current AR class. - * - * Call methods declared in [[ActiveQuery]] to further customize the relation. - * - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the attributes of the record associated with the `$class` model, while the values of the - * array refer to the corresponding attributes in **this** AR class. - * @return ActiveQueryInterface the relational query object. - */ - public function hasMany($class, $link) - { - return $this->createRelationQuery($class, $link, true); - } - - /** - * Creates a query instance for `has-one` or `has-many` relation. - * @param string $class the class name of the related record. - * @param array $link the primary-foreign key constraint. - * @param bool $multiple whether this query represents a relation to more than one record. - * @return ActiveQueryInterface the relational query object. - * @since 2.0.12 - * @see hasOne() - * @see hasMany() - */ - protected function createRelationQuery($class, $link, $multiple) - { - /* @var $class ActiveRecordInterface */ - /* @var $query ActiveQuery */ - $query = $class::find(); - $query->primaryModel = $this; - $query->link = $link; - $query->multiple = $multiple; - return $query; - } - - /** - * Populates the named relation with the related records. - * Note that this method does not check if the relation exists or not. - * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive). - * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation. - * @see getRelation() - */ - public function populateRelation($name, $records) - { - foreach ($this->_relationsDependencies as &$relationNames) { - unset($relationNames[$name]); - } - - $this->_related[$name] = $records; - } - - /** - * Check whether the named relation has been populated with records. - * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive). - * @return bool whether relation has been populated with records. - * @see getRelation() - */ - public function isRelationPopulated($name) - { - return array_key_exists($name, $this->_related); - } - - /** - * Returns all populated related records. - * @return array an array of related records indexed by relation names. - * @see getRelation() - */ - public function getRelatedRecords() - { - return $this->_related; - } - - /** - * Returns a value indicating whether the model has an attribute with the specified name. - * @param string $name the name of the attribute - * @return bool whether the model has an attribute with the specified name. - */ - public function hasAttribute($name) - { - return isset($this->_attributes[$name]) || in_array($name, $this->attributes(), true); - } - - /** - * Returns the named attribute value. - * If this record is the result of a query and the attribute is not loaded, - * `null` will be returned. - * @param string $name the attribute name - * @return mixed the attribute value. `null` if the attribute is not set or does not exist. - * @see hasAttribute() - */ - public function getAttribute($name) - { - return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - - /** - * Sets the named attribute value. - * @param string $name the attribute name - * @param mixed $value the attribute value. - * @throws InvalidArgumentException if the named attribute does not exist. - * @see hasAttribute() - */ - public function setAttribute($name, $value) - { - if ($this->hasAttribute($name)) { - if ( - !empty($this->_relationsDependencies[$name]) - && (!array_key_exists($name, $this->_attributes) || $this->_attributes[$name] !== $value) - ) { - $this->resetDependentRelations($name); - } - $this->_attributes[$name] = $value; - } else { - throw new InvalidArgumentException(get_class($this) . ' has no attribute named "' . $name . '".'); - } - } - - /** - * Returns the old attribute values. - * @return array the old attribute values (name-value pairs) - */ - public function getOldAttributes() - { - return $this->_oldAttributes === null ? [] : $this->_oldAttributes; - } - - /** - * Sets the old attribute values. - * All existing old attribute values will be discarded. - * @param array|null $values old attribute values to be set. - * If set to `null` this record is considered to be [[isNewRecord|new]]. - */ - public function setOldAttributes($values) - { - $this->_oldAttributes = $values; - } - - /** - * Returns the old value of the named attribute. - * If this record is the result of a query and the attribute is not loaded, - * `null` will be returned. - * @param string $name the attribute name - * @return mixed the old attribute value. `null` if the attribute is not loaded before - * or does not exist. - * @see hasAttribute() - */ - public function getOldAttribute($name) - { - return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - } - - /** - * Sets the old value of the named attribute. - * @param string $name the attribute name - * @param mixed $value the old attribute value. - * @throws InvalidArgumentException if the named attribute does not exist. - * @see hasAttribute() - */ - public function setOldAttribute($name, $value) - { - if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) { - $this->_oldAttributes[$name] = $value; - } else { - throw new InvalidArgumentException(get_class($this) . ' has no attribute named "' . $name . '".'); - } - } - - /** - * Marks an attribute dirty. - * This method may be called to force updating a record when calling [[update()]], - * even if there is no change being made to the record. - * @param string $name the attribute name - */ - public function markAttributeDirty($name) - { - unset($this->_oldAttributes[$name]); - } - - /** - * Returns a value indicating whether the named attribute has been changed. - * @param string $name the name of the attribute. - * @param bool $identical whether the comparison of new and old value is made for - * identical values using `===`, defaults to `true`. Otherwise `==` is used for comparison. - * This parameter is available since version 2.0.4. - * @return bool whether the attribute has been changed - */ - public function isAttributeChanged($name, $identical = true) - { - if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { - if ($identical) { - return $this->_attributes[$name] !== $this->_oldAttributes[$name]; - } - - return $this->_attributes[$name] != $this->_oldAttributes[$name]; - } - - return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]); - } - - /** - * Returns the attribute values that have been modified since they are loaded or saved most recently. - * - * The comparison of new and old values is made for identical values using `===`. - * - * @param string[]|null $names the names of the attributes whose values may be returned if they are - * changed recently. If null, [[attributes()]] will be used. - * @return array the changed attribute values (name-value pairs) - */ - public function getDirtyAttributes($names = null) - { - if ($names === null) { - $names = $this->attributes(); - } - $names = array_flip($names); - $attributes = []; - if ($this->_oldAttributes === null) { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name])) { - $attributes[$name] = $value; - } - } - } else { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { - $attributes[$name] = $value; - } - } - } - - return $attributes; - } - - /** - * Saves the current record. - * - * This method will call [[insert()]] when [[isNewRecord]] is `true`, or [[update()]] - * when [[isNewRecord]] is `false`. - * - * For example, to save a customer record: - * - * ```php - * $customer = new Customer; // or $customer = Customer::findOne($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->save(); - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return bool whether the saving succeeded (i.e. no validation errors occurred). - */ - public function save($runValidation = true, $attributeNames = null) - { - if ($this->getIsNewRecord()) { - return $this->insert($runValidation, $attributeNames); - } - - return $this->update($runValidation, $attributeNames) !== false; - } - - /** - * Saves the changes to this active record into the associated database table. - * - * This method performs the following steps in order: - * - * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]] - * returns `false`, the rest of the steps will be skipped; - * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation - * failed, the rest of the steps will be skipped; - * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, - * the rest of the steps will be skipped; - * 4. save the record into database. If this fails, it will skip the rest of the steps; - * 5. call [[afterSave()]]; - * - * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], - * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]] - * will be raised by the corresponding methods. - * - * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. - * - * For example, to update a customer record: - * - * ```php - * $customer = Customer::findOne($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->update(); - * ``` - * - * Note that it is possible the update does not affect any row in the table. - * In this case, this method will return 0. For this reason, you should use the following - * code to check if update() is successful or not: - * - * ```php - * if ($customer->update() !== false) { - * // update successful - * } else { - * // update failed - * } - * ``` - * - * @param bool $runValidation whether to perform validation (calling [[validate()]]) - * before saving the record. Defaults to `true`. If the validation fails, the record - * will not be saved to the database and this method will return `false`. - * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return int|false the number of rows affected, or `false` if validation fails - * or [[beforeSave()]] stops the updating process. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being updated is outdated. - * @throws Exception in case update failed. - */ - public function update($runValidation = true, $attributeNames = null) - { - if ($runValidation && !$this->validate($attributeNames)) { - return false; - } - - return $this->updateInternal($attributeNames); - } - - /** - * Updates the specified attributes. - * - * This method is a shortcut to [[update()]] when data validation is not needed - * and only a small set attributes need to be updated. - * - * You may specify the attributes to be updated as name list or name-value pairs. - * If the latter, the corresponding attribute values will be modified accordingly. - * The method will then save the specified attributes into database. - * - * Note that this method will **not** perform data validation and will **not** trigger events. - * - * @param array $attributes the attributes (names or name-value pairs) to be updated - * @return int the number of rows affected. - */ - public function updateAttributes($attributes) - { - $attrs = []; - foreach ($attributes as $name => $value) { - if (is_int($name)) { - $attrs[] = $value; - } else { - $this->$name = $value; - $attrs[] = $name; - } - } - - $values = $this->getDirtyAttributes($attrs); - if (empty($values) || $this->getIsNewRecord()) { - return 0; - } - - $rows = static::updateAll($values, $this->getOldPrimaryKey(true)); - - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - - return $rows; - } - - /** - * @see update() - * @param array $attributes attributes to update - * @return int|false the number of rows affected, or false if [[beforeSave()]] stops the updating process. - * @throws StaleObjectException - */ - protected function updateInternal($attributes = null) - { - if (!$this->beforeSave(false)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - $this->afterSave(false, $values); - return 0; - } - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - $values[$lock] = $this->$lock + 1; - $condition[$lock] = $this->$lock; - } - // We do not check the return value of updateAll() because it's possible - // that the UPDATE statement doesn't change anything and thus returns 0. - $rows = static::updateAll($values, $condition); - - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being updated is outdated.'); - } - - if (isset($values[$lock])) { - $this->$lock = $values[$lock]; - } - - $changedAttributes = []; - foreach ($values as $name => $value) { - $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - $this->_oldAttributes[$name] = $value; - } - $this->afterSave(false, $changedAttributes); - - return $rows; - } - - /** - * Updates one or several counter columns for the current AR object. - * Note that this method differs from [[updateAllCounters()]] in that it only - * saves counters for the current AR object. - * - * An example usage is as follows: - * - * ```php - * $post = Post::findOne($id); - * $post->updateCounters(['view_count' => 1]); - * ``` - * - * @param array $counters the counters to be updated (attribute name => increment value) - * Use negative values if you want to decrement the counters. - * @return bool whether the saving is successful - * @see updateAllCounters() - */ - public function updateCounters($counters) - { - if (static::updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { - foreach ($counters as $name => $value) { - if (!isset($this->_attributes[$name])) { - $this->_attributes[$name] = $value; - } else { - $this->_attributes[$name] += $value; - } - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - - return true; - } - - return false; - } - - /** - * Deletes the table row corresponding to this active record. - * - * This method performs the following steps in order: - * - * 1. call [[beforeDelete()]]. If the method returns `false`, it will skip the - * rest of the steps; - * 2. delete the record from the database; - * 3. call [[afterDelete()]]. - * - * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] - * will be raised by the corresponding methods. - * - * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason. - * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. - * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data - * being deleted is outdated. - * @throws Exception in case delete failed. - */ - public function delete() - { - $result = false; - if ($this->beforeDelete()) { - // we do not check the return value of deleteAll() because it's possible - // the record is already deleted in the database and thus the method will return 0 - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - $condition[$lock] = $this->$lock; - } - $result = static::deleteAll($condition); - if ($lock !== null && !$result) { - throw new StaleObjectException('The object being deleted is outdated.'); - } - $this->_oldAttributes = null; - $this->afterDelete(); - } - - return $result; - } - - /** - * Returns a value indicating whether the current record is new. - * @return bool whether the record is new and should be inserted when calling [[save()]]. - */ - public function getIsNewRecord() - { - return $this->_oldAttributes === null; - } - - /** - * Sets the value indicating whether the record is new. - * @param bool $value whether the record is new and should be inserted when calling [[save()]]. - * @see getIsNewRecord() - */ - public function setIsNewRecord($value) - { - $this->_oldAttributes = $value ? null : $this->_attributes; - } - - /** - * Initializes the object. - * This method is called at the end of the constructor. - * The default implementation will trigger an [[EVENT_INIT]] event. - */ - public function init() - { - parent::init(); - $this->trigger(self::EVENT_INIT); - } - - /** - * This method is called when the AR object is created and populated with the query result. - * The default implementation will trigger an [[EVENT_AFTER_FIND]] event. - * When overriding this method, make sure you call the parent implementation to ensure the - * event is triggered. - */ - public function afterFind() - { - $this->trigger(self::EVENT_AFTER_FIND); - } - - /** - * This method is called at the beginning of inserting or updating a record. - * - * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is `true`, - * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is `false`. - * When overriding this method, make sure you call the parent implementation like the following: - * - * ```php - * public function beforeSave($insert) - * { - * if (!parent::beforeSave($insert)) { - * return false; - * } - * - * // ...custom code here... - * return true; - * } - * ``` - * - * @param bool $insert whether this method called while inserting a record. - * If `false`, it means the method is called while updating a record. - * @return bool whether the insertion or updating should continue. - * If `false`, the insertion or updating will be cancelled. - */ - public function beforeSave($insert) - { - $event = new ModelEvent(); - $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); - - return $event->isValid; - } - - /** - * This method is called at the end of inserting or updating a record. - * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is `true`, - * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is `false`. The event class used is [[AfterSaveEvent]]. - * When overriding this method, make sure you call the parent implementation so that - * the event is triggered. - * @param bool $insert whether this method called while inserting a record. - * If `false`, it means the method is called while updating a record. - * @param array $changedAttributes The old values of attributes that had changed and were saved. - * You can use this parameter to take action based on the changes made for example send an email - * when the password had changed or implement audit trail that tracks all the changes. - * `$changedAttributes` gives you the old attribute values while the active record (`$this`) has - * already the new, updated values. - * - * Note that no automatic type conversion performed by default. You may use - * [[\yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute typecasting. - * See http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#attributes-typecasting. - */ - public function afterSave($insert, $changedAttributes) - { - $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([ - 'changedAttributes' => $changedAttributes, - ])); - } - - /** - * This method is invoked before deleting a record. - * - * The default implementation raises the [[EVENT_BEFORE_DELETE]] event. - * When overriding this method, make sure you call the parent implementation like the following: - * - * ```php - * public function beforeDelete() - * { - * if (!parent::beforeDelete()) { - * return false; - * } - * - * // ...custom code here... - * return true; - * } - * ``` - * - * @return bool whether the record should be deleted. Defaults to `true`. - */ - public function beforeDelete() - { - $event = new ModelEvent(); - $this->trigger(self::EVENT_BEFORE_DELETE, $event); - - return $event->isValid; - } - - /** - * This method is invoked after deleting a record. - * The default implementation raises the [[EVENT_AFTER_DELETE]] event. - * You may override this method to do postprocessing after the record is deleted. - * Make sure you call the parent implementation so that the event is raised properly. - */ - public function afterDelete() - { - $this->trigger(self::EVENT_AFTER_DELETE); - } - - /** - * Repopulates this active record with the latest data. - * - * If the refresh is successful, an [[EVENT_AFTER_REFRESH]] event will be triggered. - * This event is available since version 2.0.8. - * - * @return bool whether the row still exists in the database. If `true`, the latest data - * will be populated to this active record. Otherwise, this record will remain unchanged. - */ - public function refresh() - { - /* @var $record BaseActiveRecord */ - $record = static::findOne($this->getPrimaryKey(true)); - return $this->refreshInternal($record); - } - - /** - * Repopulates this active record with the latest data from a newly fetched instance. - * @param BaseActiveRecord $record the record to take attributes from. - * @return bool whether refresh was successful. - * @see refresh() - * @since 2.0.13 - */ - protected function refreshInternal($record) - { - if ($record === null) { - return false; - } - foreach ($this->attributes() as $name) { - $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null; - } - $this->_oldAttributes = $record->_oldAttributes; - $this->_related = []; - $this->_relationsDependencies = []; - $this->afterRefresh(); - - return true; - } - - /** - * This method is called when the AR object is refreshed. - * The default implementation will trigger an [[EVENT_AFTER_REFRESH]] event. - * When overriding this method, make sure you call the parent implementation to ensure the - * event is triggered. - * @since 2.0.8 - */ - public function afterRefresh() - { - $this->trigger(self::EVENT_AFTER_REFRESH); - } - - /** - * Returns a value indicating whether the given active record is the same as the current one. - * The comparison is made by comparing the table names and the primary key values of the two active records. - * If one of the records [[isNewRecord|is new]] they are also considered not equal. - * @param ActiveRecordInterface $record record to compare to - * @return bool whether the two active records refer to the same row in the same database table. - */ - public function equals($record) - { - if ($this->getIsNewRecord() || $record->getIsNewRecord()) { - return false; - } - - return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey(); - } - - /** - * Returns the primary key value(s). - * @param bool $asArray whether to return the primary key value as an array. If `true`, - * the return value will be an array with column names as keys and column values as values. - * Note that for composite primary keys, an array will always be returned regardless of this parameter value. - * @property mixed The primary key value. An array (column name => column value) is returned if - * the primary key is composite. A string is returned otherwise (null will be returned if - * the key value is null). - * @return mixed the primary key value. An array (column name => column value) is returned if the primary key - * is composite or `$asArray` is `true`. A string is returned otherwise (null will be returned if - * the key value is null). - */ - public function getPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (!$asArray && count($keys) === 1) { - return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; - } - - $values = []; - foreach ($keys as $name) { - $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - - return $values; - } - - /** - * Returns the old primary key value(s). - * This refers to the primary key value that is populated into the record - * after executing a find method (e.g. find(), findOne()). - * The value remains unchanged even if the primary key attribute is manually assigned with a different value. - * @param bool $asArray whether to return the primary key value as an array. If `true`, - * the return value will be an array with column name as key and column value as value. - * If this is `false` (default), a scalar value will be returned for non-composite primary key. - * @property mixed The old primary key value. An array (column name => column value) is - * returned if the primary key is composite. A string is returned otherwise (null will be - * returned if the key value is null). - * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key - * is composite or `$asArray` is `true`. A string is returned otherwise (null will be returned if - * the key value is null). - * @throws Exception if the AR model does not have a primary key - */ - public function getOldPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (empty($keys)) { - throw new Exception(get_class($this) . ' does not have a primary key. You should either define a primary key for the corresponding table or override the primaryKey() method.'); - } - if (!$asArray && count($keys) === 1) { - return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; - } - - $values = []; - foreach ($keys as $name) { - $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - } - - return $values; - } - - /** - * Populates an active record object using a row of data from the database/storage. - * - * This is an internal method meant to be called to create active record objects after - * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate - * the query results into active records. - * - * When calling this method manually you should call [[afterFind()]] on the created - * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. - * - * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance - * created by [[instantiate()]] beforehand. - * @param array $row attribute values (name => value) - */ - public static function populateRecord($record, $row) - { - $columns = array_flip($record->attributes()); - foreach ($row as $name => $value) { - if (isset($columns[$name])) { - $record->_attributes[$name] = $value; - } elseif ($record->canSetProperty($name)) { - $record->$name = $value; - } - } - $record->_oldAttributes = $record->_attributes; - $record->_related = []; - $record->_relationsDependencies = []; - } - - /** - * Creates an active record instance. - * - * This method is called together with [[populateRecord()]] by [[ActiveQuery]]. - * It is not meant to be used for creating new records directly. - * - * You may override this method if the instance being created - * depends on the row data to be populated into the record. - * For example, by creating a record based on the value of a column, - * you may implement the so-called single-table inheritance mapping. - * @param array $row row data to be populated into the record. - * @return static the newly created active record - */ - public static function instantiate($row) - { - return new static(); - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the interface [[\ArrayAccess]]. - * @param mixed $offset the offset to check on - * @return bool whether there is an element at the specified offset. - */ - public function offsetExists($offset) - { - return $this->__isset($offset); - } - - /** - * Returns the relation object with the specified name. - * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object. - * It can be declared in either the Active Record class itself or one of its behaviors. - * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive). - * @param bool $throwException whether to throw exception if the relation does not exist. - * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist - * and `$throwException` is `false`, `null` will be returned. - * @throws InvalidArgumentException if the named relation does not exist. - */ - public function getRelation($name, $throwException = true) - { - $getter = 'get' . $name; - try { - // the relation could be defined in a behavior - $relation = $this->$getter(); - } catch (UnknownMethodException $e) { - if ($throwException) { - throw new InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); - } - - return null; - } - if (!$relation instanceof ActiveQueryInterface) { - if ($throwException) { - throw new InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".'); - } - - return null; - } - - if (method_exists($this, $getter)) { - // relation name is case sensitive, trying to validate it when the relation is defined within this class - $method = new \ReflectionMethod($this, $getter); - $realName = lcfirst(substr($method->getName(), 3)); - if ($realName !== $name) { - if ($throwException) { - throw new InvalidArgumentException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\"."); - } - - return null; - } - } - - return $relation; - } - - /** - * Establishes the relationship between two models. - * - * The relationship is established by setting the foreign key value(s) in one model - * to be the corresponding primary key value(s) in the other model. - * The model with the foreign key will be saved into database without performing validation. - * - * If the relationship involves a junction table, a new row will be inserted into the - * junction table which contains the primary key values from both models. - * - * Note that this method requires that the primary key value is not null. - * - * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method. - * @param ActiveRecordInterface $model the model to be linked with the current one. - * @param array $extraColumns additional column values to be saved into the junction table. - * This parameter is only meaningful for a relationship involving a junction table - * (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].) - * @throws InvalidCallException if the method is unable to link two models. - */ - public function link($name, $model, $extraColumns = []) - { - $relation = $this->getRelation($name); - - if ($relation->via !== null) { - if ($this->getIsNewRecord() || $model->getIsNewRecord()) { - throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.'); - } - if (is_array($relation->via)) { - /* @var $viaRelation ActiveQuery */ - list($viaName, $viaRelation) = $relation->via; - $viaClass = $viaRelation->modelClass; - // unset $viaName so that it can be reloaded to reflect the change - unset($this->_related[$viaName]); - } else { - $viaRelation = $relation->via; - $viaTable = reset($relation->via->from); - } - $columns = []; - foreach ($viaRelation->link as $a => $b) { - $columns[$a] = $this->$b; - } - foreach ($relation->link as $a => $b) { - $columns[$b] = $model->$a; - } - foreach ($extraColumns as $k => $v) { - $columns[$k] = $v; - } - if (is_array($relation->via)) { - /* @var $viaClass ActiveRecordInterface */ - /* @var $record ActiveRecordInterface */ - $record = Yii::createObject($viaClass); - foreach ($columns as $column => $value) { - $record->$column = $value; - } - $record->insert(false); - } else { - /* @var $viaTable string */ - static::getDb()->createCommand() - ->insert($viaTable, $columns)->execute(); - } - } else { - $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = static::isPrimaryKey(array_values($relation->link)); - if ($p1 && $p2) { - if ($this->getIsNewRecord() && $model->getIsNewRecord()) { - throw new InvalidCallException('Unable to link models: at most one model can be newly created.'); - } elseif ($this->getIsNewRecord()) { - $this->bindModels(array_flip($relation->link), $this, $model); - } else { - $this->bindModels($relation->link, $model, $this); - } - } elseif ($p1) { - $this->bindModels(array_flip($relation->link), $this, $model); - } elseif ($p2) { - $this->bindModels($relation->link, $model, $this); - } else { - throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.'); - } - } - - // update lazily loaded related objects - if (!$relation->multiple) { - $this->_related[$name] = $model; - } elseif (isset($this->_related[$name])) { - if ($relation->indexBy !== null) { - if ($relation->indexBy instanceof \Closure) { - $index = call_user_func($relation->indexBy, $model); - } else { - $index = $model->{$relation->indexBy}; - } - $this->_related[$name][$index] = $model; - } else { - $this->_related[$name][] = $model; - } - } - } - - /** - * Destroys the relationship between two models. - * - * The model with the foreign key of the relationship will be deleted if `$delete` is `true`. - * Otherwise, the foreign key will be set `null` and the model will be saved without validation. - * - * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method. - * @param ActiveRecordInterface $model the model to be unlinked from the current one. - * You have to make sure that the model is really related with the current model as this method - * does not check this. - * @param bool $delete whether to delete the model that contains the foreign key. - * If `false`, the model's foreign key will be set `null` and saved. - * If `true`, the model containing the foreign key will be deleted. - * @throws InvalidCallException if the models cannot be unlinked - */ - public function unlink($name, $model, $delete = false) - { - $relation = $this->getRelation($name); - - if ($relation->via !== null) { - if (is_array($relation->via)) { - /* @var $viaRelation ActiveQuery */ - list($viaName, $viaRelation) = $relation->via; - $viaClass = $viaRelation->modelClass; - unset($this->_related[$viaName]); - } else { - $viaRelation = $relation->via; - $viaTable = reset($relation->via->from); - } - $columns = []; - foreach ($viaRelation->link as $a => $b) { - $columns[$a] = $this->$b; - } - foreach ($relation->link as $a => $b) { - $columns[$b] = $model->$a; - } - $nulls = []; - foreach (array_keys($columns) as $a) { - $nulls[$a] = null; - } - if (is_array($relation->via)) { - /* @var $viaClass ActiveRecordInterface */ - if ($delete) { - $viaClass::deleteAll($columns); - } else { - $viaClass::updateAll($nulls, $columns); - } - } else { - /* @var $viaTable string */ - /* @var $command Command */ - $command = static::getDb()->createCommand(); - if ($delete) { - $command->delete($viaTable, $columns)->execute(); - } else { - $command->update($viaTable, $nulls, $columns)->execute(); - } - } - } else { - $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = static::isPrimaryKey(array_values($relation->link)); - if ($p2) { - if ($delete) { - $model->delete(); - } else { - foreach ($relation->link as $a => $b) { - $model->$a = null; - } - $model->save(false); - } - } elseif ($p1) { - foreach ($relation->link as $a => $b) { - if (is_array($this->$b)) { // relation via array valued attribute - if (($key = array_search($model->$a, $this->$b, false)) !== false) { - $values = $this->$b; - unset($values[$key]); - $this->$b = array_values($values); - } - } else { - $this->$b = null; - } - } - $delete ? $this->delete() : $this->save(false); - } else { - throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.'); - } - } - - if (!$relation->multiple) { - unset($this->_related[$name]); - } elseif (isset($this->_related[$name])) { - /* @var $b ActiveRecordInterface */ - foreach ($this->_related[$name] as $a => $b) { - if ($model->getPrimaryKey() === $b->getPrimaryKey()) { - unset($this->_related[$name][$a]); - } - } - } - } - - /** - * Destroys the relationship in current model. - * - * The model with the foreign key of the relationship will be deleted if `$delete` is `true`. - * Otherwise, the foreign key will be set `null` and the model will be saved without validation. - * - * Note that to destroy the relationship without removing records make sure your keys can be set to null - * - * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method. - * @param bool $delete whether to delete the model that contains the foreign key. - * - * Note that the deletion will be performed using [[deleteAll()]], which will not trigger any events on the related models. - * If you need [[EVENT_BEFORE_DELETE]] or [[EVENT_AFTER_DELETE]] to be triggered, you need to [[find()|find]] the models first - * and then call [[delete()]] on each of them. - */ - public function unlinkAll($name, $delete = false) - { - $relation = $this->getRelation($name); - - if ($relation->via !== null) { - if (is_array($relation->via)) { - /* @var $viaRelation ActiveQuery */ - list($viaName, $viaRelation) = $relation->via; - $viaClass = $viaRelation->modelClass; - unset($this->_related[$viaName]); - } else { - $viaRelation = $relation->via; - $viaTable = reset($relation->via->from); - } - $condition = []; - $nulls = []; - foreach ($viaRelation->link as $a => $b) { - $nulls[$a] = null; - $condition[$a] = $this->$b; - } - if (!empty($viaRelation->where)) { - $condition = ['and', $condition, $viaRelation->where]; - } - if (!empty($viaRelation->on)) { - $condition = ['and', $condition, $viaRelation->on]; - } - if (is_array($relation->via)) { - /* @var $viaClass ActiveRecordInterface */ - if ($delete) { - $viaClass::deleteAll($condition); - } else { - $viaClass::updateAll($nulls, $condition); - } - } else { - /* @var $viaTable string */ - /* @var $command Command */ - $command = static::getDb()->createCommand(); - if ($delete) { - $command->delete($viaTable, $condition)->execute(); - } else { - $command->update($viaTable, $nulls, $condition)->execute(); - } - } - } else { - /* @var $relatedModel ActiveRecordInterface */ - $relatedModel = $relation->modelClass; - if (!$delete && count($relation->link) === 1 && is_array($this->{$b = reset($relation->link)})) { - // relation via array valued attribute - $this->$b = []; - $this->save(false); - } else { - $nulls = []; - $condition = []; - foreach ($relation->link as $a => $b) { - $nulls[$a] = null; - $condition[$a] = $this->$b; - } - if (!empty($relation->where)) { - $condition = ['and', $condition, $relation->where]; - } - if (!empty($relation->on)) { - $condition = ['and', $condition, $relation->on]; - } - if ($delete) { - $relatedModel::deleteAll($condition); - } else { - $relatedModel::updateAll($nulls, $condition); - } - } - } - - unset($this->_related[$name]); - } - - /** - * @param array $link - * @param ActiveRecordInterface $foreignModel - * @param ActiveRecordInterface $primaryModel - * @throws InvalidCallException - */ - private function bindModels($link, $foreignModel, $primaryModel) - { - foreach ($link as $fk => $pk) { - $value = $primaryModel->$pk; - if ($value === null) { - throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.'); - } - if (is_array($foreignModel->$fk)) { // relation via array valued attribute - $foreignModel->{$fk}[] = $value; - } else { - $foreignModel->{$fk} = $value; - } - } - $foreignModel->save(false); - } - - /** - * Returns a value indicating whether the given set of attributes represents the primary key for this model. - * @param array $keys the set of attributes to check - * @return bool whether the given set of attributes represents the primary key for this model - */ - public static function isPrimaryKey($keys) - { - $pks = static::primaryKey(); - if (count($keys) === count($pks)) { - return count(array_intersect($keys, $pks)) === count($pks); - } - - return false; - } - - /** - * Returns the text label for the specified attribute. - * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model. - * @param string $attribute the attribute name - * @return string the attribute label - * @see generateAttributeLabel() - * @see attributeLabels() - */ - public function getAttributeLabel($attribute) - { - $labels = $this->attributeLabels(); - if (isset($labels[$attribute])) { - return $labels[$attribute]; - } elseif (strpos($attribute, '.')) { - $attributeParts = explode('.', $attribute); - $neededAttribute = array_pop($attributeParts); - - $relatedModel = $this; - foreach ($attributeParts as $relationName) { - if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) { - $relatedModel = $relatedModel->$relationName; - } else { - try { - $relation = $relatedModel->getRelation($relationName); - } catch (InvalidParamException $e) { - return $this->generateAttributeLabel($attribute); - } - /* @var $modelClass ActiveRecordInterface */ - $modelClass = $relation->modelClass; - $relatedModel = $modelClass::instance(); - } - } - - $labels = $relatedModel->attributeLabels(); - if (isset($labels[$neededAttribute])) { - return $labels[$neededAttribute]; - } - } - - return $this->generateAttributeLabel($attribute); - } - - /** - * Returns the text hint for the specified attribute. - * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model. - * @param string $attribute the attribute name - * @return string the attribute hint - * @see attributeHints() - * @since 2.0.4 - */ - public function getAttributeHint($attribute) - { - $hints = $this->attributeHints(); - if (isset($hints[$attribute])) { - return $hints[$attribute]; - } elseif (strpos($attribute, '.')) { - $attributeParts = explode('.', $attribute); - $neededAttribute = array_pop($attributeParts); - - $relatedModel = $this; - foreach ($attributeParts as $relationName) { - if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) { - $relatedModel = $relatedModel->$relationName; - } else { - try { - $relation = $relatedModel->getRelation($relationName); - } catch (InvalidParamException $e) { - return ''; - } - /* @var $modelClass ActiveRecordInterface */ - $modelClass = $relation->modelClass; - $relatedModel = $modelClass::instance(); - } - } - - $hints = $relatedModel->attributeHints(); - if (isset($hints[$neededAttribute])) { - return $hints[$neededAttribute]; - } - } - - return ''; - } - - /** - * {@inheritdoc} - * - * The default implementation returns the names of the columns whose values have been populated into this record. - */ - public function fields() - { - $fields = array_keys($this->_attributes); - - return array_combine($fields, $fields); - } - - /** - * {@inheritdoc} - * - * The default implementation returns the names of the relations that have been populated into this record. - */ - public function extraFields() - { - $fields = array_keys($this->getRelatedRecords()); - - return array_combine($fields, $fields); - } - - /** - * Sets the element value at the specified offset to null. - * This method is required by the SPL interface [[\ArrayAccess]]. - * It is implicitly called when you use something like `unset($model[$offset])`. - * @param mixed $offset the offset to unset element - */ - public function offsetUnset($offset) - { - if (property_exists($this, $offset)) { - $this->$offset = null; - } else { - unset($this->$offset); - } - } - - /** - * Resets dependent related models checking if their links contain specific attribute. - * @param string $attribute The changed attribute name. - */ - private function resetDependentRelations($attribute) - { - foreach ($this->_relationsDependencies[$attribute] as $relation) { - unset($this->_related[$relation]); - } - unset($this->_relationsDependencies[$attribute]); - } - - /** - * Sets relation dependencies for a property - * @param string $name property name - * @param ActiveQueryInterface $relation relation instance - * @param string|null $viaRelationName intermediate relation - */ - private function setRelationDependencies($name, $relation, $viaRelationName = null) - { - if (empty($relation->via) && $relation->link) { - foreach ($relation->link as $attribute) { - $this->_relationsDependencies[$attribute][$name] = $name; - if ($viaRelationName !== null) { - $this->_relationsDependencies[$attribute][] = $viaRelationName; - } - } - } elseif ($relation->via instanceof ActiveQueryInterface) { - $this->setRelationDependencies($name, $relation->via); - } elseif (is_array($relation->via)) { - list($viaRelationName, $viaQuery) = $relation->via; - $this->setRelationDependencies($name, $viaQuery, $viaRelationName); - } - } -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/BaseObject.php.test b/src/Tests/Benchmarks/fixtures/yii/BaseObject.php.test deleted file mode 100644 index 7886ff0f8..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/BaseObject.php.test +++ /dev/null @@ -1,295 +0,0 @@ -_label; - * } - * - * public function setLabel($value) - * { - * $this->_label = $value; - * } - * ``` - * - * Property names are *case-insensitive*. - * - * A property can be accessed like a member variable of an object. Reading or writing a property will cause the invocation - * of the corresponding getter or setter method. For example, - * - * ```php - * // equivalent to $label = $object->getLabel(); - * $label = $object->label; - * // equivalent to $object->setLabel('abc'); - * $object->label = 'abc'; - * ``` - * - * If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying - * to modify the property value will cause an exception. - * - * One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property. - * - * Besides the property feature, BaseObject also introduces an important object initialization life cycle. In particular, - * creating an new instance of BaseObject or its derived class will involve the following life cycles sequentially: - * - * 1. the class constructor is invoked; - * 2. object properties are initialized according to the given configuration; - * 3. the `init()` method is invoked. - * - * In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that - * you perform object initialization in the `init()` method because at that stage, the object configuration - * is already applied. - * - * In order to ensure the above life cycles, if a child class of BaseObject needs to override the constructor, - * it should be done like the following: - * - * ```php - * public function __construct($param1, $param2, ..., $config = []) - * { - * ... - * parent::__construct($config); - * } - * ``` - * - * That is, a `$config` parameter (defaults to `[]`) should be declared as the last parameter - * of the constructor, and the parent implementation should be called at the end of the constructor. - * - * @author Qiang Xue - * @since 2.0.13 - */ -class BaseObject implements Configurable -{ - /** - * Returns the fully qualified name of this class. - * @return string the fully qualified name of this class. - * @deprecated since 2.0.14. On PHP >=5.5, use `::class` instead. - */ - public static function className() - { - return get_called_class(); - } - - /** - * Constructor. - * - * The default implementation does two things: - * - * - Initializes the object with the given configuration `$config`. - * - Call [[init()]]. - * - * If this method is overridden in a child class, it is recommended that - * - * - the last parameter of the constructor is a configuration array, like `$config` here. - * - call the parent implementation at the end of the constructor. - * - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($config = []) - { - if (!empty($config)) { - Yii::configure($this, $config); - } - $this->init(); - } - - /** - * Initializes the object. - * This method is invoked at the end of the constructor after the object is initialized with the - * given configuration. - */ - public function init() - { - } - - /** - * Returns the value of an object property. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `$value = $object->property;`. - * @param string $name the property name - * @return mixed the property value - * @throws UnknownPropertyException if the property is not defined - * @throws InvalidCallException if the property is write-only - * @see __set() - */ - public function __get($name) - { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - return $this->$getter(); - } elseif (method_exists($this, 'set' . $name)) { - throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); - } - - throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); - } - - /** - * Sets value of an object property. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `$object->property = $value;`. - * @param string $name the property name or the event name - * @param mixed $value the property value - * @throws UnknownPropertyException if the property is not defined - * @throws InvalidCallException if the property is read-only - * @see __get() - */ - public function __set($name, $value) - { - $setter = 'set' . $name; - if (method_exists($this, $setter)) { - $this->$setter($value); - } elseif (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); - } else { - throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); - } - } - - /** - * Checks if a property is set, i.e. defined and not null. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `isset($object->property)`. - * - * Note that if the property is not defined, false will be returned. - * @param string $name the property name or the event name - * @return bool whether the named property is set (not null). - * @see https://secure.php.net/manual/en/function.isset.php - */ - public function __isset($name) - { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - return $this->$getter() !== null; - } - - return false; - } - - /** - * Sets an object property to null. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `unset($object->property)`. - * - * Note that if the property is not defined, this method will do nothing. - * If the property is read-only, it will throw an exception. - * @param string $name the property name - * @throws InvalidCallException if the property is read only. - * @see https://secure.php.net/manual/en/function.unset.php - */ - public function __unset($name) - { - $setter = 'set' . $name; - if (method_exists($this, $setter)) { - $this->$setter(null); - } elseif (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name); - } - } - - /** - * Calls the named method which is not a class method. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when an unknown method is being invoked. - * @param string $name the method name - * @param array $params method parameters - * @throws UnknownMethodException when calling unknown method - * @return mixed the method return value - */ - public function __call($name, $params) - { - throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); - } - - /** - * Returns a value indicating whether a property is defined. - * - * A property is defined if: - * - * - the class has a getter or setter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @return bool whether the property is defined - * @see canGetProperty() - * @see canSetProperty() - */ - public function hasProperty($name, $checkVars = true) - { - return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false); - } - - /** - * Returns a value indicating whether a property can be read. - * - * A property is readable if: - * - * - the class has a getter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @return bool whether the property can be read - * @see canSetProperty() - */ - public function canGetProperty($name, $checkVars = true) - { - return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name); - } - - /** - * Returns a value indicating whether a property can be set. - * - * A property is writable if: - * - * - the class has a setter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @return bool whether the property can be written - * @see canGetProperty() - */ - public function canSetProperty($name, $checkVars = true) - { - return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name); - } - - /** - * Returns a value indicating whether a method is defined. - * - * The default implementation is a call to php function `method_exists()`. - * You may override this method when you implemented the php magic method `__call()`. - * @param string $name the method name - * @return bool whether the method is defined - */ - public function hasMethod($name) - { - return method_exists($this, $name); - } -} diff --git a/src/Tests/Benchmarks/fixtures/yii/Component.php.test b/src/Tests/Benchmarks/fixtures/yii/Component.php.test deleted file mode 100644 index 0d4215f38..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/Component.php.test +++ /dev/null @@ -1,766 +0,0 @@ -on('update', function ($event) { - * // send email notification - * }); - * ``` - * - * In the above, an anonymous function is attached to the "update" event of the post. You may attach - * the following types of event handlers: - * - * - anonymous function: `function ($event) { ... }` - * - object method: `[$object, 'handleAdd']` - * - static class method: `['Page', 'handleAdd']` - * - global function: `'handleAdd'` - * - * The signature of an event handler should be like the following: - * - * ```php - * function foo($event) - * ``` - * - * where `$event` is an [[Event]] object which includes parameters associated with the event. - * - * You can also attach a handler to an event when configuring a component with a configuration array. - * The syntax is like the following: - * - * ```php - * [ - * 'on add' => function ($event) { ... } - * ] - * ``` - * - * where `on add` stands for attaching an event to the `add` event. - * - * Sometimes, you may want to associate extra data with an event handler when you attach it to an event - * and then access it when the handler is invoked. You may do so by - * - * ```php - * $post->on('update', function ($event) { - * // the data can be accessed via $event->data - * }, $data); - * ``` - * - * A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple - * behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the - * component directly, as if the component owns those properties and methods. - * - * To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]]. Behaviors - * declared in [[behaviors()]] are automatically attached to the corresponding component. - * - * One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the - * following: - * - * ```php - * [ - * 'as tree' => [ - * 'class' => 'Tree', - * ], - * ] - * ``` - * - * where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]] - * to create the behavior object. - * - * For more details and usage information on Component, see the [guide article on components](guide:concept-components). - * - * @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only. - * - * @author Qiang Xue - * @since 2.0 - */ -class Component extends BaseObject -{ - /** - * @var array the attached event handlers (event name => handlers) - */ - private $_events = []; - /** - * @var array the event handlers attached for wildcard patterns (event name wildcard => handlers) - * @since 2.0.14 - */ - private $_eventWildcards = []; - /** - * @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized. - */ - private $_behaviors; - - - /** - * Returns the value of a component property. - * - * This method will check in the following order and act accordingly: - * - * - a property defined by a getter: return the getter result - * - a property of a behavior: return the behavior property value - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `$value = $component->property;`. - * @param string $name the property name - * @return mixed the property value or the value of a behavior's property - * @throws UnknownPropertyException if the property is not defined - * @throws InvalidCallException if the property is write-only. - * @see __set() - */ - public function __get($name) - { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - // read property, e.g. getName() - return $this->$getter(); - } - - // behavior property - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canGetProperty($name)) { - return $behavior->$name; - } - } - - if (method_exists($this, 'set' . $name)) { - throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); - } - - throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); - } - - /** - * Sets the value of a component property. - * - * This method will check in the following order and act accordingly: - * - * - a property defined by a setter: set the property value - * - an event in the format of "on xyz": attach the handler to the event "xyz" - * - a behavior in the format of "as xyz": attach the behavior named as "xyz" - * - a property of a behavior: set the behavior property value - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `$component->property = $value;`. - * @param string $name the property name or the event name - * @param mixed $value the property value - * @throws UnknownPropertyException if the property is not defined - * @throws InvalidCallException if the property is read-only. - * @see __get() - */ - public function __set($name, $value) - { - $setter = 'set' . $name; - if (method_exists($this, $setter)) { - // set property - $this->$setter($value); - - return; - } elseif (strncmp($name, 'on ', 3) === 0) { - // on event: attach event handler - $this->on(trim(substr($name, 3)), $value); - - return; - } elseif (strncmp($name, 'as ', 3) === 0) { - // as behavior: attach behavior - $name = trim(substr($name, 3)); - $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value)); - - return; - } - - // behavior property - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canSetProperty($name)) { - $behavior->$name = $value; - return; - } - } - - if (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); - } - - throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); - } - - /** - * Checks if a property is set, i.e. defined and not null. - * - * This method will check in the following order and act accordingly: - * - * - a property defined by a setter: return whether the property is set - * - a property of a behavior: return whether the property is set - * - return `false` for non existing properties - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `isset($component->property)`. - * @param string $name the property name or the event name - * @return bool whether the named property is set - * @see https://secure.php.net/manual/en/function.isset.php - */ - public function __isset($name) - { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - return $this->$getter() !== null; - } - - // behavior property - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canGetProperty($name)) { - return $behavior->$name !== null; - } - } - - return false; - } - - /** - * Sets a component property to be null. - * - * This method will check in the following order and act accordingly: - * - * - a property defined by a setter: set the property value to be null - * - a property of a behavior: set the property value to be null - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when executing `unset($component->property)`. - * @param string $name the property name - * @throws InvalidCallException if the property is read only. - * @see https://secure.php.net/manual/en/function.unset.php - */ - public function __unset($name) - { - $setter = 'set' . $name; - if (method_exists($this, $setter)) { - $this->$setter(null); - return; - } - - // behavior property - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canSetProperty($name)) { - $behavior->$name = null; - return; - } - } - - throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name); - } - - /** - * Calls the named method which is not a class method. - * - * This method will check if any attached behavior has - * the named method and will execute it if available. - * - * Do not call this method directly as it is a PHP magic method that - * will be implicitly called when an unknown method is being invoked. - * @param string $name the method name - * @param array $params method parameters - * @return mixed the method return value - * @throws UnknownMethodException when calling unknown method - */ - public function __call($name, $params) - { - $this->ensureBehaviors(); - foreach ($this->_behaviors as $object) { - if ($object->hasMethod($name)) { - return call_user_func_array([$object, $name], $params); - } - } - throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); - } - - /** - * This method is called after the object is created by cloning an existing one. - * It removes all behaviors because they are attached to the old object. - */ - public function __clone() - { - $this->_events = []; - $this->_eventWildcards = []; - $this->_behaviors = null; - } - - /** - * Returns a value indicating whether a property is defined for this component. - * - * A property is defined if: - * - * - the class has a getter or setter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - an attached behavior has a property of the given name (when `$checkBehaviors` is true). - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component - * @return bool whether the property is defined - * @see canGetProperty() - * @see canSetProperty() - */ - public function hasProperty($name, $checkVars = true, $checkBehaviors = true) - { - return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors); - } - - /** - * Returns a value indicating whether a property can be read. - * - * A property can be read if: - * - * - the class has a getter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true). - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component - * @return bool whether the property can be read - * @see canSetProperty() - */ - public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) - { - if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) { - return true; - } elseif ($checkBehaviors) { - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canGetProperty($name, $checkVars)) { - return true; - } - } - } - - return false; - } - - /** - * Returns a value indicating whether a property can be set. - * - * A property can be written if: - * - * - the class has a setter method associated with the specified name - * (in this case, property name is case-insensitive); - * - the class has a member variable with the specified name (when `$checkVars` is true); - * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true). - * - * @param string $name the property name - * @param bool $checkVars whether to treat member variables as properties - * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component - * @return bool whether the property can be written - * @see canGetProperty() - */ - public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) - { - if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) { - return true; - } elseif ($checkBehaviors) { - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->canSetProperty($name, $checkVars)) { - return true; - } - } - } - - return false; - } - - /** - * Returns a value indicating whether a method is defined. - * - * A method is defined if: - * - * - the class has a method with the specified name - * - an attached behavior has a method with the given name (when `$checkBehaviors` is true). - * - * @param string $name the property name - * @param bool $checkBehaviors whether to treat behaviors' methods as methods of this component - * @return bool whether the method is defined - */ - public function hasMethod($name, $checkBehaviors = true) - { - if (method_exists($this, $name)) { - return true; - } elseif ($checkBehaviors) { - $this->ensureBehaviors(); - foreach ($this->_behaviors as $behavior) { - if ($behavior->hasMethod($name)) { - return true; - } - } - } - - return false; - } - - /** - * Returns a list of behaviors that this component should behave as. - * - * Child classes may override this method to specify the behaviors they want to behave as. - * - * The return value of this method should be an array of behavior objects or configurations - * indexed by behavior names. A behavior configuration can be either a string specifying - * the behavior class or an array of the following structure: - * - * ```php - * 'behaviorName' => [ - * 'class' => 'BehaviorClass', - * 'property1' => 'value1', - * 'property2' => 'value2', - * ] - * ``` - * - * Note that a behavior class must extend from [[Behavior]]. Behaviors can be attached using a name or anonymously. - * When a name is used as the array key, using this name, the behavior can later be retrieved using [[getBehavior()]] - * or be detached using [[detachBehavior()]]. Anonymous behaviors can not be retrieved or detached. - * - * Behaviors declared in this method will be attached to the component automatically (on demand). - * - * @return array the behavior configurations. - */ - public function behaviors() - { - return []; - } - - /** - * Returns a value indicating whether there is any handler attached to the named event. - * @param string $name the event name - * @return bool whether there is any handler attached to the event. - */ - public function hasEventHandlers($name) - { - $this->ensureBehaviors(); - - foreach ($this->_eventWildcards as $wildcard => $handlers) { - if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) { - return true; - } - } - - return !empty($this->_events[$name]) || Event::hasHandlers($this, $name); - } - - /** - * Attaches an event handler to an event. - * - * The event handler must be a valid PHP callback. The following are - * some examples: - * - * ``` - * function ($event) { ... } // anonymous function - * [$object, 'handleClick'] // $object->handleClick() - * ['Page', 'handleClick'] // Page::handleClick() - * 'handleClick' // global function handleClick() - * ``` - * - * The event handler must be defined with the following signature, - * - * ``` - * function ($event) - * ``` - * - * where `$event` is an [[Event]] object which includes parameters associated with the event. - * - * Since 2.0.14 you can specify event name as a wildcard pattern: - * - * ```php - * $component->on('event.group.*', function ($event) { - * Yii::trace($event->name . ' is triggered.'); - * }); - * ``` - * - * @param string $name the event name - * @param callable $handler the event handler - * @param mixed $data the data to be passed to the event handler when the event is triggered. - * When the event handler is invoked, this data can be accessed via [[Event::data]]. - * @param bool $append whether to append new event handler to the end of the existing - * handler list. If false, the new handler will be inserted at the beginning of the existing - * handler list. - * @see off() - */ - public function on($name, $handler, $data = null, $append = true) - { - $this->ensureBehaviors(); - - if (strpos($name, '*') !== false) { - if ($append || empty($this->_eventWildcards[$name])) { - $this->_eventWildcards[$name][] = [$handler, $data]; - } else { - array_unshift($this->_eventWildcards[$name], [$handler, $data]); - } - return; - } - - if ($append || empty($this->_events[$name])) { - $this->_events[$name][] = [$handler, $data]; - } else { - array_unshift($this->_events[$name], [$handler, $data]); - } - } - - /** - * Detaches an existing event handler from this component. - * - * This method is the opposite of [[on()]]. - * - * Note: in case wildcard pattern is passed for event name, only the handlers registered with this - * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain. - * - * @param string $name event name - * @param callable $handler the event handler to be removed. - * If it is null, all handlers attached to the named event will be removed. - * @return bool if a handler is found and detached - * @see on() - */ - public function off($name, $handler = null) - { - $this->ensureBehaviors(); - if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) { - return false; - } - if ($handler === null) { - unset($this->_events[$name], $this->_eventWildcards[$name]); - return true; - } - - $removed = false; - // plain event names - if (isset($this->_events[$name])) { - foreach ($this->_events[$name] as $i => $event) { - if ($event[0] === $handler) { - unset($this->_events[$name][$i]); - $removed = true; - } - } - if ($removed) { - $this->_events[$name] = array_values($this->_events[$name]); - return $removed; - } - } - - // wildcard event names - if (isset($this->_eventWildcards[$name])) { - foreach ($this->_eventWildcards[$name] as $i => $event) { - if ($event[0] === $handler) { - unset($this->_eventWildcards[$name][$i]); - $removed = true; - } - } - if ($removed) { - $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]); - // remove empty wildcards to save future redundant regex checks: - if (empty($this->_eventWildcards[$name])) { - unset($this->_eventWildcards[$name]); - } - } - } - - return $removed; - } - - /** - * Triggers an event. - * This method represents the happening of an event. It invokes - * all attached handlers for the event including class-level handlers. - * @param string $name the event name - * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. - */ - public function trigger($name, Event $event = null) - { - $this->ensureBehaviors(); - - $eventHandlers = []; - foreach ($this->_eventWildcards as $wildcard => $handlers) { - if (StringHelper::matchWildcard($wildcard, $name)) { - $eventHandlers = array_merge($eventHandlers, $handlers); - } - } - - if (!empty($this->_events[$name])) { - $eventHandlers = array_merge($eventHandlers, $this->_events[$name]); - } - - if (!empty($eventHandlers)) { - if ($event === null) { - $event = new Event(); - } - if ($event->sender === null) { - $event->sender = $this; - } - $event->handled = false; - $event->name = $name; - foreach ($eventHandlers as $handler) { - $event->data = $handler[1]; - call_user_func($handler[0], $event); - // stop further handling if the event is handled - if ($event->handled) { - return; - } - } - } - - // invoke class-level attached handlers - Event::trigger($this, $name, $event); - } - - /** - * Returns the named behavior object. - * @param string $name the behavior name - * @return null|Behavior the behavior object, or null if the behavior does not exist - */ - public function getBehavior($name) - { - $this->ensureBehaviors(); - return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null; - } - - /** - * Returns all behaviors attached to this component. - * @return Behavior[] list of behaviors attached to this component - */ - public function getBehaviors() - { - $this->ensureBehaviors(); - return $this->_behaviors; - } - - /** - * Attaches a behavior to this component. - * This method will create the behavior object based on the given - * configuration. After that, the behavior object will be attached to - * this component by calling the [[Behavior::attach()]] method. - * @param string $name the name of the behavior. - * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following: - * - * - a [[Behavior]] object - * - a string specifying the behavior class - * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object. - * - * @return Behavior the behavior object - * @see detachBehavior() - */ - public function attachBehavior($name, $behavior) - { - $this->ensureBehaviors(); - return $this->attachBehaviorInternal($name, $behavior); - } - - /** - * Attaches a list of behaviors to the component. - * Each behavior is indexed by its name and should be a [[Behavior]] object, - * a string specifying the behavior class, or an configuration array for creating the behavior. - * @param array $behaviors list of behaviors to be attached to the component - * @see attachBehavior() - */ - public function attachBehaviors($behaviors) - { - $this->ensureBehaviors(); - foreach ($behaviors as $name => $behavior) { - $this->attachBehaviorInternal($name, $behavior); - } - } - - /** - * Detaches a behavior from the component. - * The behavior's [[Behavior::detach()]] method will be invoked. - * @param string $name the behavior's name. - * @return null|Behavior the detached behavior. Null if the behavior does not exist. - */ - public function detachBehavior($name) - { - $this->ensureBehaviors(); - if (isset($this->_behaviors[$name])) { - $behavior = $this->_behaviors[$name]; - unset($this->_behaviors[$name]); - $behavior->detach(); - return $behavior; - } - - return null; - } - - /** - * Detaches all behaviors from the component. - */ - public function detachBehaviors() - { - $this->ensureBehaviors(); - foreach ($this->_behaviors as $name => $behavior) { - $this->detachBehavior($name); - } - } - - /** - * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component. - */ - public function ensureBehaviors() - { - if ($this->_behaviors === null) { - $this->_behaviors = []; - foreach ($this->behaviors() as $name => $behavior) { - $this->attachBehaviorInternal($name, $behavior); - } - } - } - - /** - * Attaches a behavior to this component. - * @param string|int $name the name of the behavior. If this is an integer, it means the behavior - * is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name - * will be detached first. - * @param string|array|Behavior $behavior the behavior to be attached - * @return Behavior the attached behavior. - */ - private function attachBehaviorInternal($name, $behavior) - { - if (!($behavior instanceof Behavior)) { - $behavior = Yii::createObject($behavior); - } - if (is_int($name)) { - $behavior->attach($this); - $this->_behaviors[] = $behavior; - } else { - if (isset($this->_behaviors[$name])) { - $this->_behaviors[$name]->detach(); - } - $behavior->attach($this); - $this->_behaviors[$name] = $behavior; - } - - return $behavior; - } -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/Model.php.test b/src/Tests/Benchmarks/fixtures/yii/Model.php.test deleted file mode 100644 index 701659883..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/Model.php.test +++ /dev/null @@ -1,1052 +0,0 @@ - value). - * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The - * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only. - * @property array $firstErrors The first errors. The array keys are the attribute names, and the array values - * are the corresponding error messages. An empty array will be returned if there is no error. This property is - * read-only. - * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is - * read-only. - * @property string $scenario The scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]]. - * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model. - * This property is read-only. - * - * @author Qiang Xue - * @since 2.0 - */ -class Model extends Component implements StaticInstanceInterface, IteratorAggregate, ArrayAccess, Arrayable -{ - use ArrayableTrait; - use StaticInstanceTrait; - - /** - * The name of the default scenario. - */ - const SCENARIO_DEFAULT = 'default'; - /** - * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set - * [[ModelEvent::isValid]] to be false to stop the validation. - */ - const EVENT_BEFORE_VALIDATE = 'beforeValidate'; - /** - * @event Event an event raised at the end of [[validate()]] - */ - const EVENT_AFTER_VALIDATE = 'afterValidate'; - - /** - * @var array validation errors (attribute name => array of errors) - */ - private $_errors; - /** - * @var ArrayObject list of validators - */ - private $_validators; - /** - * @var string current scenario - */ - private $_scenario = self::SCENARIO_DEFAULT; - - - /** - * Returns the validation rules for attributes. - * - * Validation rules are used by [[validate()]] to check if attribute values are valid. - * Child classes may override this method to declare different validation rules. - * - * Each rule is an array with the following structure: - * - * ```php - * [ - * ['attribute1', 'attribute2'], - * 'validator type', - * 'on' => ['scenario1', 'scenario2'], - * //...other parameters... - * ] - * ``` - * - * where - * - * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass a string; - * - validator type: required, specifies the validator to be used. It can be a built-in validator name, - * a method name of the model class, an anonymous function, or a validator class name. - * - on: optional, specifies the [[scenario|scenarios]] array in which the validation - * rule can be applied. If this option is not set, the rule will apply to all scenarios. - * - additional name-value pairs can be specified to initialize the corresponding validator properties. - * Please refer to individual validator class API for possible properties. - * - * A validator can be either an object of a class extending [[Validator]], or a model class method - * (called *inline validator*) that has the following signature: - * - * ```php - * // $params refers to validation parameters given in the rule - * function validatorName($attribute, $params) - * ``` - * - * In the above `$attribute` refers to the attribute currently being validated while `$params` contains an array of - * validator configuration options such as `max` in case of `string` validator. The value of the attribute currently being validated - * can be accessed as `$this->$attribute`. Note the `$` before `attribute`; this is taking the value of the variable - * `$attribute` and using it as the name of the property to access. - * - * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. - * Each one has an alias name which can be used when specifying a validation rule. - * - * Below are some examples: - * - * ```php - * [ - * // built-in "required" validator - * [['username', 'password'], 'required'], - * // built-in "string" validator customized with "min" and "max" properties - * ['username', 'string', 'min' => 3, 'max' => 12], - * // built-in "compare" validator that is used in "register" scenario only - * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'], - * // an inline validator defined via the "authenticate()" method in the model class - * ['password', 'authenticate', 'on' => 'login'], - * // a validator of class "DateRangeValidator" - * ['dateRange', 'DateRangeValidator'], - * ]; - * ``` - * - * Note, in order to inherit rules defined in the parent class, a child class needs to - * merge the parent rules with child rules using functions such as `array_merge()`. - * - * @return array validation rules - * @see scenarios() - */ - public function rules() - { - return []; - } - - /** - * Returns a list of scenarios and the corresponding active attributes. - * - * An active attribute is one that is subject to validation in the current scenario. - * The returned array should be in the following format: - * - * ```php - * [ - * 'scenario1' => ['attribute11', 'attribute12', ...], - * 'scenario2' => ['attribute21', 'attribute22', ...], - * ... - * ] - * ``` - * - * By default, an active attribute is considered safe and can be massively assigned. - * If an attribute should NOT be massively assigned (thus considered unsafe), - * please prefix the attribute with an exclamation character (e.g. `'!rank'`). - * - * The default implementation of this method will return all scenarios found in the [[rules()]] - * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes - * found in the [[rules()]]. Each scenario will be associated with the attributes that - * are being validated by the validation rules that apply to the scenario. - * - * @return array a list of scenarios and the corresponding active attributes. - */ - public function scenarios() - { - $scenarios = [self::SCENARIO_DEFAULT => []]; - foreach ($this->getValidators() as $validator) { - foreach ($validator->on as $scenario) { - $scenarios[$scenario] = []; - } - foreach ($validator->except as $scenario) { - $scenarios[$scenario] = []; - } - } - $names = array_keys($scenarios); - - foreach ($this->getValidators() as $validator) { - if (empty($validator->on) && empty($validator->except)) { - foreach ($names as $name) { - foreach ($validator->attributes as $attribute) { - $scenarios[$name][$attribute] = true; - } - } - } elseif (empty($validator->on)) { - foreach ($names as $name) { - if (!in_array($name, $validator->except, true)) { - foreach ($validator->attributes as $attribute) { - $scenarios[$name][$attribute] = true; - } - } - } - } else { - foreach ($validator->on as $name) { - foreach ($validator->attributes as $attribute) { - $scenarios[$name][$attribute] = true; - } - } - } - } - - foreach ($scenarios as $scenario => $attributes) { - if (!empty($attributes)) { - $scenarios[$scenario] = array_keys($attributes); - } - } - - return $scenarios; - } - - /** - * Returns the form name that this model class should use. - * - * The form name is mainly used by [[\yii\widgets\ActiveForm]] to determine how to name - * the input fields for the attributes in a model. If the form name is "A" and an attribute - * name is "b", then the corresponding input name would be "A[b]". If the form name is - * an empty string, then the input name would be "b". - * - * The purpose of the above naming schema is that for forms which contain multiple different models, - * the attributes of each model are grouped in sub-arrays of the POST-data and it is easier to - * differentiate between them. - * - * By default, this method returns the model class name (without the namespace part) - * as the form name. You may override it when the model is used in different forms. - * - * @return string the form name of this model class. - * @see load() - * @throws InvalidConfigException when form is defined with anonymous class and `formName()` method is - * not overridden. - */ - public function formName() - { - $reflector = new ReflectionClass($this); - if (PHP_VERSION_ID >= 70000 && $reflector->isAnonymous()) { - throw new InvalidConfigException('The "formName()" method should be explicitly defined for anonymous models'); - } - return $reflector->getShortName(); - } - - /** - * Returns the list of attribute names. - * By default, this method returns all public non-static properties of the class. - * You may override this method to change the default behavior. - * @return array list of attribute names. - */ - public function attributes() - { - $class = new ReflectionClass($this); - $names = []; - foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { - if (!$property->isStatic()) { - $names[] = $property->getName(); - } - } - - return $names; - } - - /** - * Returns the attribute labels. - * - * Attribute labels are mainly used for display purpose. For example, given an attribute - * `firstName`, we can declare a label `First Name` which is more user-friendly and can - * be displayed to end users. - * - * By default an attribute label is generated using [[generateAttributeLabel()]]. - * This method allows you to explicitly specify attribute labels. - * - * Note, in order to inherit labels defined in the parent class, a child class needs to - * merge the parent labels with child labels using functions such as `array_merge()`. - * - * @return array attribute labels (name => label) - * @see generateAttributeLabel() - */ - public function attributeLabels() - { - return []; - } - - /** - * Returns the attribute hints. - * - * Attribute hints are mainly used for display purpose. For example, given an attribute - * `isPublic`, we can declare a hint `Whether the post should be visible for not logged in users`, - * which provides user-friendly description of the attribute meaning and can be displayed to end users. - * - * Unlike label hint will not be generated, if its explicit declaration is omitted. - * - * Note, in order to inherit hints defined in the parent class, a child class needs to - * merge the parent hints with child hints using functions such as `array_merge()`. - * - * @return array attribute hints (name => hint) - * @since 2.0.4 - */ - public function attributeHints() - { - return []; - } - - /** - * Performs the data validation. - * - * This method executes the validation rules applicable to the current [[scenario]]. - * The following criteria are used to determine whether a rule is currently applicable: - * - * - the rule must be associated with the attributes relevant to the current scenario; - * - the rules must be effective for the current scenario. - * - * This method will call [[beforeValidate()]] and [[afterValidate()]] before and - * after the actual validation, respectively. If [[beforeValidate()]] returns false, - * the validation will be cancelled and [[afterValidate()]] will not be called. - * - * Errors found during the validation can be retrieved via [[getErrors()]], - * [[getFirstErrors()]] and [[getFirstError()]]. - * - * @param string[]|string $attributeNames attribute name or list of attribute names that should be validated. - * If this parameter is empty, it means any attribute listed in the applicable - * validation rules should be validated. - * @param bool $clearErrors whether to call [[clearErrors()]] before performing validation - * @return bool whether the validation is successful without any error. - * @throws InvalidArgumentException if the current scenario is unknown. - */ - public function validate($attributeNames = null, $clearErrors = true) - { - if ($clearErrors) { - $this->clearErrors(); - } - - if (!$this->beforeValidate()) { - return false; - } - - $scenarios = $this->scenarios(); - $scenario = $this->getScenario(); - if (!isset($scenarios[$scenario])) { - throw new InvalidArgumentException("Unknown scenario: $scenario"); - } - - if ($attributeNames === null) { - $attributeNames = $this->activeAttributes(); - } - - $attributeNames = (array)$attributeNames; - - foreach ($this->getActiveValidators() as $validator) { - $validator->validateAttributes($this, $attributeNames); - } - $this->afterValidate(); - - return !$this->hasErrors(); - } - - /** - * This method is invoked before validation starts. - * The default implementation raises a `beforeValidate` event. - * You may override this method to do preliminary checks before validation. - * Make sure the parent implementation is invoked so that the event can be raised. - * @return bool whether the validation should be executed. Defaults to true. - * If false is returned, the validation will stop and the model is considered invalid. - */ - public function beforeValidate() - { - $event = new ModelEvent(); - $this->trigger(self::EVENT_BEFORE_VALIDATE, $event); - - return $event->isValid; - } - - /** - * This method is invoked after validation ends. - * The default implementation raises an `afterValidate` event. - * You may override this method to do postprocessing after validation. - * Make sure the parent implementation is invoked so that the event can be raised. - */ - public function afterValidate() - { - $this->trigger(self::EVENT_AFTER_VALIDATE); - } - - /** - * Returns all the validators declared in [[rules()]]. - * - * This method differs from [[getActiveValidators()]] in that the latter - * only returns the validators applicable to the current [[scenario]]. - * - * Because this method returns an ArrayObject object, you may - * manipulate it by inserting or removing validators (useful in model behaviors). - * For example, - * - * ```php - * $model->validators[] = $newValidator; - * ``` - * - * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model. - */ - public function getValidators() - { - if ($this->_validators === null) { - $this->_validators = $this->createValidators(); - } - - return $this->_validators; - } - - /** - * Returns the validators applicable to the current [[scenario]]. - * @param string $attribute the name of the attribute whose applicable validators should be returned. - * If this is null, the validators for ALL attributes in the model will be returned. - * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]]. - */ - public function getActiveValidators($attribute = null) - { - $activeAttributes = $this->activeAttributes(); - if ($attribute !== null && !in_array($attribute, $activeAttributes, true)) { - return []; - } - $scenario = $this->getScenario(); - $validators = []; - foreach ($this->getValidators() as $validator) { - if ($attribute === null) { - $validatorAttributes = $validator->getValidationAttributes($activeAttributes); - $attributeValid = !empty($validatorAttributes); - } else { - $attributeValid = in_array($attribute, $validator->getValidationAttributes($attribute), true); - } - if ($attributeValid && $validator->isActive($scenario)) { - $validators[] = $validator; - } - } - - return $validators; - } - - /** - * Creates validator objects based on the validation rules specified in [[rules()]]. - * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned. - * @return ArrayObject validators - * @throws InvalidConfigException if any validation rule configuration is invalid - */ - public function createValidators() - { - $validators = new ArrayObject(); - foreach ($this->rules() as $rule) { - if ($rule instanceof Validator) { - $validators->append($rule); - } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type - $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2)); - $validators->append($validator); - } else { - throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); - } - } - - return $validators; - } - - /** - * Returns a value indicating whether the attribute is required. - * This is determined by checking if the attribute is associated with a - * [[\yii\validators\RequiredValidator|required]] validation rule in the - * current [[scenario]]. - * - * Note that when the validator has a conditional validation applied using - * [[\yii\validators\RequiredValidator::$when|$when]] this method will return - * `false` regardless of the `when` condition because it may be called be - * before the model is loaded with data. - * - * @param string $attribute attribute name - * @return bool whether the attribute is required - */ - public function isAttributeRequired($attribute) - { - foreach ($this->getActiveValidators($attribute) as $validator) { - if ($validator instanceof RequiredValidator && $validator->when === null) { - return true; - } - } - - return false; - } - - /** - * Returns a value indicating whether the attribute is safe for massive assignments. - * @param string $attribute attribute name - * @return bool whether the attribute is safe for massive assignments - * @see safeAttributes() - */ - public function isAttributeSafe($attribute) - { - return in_array($attribute, $this->safeAttributes(), true); - } - - /** - * Returns a value indicating whether the attribute is active in the current scenario. - * @param string $attribute attribute name - * @return bool whether the attribute is active in the current scenario - * @see activeAttributes() - */ - public function isAttributeActive($attribute) - { - return in_array($attribute, $this->activeAttributes(), true); - } - - /** - * Returns the text label for the specified attribute. - * @param string $attribute the attribute name - * @return string the attribute label - * @see generateAttributeLabel() - * @see attributeLabels() - */ - public function getAttributeLabel($attribute) - { - $labels = $this->attributeLabels(); - return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute); - } - - /** - * Returns the text hint for the specified attribute. - * @param string $attribute the attribute name - * @return string the attribute hint - * @see attributeHints() - * @since 2.0.4 - */ - public function getAttributeHint($attribute) - { - $hints = $this->attributeHints(); - return isset($hints[$attribute]) ? $hints[$attribute] : ''; - } - - /** - * Returns a value indicating whether there is any validation error. - * @param string|null $attribute attribute name. Use null to check all attributes. - * @return bool whether there is any error. - */ - public function hasErrors($attribute = null) - { - return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]); - } - - /** - * Returns the errors for all attributes or a single attribute. - * @param string $attribute attribute name. Use null to retrieve errors for all attributes. - * @property array An array of errors for all attributes. Empty array is returned if no error. - * The result is a two-dimensional array. See [[getErrors()]] for detailed description. - * @return array errors for all attributes or the specified attribute. Empty array is returned if no error. - * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following: - * - * ```php - * [ - * 'username' => [ - * 'Username is required.', - * 'Username must contain only word characters.', - * ], - * 'email' => [ - * 'Email address is invalid.', - * ] - * ] - * ``` - * - * @see getFirstErrors() - * @see getFirstError() - */ - public function getErrors($attribute = null) - { - if ($attribute === null) { - return $this->_errors === null ? [] : $this->_errors; - } - - return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : []; - } - - /** - * Returns the first error of every attribute in the model. - * @return array the first errors. The array keys are the attribute names, and the array - * values are the corresponding error messages. An empty array will be returned if there is no error. - * @see getErrors() - * @see getFirstError() - */ - public function getFirstErrors() - { - if (empty($this->_errors)) { - return []; - } - - $errors = []; - foreach ($this->_errors as $name => $es) { - if (!empty($es)) { - $errors[$name] = reset($es); - } - } - - return $errors; - } - - /** - * Returns the first error of the specified attribute. - * @param string $attribute attribute name. - * @return string the error message. Null is returned if no error. - * @see getErrors() - * @see getFirstErrors() - */ - public function getFirstError($attribute) - { - return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null; - } - - /** - * Returns the errors for all attributes as a one-dimensional array. - * @param bool $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise - * only the first error message for each attribute will be shown. - * @return array errors for all attributes as a one-dimensional array. Empty array is returned if no error. - * @see getErrors() - * @see getFirstErrors() - * @since 2.0.14 - */ - public function getErrorSummary($showAllErrors) - { - $lines = []; - $errors = $showAllErrors ? $this->getErrors() : $this->getFirstErrors(); - foreach ($errors as $es) { - $lines = array_merge((array)$es, $lines); - } - return $lines; - } - - /** - * Adds a new error to the specified attribute. - * @param string $attribute attribute name - * @param string $error new error message - */ - public function addError($attribute, $error = '') - { - $this->_errors[$attribute][] = $error; - } - - /** - * Adds a list of errors. - * @param array $items a list of errors. The array keys must be attribute names. - * The array values should be error messages. If an attribute has multiple errors, - * these errors must be given in terms of an array. - * You may use the result of [[getErrors()]] as the value for this parameter. - * @since 2.0.2 - */ - public function addErrors(array $items) - { - foreach ($items as $attribute => $errors) { - if (is_array($errors)) { - foreach ($errors as $error) { - $this->addError($attribute, $error); - } - } else { - $this->addError($attribute, $errors); - } - } - } - - /** - * Removes errors for all attributes or a single attribute. - * @param string $attribute attribute name. Use null to remove errors for all attributes. - */ - public function clearErrors($attribute = null) - { - if ($attribute === null) { - $this->_errors = []; - } else { - unset($this->_errors[$attribute]); - } - } - - /** - * Generates a user friendly attribute label based on the give attribute name. - * This is done by replacing underscores, dashes and dots with blanks and - * changing the first letter of each word to upper case. - * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'. - * @param string $name the column name - * @return string the attribute label - */ - public function generateAttributeLabel($name) - { - return Inflector::camel2words($name, true); - } - - /** - * Returns attribute values. - * @param array $names list of attributes whose value needs to be returned. - * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned. - * If it is an array, only the attributes in the array will be returned. - * @param array $except list of attributes whose value should NOT be returned. - * @return array attribute values (name => value). - */ - public function getAttributes($names = null, $except = []) - { - $values = []; - if ($names === null) { - $names = $this->attributes(); - } - foreach ($names as $name) { - $values[$name] = $this->$name; - } - foreach ($except as $name) { - unset($values[$name]); - } - - return $values; - } - - /** - * Sets the attribute values in a massive way. - * @param array $values attribute values (name => value) to be assigned to the model. - * @param bool $safeOnly whether the assignments should only be done to the safe attributes. - * A safe attribute is one that is associated with a validation rule in the current [[scenario]]. - * @see safeAttributes() - * @see attributes() - */ - public function setAttributes($values, $safeOnly = true) - { - if (is_array($values)) { - $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes()); - foreach ($values as $name => $value) { - if (isset($attributes[$name])) { - $this->$name = $value; - } elseif ($safeOnly) { - $this->onUnsafeAttribute($name, $value); - } - } - } - } - - /** - * This method is invoked when an unsafe attribute is being massively assigned. - * The default implementation will log a warning message if YII_DEBUG is on. - * It does nothing otherwise. - * @param string $name the unsafe attribute name - * @param mixed $value the attribute value - */ - public function onUnsafeAttribute($name, $value) - { - if (YII_DEBUG) { - Yii::debug("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__); - } - } - - /** - * Returns the scenario that this model is used in. - * - * Scenario affects how validation is performed and which attributes can - * be massively assigned. - * - * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]]. - */ - public function getScenario() - { - return $this->_scenario; - } - - /** - * Sets the scenario for the model. - * Note that this method does not check if the scenario exists or not. - * The method [[validate()]] will perform this check. - * @param string $value the scenario that this model is in. - */ - public function setScenario($value) - { - $this->_scenario = $value; - } - - /** - * Returns the attribute names that are safe to be massively assigned in the current scenario. - * @return string[] safe attribute names - */ - public function safeAttributes() - { - $scenario = $this->getScenario(); - $scenarios = $this->scenarios(); - if (!isset($scenarios[$scenario])) { - return []; - } - $attributes = []; - foreach ($scenarios[$scenario] as $attribute) { - if (strncmp($attribute, '!', 1) !== 0 && !in_array('!' . $attribute, $scenarios[$scenario])) { - $attributes[] = $attribute; - } - } - - return $attributes; - } - - /** - * Returns the attribute names that are subject to validation in the current scenario. - * @return string[] safe attribute names - */ - public function activeAttributes() - { - $scenario = $this->getScenario(); - $scenarios = $this->scenarios(); - if (!isset($scenarios[$scenario])) { - return []; - } - $attributes = array_keys(array_flip($scenarios[$scenario])); - foreach ($attributes as $i => $attribute) { - if (strncmp($attribute, '!', 1) === 0) { - $attributes[$i] = substr($attribute, 1); - } - } - - return $attributes; - } - - /** - * Populates the model with input data. - * - * This method provides a convenient shortcut for: - * - * ```php - * if (isset($_POST['FormName'])) { - * $model->attributes = $_POST['FormName']; - * if ($model->save()) { - * // handle success - * } - * } - * ``` - * - * which, with `load()` can be written as: - * - * ```php - * if ($model->load($_POST) && $model->save()) { - * // handle success - * } - * ``` - * - * `load()` gets the `'FormName'` from the model's [[formName()]] method (which you may override), unless the - * `$formName` parameter is given. If the form name is empty, `load()` populates the model with the whole of `$data`, - * instead of `$data['FormName']`. - * - * Note, that the data being populated is subject to the safety check by [[setAttributes()]]. - * - * @param array $data the data array to load, typically `$_POST` or `$_GET`. - * @param string $formName the form name to use to load the data into the model. - * If not set, [[formName()]] is used. - * @return bool whether `load()` found the expected form in `$data`. - */ - public function load($data, $formName = null) - { - $scope = $formName === null ? $this->formName() : $formName; - if ($scope === '' && !empty($data)) { - $this->setAttributes($data); - - return true; - } elseif (isset($data[$scope])) { - $this->setAttributes($data[$scope]); - - return true; - } - - return false; - } - - /** - * Populates a set of models with the data from end user. - * This method is mainly used to collect tabular data input. - * The data to be loaded for each model is `$data[formName][index]`, where `formName` - * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array. - * If [[formName()]] is empty, `$data[index]` will be used to populate each model. - * The data being populated to each model is subject to the safety check by [[setAttributes()]]. - * @param array $models the models to be populated. Note that all models should have the same class. - * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array - * supplied by end user. - * @param string $formName the form name to be used for loading the data into the models. - * If not set, it will use the [[formName()]] value of the first model in `$models`. - * This parameter is available since version 2.0.1. - * @return bool whether at least one of the models is successfully populated. - */ - public static function loadMultiple($models, $data, $formName = null) - { - if ($formName === null) { - /* @var $first Model|false */ - $first = reset($models); - if ($first === false) { - return false; - } - $formName = $first->formName(); - } - - $success = false; - foreach ($models as $i => $model) { - /* @var $model Model */ - if ($formName == '') { - if (!empty($data[$i]) && $model->load($data[$i], '')) { - $success = true; - } - } elseif (!empty($data[$formName][$i]) && $model->load($data[$formName][$i], '')) { - $success = true; - } - } - - return $success; - } - - /** - * Validates multiple models. - * This method will validate every model. The models being validated may - * be of the same or different types. - * @param array $models the models to be validated - * @param array $attributeNames list of attribute names that should be validated. - * If this parameter is empty, it means any attribute listed in the applicable - * validation rules should be validated. - * @return bool whether all models are valid. False will be returned if one - * or multiple models have validation error. - */ - public static function validateMultiple($models, $attributeNames = null) - { - $valid = true; - /* @var $model Model */ - foreach ($models as $model) { - $valid = $model->validate($attributeNames) && $valid; - } - - return $valid; - } - - /** - * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. - * - * A field is a named element in the returned array by [[toArray()]]. - * - * This method should return an array of field names or field definitions. - * If the former, the field name will be treated as an object property name whose value will be used - * as the field value. If the latter, the array key should be the field name while the array value should be - * the corresponding field definition which can be either an object property name or a PHP callable - * returning the corresponding field value. The signature of the callable should be: - * - * ```php - * function ($model, $field) { - * // return field value - * } - * ``` - * - * For example, the following code declares four fields: - * - * - `email`: the field name is the same as the property name `email`; - * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their - * values are obtained from the `first_name` and `last_name` properties; - * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` - * and `last_name`. - * - * ```php - * return [ - * 'email', - * 'firstName' => 'first_name', - * 'lastName' => 'last_name', - * 'fullName' => function ($model) { - * return $model->first_name . ' ' . $model->last_name; - * }, - * ]; - * ``` - * - * In this method, you may also want to return different lists of fields based on some context - * information. For example, depending on [[scenario]] or the privilege of the current application user, - * you may return different sets of visible fields or filter out some fields. - * - * The default implementation of this method returns [[attributes()]] indexed by the same attribute names. - * - * @return array the list of field names or field definitions. - * @see toArray() - */ - public function fields() - { - $fields = $this->attributes(); - - return array_combine($fields, $fields); - } - - /** - * Returns an iterator for traversing the attributes in the model. - * This method is required by the interface [[\IteratorAggregate]]. - * @return ArrayIterator an iterator for traversing the items in the list. - */ - public function getIterator() - { - $attributes = $this->getAttributes(); - return new ArrayIterator($attributes); - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the SPL interface [[\ArrayAccess]]. - * It is implicitly called when you use something like `isset($model[$offset])`. - * @param mixed $offset the offset to check on. - * @return bool whether or not an offset exists. - */ - public function offsetExists($offset) - { - return isset($this->$offset); - } - - /** - * Returns the element at the specified offset. - * This method is required by the SPL interface [[\ArrayAccess]]. - * It is implicitly called when you use something like `$value = $model[$offset];`. - * @param mixed $offset the offset to retrieve element. - * @return mixed the element at the offset, null if no element is found at the offset - */ - public function offsetGet($offset) - { - return $this->$offset; - } - - /** - * Sets the element at the specified offset. - * This method is required by the SPL interface [[\ArrayAccess]]. - * It is implicitly called when you use something like `$model[$offset] = $item;`. - * @param int $offset the offset to set element - * @param mixed $item the element value - */ - public function offsetSet($offset, $item) - { - $this->$offset = $item; - } - - /** - * Sets the element value at the specified offset to null. - * This method is required by the SPL interface [[\ArrayAccess]]. - * It is implicitly called when you use something like `unset($model[$offset])`. - * @param mixed $offset the offset to unset element - */ - public function offsetUnset($offset) - { - $this->$offset = null; - } -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/Record.php.test b/src/Tests/Benchmarks/fixtures/yii/Record.php.test deleted file mode 100644 index 449998a66..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/Record.php.test +++ /dev/null @@ -1,21 +0,0 @@ - - * @since 2.0.13 - * @see StaticInstanceTrait - */ -interface StaticInstanceInterface -{ - /** - * Returns static class instance, which can be used to obtain meta information. - * @param bool $refresh whether to re-create static instance even, if it is already cached. - * @return static class instance. - */ - public static function instance($refresh = false); -} - diff --git a/src/Tests/Benchmarks/fixtures/yii/StaticInstanceTrait.php.test b/src/Tests/Benchmarks/fixtures/yii/StaticInstanceTrait.php.test deleted file mode 100644 index 2b89e77d3..000000000 --- a/src/Tests/Benchmarks/fixtures/yii/StaticInstanceTrait.php.test +++ /dev/null @@ -1,41 +0,0 @@ - - * @since 2.0.13 - */ -trait StaticInstanceTrait -{ - /** - * @var static[] static instances in format: `[className => object]` - */ - private static $_instances = []; - - - /** - * Returns static class instance, which can be used to obtain meta information. - * @param bool $refresh whether to re-create static instance even, if it is already cached. - * @return static class instance. - */ - public static function instance($refresh = false) - { - $className = get_called_class(); - if ($refresh || !isset(self::$_instances[$className])) { - self::$_instances[$className] = Yii::createObject($className); - } - return self::$_instances[$className]; - } -} diff --git a/src/Tests/Inference/SelfTest.php b/src/Tests/Inference/SelfTest.php deleted file mode 100644 index 164535838..000000000 --- a/src/Tests/Inference/SelfTest.php +++ /dev/null @@ -1,40 +0,0 @@ -build(); - $reflector = $this->createBuilder($source)->enableCache()->build(); - $reflector->reflectOffset($source, mb_strlen($source)); - - // the wrAssertType function in the source code will cause - // an exception to be thrown if it fails - $this->addToAssertionCount(1); - } - - /** - * @return Generatormixed - */ - public function provideSelf(): Generator - { - foreach ((array)glob(__DIR__ . '/*/*.test') as $fname) { - $dirName = basename(dirname((string)$fname)); - if ($dirName === 'enum' && !defined('T_ENUM')) { - continue; - } - yield $dirName .' ' . basename((string)$fname) => [ - $fname - ]; - } - } -} diff --git a/src/Tests/Inference/anonymous_function/as_closure.test b/src/Tests/Inference/anonymous_function/as_closure.test deleted file mode 100644 index 253f73e3d..000000000 --- a/src/Tests/Inference/anonymous_function/as_closure.test +++ /dev/null @@ -1,3 +0,0 @@ - null); diff --git a/src/Tests/Inference/arrow_function/as_closure_with_args.test b/src/Tests/Inference/arrow_function/as_closure_with_args.test deleted file mode 100644 index 6243d3385..000000000 --- a/src/Tests/Inference/arrow_function/as_closure_with_args.test +++ /dev/null @@ -1,3 +0,0 @@ - 'foo'); diff --git a/src/Tests/Inference/arrow_function/parameter.test b/src/Tests/Inference/arrow_function/parameter.test deleted file mode 100644 index f7c4b295e..000000000 --- a/src/Tests/Inference/arrow_function/parameter.test +++ /dev/null @@ -1,3 +0,0 @@ - wrAssertType('string', $foo); diff --git a/src/Tests/Inference/arrow_function/parameter2.test b/src/Tests/Inference/arrow_function/parameter2.test deleted file mode 100644 index dbb13d97d..000000000 --- a/src/Tests/Inference/arrow_function/parameter2.test +++ /dev/null @@ -1,8 +0,0 @@ - $this->convert(wrAssertType('Foo\TypeNode', $node)), - $parameters -); diff --git a/src/Tests/Inference/arrow_function/parameter3.test b/src/Tests/Inference/arrow_function/parameter3.test deleted file mode 100644 index c259eda0e..000000000 --- a/src/Tests/Inference/arrow_function/parameter3.test +++ /dev/null @@ -1,9 +0,0 @@ - $this->convert(wrAssertType('"hello"', $foo)), - $parameters -); diff --git a/src/Tests/Inference/assignment/array1.test b/src/Tests/Inference/assignment/array1.test deleted file mode 100644 index dc6789819..000000000 --- a/src/Tests/Inference/assignment/array1.test +++ /dev/null @@ -1,22 +0,0 @@ - [ - 'hello' => 'world', - ], - 'bar' => 'baz', -]; -wrAssertType('array{foo:array{hello:"world"},bar:"baz"}', $foo); - -// list -$foo = [1, 2, 3, 4, 5]; -wrAssertType('array{1,2,3,4,5}', $foo); - -// numerical keys -$foo = [1 => "a", 2 => "b"]; -wrAssertType('array{1:"a",2:"b"}', $foo); diff --git a/src/Tests/Inference/assignment/array_2.test b/src/Tests/Inference/assignment/array_2.test deleted file mode 100644 index 14f7aa04d..000000000 --- a/src/Tests/Inference/assignment/array_2.test +++ /dev/null @@ -1,13 +0,0 @@ - 'one', - 'type' => 'two', -]; - -if ($node === null || !($node->getParent() instanceof NamespaceUseClause)) { - $options['class_import'] = true; - $options['name_import'] = false; -} - -wrAssertType('array{short_description:"one",type:"two"}|array{short_description:"one",type:"two",class_import:true,name_import:false}', $options); diff --git a/src/Tests/Inference/assignment/array_add.test b/src/Tests/Inference/assignment/array_add.test deleted file mode 100644 index 80ab406aa..000000000 --- a/src/Tests/Inference/assignment/array_add.test +++ /dev/null @@ -1,6 +0,0 @@ - - */ -function bar(): Generator { -} - -$highlights = []; -foreach (bar() as $highlight) { - wrAssertType("Bar", $highlight); - $highlights[] = $highlight; -} - - -wrAssertType("Bar[]", $highlights); - -$lineCols = EfficientLineCols::fromByteOffsetInts($source, $offsets, true); -$lspHighlights = []; - -foreach ($highlights as $highlight) { - wrAssertType("Bar", $highlight); -} diff --git a/src/Tests/Inference/assignment/list_assignment.test b/src/Tests/Inference/assignment/list_assignment.test deleted file mode 100644 index 14a95fe72..000000000 --- a/src/Tests/Inference/assignment/list_assignment.test +++ /dev/null @@ -1,10 +0,0 @@ -withFoo($bar); - -wrAssertType('Test\Bar', $foo); -wrAssertOffset('Test\Foo', 153); -wrAssertOffset('Test\Bar', 145); diff --git a/src/Tests/Inference/assignment/ternary_expression.test b/src/Tests/Inference/assignment/ternary_expression.test deleted file mode 100644 index 3d7389dab..000000000 --- a/src/Tests/Inference/assignment/ternary_expression.test +++ /dev/null @@ -1,10 +0,0 @@ -cost <= 10) ? $two : $one; - - wrAssertType('Foo|Bar', $true); -} diff --git a/src/Tests/Inference/assignment/unknown_key.test b/src/Tests/Inference/assignment/unknown_key.test deleted file mode 100644 index 5ccc77733..000000000 --- a/src/Tests/Inference/assignment/unknown_key.test +++ /dev/null @@ -1,10 +0,0 @@ -analyser->analyse($input->getArgument(self::ARG_PATH)) as $file => $diagnostics) { - $results[$file] = 1234; -} - -wrAssertType('array<,1234>', $results); - diff --git a/src/Tests/Inference/binary-expression/arithmetic.test b/src/Tests/Inference/binary-expression/arithmetic.test deleted file mode 100644 index a58a70089..000000000 --- a/src/Tests/Inference/binary-expression/arithmetic.test +++ /dev/null @@ -1,22 +0,0 @@ - 1]); diff --git a/src/Tests/Inference/binary-expression/bitwise.test b/src/Tests/Inference/binary-expression/bitwise.test deleted file mode 100644 index 76ac3349c..000000000 --- a/src/Tests/Inference/binary-expression/bitwise.test +++ /dev/null @@ -1,30 +0,0 @@ -> 2); diff --git a/src/Tests/Inference/binary-expression/compare.scalar.test b/src/Tests/Inference/binary-expression/compare.scalar.test deleted file mode 100644 index fcb3e958f..000000000 --- a/src/Tests/Inference/binary-expression/compare.scalar.test +++ /dev/null @@ -1,44 +0,0 @@ - 1, 'greater than'); -wrAssertType('false', 1 > 1, 'greater than'); - -wrAssertType('true', 2 >= 1, 'greater than equal'); -wrAssertType('true', 1 >= 1, 'greater than equal'); -wrAssertType('false', 0 >= 1, 'greater than equal'); - -wrAssertType('false', 2 < 1, 'less than'); -wrAssertType('true', 1 < 2, 'less than'); - -wrAssertType('false', 2 <= 1, 'less than equal'); -wrAssertType('true', 1 <= 2, 'less than equal'); -wrAssertType('true', 2 <= 2, 'less than equal'); - -// floats -wrAssertType('true', 1.0 == 1.0, 'equality'); -wrAssertType('true', 1.0 === 1.0, 'identity'); -wrAssertType('true', 1.0 === 1.0, 'identity'); - -// bool -wrAssertType('true', true == true, 'equality'); -wrAssertType('false', true === false, 'identity'); - - diff --git a/src/Tests/Inference/binary-expression/concat.test b/src/Tests/Inference/binary-expression/concat.test deleted file mode 100644 index 8a1fba9ac..000000000 --- a/src/Tests/Inference/binary-expression/concat.test +++ /dev/null @@ -1,9 +0,0 @@ - - */ - public function __invoke(): Generator - { - yield new DateTime(); - } -} - -class Baz -{ - private Foobar $foo; - - public function __construct(Foobar $foo) { - $this->foo = $foo; - } - - public function baz(): void { - $f = $this->foo; - - foreach (($this->foo)() as $bar) { - wrAssertType('DateTime', $bar); - } - } -} - diff --git a/src/Tests/Inference/call-expression/type-from-invoked-callable.test b/src/Tests/Inference/call-expression/type-from-invoked-callable.test deleted file mode 100644 index e6f5f6072..000000000 --- a/src/Tests/Inference/call-expression/type-from-invoked-callable.test +++ /dev/null @@ -1,4 +0,0 @@ - 'string')(); -wrAssertType('string', $type); diff --git a/src/Tests/Inference/cast/cast.test b/src/Tests/Inference/cast/cast.test deleted file mode 100644 index 20f316977..000000000 --- a/src/Tests/Inference/cast/cast.test +++ /dev/null @@ -1,5 +0,0 @@ -timeline instanceof Trip) { - wrAssertType('Trip', $this->timeline); - } - } -} diff --git a/src/Tests/Inference/combination/union.test b/src/Tests/Inference/combination/union.test deleted file mode 100644 index 203161605..000000000 --- a/src/Tests/Inference/combination/union.test +++ /dev/null @@ -1,8 +0,0 @@ -value); diff --git a/src/Tests/Inference/enum/custom_member.test b/src/Tests/Inference/enum/custom_member.test deleted file mode 100644 index d5a6e44c4..000000000 --- a/src/Tests/Inference/enum/custom_member.test +++ /dev/null @@ -1,18 +0,0 @@ -isA()); diff --git a/src/Tests/Inference/enum/enum_case.test b/src/Tests/Inference/enum/enum_case.test deleted file mode 100644 index decf57508..000000000 --- a/src/Tests/Inference/enum/enum_case.test +++ /dev/null @@ -1,48 +0,0 @@ -', Foo::FOO->value); -wrAssertType('string', Foo::FOO->name); -wrAssertType('"bar"', Foo::BAR); -wrAssertType('Foo[]', Foo::cases()); - -class UnitEnumCase { - public string $name; -} - -class BackedEnumCase extends UnitEnumCase { - /** @var int|string */ - public $value; -} - -interface UnitEnum -{ - /** - * @return UnitEnumCase[] - */ - public static function cases(): array; -} - -/** - * @method static BackedEnumCase[] cases() - */ -interface BackedEnum extends UnitEnum -{ - /** - * @param int|string $value - * @return static - */ - public static function from($value): static; - - /** - * @param int|string $value - * @return static|null - */ - public static function tryFrom($value): ?static; -} - diff --git a/src/Tests/Inference/enum/enum_trait.test b/src/Tests/Inference/enum/enum_trait.test deleted file mode 100644 index af1828e20..000000000 --- a/src/Tests/Inference/enum/enum_trait.test +++ /dev/null @@ -1,20 +0,0 @@ -foo()); - } -} diff --git a/src/Tests/Inference/enum/gh-2220.test b/src/Tests/Inference/enum/gh-2220.test deleted file mode 100644 index 2ac224d15..000000000 --- a/src/Tests/Inference/enum/gh-2220.test +++ /dev/null @@ -1,16 +0,0 @@ -cases(); - } -} - -wrAssertType('string', Test::from('bar')->foo()); diff --git a/src/Tests/Inference/foreach/assigns_type_to_item.test b/src/Tests/Inference/foreach/assigns_type_to_item.test deleted file mode 100644 index 585056816..000000000 --- a/src/Tests/Inference/foreach/assigns_type_to_item.test +++ /dev/null @@ -1,9 +0,0 @@ - $items */ -$items; - -foreach ($items as $key => $item) { - wrAssertType('string', $key); -} - diff --git a/src/Tests/Inference/foreach/generic_iterator_aggregate.test b/src/Tests/Inference/foreach/generic_iterator_aggregate.test deleted file mode 100644 index f2a1ef3ff..000000000 --- a/src/Tests/Inference/foreach/generic_iterator_aggregate.test +++ /dev/null @@ -1,16 +0,0 @@ - - */ -final class TypeAssertions implements \IteratorAggregate {} - -function (TypeAssertions $assertions): void -{ - foreach ($assertions as $typeAssertion) { - wrAssertType('Foo\TypeAssertion', $typeAssertion); - } -} - diff --git a/src/Tests/Inference/foreach/generic_iterator_aggregate_then_foreach.test b/src/Tests/Inference/foreach/generic_iterator_aggregate_then_foreach.test deleted file mode 100644 index b85787493..000000000 --- a/src/Tests/Inference/foreach/generic_iterator_aggregate_then_foreach.test +++ /dev/null @@ -1,21 +0,0 @@ - - */ -final class TypeAssertions implements \IteratorAggregate {} - -function (TypeAssertions $assertions): void -{ - foreach ([ - [ $assertions, 'bar', ], - [ $assertions, 'foo', ], - ] as [ $typeAssertions, $frameVariables ]) { - foreach ($typeAssertions as $typeAssertion) { - wrAssertType('Foo\TypeAssertion', $typeAssertion); - } - } -} - diff --git a/src/Tests/Inference/foreach/gh-1708.test b/src/Tests/Inference/foreach/gh-1708.test deleted file mode 100644 index bb35beb1d..000000000 --- a/src/Tests/Inference/foreach/gh-1708.test +++ /dev/null @@ -1,6 +0,0 @@ - - */ - private $subjects = []; - - public function search() - { - $this->open(); - - foreach ($this->subjects as [ $recordType, $identifier ]) { - wrAssertType('string', $recordType); - } - } -} - diff --git a/src/Tests/Inference/foreach/literal_keys.test b/src/Tests/Inference/foreach/literal_keys.test deleted file mode 100644 index 33f162e45..000000000 --- a/src/Tests/Inference/foreach/literal_keys.test +++ /dev/null @@ -1,9 +0,0 @@ - 1, "two" => 2]; - -foreach ($items as $key => $item) { - wrAssertType('"one"|"two"', $key); -} - - diff --git a/src/Tests/Inference/foreach/literal_values.test b/src/Tests/Inference/foreach/literal_values.test deleted file mode 100644 index 6e016e9cd..000000000 --- a/src/Tests/Inference/foreach/literal_values.test +++ /dev/null @@ -1,9 +0,0 @@ - $array */ - public function analyse(array $array): Generator - { - $background = null; - - foreach ($array as $child) { - if ($child) { - $background = $child; - break; - } - } - - wrAssertType('?string', $background); - } -} - diff --git a/src/Tests/Inference/foreach/with_docblock.test b/src/Tests/Inference/foreach/with_docblock.test deleted file mode 100644 index 0606abfbb..000000000 --- a/src/Tests/Inference/foreach/with_docblock.test +++ /dev/null @@ -1,13 +0,0 @@ -foobar(); - wrAssertType('Foobar', $foobar); - } - } -} diff --git a/src/Tests/Inference/function-like/function_intersection_docblock-param.test b/src/Tests/Inference/function-like/function_intersection_docblock-param.test deleted file mode 100644 index dcfb1bfa7..000000000 --- a/src/Tests/Inference/function-like/function_intersection_docblock-param.test +++ /dev/null @@ -1,9 +0,0 @@ - 'hello', [10, 20, 30]), - 'arrow', -); - -wrAssertType( - 'string[]', - array_map(function (): string { - return 'hello'; - }, [10, 20, 30]), - 'anonymous' -); diff --git a/src/Tests/Inference/function/array_merge.test b/src/Tests/Inference/function/array_merge.test deleted file mode 100644 index acc81be8f..000000000 --- a/src/Tests/Inference/function/array_merge.test +++ /dev/null @@ -1,25 +0,0 @@ - */ -$arr1; -/** @var array */ -$arr2; - -wrAssertType( - 'array', - array_merge($arr1, $arr2, $arr1), -); - -function childNames(): array -{ - return array_merge(['methods'], [ - 'properties', - 'constants', - ]); - wrReturnType('array{"methods","properties","constants"}'); -} diff --git a/src/Tests/Inference/function/array_pop.test b/src/Tests/Inference/function/array_pop.test deleted file mode 100644 index 874fc921b..000000000 --- a/src/Tests/Inference/function/array_pop.test +++ /dev/null @@ -1,10 +0,0 @@ -', array_sum()); diff --git a/src/Tests/Inference/function/assert.properties.test b/src/Tests/Inference/function/assert.properties.test deleted file mode 100644 index 5b38492ad..000000000 --- a/src/Tests/Inference/function/assert.properties.test +++ /dev/null @@ -1,10 +0,0 @@ -foo instanceof Bar); - wrAssertType('Bar', $this->foo); - } -} diff --git a/src/Tests/Inference/function/assert.test b/src/Tests/Inference/function/assert.test deleted file mode 100644 index c053903d7..000000000 --- a/src/Tests/Inference/function/assert.test +++ /dev/null @@ -1,34 +0,0 @@ -', $foo); diff --git a/src/Tests/Inference/function/assert_not_object.test b/src/Tests/Inference/function/assert_not_object.test deleted file mode 100644 index 25cae6e4a..000000000 --- a/src/Tests/Inference/function/assert_not_object.test +++ /dev/null @@ -1,7 +0,0 @@ - $iterable */ -$iterable; - -wrAssertType('Foobar[]', iterator_to_array($iterable)); diff --git a/src/Tests/Inference/function/iterator_to_array_from_generic.test b/src/Tests/Inference/function/iterator_to_array_from_generic.test deleted file mode 100644 index 343dab96b..000000000 --- a/src/Tests/Inference/function/iterator_to_array_from_generic.test +++ /dev/null @@ -1,18 +0,0 @@ - - */ -function suggestions(): Generator {} - -$array = iterator_to_array(suggestions()); - -wrAssertType('Foo\Suggestion[]', iterator_to_array($array)); -wrAssertType('Foo\Suggestion', iterator_to_array($array)[0]); -wrAssertType('string', iterator_to_array($array)[0]->foo()); diff --git a/src/Tests/Inference/function/namespaced.test b/src/Tests/Inference/function/namespaced.test deleted file mode 100644 index 5be1d17d1..000000000 --- a/src/Tests/Inference/function/namespaced.test +++ /dev/null @@ -1,8 +0,0 @@ - - assert(is_string($f)); - wrAssertType('string', $f); -} -function t2($f) { - // int and string are narrower than - assert(is_string($f) || is_int($f)); - wrAssertType('string|int', $f); -} -function t3($f) { - assert(is_string($f) && is_int($f)); - wrAssertType('', $f, 'impossible to be string and int'); -} -function t4($f) { - assert($f instanceof Bar); - wrAssertType('Bar', $f); -} -function t5($f) { - assert($f instanceof Bar || $f instanceof Baz); - wrAssertType('Bar|Baz', $f); -} -function t6($f) { - assert($f instanceof Bar || $f instanceof Baz); - wrAssertType('Bar|Baz', $f); -} -function t7(Bar|Baz $f) { - assert($f instanceof Bar); - wrAssertType('Bar', $f); -} -function t8(Bar|Baz $f) { - assert($f instanceof Bar || $f instanceof Baz); - wrAssertType('Bar|Baz', $f); -} -function t9(Bar $f) { - assert($f instanceof Bar || $f instanceof Baz); - wrAssertType('Bar|(Bar&Baz)', $f, 'can never be Baz'); -} -function t10(Bar|Boo $f) { - assert($f instanceof Baz); - wrAssertType('(Bar&Baz)|(Boo&Baz)', $f, 'intersection'); -} -function t11(Foo $f) { - assert($f instanceof Baz && $f instanceof Boo); - wrAssertType('Foo&Baz&Boo', $f, 'intersection'); -} diff --git a/src/Tests/Inference/generator/yield.test b/src/Tests/Inference/generator/yield.test deleted file mode 100644 index b58ce3aa0..000000000 --- a/src/Tests/Inference/generator/yield.test +++ /dev/null @@ -1,51 +0,0 @@ -'); -} - -function t1() -{ - yield 'foo'; - yield 12; - wrReturnType('Generator<"foo"|12>'); -} -function t2() -{ - yield 'string' => 'foo'; - yield 52 => 12; - wrReturnType('Generator<"string"|52,"foo"|12>'); -} -function t3() -{ - yield 'string' => 'foo'; - yield 'string' => 123; - yield 52 => 12; - wrReturnType('Generator<"string"|52,"foo"|123|12>'); -} -function t4() -{ - yield 'string' => 'foo'; - yield 'string' => 123; - yield 52 => 12; - yield 52 => new stdClass(); - wrReturnType('Generator<"string"|52,"foo"|123|12|stdClass>'); -} -function t5() -{ - yield 'foo' => [ - 'string', - new stdClass(), - ]; - yield 'bar' => [ - 'string', - new stdClass(), - ]; - yield 'baz' => [ - 'string', - new stdClass(), - ]; - wrReturnType('Generator<"foo"|"bar"|"baz",array{"string",stdClass}>'); -} diff --git a/src/Tests/Inference/generics/array_access1.test b/src/Tests/Inference/generics/array_access1.test deleted file mode 100644 index 7c51c7028..000000000 --- a/src/Tests/Inference/generics/array_access1.test +++ /dev/null @@ -1,7 +0,0 @@ -bar()); - diff --git a/src/Tests/Inference/generics/class-string-generic-decared-interface.test b/src/Tests/Inference/generics/class-string-generic-decared-interface.test deleted file mode 100644 index 415ad94f1..000000000 --- a/src/Tests/Inference/generics/class-string-generic-decared-interface.test +++ /dev/null @@ -1,24 +0,0 @@ - $class - * @return T - */ - public function foobar($class): object; -} - - -class Foo implements FooInterface { - public function foobar($class): object - { - } -} - -$f = new Foo(); -$f = $f->foobar(Foo::class); - -wrAssertType('Foo', $f); - - diff --git a/src/Tests/Inference/generics/class-string-generic-nested-return.test b/src/Tests/Inference/generics/class-string-generic-nested-return.test deleted file mode 100644 index dd5563398..000000000 --- a/src/Tests/Inference/generics/class-string-generic-nested-return.test +++ /dev/null @@ -1,20 +0,0 @@ - $class - * @return Bar - */ - public function foobar($class) - { - } -} - -$f = new Foo(); -$f = $f->foobar(Foo::class); - -wrAssertType('Bar', $f); - - diff --git a/src/Tests/Inference/generics/class-string-generic-union.test b/src/Tests/Inference/generics/class-string-generic-union.test deleted file mode 100644 index 531dd20f1..000000000 --- a/src/Tests/Inference/generics/class-string-generic-union.test +++ /dev/null @@ -1,19 +0,0 @@ -|null $tagFqn - * @return ($tagFqn is string ? Generator : Generator) - */ - public function tags(?string $tagFqn = null): Generator - { - } -} - -$f = new Foo(); -$f = $f->tags(Foo::class); - -wrAssertType('Generator', $f); - diff --git a/src/Tests/Inference/generics/class-string-generic.test b/src/Tests/Inference/generics/class-string-generic.test deleted file mode 100644 index c7338be05..000000000 --- a/src/Tests/Inference/generics/class-string-generic.test +++ /dev/null @@ -1,20 +0,0 @@ - $class - * @return T - */ - public function foobar($class): object - { - } -} - -$f = new Foo(); -$f = $f->foobar(Foo::class); - -wrAssertType('Foo', $f); - - diff --git a/src/Tests/Inference/generics/class_extend1.test b/src/Tests/Inference/generics/class_extend1.test deleted file mode 100644 index 95256bc92..000000000 --- a/src/Tests/Inference/generics/class_extend1.test +++ /dev/null @@ -1,24 +0,0 @@ - - */ -class Foo extends Bar -{ -} - -$foo = new Foo(); - -wrAssertType('Baz', $foo->bar()); diff --git a/src/Tests/Inference/generics/class_extend2.test b/src/Tests/Inference/generics/class_extend2.test deleted file mode 100644 index 6c0d9e241..000000000 --- a/src/Tests/Inference/generics/class_extend2.test +++ /dev/null @@ -1,39 +0,0 @@ - - */ -class Second extends First { - /** - * @return Y - */ - public function boo() - { - } -} - -/** - * @extends Second - */ -class Foo extends Second -{ -} - -$foo = new Foo(); - -wrAssertType('Baz', $foo->bar()); -wrAssertType('Boo', $foo->boo()); diff --git a/src/Tests/Inference/generics/class_implements_multiple1.test b/src/Tests/Inference/generics/class_implements_multiple1.test deleted file mode 100644 index e07e6e36b..000000000 --- a/src/Tests/Inference/generics/class_implements_multiple1.test +++ /dev/null @@ -1,41 +0,0 @@ -, First - */ -class Foo implements Second, First -{ - public function boo() - { - } - public function bar() - { - } -} - -$foo = new Foo(); - -wrAssertType('Boo', $foo->bar()); -wrAssertType('Baz', $foo->boo()); diff --git a/src/Tests/Inference/generics/class_implements_single1.test b/src/Tests/Inference/generics/class_implements_single1.test deleted file mode 100644 index 230c6bf93..000000000 --- a/src/Tests/Inference/generics/class_implements_single1.test +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class Foo implements Second -{ - public function boo() - { - } -} - -$foo = new Foo(); - -wrAssertType('Baz', $foo->boo()); diff --git a/src/Tests/Inference/generics/class_template_extends1.test b/src/Tests/Inference/generics/class_template_extends1.test deleted file mode 100644 index b5ee07a80..000000000 --- a/src/Tests/Inference/generics/class_template_extends1.test +++ /dev/null @@ -1,39 +0,0 @@ - - */ -class Second extends First { - /** - * @return Y - */ - public function boo() - { - } -} - -/** - * @template-extends Second - */ -class Foo extends Second -{ -} - -$foo = new Foo(); - -wrAssertType('Baz', $foo->bar()); -wrAssertType('Boo', $foo->boo()); diff --git a/src/Tests/Inference/generics/class_template_implements1.test b/src/Tests/Inference/generics/class_template_implements1.test deleted file mode 100644 index 67348614d..000000000 --- a/src/Tests/Inference/generics/class_template_implements1.test +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class Foo implements Second -{ - public function boo() - { - } -} - -$foo = new Foo(); - -wrAssertType('Baz', $foo->boo()); diff --git a/src/Tests/Inference/generics/constructor-array_arg.test b/src/Tests/Inference/generics/constructor-array_arg.test deleted file mode 100644 index 753af90b0..000000000 --- a/src/Tests/Inference/generics/constructor-array_arg.test +++ /dev/null @@ -1,26 +0,0 @@ -a = $a; - } - - /** - * @return T - */ - public function a() - { - } -} - -$f = new Foo(['hello']); -wrAssertType('string', $f->a()); diff --git a/src/Tests/Inference/generics/constructor-generic-arg.test b/src/Tests/Inference/generics/constructor-generic-arg.test deleted file mode 100644 index b4159fd0b..000000000 --- a/src/Tests/Inference/generics/constructor-generic-arg.test +++ /dev/null @@ -1,29 +0,0 @@ - $a */ - public function __construct(Foobar $a) {} - - /** - * @return T - */ - public function a() - { - } -} - -$f = new Foo(new Foobar('foobar')); -wrAssertType('string', $f->a()); diff --git a/src/Tests/Inference/generics/constructor-param-and-extend.test b/src/Tests/Inference/generics/constructor-param-and-extend.test deleted file mode 100644 index c9f9ac970..000000000 --- a/src/Tests/Inference/generics/constructor-param-and-extend.test +++ /dev/null @@ -1,41 +0,0 @@ - - */ -class Foo extends Bar { - /** - * @var T - */ - private $a; - - /** @param T $a */ - public function __construct($a) { - $this->a = $a; - } - - /** - * @return T - */ - public function a() - { - return $this->a; - } -} - -$f = new Foo('hello'); -wrAssertType('Foo', $f); -wrAssertType('string', $f->b()); diff --git a/src/Tests/Inference/generics/constructor-params.test b/src/Tests/Inference/generics/constructor-params.test deleted file mode 100644 index f367fce1a..000000000 --- a/src/Tests/Inference/generics/constructor-params.test +++ /dev/null @@ -1,27 +0,0 @@ -a = $a; - } - - /** - * @return T - */ - public function a() - { - return $this->a; - } -} - -$f = new Foo('hello'); -wrAssertType('string', $f->a()); diff --git a/src/Tests/Inference/generics/generator_1.test b/src/Tests/Inference/generics/generator_1.test deleted file mode 100644 index 8b22e997a..000000000 --- a/src/Tests/Inference/generics/generator_1.test +++ /dev/null @@ -1,17 +0,0 @@ - - */ - public function gen(): Generator - { - } -} - -$foo = new Foobar(); - -foreach ($foo->gen() as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/generator_2.test b/src/Tests/Inference/generics/generator_2.test deleted file mode 100644 index 5f60a43f6..000000000 --- a/src/Tests/Inference/generics/generator_2.test +++ /dev/null @@ -1,17 +0,0 @@ - - */ - public function gen(): Generator - { - } -} - -$foo = new Foobar(); - -foreach ($foo->gen() as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/generator_yield_from_1.test b/src/Tests/Inference/generics/generator_yield_from_1.test deleted file mode 100644 index 5ed4179ce..000000000 --- a/src/Tests/Inference/generics/generator_yield_from_1.test +++ /dev/null @@ -1,15 +0,0 @@ -gen2(); - wrReturnType('Generator'); - } - /** - * @return Generator - */ - public function gen2(): Generator - { - } -} diff --git a/src/Tests/Inference/generics/generic_with_this.test b/src/Tests/Inference/generics/generic_with_this.test deleted file mode 100644 index cf541b469..000000000 --- a/src/Tests/Inference/generics/generic_with_this.test +++ /dev/null @@ -1,29 +0,0 @@ - - */ - public function builder() { - } -} - -wrAssertType('Parent', (new Parent())->builder()->parent()); diff --git a/src/Tests/Inference/generics/gh-1530-example.test b/src/Tests/Inference/generics/gh-1530-example.test deleted file mode 100644 index 27feae44a..000000000 --- a/src/Tests/Inference/generics/gh-1530-example.test +++ /dev/null @@ -1,49 +0,0 @@ - - */ - public function all(): Collection; -} - -/** - * @template TKey of array-key - * @template T - * @template-extends \IteratorAggregate - * @template-extends \ArrayAccess - */ -interface Collection extends \IteratorAggregate, \ArrayAccess -{ - /** - * @return T - */ - public function first(); - - /** - * @return Collection - */ - public function filter(\Closure $p); -} - -function foobar(Foos $foos) { - foreach ($foos->all() as $foo) { - wrAssertType('void', $foo->bar()); // OK - } - - wrAssertType('Test\Collection', $foos->all()); - wrAssertType('Test\Foo', $foos->all()->first()); - wrAssertType('void', $foos->all()->first()->bar()); - wrAssertType('Test\Collection', $foos->all()->filter(fn($foo) => $foo)); - - $filtered = $foos->all()->filter(fn($foo) => $foo); - foreach ($filtered as $foo) { - wrAssertType('void', $foo->bar()); - } -} diff --git a/src/Tests/Inference/generics/gh-1771.test b/src/Tests/Inference/generics/gh-1771.test deleted file mode 100644 index b46692f68..000000000 --- a/src/Tests/Inference/generics/gh-1771.test +++ /dev/null @@ -1,31 +0,0 @@ - $x */ -foo($x); - -wrAssertOffset('B', 264); diff --git a/src/Tests/Inference/generics/gh-1800.test b/src/Tests/Inference/generics/gh-1800.test deleted file mode 100644 index d689306fa..000000000 --- a/src/Tests/Inference/generics/gh-1800.test +++ /dev/null @@ -1,52 +0,0 @@ - - */ -class ReflectionArgumentCollection extends AbstractReflectionCollection -{ -} - -/** - * @template T - * @implements ReflectionCollection - */ -abstract class AbstractReflectionCollection implements ReflectionCollection -{ -} - -/** - * @template T - * @extends IteratorAggregate - */ -interface ReflectionCollection extends IteratorAggregate -{ -} - -interface ReflectionArgument -{ -} - -interface IteratorAggregate {} - -class ReflectionNode {} -class ReflectionNodeArgumentExpression extends ReflectioNode { - public function arguments(): ReflectionArgumentCollection {} -} - -function a(): ReflectionNode {} - -$foo = a(); -if (!$foo instanceof ReflectionNodeArgumentExpression) { - throw new \Exception(); -} - -$foo = $foo->arguments(); -wrAssertType('ReflectionArgumentCollection', $foo); - -foreach ($foo as $a => $bar) { - $bar; - wrAssertType('ReflectionArgument', $bar); -} - -wrAssertOffset('ReflectionArgument', 883); diff --git a/src/Tests/Inference/generics/gh-1875.test b/src/Tests/Inference/generics/gh-1875.test deleted file mode 100644 index 0fa12d28f..000000000 --- a/src/Tests/Inference/generics/gh-1875.test +++ /dev/null @@ -1,28 +0,0 @@ - - */ -abstract class Test1 implements Iterator -{ -} - -/** - * @extends Test1 - */ -class Test2 extends Test1 -{ -} - -/** @var Test2 $a */ -foreach ($a as $key => $value) { - wrAssertType('string', $value); -} diff --git a/src/Tests/Inference/generics/gh-2295-test.test b/src/Tests/Inference/generics/gh-2295-test.test deleted file mode 100644 index 0f14555e6..000000000 --- a/src/Tests/Inference/generics/gh-2295-test.test +++ /dev/null @@ -1,49 +0,0 @@ - - */ -class ScheduleFactory extends Factory -{ - /** - * @param TModel $schedule - */ - public function __construct(Schedule $schedule) - { - } -} - -class Schedule extends Model -{ -} - -/** @extends ScheduleFactory */ -class ChildScheduleFactory extends ScheduleFactory -{ -} - -class ChildSchedule extends Schedule -{ -} - -$s = (new ScheduleFactory(new ChildSchedule()))->create(); -wrAssertType('ChildSchedule', $s); - -$s = (new ChildScheduleFactory())->create(); -wrAssertType('ChildSchedule', $s); diff --git a/src/Tests/Inference/generics/interface.test b/src/Tests/Inference/generics/interface.test deleted file mode 100644 index 5f7323fb2..000000000 --- a/src/Tests/Inference/generics/interface.test +++ /dev/null @@ -1,19 +0,0 @@ - - * @extends \Traversable - */ -interface ConstraintViolationListInterface extends \Traversable, \Countable, \ArrayAccess -{ -} - -function foo(ConstraintViolationListInterface $list) { - foreach ($list as $violation) { - wrAssertType('ConstraintViolationInterface', $violation); - } -} diff --git a/src/Tests/Inference/generics/iterable.test b/src/Tests/Inference/generics/iterable.test deleted file mode 100644 index e19cb2efa..000000000 --- a/src/Tests/Inference/generics/iterable.test +++ /dev/null @@ -1,7 +0,0 @@ - $foo */ -foreach ($foo as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/iterator1.test b/src/Tests/Inference/generics/iterator1.test deleted file mode 100644 index 0c1d71045..000000000 --- a/src/Tests/Inference/generics/iterator1.test +++ /dev/null @@ -1,14 +0,0 @@ - - */ -class Foobar implements Iterator { -} - -$foo = new Foobar(); - -foreach ($foo as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/iterator2.test b/src/Tests/Inference/generics/iterator2.test deleted file mode 100644 index 22759dd2b..000000000 --- a/src/Tests/Inference/generics/iterator2.test +++ /dev/null @@ -1,14 +0,0 @@ - - */ -class Foobar implements Iterator { -} - -$foo = new Foobar(); - -foreach ($foo as $bar) { - wrAssertType('string', $bar); -} - diff --git a/src/Tests/Inference/generics/iterator_aggregate1.test b/src/Tests/Inference/generics/iterator_aggregate1.test deleted file mode 100644 index 40d689983..000000000 --- a/src/Tests/Inference/generics/iterator_aggregate1.test +++ /dev/null @@ -1,14 +0,0 @@ - - */ -class Foobar implements IteratorAggregate { -} - -$foo = new Foobar(); - -foreach ($foo as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/iterator_aggregate2.test b/src/Tests/Inference/generics/iterator_aggregate2.test deleted file mode 100644 index 56f315ffa..000000000 --- a/src/Tests/Inference/generics/iterator_aggregate2.test +++ /dev/null @@ -1,14 +0,0 @@ -> - */ -class Foobar implements IteratorAggregate { -} - -$foo = new Foobar(); - -foreach ($foo as $bar) { - wrAssertType('array', $bar); -} - diff --git a/src/Tests/Inference/generics/method_generic.test b/src/Tests/Inference/generics/method_generic.test deleted file mode 100644 index 84fd825f0..000000000 --- a/src/Tests/Inference/generics/method_generic.test +++ /dev/null @@ -1,19 +0,0 @@ -bar('hello'); -wrAssertType('string', $res); diff --git a/src/Tests/Inference/generics/method_generic_class-string-2nd-arg.test b/src/Tests/Inference/generics/method_generic_class-string-2nd-arg.test deleted file mode 100644 index e3e119a4e..000000000 --- a/src/Tests/Inference/generics/method_generic_class-string-2nd-arg.test +++ /dev/null @@ -1,21 +0,0 @@ - $class - * @return T - */ - public function bar(string $foo, string $class) - { - return $foo; - } -} - - -$f = new Foo(); -$res = $f->bar('Hello', B::class); -wrAssertType('B', $res); diff --git a/src/Tests/Inference/generics/method_generic_class-string-union_return.test b/src/Tests/Inference/generics/method_generic_class-string-union_return.test deleted file mode 100644 index 8c727f981..000000000 --- a/src/Tests/Inference/generics/method_generic_class-string-union_return.test +++ /dev/null @@ -1,24 +0,0 @@ - $foo - * @return T - */ - public function bar(string ...$foo) - { - return $foo; - } -} - - -$f = new Foo(); -$res = $f->bar(A::class, B::class); -wrAssertType('A|B', $res); -$res = $f->bar(A::class, 'B'); -wrAssertType('A|B', $res); diff --git a/src/Tests/Inference/generics/method_returns_collection.test b/src/Tests/Inference/generics/method_returns_collection.test deleted file mode 100644 index 965428754..000000000 --- a/src/Tests/Inference/generics/method_returns_collection.test +++ /dev/null @@ -1,27 +0,0 @@ - - */ -class Collection implements IteratorAggregate { -} - -/** - * @extends Collection - */ -class Foobar extends Collection { -} - -class Test{ - public function foobar(): Foobar - { - } -} - -$foo = new Test(); - -foreach ($foo->foobar() as $bar) { - wrAssertType('int', $bar); -} - diff --git a/src/Tests/Inference/generics/method_returns_collection2.test b/src/Tests/Inference/generics/method_returns_collection2.test deleted file mode 100644 index f2b0b9ed3..000000000 --- a/src/Tests/Inference/generics/method_returns_collection2.test +++ /dev/null @@ -1,31 +0,0 @@ - - */ -interface Collection extends IteratorAggregate { -} - -/** - * @extends Collection - */ -interface Foobar extends Collection { -} - -class Test{ - public function foobar(): Foobar - { - } -} - -$foo = new Test(); - -foreach ($foo->foobar() as $bar) { - wrAssertType('Bar\Five', $bar); -} - diff --git a/src/Tests/Inference/generics/method_returns_templated_generic.test b/src/Tests/Inference/generics/method_returns_templated_generic.test deleted file mode 100644 index e6bf574fb..000000000 --- a/src/Tests/Inference/generics/method_returns_templated_generic.test +++ /dev/null @@ -1,37 +0,0 @@ - - */ -interface Collection -{ - /** - * @return T - */ - public function get(); -} - -/** - * @template T - */ -class Test -{ - /** - * @param T $foo - */ - public function __construct($foo) - { - } - /** - * @return Collection - */ - public function foobar(): Collection - { - } -} - -$foo = new Test(new \stdClass()); -wrAssertType('stdClass', $foo->foobar()->get()); diff --git a/src/Tests/Inference/generics/nullable_template_param.test b/src/Tests/Inference/generics/nullable_template_param.test deleted file mode 100644 index 11cab5285..000000000 --- a/src/Tests/Inference/generics/nullable_template_param.test +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class A extends AbstractFoo {} - -class B {} - -$a = new A(); -wrAssertType('?B', $a->foo()); diff --git a/src/Tests/Inference/generics/parameter.test b/src/Tests/Inference/generics/parameter.test deleted file mode 100644 index 0566c8816..000000000 --- a/src/Tests/Inference/generics/parameter.test +++ /dev/null @@ -1,26 +0,0 @@ - - */ -class Foo implements PluginInterface -{ - public function map(Mapper $mapper, object $entity, TransferInterface $transfer): void - { - wrAssertType('Bar', $transfer); - wrAssertType('Boo', $entity); - } -} diff --git a/src/Tests/Inference/generics/phpactor_reflection_collection.test b/src/Tests/Inference/generics/phpactor_reflection_collection.test deleted file mode 100644 index 42cc9434c..000000000 --- a/src/Tests/Inference/generics/phpactor_reflection_collection.test +++ /dev/null @@ -1,45 +0,0 @@ - - */ -interface ReflectionCollection extends \IteratorAggregate, \Countable -{ -} - -/** - * @template T of ReflectionMember - * @extends ReflectionCollection - */ -interface ReflectionMemberCollection extends ReflectionCollection -{ - /** - * @return ReflectionMemberCollection - */ - public function byName(string $name): ReflectionMemberCollection; -} - -/** - * @extends ReflectionMemberCollection - */ -interface ReflectionMethodCollection extends ReflectionMemberCollection -{ -} - -interface ReflectionClassLike -{ - public function methods(): ReflectionMethodCollection; -} - - -/** @var ReflectionClassLike $reflection */ -$reflection; -foreach ($reflection->methods()->byName('__construct') as $constructor) { - wrAssertType('Foo\ReflectionClassLike', $reflection); - wrAssertType('Foo\ReflectionMethodCollection', $reflection->methods()); - wrAssertType('Foo\ReflectionMemberCollection', $reflection->methods()->byName('foo')); - wrAssertType('Foo\ReflectionMethod', $constructor); -} diff --git a/src/Tests/Inference/generics/phpactor_reflection_of_type.test b/src/Tests/Inference/generics/phpactor_reflection_of_type.test deleted file mode 100644 index 5f0f2a2cd..000000000 --- a/src/Tests/Inference/generics/phpactor_reflection_of_type.test +++ /dev/null @@ -1,45 +0,0 @@ - - */ -interface ReflectionCollection extends \IteratorAggregate, \Countable -{ -} - -/** - * @template T of ReflectionMember - * @extends ReflectionCollection - */ -interface ReflectionMemberCollection extends ReflectionCollection -{ - /** - * @return ReflectionMemberCollection - */ - public function byName(string $name): ReflectionMemberCollection; - - /** - * @return ReflectionMemberCollection - */ - public function byMemberType(string $type): ReflectionMemberCollection; -} - -interface ReflectionClassLike -{ - public function members(): ReflectionMemberCollection; -} - - -/** @var ReflectionClassLike $reflection */ -$reflection; -foreach ($reflection->members()->byMemberType('fii')->byName('__construct') as $constructor) { - wrAssertType('Foo\ReflectionMemberCollection', $reflection->members()->byName('foo')->byMemberType('asd')); - wrAssertType('Foo\ReflectionClassLike', $reflection); - wrAssertType('Foo\ReflectionMemberCollection', $reflection->members()); - - - wrAssertType('Foo\ReflectionMember', $constructor); -} diff --git a/src/Tests/Inference/generics/type_from_template_in_class.test b/src/Tests/Inference/generics/type_from_template_in_class.test deleted file mode 100644 index 5c70ffbce..000000000 --- a/src/Tests/Inference/generics/type_from_template_in_class.test +++ /dev/null @@ -1,17 +0,0 @@ - - */ - private array $collections = []; - - public function foobar(){ - wrAssertType('ReflectionMemberCollection[]', $this->collections); - } -} - diff --git a/src/Tests/Inference/global/global_keyword.test b/src/Tests/Inference/global/global_keyword.test deleted file mode 100644 index 0e2767e6b..000000000 --- a/src/Tests/Inference/global/global_keyword.test +++ /dev/null @@ -1,8 +0,0 @@ - ', $foobar); - die('asd'); -} -wrAssertType('Foobar', $foobar); diff --git a/src/Tests/Inference/if-statement/bangbang.test b/src/Tests/Inference/if-statement/bangbang.test deleted file mode 100644 index 620220918..000000000 --- a/src/Tests/Inference/if-statement/bangbang.test +++ /dev/null @@ -1,8 +0,0 @@ -', $foobar); diff --git a/src/Tests/Inference/if-statement/die.test b/src/Tests/Inference/if-statement/die.test deleted file mode 100644 index c0a069358..000000000 --- a/src/Tests/Inference/if-statement/die.test +++ /dev/null @@ -1,15 +0,0 @@ -', $foobar); - die(); -} - -wrAssertType('Foobar', $foobar); diff --git a/src/Tests/Inference/if-statement/if_or.test b/src/Tests/Inference/if-statement/if_or.test deleted file mode 100644 index 2b428fd85..000000000 --- a/src/Tests/Inference/if-statement/if_or.test +++ /dev/null @@ -1,5 +0,0 @@ -', $foobar); diff --git a/src/Tests/Inference/if-statement/instanceof_removes_null.test b/src/Tests/Inference/if-statement/instanceof_removes_null.test deleted file mode 100644 index 2e25fccf7..000000000 --- a/src/Tests/Inference/if-statement/instanceof_removes_null.test +++ /dev/null @@ -1,8 +0,0 @@ -', $foobar); - diff --git a/src/Tests/Inference/if-statement/multiple_statements.test b/src/Tests/Inference/if-statement/multiple_statements.test deleted file mode 100644 index 1571494ab..000000000 --- a/src/Tests/Inference/if-statement/multiple_statements.test +++ /dev/null @@ -1,25 +0,0 @@ -', $foo); diff --git a/src/Tests/Inference/if-statement/no_vars.test b/src/Tests/Inference/if-statement/no_vars.test deleted file mode 100644 index e7a8d6d87..000000000 --- a/src/Tests/Inference/if-statement/no_vars.test +++ /dev/null @@ -1,8 +0,0 @@ -', $f); diff --git a/src/Tests/Inference/if-statement/nullable.test b/src/Tests/Inference/if-statement/nullable.test deleted file mode 100644 index d87e0b204..000000000 --- a/src/Tests/Inference/if-statement/nullable.test +++ /dev/null @@ -1,11 +0,0 @@ -bar instanceof Bar) { - wrAssertType('Bar', $this->bar); - } - } -} - diff --git a/src/Tests/Inference/if-statement/property_negated.test b/src/Tests/Inference/if-statement/property_negated.test deleted file mode 100644 index 7d26cc84d..000000000 --- a/src/Tests/Inference/if-statement/property_negated.test +++ /dev/null @@ -1,16 +0,0 @@ -bar instanceof Bar) { - return; - } - - wrAssertType('Bar', $this->bar); - } -} - diff --git a/src/Tests/Inference/if-statement/remove_null_type1.test b/src/Tests/Inference/if-statement/remove_null_type1.test deleted file mode 100644 index 8a79c76c4..000000000 --- a/src/Tests/Inference/if-statement/remove_null_type1.test +++ /dev/null @@ -1,10 +0,0 @@ -', $foobar); -} diff --git a/src/Tests/Inference/if-statement/type_after_exception.test b/src/Tests/Inference/if-statement/type_after_exception.test deleted file mode 100644 index 802915dbc..000000000 --- a/src/Tests/Inference/if-statement/type_after_exception.test +++ /dev/null @@ -1,7 +0,0 @@ -', $foobar); - diff --git a/src/Tests/Inference/if-statement/type_after_return.test b/src/Tests/Inference/if-statement/type_after_return.test deleted file mode 100644 index 4a5783a32..000000000 --- a/src/Tests/Inference/if-statement/type_after_return.test +++ /dev/null @@ -1,13 +0,0 @@ -', $foobar); - diff --git a/src/Tests/Inference/if-statement/union_and_else.test b/src/Tests/Inference/if-statement/union_and_else.test deleted file mode 100644 index 2adc06999..000000000 --- a/src/Tests/Inference/if-statement/union_and_else.test +++ /dev/null @@ -1,12 +0,0 @@ -', $foobar); - diff --git a/src/Tests/Inference/if-statement/union_or_else.test b/src/Tests/Inference/if-statement/union_or_else.test deleted file mode 100644 index e3ca0f20c..000000000 --- a/src/Tests/Inference/if-statement/union_or_else.test +++ /dev/null @@ -1,25 +0,0 @@ -timeline instanceof Trip) { - wrAssertType('string', $this->timeline->getLinkedCustomer()); - } - } -} - diff --git a/src/Tests/Inference/member-access/class-constant-glob-array-shape.test b/src/Tests/Inference/member-access/class-constant-glob-array-shape.test deleted file mode 100644 index 88b04928c..000000000 --- a/src/Tests/Inference/member-access/class-constant-glob-array-shape.test +++ /dev/null @@ -1,16 +0,0 @@ -s()); - - diff --git a/src/Tests/Inference/member-access/class-constant-glob-self.test b/src/Tests/Inference/member-access/class-constant-glob-self.test deleted file mode 100644 index 2e5d84bf9..000000000 --- a/src/Tests/Inference/member-access/class-constant-glob-self.test +++ /dev/null @@ -1,18 +0,0 @@ -s()); - - diff --git a/src/Tests/Inference/member-access/nested_trait.test b/src/Tests/Inference/member-access/nested_trait.test deleted file mode 100644 index ad1426a5d..000000000 --- a/src/Tests/Inference/member-access/nested_trait.test +++ /dev/null @@ -1,22 +0,0 @@ -onTraitOne()); -wrAssertType('string', $one->onTraitTwo()); -wrAssertType('int', $one->onTraitThree()); diff --git a/src/Tests/Inference/null-coalesce/null-coalesce_null.test b/src/Tests/Inference/null-coalesce/null-coalesce_null.test deleted file mode 100644 index ae9dca23a..000000000 --- a/src/Tests/Inference/null-coalesce/null-coalesce_null.test +++ /dev/null @@ -1,7 +0,0 @@ -$name(...$arguments); - } -} - -$b = new B(); -wrAssertType('void', $b->doB()); -wrAssertType('string', $b->doA()); diff --git a/src/Tests/Inference/reflection/mixin_generic.test b/src/Tests/Inference/reflection/mixin_generic.test deleted file mode 100644 index 89f760fa9..000000000 --- a/src/Tests/Inference/reflection/mixin_generic.test +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class B -{ -} - -$b = new B(); -wrAssertType('bool', $b->toBeTrue()); diff --git a/src/Tests/Inference/reflection/mixin_properties.test b/src/Tests/Inference/reflection/mixin_properties.test deleted file mode 100644 index 9ed03a646..000000000 --- a/src/Tests/Inference/reflection/mixin_properties.test +++ /dev/null @@ -1,16 +0,0 @@ -foo); diff --git a/src/Tests/Inference/reflection/mixin_recursive.test b/src/Tests/Inference/reflection/mixin_recursive.test deleted file mode 100644 index 441fefa91..000000000 --- a/src/Tests/Inference/reflection/mixin_recursive.test +++ /dev/null @@ -1,26 +0,0 @@ -doA()); diff --git a/src/Tests/Inference/reflection/mixin_static.test b/src/Tests/Inference/reflection/mixin_static.test deleted file mode 100644 index d77294087..000000000 --- a/src/Tests/Inference/reflection/mixin_static.test +++ /dev/null @@ -1,23 +0,0 @@ -doA()); diff --git a/src/Tests/Inference/reflection/multiple_mixins.test b/src/Tests/Inference/reflection/multiple_mixins.test deleted file mode 100644 index c323b2f58..000000000 --- a/src/Tests/Inference/reflection/multiple_mixins.test +++ /dev/null @@ -1,37 +0,0 @@ -$name(...$arguments); - } -} - -$b = new B(); -wrAssertType('int', $b->doC()); -wrAssertType('void', $b->doB()); -wrAssertType('string', $b->doA()); diff --git a/src/Tests/Inference/reflection/promoted_property_with_params.test b/src/Tests/Inference/reflection/promoted_property_with_params.test deleted file mode 100644 index 04bbb6e59..000000000 --- a/src/Tests/Inference/reflection/promoted_property_with_params.test +++ /dev/null @@ -1,17 +0,0 @@ - $tags - */ - public function __construct( - public Location $location = new Location(), - public array $tags = [], - ) {} - - public function bar() - { - wrAssertType('array', $this->tags); - } -} - diff --git a/src/Tests/Inference/reflection/virtial_static_method.test b/src/Tests/Inference/reflection/virtial_static_method.test deleted file mode 100644 index d245ce7d4..000000000 --- a/src/Tests/Inference/reflection/virtial_static_method.test +++ /dev/null @@ -1,10 +0,0 @@ - sendMessage(string $text) - */ -class Mailer -{ -} - -wrAssertType('Promise', Mailer::sendMessage('foo')); diff --git a/src/Tests/Inference/require_and_include/foo.php b/src/Tests/Inference/require_and_include/foo.php deleted file mode 100644 index 0fd1c8df5..000000000 --- a/src/Tests/Inference/require_and_include/foo.php +++ /dev/null @@ -1,7 +0,0 @@ -'); -} - -function bar() -{ -} diff --git a/src/Tests/Inference/return-statement/multiple_return.test b/src/Tests/Inference/return-statement/multiple_return.test deleted file mode 100644 index 4280d99fc..000000000 --- a/src/Tests/Inference/return-statement/multiple_return.test +++ /dev/null @@ -1,13 +0,0 @@ - - */ -$arr = ['hello' => 'world']; - -/** @return array{name: string, data: list} */ -function data() -{ - // ... -} - - -$data = data(); - -$list = $data['data']; -wrAssertType('array', $list); diff --git a/src/Tests/Inference/ternary_expression/for_missing.test b/src/Tests/Inference/ternary_expression/for_missing.test deleted file mode 100644 index ec8edfab5..000000000 --- a/src/Tests/Inference/ternary_expression/for_missing.test +++ /dev/null @@ -1,3 +0,0 @@ -|stdClass', $barfoo ?: new \stdClass()); diff --git a/src/Tests/Inference/ternary_expression/use_if_branch_if_truthy.test b/src/Tests/Inference/ternary_expression/use_if_branch_if_truthy.test deleted file mode 100644 index 63e799c58..000000000 --- a/src/Tests/Inference/ternary_expression/use_if_branch_if_truthy.test +++ /dev/null @@ -1,7 +0,0 @@ -', $bar); -wrAssertType('string', $bar::demo()); diff --git a/src/Tests/Inference/type/class-string.test b/src/Tests/Inference/type/class-string.test deleted file mode 100644 index ab9f28cc8..000000000 --- a/src/Tests/Inference/type/class-string.test +++ /dev/null @@ -1,5 +0,0 @@ -', $f); diff --git a/src/Tests/Inference/type/closure.test b/src/Tests/Inference/type/closure.test deleted file mode 100644 index 87282eb1f..000000000 --- a/src/Tests/Inference/type/closure.test +++ /dev/null @@ -1,8 +0,0 @@ -map('foo', 'bar'); - - wrAssertType('float', $map); -} diff --git a/src/Tests/Inference/type/conditional-type-on-function.test b/src/Tests/Inference/type/conditional-type-on-function.test deleted file mode 100644 index b7c8fcaa4..000000000 --- a/src/Tests/Inference/type/conditional-type-on-function.test +++ /dev/null @@ -1,19 +0,0 @@ - - * ? int - * : ($array is array - * ? float - * : float|int - * ) - * ) - */ -function array_some(array $array) {} - -wrAssertType('float|int', array_some([])); -wrAssertType('int', array_some([1])); -wrAssertType('float', array_some([1.2])); diff --git a/src/Tests/Inference/type/conditional-type.test b/src/Tests/Inference/type/conditional-type.test deleted file mode 100644 index 21585023e..000000000 --- a/src/Tests/Inference/type/conditional-type.test +++ /dev/null @@ -1,28 +0,0 @@ - $signature - * @param mixed $source - * @return ( - * $signature is class-string - * ? T - * : mixed - * ) - * - * @throws MappingError - */ - public function map(string $signature, $source); -} - -function foo(TreeMapper $mapper) { - $map = $mapper->map('foo', 'bar'); - wrAssertType('mixed', $map); - - $map = $mapper->map(Foo::class, 'bar'); - wrAssertType('Foo', $map); -} diff --git a/src/Tests/Inference/type/conditional-type2.test b/src/Tests/Inference/type/conditional-type2.test deleted file mode 100644 index 4fe3dd73d..000000000 --- a/src/Tests/Inference/type/conditional-type2.test +++ /dev/null @@ -1,17 +0,0 @@ -map('foo', 'bar'); - - wrAssertType('string', $map); -} diff --git a/src/Tests/Inference/type/conditional-type3.test b/src/Tests/Inference/type/conditional-type3.test deleted file mode 100644 index 8df220d9a..000000000 --- a/src/Tests/Inference/type/conditional-type3.test +++ /dev/null @@ -1,17 +0,0 @@ -map('bar', 'bar'); - - wrAssertType('int', $map); -} diff --git a/src/Tests/Inference/type/gh-1707.test b/src/Tests/Inference/type/gh-1707.test deleted file mode 100644 index 58617b442..000000000 --- a/src/Tests/Inference/type/gh-1707.test +++ /dev/null @@ -1,8 +0,0 @@ - $minMax - * @param int<1,max> $max - * @param int $min - * @param int<1, 2> $range - */ -function foo(int $minMax, int $max, int $min, int $range) { - wrAssertType('int', $minMax); - wrAssertType('int', $min); - wrAssertType('int<1, max>', $max); - wrAssertType('int<1, 2>', $range); -} - - diff --git a/src/Tests/Inference/type/list.test b/src/Tests/Inference/type/list.test deleted file mode 100644 index 364737223..000000000 --- a/src/Tests/Inference/type/list.test +++ /dev/null @@ -1,17 +0,0 @@ - $foo - */ -function listWithArgument(array $foo): void -{ - wrAssertType('array', $foo); -} - -/** - * @param list $foo - */ -function listWithoutArgument(array $foo): void -{ - wrAssertType('array', $foo); -} diff --git a/src/Tests/Inference/type/never.test b/src/Tests/Inference/type/never.test deleted file mode 100644 index 66653fef7..000000000 --- a/src/Tests/Inference/type/never.test +++ /dev/null @@ -1,16 +0,0 @@ -baz()); diff --git a/src/Tests/Inference/type/static_context.test b/src/Tests/Inference/type/static_context.test deleted file mode 100644 index 3eac72c20..000000000 --- a/src/Tests/Inference/type/static_context.test +++ /dev/null @@ -1,8 +0,0 @@ -baz()); diff --git a/src/Tests/Inference/type/string-literal.test b/src/Tests/Inference/type/string-literal.test deleted file mode 100644 index 67e6b0f91..000000000 --- a/src/Tests/Inference/type/string-literal.test +++ /dev/null @@ -1,2 +0,0 @@ -$a = "abc"; -wrAssertType('"abc"', $a); diff --git a/src/Tests/Inference/type/union_from_relative_docblock.test b/src/Tests/Inference/type/union_from_relative_docblock.test deleted file mode 100644 index 21094d2af..000000000 --- a/src/Tests/Inference/type/union_from_relative_docblock.test +++ /dev/null @@ -1,11 +0,0 @@ -typeDeclarationList); diff --git a/src/Tests/Inference/type/variadic.test b/src/Tests/Inference/type/variadic.test deleted file mode 100644 index 17372c42c..000000000 --- a/src/Tests/Inference/type/variadic.test +++ /dev/null @@ -1,5 +0,0 @@ -reflectClass('ClassOne'); -$methods = $reflection->methods(); -$method = $methods->get('foobar'); -wrAssertType('Wr\Reflector', $reflector); -wrAssertType('Wr\ReflectionClass', $reflection); -wrAssertType('Wr\MethodCollection', $methods); -wrAssertType('Wr\ReflectionMethod', $method); - diff --git a/src/Tests/Inference/virtual_member/method2.test b/src/Tests/Inference/virtual_member/method2.test deleted file mode 100644 index 517e6fd51..000000000 --- a/src/Tests/Inference/virtual_member/method2.test +++ /dev/null @@ -1,41 +0,0 @@ - - */ -interface MethodCollection extends MemberCollection -{ -} - -class Reflector -{ - public function reflectClass(string $name): ReflectionClass; -} - -/** @var Reflector $reflector */ -$reflector; - -$reflection = $reflector->reflectClass('ClassOne'); -$methods = $reflection->methods(); -$method = $methods->get('foobar'); -wrAssertType('Wr\Reflector', $reflector); -wrAssertType('Wr\ReflectionMethod', $method); - diff --git a/src/Tests/Inference/virtual_member/method_and_property_with_same_name.test b/src/Tests/Inference/virtual_member/method_and_property_with_same_name.test deleted file mode 100644 index a62c2f990..000000000 --- a/src/Tests/Inference/virtual_member/method_and_property_with_same_name.test +++ /dev/null @@ -1,10 +0,0 @@ -foo()); diff --git a/src/Tests/Inference/virtual_member/property.test b/src/Tests/Inference/virtual_member/property.test deleted file mode 100644 index b839fd16e..000000000 --- a/src/Tests/Inference/virtual_member/property.test +++ /dev/null @@ -1,11 +0,0 @@ -get); diff --git a/src/Tests/Inference/virtual_member/trait_method1.test b/src/Tests/Inference/virtual_member/trait_method1.test deleted file mode 100644 index f151397f9..000000000 --- a/src/Tests/Inference/virtual_member/trait_method1.test +++ /dev/null @@ -1,16 +0,0 @@ -sayHello()); - } -} diff --git a/src/Tests/Inference/virtual_member/virtual-method-returns-static.test b/src/Tests/Inference/virtual_member/virtual-method-returns-static.test deleted file mode 100644 index 6c8642a0d..000000000 --- a/src/Tests/Inference/virtual_member/virtual-method-returns-static.test +++ /dev/null @@ -1,18 +0,0 @@ -sendMessage('foo')); - -$coolMailer = new CoolMailer(); -wrAssertType('CoolMailer', $coolMailer->sendMessage('foo')); diff --git a/src/Tests/Inference/virtual_member/virtual-method-returns-this.test b/src/Tests/Inference/virtual_member/virtual-method-returns-this.test deleted file mode 100644 index 0379befc8..000000000 --- a/src/Tests/Inference/virtual_member/virtual-method-returns-this.test +++ /dev/null @@ -1,18 +0,0 @@ -sendMessage('foo')); -t ,fjk -$coolMailer = new CoolMailer(); -wrAssertType('CoolMailer', $coolMailer->sendMessage('foo')); diff --git a/src/Tests/Integration/Bridge/Composer/ComposerSourceCodeLocatorTest.php b/src/Tests/Integration/Bridge/Composer/ComposerSourceCodeLocatorTest.php deleted file mode 100644 index 0d6c515f4..000000000 --- a/src/Tests/Integration/Bridge/Composer/ComposerSourceCodeLocatorTest.php +++ /dev/null @@ -1,18 +0,0 @@ -locate(Name::fromString(__CLASS__)); - $this->assertSame(file_get_contents(__FILE__), (string) $code); - } -} diff --git a/src/Tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php b/src/Tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php deleted file mode 100644 index a9d0cae29..000000000 --- a/src/Tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php +++ /dev/null @@ -1,39 +0,0 @@ -locator = new ClassToFileSourceLocator($classToFile); - } - - /** - * It should locate source. - */ - public function testLocator(): void - { - $source = $this->locator->locate(ClassName::fromString(__CLASS__)); - $this->assertEquals(file_get_contents(__FILE__), (string) $source); - $this->assertEquals(__FILE__, $source->uri()->path()); - } - - /** - * It should throw an exception if class was not found. - */ - public function testLocateNotFound(): void - { - $this->expectException(SourceNotFound::class); - $this->locator->locate(ClassName::fromString('asdDSA')); - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php deleted file mode 100644 index 31afdbe0f..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php +++ /dev/null @@ -1,58 +0,0 @@ -createReflector($source)->reflectClassesIn(TextDocumentBuilder::create($source)->build()); - $assertion($collection); - } - - public function provideCollection() - { - return [ - 'It has all the classes' => [ - <<<'EOT' - assertEquals(2, $collection->count()); - }, - ], - 'It reflects nested classes' => [ - <<<'EOT' - assertEquals(1, $collection->count()); - }, - ], - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionMethodCollectionTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionMethodCollectionTest.php deleted file mode 100644 index ec1efc994..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionMethodCollectionTest.php +++ /dev/null @@ -1,63 +0,0 @@ -createReflector($source)->reflectClass('Foobar'); - $assertion($collection); - } - - public function provideCollection() - { - yield 'Get abstract methods' => [ - <<<'EOT' - assertEquals(2, $class->methods()->abstract()->count()); - }, - ]; - - yield 'Get abstract methods with virtual methods' => [ - <<<'EOT' - assertEquals(2, $class->methods()->abstract()->count()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionParameterCollectionTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionParameterCollectionTest.php deleted file mode 100644 index 28832204e..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/Collection/ReflectionParameterCollectionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -createReflector($source)->reflectClassesIn($source)->first()->methods()->first()->parameters(); - $assertion($collection); - } - - public function provideCollection() - { - return [ - 'returns promoted parameters' => [ - <<<'EOT' - assertEquals(3, $collection->count()); - $this->assertEquals(1, $collection->notPromoted()->count()); - $this->assertEquals(2, $collection->promoted()->count()); - }, - ], - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionArgumentTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionArgumentTest.php deleted file mode 100644 index 89af0add5..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionArgumentTest.php +++ /dev/null @@ -1,153 +0,0 @@ -reflectMethodCall(TextDocumentBuilder::create($source)->build(), $offset); - $assertion($reflection->arguments()); - } - - public static function provideReflectionMethod():Generator - { - yield 'It guesses the name from the var name' => [ - <<<'EOT' - b<>ar($foo); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('foo', $arguments->first()->guessName()); - }, - ]; - yield 'It returns a named argument' => [ - <<<'EOT' - b<>ar(foo: 'hello'); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('"hello"', $arguments->get('foo')->type()->__toString()); - }, - ]; - yield 'It returns node context' => [ - <<<'EOT' - b<>ar(foo: 'hello'); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertInstanceOf(NodeContext::class, $arguments->get('foo')->nodeContext()); - }, - ]; - yield 'It guesses the name from return type' => [ - <<<'EOT' - b<>ar($foo->bob()); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('alice', $arguments->first()->guessName()); - }, - ]; - yield 'It returns a generated name if it cannot be determined' => [ - <<<'EOT' - b<>ar($foo->bob(), $foo->zed()); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('argument0', $arguments->first()->guessName()); - self::assertEquals('argument1', $arguments->last()->guessName()); - }, - ]; - yield 'It returns the argument type' => [ - <<<'EOT' - b<>ar($integer); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('1', (string) $arguments->first()->type()); - }, - ]; - yield 'It returns the value' => [ - <<<'EOT' - b<>ar($integer); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals(1, $arguments->first()->value()); - }, - ]; - yield 'It returns the position' => [ - <<<'EOT' - b<>ar($integer); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals(17, $arguments->first()->position()->start()->toInt()); - self::assertEquals(25, $arguments->first()->position()->end()->toInt()); - }, - ]; - yield 'It infers named parameters' => [ - <<<'EOT' - b<>ar(foo: $integer); - EOT - , [ - ], - function (ReflectionArgumentCollection $arguments): void { - self::assertEquals('foo', $arguments->first()->guessName()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionClassTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionClassTest.php deleted file mode 100644 index 92121c889..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionClassTest.php +++ /dev/null @@ -1,1193 +0,0 @@ -expectException(\Phpactor\WorseReflection\Core\Exception\ClassNotFound::class); - $this->createReflector('')->reflectClassLike(ClassName::fromString('Foobar')); - } - - /** - * @dataProvider provideReflectionClass - */ - public function testReflectClass(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - /** - * @return Generator - */ - public function provideReflectionClass(): Generator - { - yield 'It reflects an empty class' => [ - <<<'EOT' - assertEquals('Foobar', (string) $class->name()->short()); - $this->assertInstanceOf(ReflectionClass::class, $class); - $this->assertFalse($class->isInterface()); - }, - ]; - - yield 'It reflects a class which extends another' => [ - <<<'EOT' - assertEquals('Foobar', (string) $class->name()->short()); - $this->assertEquals('Barfoo', (string) $class->parent()->name()->short()); - }, - ]; - - yield 'It reflects class constants' => [ - <<<'EOT' - assertCount(3, $class->constants()); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('FOOBAR')); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('EEEBAR')); - }, - ]; - - yield 'It can provide the name of its last member' => [ - <<<'EOT' - assertEquals('bar', $class->properties()->last()->name()); - }, - ]; - - yield 'It can provide the name of its first member' => [ - <<<'EOT' - assertEquals('foo', $class->properties()->first()->name()); - }, - ]; - - yield 'It can provide its position' => [ - <<<'EOT' - assertEquals(7, $class->position()->start()->toInt()); - }, - ]; - - yield 'It can provide the position of its member declarations' => [ - <<<'EOT' - assertEquals(20, $class->memberListPosition()->start()->toInt()); - }, - ]; - - yield 'It provides list of its interfaces' => [ - <<<'EOT' - assertEquals(1, $class->interfaces()->count()); - $this->assertEquals('InterfaceOne', $class->interfaces()->first()->name()); - }, - ]; - - yield 'It list of interfaces includes interfaces from parent classes' => [ - <<<'EOT' - assertEquals(1, $class->interfaces()->count()); - $this->assertEquals('InterfaceOne', $class->interfaces()->first()->name()); - }, - ]; - - yield 'It provides list of its traits' => [ - <<<'EOT' - assertEquals(2, $class->traits()->count()); - $this->assertEquals('TraitNUMBERone', $class->traits()->get('TraitNUMBERone')->name()); - $this->assertEquals('TraitNUMBERtwo', $class->traits()->get('TraitNUMBERtwo')->name()); - }, - ]; - - yield 'Traits are inherited from parent classes (?)' => [ - <<<'EOT' - assertEquals(1, $class->traits()->count()); - $this->assertEquals('TraitNUMBERone', $class->traits()->first()->name()); - }, - ]; - - yield 'Get methods includes trait methods' => [ - <<<'EOT' - assertEquals(3, $class->methods()->count()); - $this->assertTrue($class->methods()->has('traitMethod1')); - $this->assertTrue($class->methods()->has('traitMethod2')); - }, - ]; - - yield 'Tolerates not found traits' => [ - <<<'EOT' - assertEquals(1, $class->methods()->count()); - }, - ]; - - yield 'Get methods includes aliased trait methods' => [ - <<<'EOT' - assertEquals(4, $class->methods()->count()); - $this->assertTrue($class->methods()->has('one')); - $this->assertTrue($class->methods()->has('two')); - $this->assertTrue($class->methods()->has('three')); - $this->assertTrue($class->methods()->has('four')); - $this->assertEquals(Visibility::private(), $class->methods()->get('two')->visibility()); - $this->assertEquals(Visibility::protected(), $class->methods()->get('three')->visibility()); - $this->assertFalse($class->methods()->belongingTo(ClassName::fromString('Class2'))->has('two')); - $this->assertEquals('TraitOne', $class->methods()->get('two')->declaringClass()->name()->short()); - }, - ]; - - yield 'Get methods includes namespaced aliased trait methods' => [ - <<<'EOT' - assertEquals(3, $class->methods()->count()); - $this->assertTrue($class->methods()->has('one')); - $this->assertTrue($class->methods()->has('three')); - }, - ]; - - yield 'Get trait properties' => [ - <<<'EOT' - assertEquals(1, $class->properties()->count()); - $this->assertEquals('prop1', $class->properties()->first()->name()); - }, - ]; - - yield 'Get methods at offset' => [ - <<<'EOT' - assertEquals(1, $class->methods()->atOffset(27)->count()); - }, - ]; - - yield 'Get properties includes trait properties' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - }, - ]; - - yield 'Get properties for belonging to' => [ - <<<'EOT' - assertCount(1, $class->properties()->belongingTo(ClassName::fromString('Class1'))); - $this->assertCount(0, $class->properties()->belongingTo(ClassName::fromString('Class2'))); - }, - ]; - - - yield 'If it extends an interface, then ignore' => [ - <<<'EOT' - assertEquals(0, $class->methods()->count()); - }, - ]; - - - yield 'isInstanceOf returns false when it is not an instance of' => [ - <<<'EOT' - assertFalse($class->isInstanceOf(ClassName::fromString('Foobar'))); - }, - ]; - - yield 'isInstanceOf returns true for itself' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('Class2'))); - }, - ]; - - yield 'isInstanceOf returns true when it is not an instance of an interface' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeInterface'))); - }, - ]; - - yield 'isInstanceOf returns true when a class implements the interface and has a parent' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeInterface'))); - }, - ]; - - yield 'isInstanceOf returns true for a parent class' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('SomeParent'))); - }, - ]; - - yield 'Returns source code' => [ - <<<'EOT' - assertStringContainsString('class Class2', (string) $class->sourceCode()); - }, - ]; - - yield 'Returns imported classes' => [ - <<<'EOT' - assertEquals(NameImports::fromNames([ - 'Barfoo' => Name::fromString('Foobar\\Barfoo'), - 'Carzatz' => Name::fromString('Barfoo\\Foobaz'), - ]), $class->scope()->nameImports()); - }, - ]; - - yield 'Inherits constants from interface' => [ - <<<'EOT' - assertCount(1, $class->constants()); - $this->assertEquals('SOME_CONSTANT', $class->constants()->get('SOME_CONSTANT')->name()); - }, - ]; - - yield 'Returns all members' => [ - <<<'EOT' - assertCount(3, $class->members()); - $this->assertTrue($class->members()->has('FOOBAR')); - $this->assertTrue($class->members()->has('foobar')); - $this->assertTrue($class->members()->has('foo')); - }, - ]; - - yield 'Incomplete extends' => [ - <<<'EOT' - assertNull($class->parent()); - $this->assertEquals('Class1', $class->name()->short()); - }, - ]; - - yield 'Does not infinite loop with self-referencing class on get interfaces' => [ - <<<'EOT' - assertCount(0, $class->interfaces()); - }, - ]; - - yield 'Says if class is abstract' => [ - <<<'EOT' - assertTrue($class->isAbstract()); - }, - ]; - - yield 'Says if class is not abstract' => [ - <<<'EOT' - assertFalse($class->isAbstract()); - }, - ]; - - yield 'Says if class is final' => [ - <<<'EOT' - assertTrue($class->isFinal()); - }, - ]; - - yield 'Says if class is deprecated' => [ - <<<'EOT' - assertTrue($class->deprecation()->isDefined()); - }, - ]; - } - - /** - * @dataProvider provideVirtualMethods - */ - public function testVirtualMethods(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - /** - * @return Generator - */ - public function provideVirtualMethods(): Generator - { - yield 'virtual methods' => [ - <<<'EOT' - assertEquals(2, $class->methods()->count()); - $this->assertEquals('foobar', $class->methods()->first()->name()); - } - ]; - - yield 'virtual methods merge onto existing ones' => [ - <<<'EOT' - assertCount(1, $class->methods()); - - // originally this returned the declared type - $this->assertEquals( - 'Foobar', - $class->methods()->first()->type()->__toString(), - ); - $this->assertEquals( - 'Foobar', - $class->methods()->first()->inferredType()->__toString(), - ); - }, - ]; - - yield 'virtual methods are inherited' => [ - <<<'EOT' - assertCount(2, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from interface' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from multiple layers of interfaces' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are inherited from parent class which implements interface' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - $this->assertEquals( - 'ParentInterface', - $class->methods()->get('foobar')->declaringClass()->name()->__toString() - ); - }, - ]; - - yield 'virtual method types can be relative' => [ - 'assertEquals( - 'Bosh\Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual method types can be absolute' => [ - 'assertEquals( - 'Foobar', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods of child classes override those of parents' => [ - <<<'EOT' - assertCount(2, $class->methods()); - $this->assertEquals( - 'Barfoo', - $class->methods()->get('foobar')->inferredType()->__toString() - ); - }, - ]; - - yield 'virtual methods are extracted from traits' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals('Foobar', $class->methods()->first()->inferredType()->__toString()); - }, - ]; - - yield 'virtual methods are extracted from traits of a parent class' => [ - <<<'EOT' - assertCount(1, $class->methods()); - $this->assertEquals('Foobar', $class->methods()->first()->inferredType()->__toString()); - }, - ]; - } - - /** - * @dataProvider provideVirtualProperties - */ - public function testVirtualProperties(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - /** - * @return Generator - */ - public function provideVirtualProperties(): Generator - { - yield 'virtual properties' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - } - ]; - - yield 'invalid properties' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - } - ]; - - yield 'multiple types' => [ - <<<'EOT' - assertEquals(1, $class->properties()->count()); - self::assertInstanceOf(UnionType::class, $class->properties()->first()->type()); - self::assertEquals('string|int', $class->properties()->first()->type()); - } - ]; - - yield 'virtual properties are extracted from traits' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - $this->assertEquals('Foobar', $class->properties()->first()->inferredType()->__toString()); - $this->assertEquals('barfoo', $class->properties()->last()->name()); - $this->assertEquals('Barfoo', $class->properties()->last()->inferredType()->__toString()); - } - ]; - - yield 'virtual properties are extracted from traits of a parent class' => [ - <<<'EOT' - assertEquals(2, $class->properties()->count()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - $this->assertEquals('Foobar', $class->properties()->first()->inferredType()->__toString()); - $this->assertEquals('barfoo', $class->properties()->last()->name()); - $this->assertEquals('Barfoo', $class->properties()->last()->inferredType()->__toString()); - } - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionConstantTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionConstantTest.php deleted file mode 100644 index f7933eb95..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionConstantTest.php +++ /dev/null @@ -1,198 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - assert($class instanceof ReflectionClass); - $assertion($class->constants()); - } - - public function provideReflectionConstant() - { - yield 'Returns declaring class' => [ - <<<'EOT' - assertEquals('Foobar', $constants->get('FOOBAR')->declaringClass()->name()->__toString()); - $this->assertEquals(Visibility::public(), $constants->first()->visibility()); - } - ]; - - yield 'Returns original member' => [ - <<<'EOT' - assertEquals('Barfoo', $constants->get('FOOBAR')->original()->declaringClass()->name()->__toString()); - $this->assertEquals(Visibility::public(), $constants->first()->visibility()); - } - ]; - - yield 'Returns visibility' => [ - <<<'EOT' - assertEquals(Visibility::private(), $constants->first()->visibility()); - } - ]; - - yield 'Returns docblock' => [ - <<<'EOT' - assertStringContainsString('/** Hello! */', $constants->first()->docblock()->raw()); - } - ]; - - yield 'Returns type' => [ - <<<'EOT' - assertEquals('"foobar"', $constants->first()->type()->__toString()); - } - ]; - - yield 'Doesnt return inferred retutrn types (not implemented)' => [ - <<<'EOT' - assertEquals('"foobar"', $constants->first()->inferredType()); - } - ]; - - yield 'Delimited constant list' => [ - <<<'EOT' - assertCount(2, $constants); - } - ]; - - yield 'returns value' => [ - <<<'EOT' - first(); - self::assertEquals('foobar', $constant->value()); - } - ]; - - yield 'array value' => [ - <<<'EOT' - first(); - self::assertEquals(['one', 'two'], $constant->value()); - } - ]; - - yield 'no value' => [ - <<<'EOT' - first(); - self::assertEquals(null, $constant->value()); - } - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php deleted file mode 100644 index 4a0b7ddeb..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php +++ /dev/null @@ -1,155 +0,0 @@ -markTestSkipped('PHP 8.1'); - return; - } - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideReflectionEnum() - { - yield 'It reflects a enum' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertInstanceOf(ReflectionEnum::class, $class); - $this->assertTrue($class->isEnum()); - }, - ]; - yield 'It reflect enum methods' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertEquals(['foobar', 'cases'], $class->methods()->keys()); - }, - ]; - yield 'Returns all members' => [ - <<<'EOT' - assertCount(4, $class->members()); - $this->assertInstanceOf(ReflectionEnumCase::class, $class->members()->get('FOOBAR')); - $this->assertInstanceOf(ReflectionMethod::class, $class->members()->get('cases')); - }, - ]; - - yield 'Return case' => [ - <<<'EOT' - cases()->get('FOOBAR'); - self::assertEquals('FOOBAR', $case->name()); - self::assertEquals('enum(Enum1::FOOBAR)', $case->type()->__toString()); - self::assertInstanceOf(MissingType::class, $case->value()); - self::assertInstanceOf(EnumCaseType::class, $case->type()); - self::assertEquals('FOOBAR', $case->name()); - self::assertFalse($class->isBacked()); - }, - ]; - yield 'Return backed case' => [ - <<<'EOT' - cases()->get('FOOBAR'); - self::assertEquals('FOOBAR', $case->name()); - self::assertEquals('"FOO"', $case->value()->__toString()); - self::assertEquals('enum(Enum1::FOOBAR)', $case->type()->__toString()); - self::assertInstanceOf(EnumBackedCaseType::class, $case->type()); - self::assertTrue($class->isBacked()); - self::assertEquals('string', $class->backedType()); - }, - ]; - yield 'Return backed methods' => [ - <<<'EOT' - methods()->get('from'); - self::assertTrue($class->methods()->has('cases')); - self::assertEquals('Enum1', $method->returnType()->__toString()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionFunctionTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionFunctionTest.php deleted file mode 100644 index 2bb153755..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionFunctionTest.php +++ /dev/null @@ -1,169 +0,0 @@ -createReflector($source)->reflectFunctionsIn($source); - $assertion($functions->get($functionName)); - } - - public function provideReflectsFunction() - { - yield 'single function with no params' => [ - <<<'EOT' - assertEquals('hello', $function->name()); - $this->assertEquals(ByteOffsetRange::fromInts(6, 26), $function->position()); - } - ]; - - yield 'function\'s frame' => [ - <<<'EOT' - assertCount(1, $function->frame()->locals()); - } - ]; - - yield 'the docblock' => [ - <<<'EOT' - assertEquals('/** Hello */', trim($function->docblock()->raw())); - } - ]; - - yield 'the declared scalar type' => [ - <<<'EOT' - assertEquals('string', $function->type()); - } - ]; - - yield 'the declared class type' => [ - <<<'EOT' - assertEquals('Foobar\Barfoo', $function->type()); - } - ]; - - yield 'the declared union type' => [ - <<<'EOT' - assertEquals('string|Foobar\Barfoo', $function->type()->__toString()); - } - ]; - yield 'unknown if nothing declared as type' => [ - <<<'EOT' - assertEquals(TypeFactory::unknown(), $function->type()); - } - ]; - - yield 'type from docblock' => [ - <<<'EOT' - assertEquals(TypeFactory::string(), $function->inferredType()); - } - ]; - - yield 'resolved type class from docblock' => [ - <<<'EOT' - assertEquals('Foo\Goodbye', $function->inferredType()->__toString()); - } - ]; - - - yield 'parameters' => [ - <<<'EOT' - assertCount(3, $function->parameters()); - $this->assertEquals('Bar\Barfoo', $function->parameters()->get('barfoo')->inferredType()); - }, - ]; - - yield 'returns the source code' => [ - <<<'EOT' - assertStringContainsString('function hello(', (string) $function->sourceCode()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionInterfaceTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionInterfaceTest.php deleted file mode 100644 index db1839a34..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionInterfaceTest.php +++ /dev/null @@ -1,234 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - /** - * @return Generator - */ - public function provideReflectionInterface(): Generator - { - yield 'It reflects an interface' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertInstanceOf(ReflectionInterface::class, $class); - $this->assertTrue($class->isInterface()); - }, - ]; - - yield 'It reflects a classes interfaces' => [ - <<<'EOT' - interfaces(); - $this->assertCount(2, $interfaces); - $interface = $interfaces->get('Barfoo'); - $this->assertInstanceOf(ReflectionInterface::class, $interface); - }, - ]; - - yield 'It reflects a class which implements an interface which extends other interfaces' => [ - <<<'EOT' - parents(); - $this->assertCount(2, $interfaces); - $interface = $interfaces->get('Barfoo'); - $this->assertInstanceOf(ReflectionInterface::class, $interface); - }, - ]; - - yield 'It reflects inherited methods in an interface' => [ - <<<'EOT' - assertInstanceOf(ReflectionInterface::class, $interface); - $this->assertCount(2, $interface->methods()); - }, - ]; - - yield 'It reflect interface methods' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertEquals(['foobar'], $class->methods()->keys()); - }, - ]; - - yield 'It interface constants' => [ - <<<'EOT' - assertCount(3, $class->constants()); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('FOOBAR')); - $this->assertInstanceOf(ReflectionConstant::class, $class->constants()->get('EEEBAR')); - }, - ]; - - yield 'instanceof' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('Interface2'))); - $this->assertTrue($class->isInstanceOf(ClassName::fromString('Interface1'))); - $this->assertFalse($class->isInstanceOf(ClassName::fromString('Interface3'))); - }, - ]; - - yield 'Method class is of context class, not declaration class' => [ - <<<'EOT' - assertEquals( - 'Acme\Foobar', - (string) $class->methods()->get('method1')->class()->name() - ); - $this->assertEquals( - 'Acme\Barfoo', - (string) $class->methods()->get('method1')->declaringClass()->name() - ); - }, - ]; - - yield 'Returns all members' => [ - <<<'EOT' - assertCount(2, $class->members()); - $this->assertTrue($class->members()->has('FOOBAR')); - $this->assertTrue($class->members()->has('foobar')); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodCallTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodCallTest.php deleted file mode 100644 index e5f0e870b..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodCallTest.php +++ /dev/null @@ -1,134 +0,0 @@ -createReflector($source)->reflectMethodCall($source, $offset); - $assertion($reflection); - } - - public function provideReflectionMethod() - { - return [ - 'It reflects the method name' => [ - <<<'EOT' - b<>ar(); - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertEquals('bar', $method->name()); - }, - ], - 'It reflects a method' => [ - <<<'EOT' - b<>ar(); - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertEquals('bar', $method->name()); - }, - ], - 'It returns the position' => [ - <<<'EOT' - foo->b<>ar(); - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertInstanceOf(ByteOffsetRange::class, $method->position()); - $this->assertEquals(7, $method->position()->start()->toInt()); - $this->assertEquals(21, $method->position()->end()->toInt()); - }, - ], - 'It returns the containing class' => [ - <<<'EOT' - foo()->b<>ar(); - - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertInstanceOf(ByteOffsetRange::class, $method->position()); - $this->assertEquals(ClassName::fromString('BBB'), $method->class()->name()); - }, - ], - 'It returns if the call is static' => [ - <<<'EOT' - ar(); - - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertInstanceOf(ByteOffsetRange::class, $method->position()); - $this->assertTrue($method->isStatic()); - $this->assertEquals(ClassName::fromString('AAA'), $method->class()->name()); - }, - ], - 'It has arguments' => [ - <<<'EOT' - b<>ar($a); - - EOT - , [ - ], - function (ReflectionMethodCall $method): void { - $this->assertInstanceOf(ByteOffsetRange::class, $method->position()); - $this->assertEquals('a', $method->arguments()->first()->guessName()); - }, - ], - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodTest.php deleted file mode 100644 index 428ea6027..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionMethodTest.php +++ /dev/null @@ -1,827 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class->methods(), $this->logger()); - } - - public function provideReflectionMethod() - { - yield 'It reflects a method' => [ - <<<'EOT' - assertEquals('method', $methods->get('method')->name()); - $this->assertInstanceOf(ReflectionMethod::class, $methods->get('method')); - }, - ]; - yield 'Private visibility' => [ - <<<'EOT' - assertEquals(Visibility::private(), $methods->get('method')->visibility()); - }, - ]; - yield 'Protected visibility' => [ - <<<'EOT' - assertEquals(Visibility::protected(), $methods->get('method')->visibility()); - }, - ]; - yield 'Public visibility' => [ - <<<'EOT' - assertEquals(Visibility::public(), $methods->get('method')->visibility()); - }, - ]; - yield 'Union type' => [ - <<<'EOT' - assertEquals(new UnionType( - TypeFactory::string(), - TypeFactory::int(), - ), $methods->get('method1')->inferredType()); - }, - ]; - yield 'Return type' => [ - <<<'EOT' - assertEquals(TypeFactory::int(), $methods->get('method1')->returnType()); - $this->assertEquals(TypeFactory::string(), $methods->get('method2')->returnType()); - $this->assertEquals(TypeFactory::float(), $methods->get('method3')->returnType()); - $this->assertEquals(TypeFactory::array(), $methods->get('method4')->returnType()); - $this->assertEquals(ClassName::fromString('Test\Barfoo'), $methods->get('method5')->returnType()->name); - $this->assertEquals(ClassName::fromString('Acme\Post'), $methods->get('method6')->returnType()->name); - $this->assertEquals('self(Test\Foobar)', $methods->get('method7')->returnType()->__toString()); - $this->assertEquals(TypeFactory::iterable(), $methods->get('method8')->returnType()); - $this->assertEquals(TypeFactory::callable(), $methods->get('method9')->returnType()); - $this->assertEquals(TypeFactory::resource(), $methods->get('method10')->returnType()); - }, - ]; - yield 'Nullable return type' => [ - <<<'EOT' - assertEquals( - TypeFactory::fromString('?int'), - $methods->get('method1')->returnType() - ); - }, - ]; - yield 'Inherited methods' => [ - <<<'EOT' - assertEquals( - ['method5', 'method2', 'method3', 'method4'], - $methods->keys() - ); - self::assertEquals('Foobar', $methods->get('method5')->class()->name()->head()->__toString()); - }, - ]; - - yield 'Return type from docblock' => [ - <<<'EOT' - assertEquals( - 'Acme\Post', - $methods->get('method1')->inferredType()->__toString(), - ); - }, - ]; - - yield 'Return type from array docblock' => [ - <<<'EOT' - assertEquals( - 'Acme\Post[]', - $methods->get('method1')->inferredType()->__toString() - ); - }, - ]; - yield 'Return type from docblock this and static' => [ - <<<'EOT' - assertEquals('$this(Foobar)', $methods->get('method1')->inferredType()->__toString(), '$this(Foobar)'); - $this->assertEquals('static(Foobar)', $methods->get('method2')->inferredType()->__toString(), 'static(Foobar)'); - }, - ]; - yield 'Return type from docblock this and static from a trait' => [ - <<<'EOT' - assertEquals('$this(Foobar)', $methods->get('method1')->inferredType()->__toString()); - $this->assertEquals('static(Foobar)', $methods->get('method2')->inferredType()->__toString()); - }, - ]; - yield 'Return type from class @method annotation' => [ - <<<'EOT' - is( - $methods->get('method1')->inferredType() - ) - ); - }, - ]; - yield 'Return type from overridden @method annotation' => [ - <<<'EOT' - is( - $methods->get('method1')->inferredType() - ) - ); - }, - ]; - yield 'Return type from inherited docblock' => [ - <<<'EOT' - assertEquals('Articles\Blog', $methods->get('method1')->inferredType()->__toString()); - }, - ]; - yield 'Return type from inherited docblock (from interface)' => [ - <<<'EOT' - assertEquals('Articles\Blog', $methods->get('method1')->inferredType()->__toString()); - }, - ]; - yield 'It reflects an abstract method' => [ - <<<'EOT' - assertTrue($methods->get('method')->isAbstract()); - $this->assertFalse($methods->get('methodNonAbstract')->isAbstract()); - }, - ]; - yield 'It returns the method parameters' => [ - <<<'EOT' - assertCount(3, $methods->get('barfoo')->parameters()); - }, - ]; - yield 'It returns the nullable parameter types' => [ - <<<'EOT' - assertCount(1, $methods->get('barfoo')->parameters()); - $this->assertEquals( - '?Test\Barfoo', - $methods->get('barfoo')->parameters()->first()->type()->__toString(), - ); - }, - ]; - yield 'It tolerantes and logs method parameters with missing variables parameter' => [ - <<<'EOT' - assertEquals('', $methods->get('barfoo')->parameters()->first()->name()); - $this->assertStringContainsString( - 'Parameter has no variable', - $logger->messages()[2] - ); - }, - ]; - yield 'It returns the raw docblock' => [ - <<<'EOT' - assertStringContainsString(<<get('barfoo')->docblock()->raw()); - }, - ]; - yield 'It returns the formatted docblock' => [ - <<<'EOT' - assertEquals(<<get('barfoo')->docblock()->formatted()); - }, - ]; - yield 'It returns true if the method is static' => [ - <<<'EOT' - assertTrue($methods->get('barfoo')->isStatic()); - }, - ]; - yield 'It returns the method body' => [ - <<<'EOT' - assertEquals('echo "Hello!";', (string) $methods->get('barfoo')->body()); - }, - ]; - yield 'It reflects a method from an inteface' => [ - <<<'EOT' - assertTrue($methods->has('barfoo')); - $this->assertEquals('Foobar', (string) $methods->get('barfoo')->declaringClass()->name()); - }, - ]; - yield 'It reflects a method from a trait' => [ - <<<'EOT' - assertTrue($methods->has('barfoo')); - $this->assertEquals('Foobar', (string) $methods->get('barfoo')->declaringClass()->name()); - }, - ]; - yield 'It returns methods when a class extends itself' => [ - <<<'EOT' - assertTrue($methods->has('barfoo')); - }, - ]; - } - - /** - * Note that generics are now resolved during analysis and not statically. - * - * @return Generator - */ - public function provideGenerics(): Generator - { - yield 'return type from generic' => [ - <<<'PHP' - - */ - class Foobar extends Generic - { - } - PHP - , - 'Foobar', - function (ReflectionMethodCollection $methods): void { - self::assertTrue($methods->has('bar')); - self::assertEquals('T', $methods->get('bar')->inferredType()->__toString()); - }, - ]; - yield 'return type from generic with multiple parameters' => [ - <<<'PHP' - - */ - class Foobar extends Generic - { - } - PHP - , - 'Foobar', - function (ReflectionMethodCollection $methods): void { - self::assertTrue($methods->has('tee')); - self::assertTrue($methods->has('vee')); - self::assertEquals('T', $methods->get('tee')->inferredType()->__toString()); - self::assertEquals('V', $methods->get('vee')->inferredType()->__toString()); - }, - ]; - yield 'return type from generic with multiple parameters at a distance' => [ - <<<'PHP' - - */ - abstract class Middle extends Generic { - /** @return G */ - public function gee() {} - } - - /** - * @extends Middle - */ - class Foobar extends Middle - { - } - PHP - , - 'Foobar', - function (ReflectionMethodCollection $methods): void { - self::assertTrue($methods->has('tee')); - self::assertTrue($methods->has('vee')); - self::assertTrue($methods->has('gee')); - self::assertEquals('T', $methods->get('tee')->inferredType()->__toString()); - self::assertEquals('V', $methods->get('vee')->inferredType()->__toString()); - self::assertEquals('G', $methods->get('gee')->inferredType()->__toString()); - }, - ]; - } - - /** - * @return Generator - */ - public function provideDeprecations(): Generator - { - yield 'It shows when method is deprecated' => [ - <<<'EOT' - assertTrue($methods->has('barfoo')); - $this->assertTrue($methods->get('barfoo')->deprecation()->isDefined()); - }, - ]; - } - - /** - * @dataProvider provideReflectionMethodCollection - */ - public function testReflectCollection(string $source, string $class, Closure $assertion): void - { - $class = $this->createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideReflectionMethodCollection() - { - return [ - 'Only methods belonging to a given class' => [ - <<<'EOT' - methods()->belongingTo($class->name()); - $this->assertEquals( - ['method4'], - $methods->keys() - ); - }, - ], - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionParameterTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionParameterTest.php deleted file mode 100644 index 5bd272dd0..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionParameterTest.php +++ /dev/null @@ -1,236 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString('Acme\Foobar')); - $assertion($class->methods()->get('method')); - } - - public function provideReflectionParameter() - { - yield 'It reflects a an empty list with no parameters' => [ - '', - function (ReflectionMethod $method): void { - $this->assertCount(0, $method->parameters()); - }, - ]; - - yield 'It reflects a single parameter' => [ - '$foobar', - function (ReflectionMethod $method): void { - $this->assertCount(1, $method->parameters()); - $parameter = $method->parameters()->get('foobar'); - $this->assertInstanceOf(ReflectionParameter::class, $parameter); - $this->assertEquals('foobar', $parameter->name()); - }, - ]; - - yield 'It returns false if the parameter has no type' => [ - '$foobar', - function (ReflectionMethod $method): void { - $this->assertTrue( - $method->parameters()->get('foobar')->type() instanceof MissingType - ); - }, - ]; - - yield 'It returns the parameter type' => [ - 'Foobar $foobar', - function (ReflectionMethod $method): void { - $this->assertEquals('Acme\Foobar', $method->parameters()->get('foobar')->type()->__toString()); - }, - ]; - - yield 'It returns false if the parameter has no default' => [ - '$foobar', - function (ReflectionMethod $method): void { - $this->assertFalse($method->parameters()->get('foobar')->default()->isDefined()); - }, - ]; - - yield 'It returns the default value for a string' => [ - '$foobar = "foo"', - function (ReflectionMethod $method): void { - $this->assertTrue($method->parameters()->get('foobar')->default()->isDefined()); - $this->assertEquals( - 'foo', - $method->parameters()->get('foobar')->default()->value() - ); - }, - ]; - - yield 'It returns the default value for a number' => [ - '$foobar = 1234', - function (ReflectionMethod $method): void { - $this->assertEquals( - 1234, - $method->parameters()->get('foobar')->default()->value() - ); - }, - ]; - - yield 'It returns the default value for an array' => [ - '$foobar = [ "foobar" ]', - function (ReflectionMethod $method): void { - $this->assertEquals( - ['foobar'], - $method->parameters()->get('foobar')->default()->value() - ); - }, - ]; - - yield 'It returns the default value for null' => [ - '$foobar = null', - function (ReflectionMethod $method): void { - $this->assertEquals( - null, - $method->parameters()->get('foobar')->default()->value() - ); - }, - ]; - - yield 'It returns the default value for empty array' => [ - '$foobar = []', - function (ReflectionMethod $method): void { - $foobar = $method->parameters()->get('foobar'); - $this->assertEquals( - [], - $foobar->default()->value() - ); - }, - ]; - - yield 'It returns the default value for a boolean' => [ - '$foobar = false', - function (ReflectionMethod $method): void { - $this->assertEquals( - false, - $method->parameters()->get('foobar')->default()->value() - ); - }, - ]; - - yield 'Passed by reference' => [ - '&$foobar', - function (ReflectionMethod $method): void { - $this->assertTrue( - $method->parameters()->get('foobar')->byReference() - ); - }, - ]; - - yield 'Not passed by reference' => [ - '$foobar', - function (ReflectionMethod $method): void { - $this->assertFalse( - $method->parameters()->get('foobar')->byReference() - ); - }, - ]; - - yield 'It reflects iterable type properly' => [ - 'iterable $foobar', - function (ReflectionMethod $method): void { - $this->assertEquals( - TypeFactory::fromString('iterable'), - $method->parameters()->get('foobar')->type() - ); - }, - ]; - - yield 'It reflects resource type properly' => [ - 'resource $foobar', - function (ReflectionMethod $method): void { - $this->assertEquals( - TypeFactory::fromString('resource')->__toString(), - $method->parameters()->get('foobar')->type()->__toString() - ); - }, - ]; - - yield 'It reflects callable type properly' => [ - 'callable $foobar', - function (ReflectionMethod $method): void { - $this->assertEquals( - TypeFactory::fromString('callable'), - $method->parameters()->get('foobar')->type() - ); - }, - ]; - - yield 'It reflects a nullable parameter' => [ - '?string $foobar', - function (ReflectionMethod $method): void { - $this->assertEquals( - TypeFactory::nullable(TypeFactory::string()), - $method->parameters()->get('foobar')->type() - ); - }, - ]; - - yield 'It reflects a promoted parameter' => [ - 'private string $foobar', - function (ReflectionMethod $method): void { - $this->assertTrue( - $method->parameters()->get('foobar')->isPromoted() - ); - }, - ]; - - yield 'It reflects a (not) promoted parameter' => [ - 'string $foobar', - function (ReflectionMethod $method): void { - $this->assertFalse( - $method->parameters()->get('foobar')->isPromoted() - ); - }, - ]; - } - - /** - * @dataProvider provideReflectionParameterWithDocblock - */ - public function testReflectParameterWithDocblock(string $source, string $docblock, Closure $assertion): void - { - $source = sprintf('createReflector($source)->reflectClassLike(ClassName::fromString('Acme\Foobar')); - $assertion($class->methods()->get('method')); - } - - public function provideReflectionParameterWithDocblock() - { - yield 'It returns docblock parameter type' => [ - '$foobar', - '/** @param Foobar $foobar */', - function (ReflectionMethod $method): void { - $this->assertCount(1, $method->parameters()); - $this->assertEquals('Acme\Foobar', (string) $method->parameters()->get('foobar')->inferredType()); - }, - ]; - - yield 'It returns unknown type when no type hinting is available' => [ - '$foobar', - '/** */', - function (ReflectionMethod $method): void { - $this->assertCount(1, $method->parameters()); - $this->assertEquals(TypeFactory::unknown(), $method->parameters()->get('foobar')->inferredType()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPromotedPropertyTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPromotedPropertyTest.php deleted file mode 100644 index 6ad2bbcc7..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPromotedPropertyTest.php +++ /dev/null @@ -1,105 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class->properties()); - } - - public function provideConsturctorPropertyPromotion(): Generator - { - yield 'Typed properties' => [ - <<<'EOT' - assertTrue($properties->get('foobar')->isPromoted()); - $this->assertEquals( - TypeFactory::string(), - $properties->get('foobar')->type() - ); - $this->assertEquals(Visibility::private(), $properties->get('foobar')->visibility()); - $this->assertEquals( - TypeFactory::int(), - $properties->get('barfoo')->type() - ); - $this->assertEquals( - TypeFactory::union( - TypeFactory::string(), - TypeFactory::int(), - ), - $properties->get('baz')->inferredType() - ); - }, - ]; - - yield 'Nullable' => [ - 'assertEquals( - TypeFactory::fromString('?string'), - $properties->get('foobar')->type() - ); - }, - ]; - - yield 'No types' => [ - 'assertEquals( - TypeFactory::undefined(), - $properties->get('foobar')->type() - ); - }, - ]; - - yield 'With docblock' => [ - <<<'EOT' - assertEquals( - 'Foobar', - $properties->get('foobar')->inferredType()->__toString(), - ); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPropertyTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPropertyTest.php deleted file mode 100644 index 7413eeb6c..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionPropertyTest.php +++ /dev/null @@ -1,406 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class->properties()); - } - - /** - * @return Generator - */ - public function provideReflectionPropertyTypes(): Generator - { - yield 'It reflects a property with union type' => [ - 'assertEquals('property', $properties->get('property')->name()); - $this->assertEquals(TypeFactory::union(...[ - TypeFactory::int(), - TypeFactory::string(), - ]), $properties->get('property')->inferredType()); - }, - ]; - } - - /** - * @return Generator - */ - public function provideReflectionProperty() - { - yield 'It reflects a property' => [ - <<<'EOT' - assertEquals('property', $properties->get('property')->name()); - $this->assertInstanceOf(ReflectionProperty::class, $properties->get('property')); - }, - ]; - - yield 'Private visibility' => [ - <<<'EOT' - assertEquals(Visibility::private(), $properties->get('property')->visibility()); - }, - ]; - - yield 'Protected visibility' => [ - <<<'EOT' - assertEquals(Visibility::protected(), $properties->get('property')->visibility()); - }, - ]; - - yield 'Public visibility' => [ - <<<'EOT' - assertEquals(Visibility::public(), $properties->get('property')->visibility()); - }, - ]; - - yield 'Inherited properties' => [ - <<<'EOT' - assertEquals( - ['property5', 'property2', 'property3', 'property4'], - $properties->keys() - ); - self::assertEquals( - 'ParentParentClass', - $properties->get('property5')->declaringClass()->name()->head()->__toString() - ); - self::assertEquals('Foobar', $properties->get('property5')->class()->name()->head()->__toString()); - }, - ]; - - yield 'Return type from docblock' => [ - <<<'EOT' - assertEquals( - 'Acme\Post', - $properties->get('property1')->inferredType()->__toString(), - ); - $this->assertFalse($properties->get('property1')->isStatic()); - }, - ]; - - yield 'Returns unknown type for (real) type' => [ - <<<'EOT' - assertEquals( - TypeFactory::unknown(), - $properties->get('property1')->type() - ); - }, - ]; - - yield 'Property with assignment' => [ - <<<'EOT' - assertTrue($properties->has('property1')); - }, - ]; - - yield 'Return true if property is static' => [ - <<<'EOT' - assertTrue($properties->get('property1')->isStatic()); - }, - ]; - - yield 'Returns declaring class' => [ - <<<'EOT' - assertEquals('Foobar', $properties->get('property1')->declaringClass()->name()->__toString()); - }, - ]; - - yield 'Property type from class @property annotation' => [ - <<<'EOT' - assertEquals(TypeFactory::fromString('string'), $properties->get('bar')->inferredType()); - }, - ]; - - yield 'Property type from class @property annotation with imported name' => [ - <<<'EOT' - assertEquals('Bar\Foo', $properties->get('bar')->inferredType()->__toString()); - }, - ]; - - yield 'Property type from parent class @property annotation with imported name' => [ - <<<'EOT' - assertEquals('Bar\Foo', $properties->get('bar')->inferredType()->__toString()); - }, - ]; - - yield 'Typed property from imported class' => [ - <<<'EOT' - assertEquals('Bar\Foo', $properties->get('bar')->type()->__toString()); - $this->assertEquals('Bar\Foo', $properties->get('bar')->inferredType()->__toString()); - - $this->assertEquals(TypeFactory::string(), $properties->get('baz')->type()); - - $this->assertEquals(TypeFactory::undefined(), $properties->get('undefined')->type()); - - $this->assertEquals(TypeFactory::iterable(), $properties->get('collection')->type()); - $this->assertEquals('Bar\Foo[]', $properties->get('collection')->inferredType()->__toString()); - $this->assertEquals( - TypeFactory::iterable(), - $properties->get('it')->type() - ); - }, - ]; - - yield 'Nullable typed property' => [ - <<<'EOT' - assertEquals( - TypeFactory::fromString('?string'), - $properties->get('foo')->type() - ); - }, - ]; - - yield 'Property with intersection' => [ - <<<'EOT' - assertEquals( - TypeFactory::intersection( - TypeFactory::class('Test\Foo'), - TypeFactory::class('Test\Bar'), - )->__toString(), - $properties->get('foo')->type()->__toString() - ); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionScopeTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionScopeTest.php deleted file mode 100644 index 6c25bceaa..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionScopeTest.php +++ /dev/null @@ -1,68 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideScope() - { - yield 'Returns imported classes' => [ - <<<'EOT' - assertEquals(NameImports::fromNames([ - 'Barfoo' => Name::fromString('Foobar\\Barfoo'), - 'Carzatz' => Name::fromString('Barfoo\\Foobaz'), - ]), $class->scope()->nameImports()); - }, - ]; - - yield 'Returns local name' => [ - <<<'EOT' - assertEquals( - Name::fromString('Barfoo'), - $class->scope()->resolveLocalName(Name::fromString('Foobar\Barfoo')) - ); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionTraitTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionTraitTest.php deleted file mode 100644 index 13615c7e6..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionTraitTest.php +++ /dev/null @@ -1,155 +0,0 @@ -createReflector($source)->reflectClassLike(ClassName::fromString($class)); - $assertion($class); - } - - public function provideReflectionTrait() - { - return [ - 'It reflects a trait' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertInstanceOf(ReflectionTrait::class, $class); - $this->assertTrue($class->isTrait()); - }, - ], - 'It reflects a classes traits' => [ - <<<'EOT' - traits(); - $this->assertCount(2, $traits); - $trait = $traits[0]; - $this->assertInstanceOf(ReflectionTrait::class, $trait); - }, - ], - 'It reflect trait methods' => [ - <<<'EOT' - assertEquals('Barfoo', (string) $class->name()->short()); - $this->assertEquals(['foobar'], $class->methods()->keys()); - }, - ], - 'Trait properties' => [ - <<<'EOT' - assertCount(2, $class->properties()); - $this->assertEquals('foobar', $class->properties()->first()->name()); - }, - ], - 'Ignores inherit docs on trait' => [ - <<<'EOT' - assertEquals(TypeFactory::unknown(), $class->methods()->first()->inferredReturnTypes()->best()); - }, - ], - 'instanceof' => [ - <<<'EOT' - assertTrue($class->isInstanceOf(ClassName::fromString('Trait1'))); - $this->assertFalse($class->isInstanceOf(ClassName::fromString('Interface1'))); - }, - ], - ]; - - yield 'Returns all members' => [ - <<<'EOT' - assertCount(2, $class->members()); - $this->assertTrue($class->members()->has('foovar')); - $this->assertTrue($class->members()->has('foobar')); - }, - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/TraitImport/TraitImportsTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/TraitImport/TraitImportsTest.php deleted file mode 100644 index ec8adb306..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/TraitImport/TraitImportsTest.php +++ /dev/null @@ -1,90 +0,0 @@ -parseSource($source); - $classDeclaration = $rootNode->getFirstDescendantNode(ClassDeclaration::class); - $assertion(TraitImports::forClassDeclaration($classDeclaration)); - } - - public function provideTraitImports() - { - yield 'simple use' => [ - 'assertCount(1, $traitImports); - $this->assertTrue($traitImports->has('A')); - $this->assertEquals('A', $traitImports->get('A')->name()); - } - ]; - - yield 'incomplete statement' => [ - 'assertCount(0, $traitImports); - } - ]; - - yield 'simple use with alias' => [ - 'assertCount(1, $traitImports); - $traitImport = $traitImports->get('A'); - $this->assertCount(1, $traitImport->traitAliases()); - $traitAlias = $traitImport->traitAliases()['foo']; - assert($traitAlias instanceof TraitAlias); - $this->assertEquals('foo', $traitAlias->originalName()); - $this->assertEquals('bar', $traitAlias->newName()); - } - ]; - - yield 'simple use with alias and visiblity' => [ - 'get('A'); - ; - $this->assertEquals(Visibility::private(), $traitImport->traitAliases()['foo']->visiblity()); - $this->assertEquals(Visibility::protected(), $traitImport->traitAliases()['bar']->visiblity()); - $this->assertEquals(Visibility::public(), $traitImport->traitAliases()['zed']->visiblity()); - } - ]; - - yield 'does not support insteadof' => [ - 'get('A'); - $this->assertCount(0, $traitImport->traitAliases()); - } - ]; - - yield 'multiple traits with single alias maping' => [ - 'assertCount(2, $traitImports); - $traitImport = $traitImports->get('A'); - $this->assertCount(2, $traitImport->traitAliases()); - - $traitImport = $traitImports->get('B'); - $this->assertCount(2, $traitImport->traitAliases()); - } - ]; - } -} diff --git a/src/Tests/Integration/Bridge/TolerantParser/Reflection/TypeResolver/DeclaredMemberTypeResolverTest.php b/src/Tests/Integration/Bridge/TolerantParser/Reflection/TypeResolver/DeclaredMemberTypeResolverTest.php deleted file mode 100644 index c65128500..000000000 --- a/src/Tests/Integration/Bridge/TolerantParser/Reflection/TypeResolver/DeclaredMemberTypeResolverTest.php +++ /dev/null @@ -1,44 +0,0 @@ -createReflector($source)->reflectClass(ClassName::fromString($class)); - $assertion($class->properties()->get('p')); - } - - public function provideResolveTypes(): Generator - { - yield 'union type' => [ - 'assertEquals(TypeFactory::union(...[ - TypeFactory::int(), - TypeFactory::string(), - ]), $property->inferredType()); - }, - ]; - - yield 'union type with FQN' => [ - 'assertEquals('int|Foobar|Baz', $property->inferredType()); - }, - ]; - } -} diff --git a/src/Tests/Integration/Core/ClassReflectorTest.php b/src/Tests/Integration/Core/ClassReflectorTest.php deleted file mode 100644 index 933c7e1b7..000000000 --- a/src/Tests/Integration/Core/ClassReflectorTest.php +++ /dev/null @@ -1,80 +0,0 @@ -createReflector($source)->$method($class); - $this->assertInstanceOf($expectedType, $reflection); - } - - public function provideReflectClassSuccess() - { - return [ - 'Class' => [ - ' [ - ' [ - 'expectException(ClassNotFound::class); - $this->expectExceptionMessage($expectedErrorMessage); - - $this->createReflector($source)->$method($class); - } - - public function provideReflectClassNotCorrectType() - { - return [ - 'Class' => [ - ' [ - ' [ - 'workspace()->reset(); - $this->workspace()->loadManifest($manifest); - - $locator = new StubSourceLocator( - ReflectorBuilder::create()->build(), - $this->workspace()->path('project'), - $this->workspace()->path('cache'), - ); - $reflection = ReflectorBuilder::create()->addLocator($locator)->build()->reflectConstant($name); - $assertion($reflection); - } - - /** - * @return Generator - */ - public function provideReflectDeclaredConstant(): Generator - { - yield 'reflect in root namespace' => [ - <<<'EOT' - // File: project/hello.php - assertEquals('"hello"', $constant->type()->__toString()); - } - ]; - - yield 'fallback to global constant' => [ - <<<'EOT' - // File: project/global.php - assertEquals('HELLO', $constant->name()); - } - ]; - - yield 'namespaced function' => [ - <<<'EOT' - // File: project/global.php - assertEquals('Foo\HELLO', $function->name()); - } - ]; - } - - public function testThrowsExceptionIfFunctionNotFound(): void - { - $this->expectException(ConstantNotFound::class); - $this->createReflector('reflectConstant('hallo'); - } -} diff --git a/src/Tests/Integration/Core/FunctionReflectorTest.php b/src/Tests/Integration/Core/FunctionReflectorTest.php deleted file mode 100644 index c67af2434..000000000 --- a/src/Tests/Integration/Core/FunctionReflectorTest.php +++ /dev/null @@ -1,82 +0,0 @@ -workspace()->reset(); - $this->workspace()->loadManifest($manifest); - - $locator = new StubSourceLocator( - ReflectorBuilder::create()->build(), - $this->workspace()->path('project'), - $this->workspace()->path('cache'), - ); - $reflection = ReflectorBuilder::create()->addLocator($locator)->build()->reflectFunction($name); - $assertion($reflection); - } - - /** - * @return Generator - */ - public function provideReflectFunction(): Generator - { - yield 'reflect function' => [ - <<<'EOT' - // File: project/hello.php - assertEquals('hello', $function->name()); - } - ]; - - yield 'fallback to global function' => [ - <<<'EOT' - // File: project/global.php - assertEquals('hello', $function->name()); - } - ]; - - yield 'namespaced function' => [ - <<<'EOT' - // File: project/global.php - assertEquals('Foo\hello', $function->name()); - } - ]; - } - - public function testThrowsExceptionIfFunctionNotFound(): void - { - $this->expectException(FunctionNotFound::class); - $this->createReflector('reflectFunction('hallo'); - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameBuilderTest.php b/src/Tests/Integration/Core/Inference/FrameBuilderTest.php deleted file mode 100644 index 7fa2368c1..000000000 --- a/src/Tests/Integration/Core/Inference/FrameBuilderTest.php +++ /dev/null @@ -1,63 +0,0 @@ -createReflector($source); - $method = $reflector->reflectClassLike(ClassName::fromString($className))->methods()->get($methodName); - $frame = $method->frame(); - - $assertion($frame, $this->logger()); - } - - /** - * @return Generator,Closure(Frame,LoggerInterface): void}> - */ - public function provideForMethod(): Generator - { - yield 'Tolerates missing assert arguments' => [ - <<<'EOT' - assertEquals(0, $frame->problems()->count(), $frame->problems()->__toString()); - }]; - - yield 'Tolerates missing tokens' => [ - <<<'EOT' - classReflector->reflect(TestCase::class); - } - } - EOT - , [ 'Foobar', 'hello' ], function (Frame $frame, $logger): void { - $this->assertEquals(0, $frame->problems()->count()); - }]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/AssertWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/AssertWalkerTest.php deleted file mode 100644 index 15ef8ce8b..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/AssertWalkerTest.php +++ /dev/null @@ -1,65 +0,0 @@ - [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('Foobar', (string) $frame->locals()->first()->type()); - } - ]; - - yield 'assert instanceof negative' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertEquals(1, $frame->locals()->count()); - $this->assertEquals('', $frame->locals()->first()->type()->__toString()); - } - ]; - - yield 'should handle properties' => [ - <<<'EOT' - bar instanceof Bar); - - <> - } - } - EOT - , function (Frame $frame, int $offset): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('Foo', $frame->locals()->atIndex(0)->type()->__toString()); - $this->assertCount(2, $frame->properties()); - $this->assertEquals('Foo', $frame->properties()->atIndex(1)->classType()->__toString()); - $this->assertEquals('Bar', $frame->properties()->atIndex(1)->type()->__toString()); - }]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/AssignmentWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/AssignmentWalkerTest.php deleted file mode 100644 index b1b433215..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/AssignmentWalkerTest.php +++ /dev/null @@ -1,321 +0,0 @@ - [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('foobar')); - $var = $frame->locals()->byName('foobar')->first(); - $this->assertEquals('"foobar"', (string) $var->type()); - }]; - yield 'It returns types for reassigned variables' => [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $vars = $frame->locals()->byName('foobar'); - $this->assertCount(1, $vars); - $var = $vars->first(); - $this->assertEquals('World', (string) $var->type()); - }]; - - yield 'It returns type for $this' => [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $vars = $frame->locals()->byName('this'); - $this->assertCount(1, $vars); - $var = $vars->first(); - $this->assertEquals('Foobar', (string) $var->type()); - }]; - - yield 'It tracks assigned properties' => [ - <<<'EOT' - foobar = 'foobar'; - <> - } - } - EOT - , function (Frame $frame): void { - $vars = $frame->properties()->byName('foobar'); - $this->assertCount(1, $vars); - $var = $vars->first(); - $this->assertEquals('"foobar"', (string) $var->type()); - }]; - - yield 'It assigns property values to assignments' => [ - <<<'EOT' - foobar; - <> - } - } - EOT - , function (Frame $frame): void { - $vars = $frame->locals()->byName('foobar'); - $this->assertCount(1, $vars); - $var = $vars->last(); - $type = $var->type(); - assert($type instanceof IterableType); - $this->assertEquals('Foobar[]', (string) $type); - $this->assertEquals('Foobar', (string) $type->iterableValueType()); - }]; - - - yield 'It tracks assigned array properties' => [ - <<<'EOT' - foobar[] = 'foobar'; - <> - } - } - EOT - , function (Frame $frame): void { - $vars = $frame->properties()->byName('foobar'); - $this->assertCount(1, $vars); - $var = $vars->first(); - $this->assertEquals('array', (string) $var->type()); - }]; - - yield 'It tracks assigned from variable' => [ - <<<'EOT' - $foobar = 'foobar'; - <> - } - } - EOT - , function (Frame $frame): void { - $vars = $frame->properties()->byName('foobar'); - $this->assertCount(1, $vars); - $var = $vars->first(); - $this->assertEquals('"foobar"', (string) $var->type()); - }]; - - yield 'Handles array assignments' => [ - <<<'EOT' - 'bar' ]; - $bar = $foo['foo']; - <> - EOT - , - function (Frame $frame): void { - $this->assertCount(2, $frame->locals()); - $this->assertEquals('array{foo:"bar"}', (string) $frame->locals()->first()->type()); - $this->assertEquals('"bar"', (string) $frame->locals()->last()->type()); - } - ]; - - yield 'Includes list assignments' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(2, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->first()->name()); - $this->assertEquals('"foo"', (string)$frame->locals()->first()->type()); - $this->assertEquals('bar', $frame->locals()->atIndex(1)->name()); - $this->assertEquals('"bar"', (string)$frame->locals()->atIndex(1)->type()); - } - ]; - - yield 'New list assignment' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(2, $frame->locals()); - $this->assertEquals('"foo"', (string)$frame->locals()->atIndex(0)->type()); - $this->assertEquals('"bar"', (string)$frame->locals()->atIndex(1)->type()); - } - ]; - - yield 'From generic return type with docblock' => [ - <<<'EOT' - - */ - interface Listy extends \Iterator { - } - - interface Barfoo - { - /** - * @return Listy - */ - public static function bar(): Listy; - } - - class Baz - { - public function (Barfoo $barfoo) - { - $bar = $barfoo->bar(); - <> - } - } - <> - } - EOT - , - function (Frame $frame): void { - $this->assertCount(3, $frame->locals()); - $this->assertEquals( - 'Foobar\Listy', - (string) $frame->locals()->byName('bar')->first()->type() - ); - $type = $frame->locals()->byName('bar')->first()->type(); - $this->assertEquals( - 'Foobar\Collection', - $type->iterableValueType()->__toString() - ); - } - ]; - - yield 'From incomplete assignment' => [ - <<<'EOT' - as<> - } - - public function function2(): Baz; - } - <> - } - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('barfoo')); - $type = $frame->locals()->byName('barfoo')->first()->type(); - assert($type instanceof ClassType); - $this->assertEquals('Barfoo', $type->name->short()); - } - ]; - - yield 'References previously walked member' => [ - <<<'EOT' - foobar = new Car(); - $this->options = $this->foobar->options; - } - public function bar() - { - $barfoo = $this->options->foobar(); - $barfo<>o; - } - } - <> - } - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('barfoo')); - } - ]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/FunctionLikeWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/FunctionLikeWalkerTest.php deleted file mode 100644 index c46f5bf80..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/FunctionLikeWalkerTest.php +++ /dev/null @@ -1,220 +0,0 @@ - [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('this')); - $this->assertEquals('Foobar\Barfoo\Foobar', $frame->locals()->byName('this')->first()->type()->__toString()); - $this->assertEquals(false, $frame->locals()->byName('this')->first()->isProperty()); - }]; - - yield 'It returns method arguments' => [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('this')); - $this->assertEquals( - 'Foobar\Barfoo\Foobar', - $frame->locals()->byName('this')->first()->type()->__toString() - ); - }]; - - yield 'It injects method argument with inferred types' => [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('many')); - $this->assertEquals('string', (string) $frame->locals()->byName('many')->first()->type()); - - $this->assertCount(1, $frame->locals()->byName('worlds')); - $this->assertEquals('Foobar\Barfoo\World[]', (string) $frame->locals()->byName('worlds')->first()->type()); - $type = $frame->locals()->byName('worlds')->first()->type(); - assert($type instanceof IterableType); - $this->assertEquals('Foobar\Barfoo\World', (string) $type->iterableValueType()); - }]; - - yield 'Variadic argument' => [ - <<<'EOT' - - } - } - - EOT - , function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('hellos')); - $variable = $frame->locals()->byName('hellos')->first(); - $type = $variable->type(); - assert($type instanceof IterableType); - $this->assertEquals('string', (string)$type->iterableValueType()); - }]; - - yield 'Respects closure scope' => [ - <<<'EOT' - - }; - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$bar'), 'Scoped variable exists'); - $this->assertCount(0, $frame->locals()->byName('$foo'), 'Parent scoped variable doesnt exist'); - } - ]; - - yield 'Static anonymous function has no $this or self::' => [ - <<<'EOT' - - }; - } - } - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$bar')); - $this->assertCount(0, $frame->locals()->byName('$this')); - } - ]; - - yield 'Injects closure parameters' => [ - <<<'EOT' - - }; - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$foo')); - $variable = $frame->locals()->byName('$foo')->first(); - $this->assertEquals('Foobar', $variable->type()->__toString()); - } - ]; - - yield 'Injects imported closure parent scope variables' => [ - <<<'EOT' - - }; - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $zed = $frame->locals()->byName('$zed')->first(); - $this->assertEquals('"zed"', (string) $zed->type()); - } - ]; - - yield 'Incomplete use name' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(0, $frame->locals()); - } - ]; - - yield 'Injects variables with @var (non-standard)' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('string', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/IncludeWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/IncludeWalkerTest.php deleted file mode 100644 index b04bb4bb9..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/IncludeWalkerTest.php +++ /dev/null @@ -1,93 +0,0 @@ -workspace()->reset(); - $this->workspace()->put('foo.php', 'workspace()->put('return_value.php', ' [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->first()->name()); - } - ]; - - yield 'Require absolute' => [ - <<workspace()->path('foo.php')}'); - <> - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->first()->name()); - } - ]; - - yield 'Include' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->first()->name()); - } - ]; - - yield 'Returns value' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->last()->name()); - $this->assertEquals('"bar"', (string) $frame->locals()->last()->type()); - } - ]; - - yield 'Returns from constant' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()); - $this->assertEquals('foo', $frame->locals()->last()->name()); - $this->assertEquals('"bar"', (string) $frame->locals()->last()->type()); - } - ]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/ReturnTypeWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/ReturnTypeWalkerTest.php deleted file mode 100644 index 9b4b297af..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/ReturnTypeWalkerTest.php +++ /dev/null @@ -1,74 +0,0 @@ - [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - self::assertEquals('"string"', $frame->returnType()->__toString()); - } - ]; - - yield 'Get union return type from frame' => [ - <<<'EOT' - - } - - <> - EOT - , - function (Frame $frame): void { - self::assertEquals('null|"string"', $frame->returnType()->__toString()); - } - ]; - - yield 'Get do not duplicate union return type from frame' => [ - <<<'EOT' - - } - - <> - EOT - , - function (Frame $frame): void { - self::assertEquals('null|"string"', $frame->returnType()->__toString()); - } - ]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalker/VariableWalkerTest.php b/src/Tests/Integration/Core/Inference/FrameWalker/VariableWalkerTest.php deleted file mode 100644 index 6df35c21d..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalker/VariableWalkerTest.php +++ /dev/null @@ -1,174 +0,0 @@ - [ - <<<'EOT' - - } - } - EOT - , function (Frame $frame): void { - $vars = $frame->locals()->byName('$foobar'); - $this->assertCount(2, $vars); - $this->assertEquals('Foobar', (string) $vars->first()->type()); - $this->assertEquals('stdClass', (string) $vars->last()->type()); - }]; - - yield 'Injects variables with @var (standard)' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('string', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - - yield 'Injects variables with @var namespaced' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Foo\\Bar', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - - yield 'Injects variables with @var namespaced and qualified name' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Foo\\Bar\\Baz', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - - yield 'Injects variables with @var namespaced with fully qualified name' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Bar\\Baz', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - - yield 'Injects variables with @var with imported namespace' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Foo\Bar\Zed\Baz', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - - yield 'Injects named union type' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Bar|Baz', $frame->locals()->byName('$zed')->last()->type()->__toString()); - } - ]; - - yield 'Unspecified type for following variable' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Zed\Baz', (string) $frame->locals()->byName('$zed')->first()->type()); - } - ]; - - yield 'Unspecified type for following variable with class import' => [ - <<<'EOT' - - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('Zed\Baz', (string) $frame->locals()->byName('$zed')->first()->type()); - } - ]; - - yield 'Targeted variable not matching following variable assignment' => [ - <<<'EOT' - hello(); - <> - EOT - , - function (Frame $frame): void { - $this->assertCount(1, $frame->locals()->byName('$zed')); - $this->assertEquals('string', (string) $frame->locals()->byName('$zed')->last()->type()); - } - ]; - } -} diff --git a/src/Tests/Integration/Core/Inference/FrameWalkerTestCase.php b/src/Tests/Integration/Core/Inference/FrameWalkerTestCase.php deleted file mode 100644 index 7ea851a60..000000000 --- a/src/Tests/Integration/Core/Inference/FrameWalkerTestCase.php +++ /dev/null @@ -1,45 +0,0 @@ -workspace()->path('test.php'); - $source = TextDocumentBuilder::create($source)->uri($path)->build(); - $reflector = $this->createReflectorWithWalker($source, $this->walker()); - $reflectionOffset = $reflector->reflectOffset($source, $offset); - $assertion($reflectionOffset->frame(), $offset); - } - - abstract public function provideWalk(): Generator; - - public function walker(): ?Framewalker - { - return null; - } - - private function createReflectorWithWalker($source, Walker $frameWalker = null): Reflector - { - $reflector = $this->createBuilder($source); - - if ($frameWalker) { - $reflector->addFrameWalker($frameWalker); - } - - return $reflector->build(); - } -} diff --git a/src/Tests/Integration/Core/Inference/NodeContextResolverTest.php b/src/Tests/Integration/Core/Inference/NodeContextResolverTest.php deleted file mode 100644 index 2f86b7ee1..000000000 --- a/src/Tests/Integration/Core/Inference/NodeContextResolverTest.php +++ /dev/null @@ -1,1212 +0,0 @@ -logger()); - } - - /** - * @dataProvider provideGeneral - */ - public function testGeneral(string $source, array $locals, array $expectedInformation): void - { - $variables = []; - $properties = []; - $offset = 0; - foreach ($locals as $name => $varSymbolInfo) { - $offset++; - if ($varSymbolInfo instanceof Type) { - $varSymbolInfo = NodeContext::for( - Symbol::fromTypeNameAndPosition( - 'variable', - $name, - ByteOffsetRange::fromInts($offset, $offset) - ) - )->withType($varSymbolInfo); - } - - $variable = Variable::fromSymbolContext($varSymbolInfo); - - if (Symbol::PROPERTY === $varSymbolInfo->symbol()->symbolType()) { - $properties[$varSymbolInfo->symbol()->position()->start()->toInt()] = $variable; - - continue; - } - - $variables[$varSymbolInfo->symbol()->position()->start()->toInt()] = $variable; - } - - $symbolInfo = $this->resolveNodeAtOffset( - LocalAssignments::fromArray($variables), - PropertyAssignments::fromArray($properties), - $source, - ); - $this->assertExpectedInformation($expectedInformation, $symbolInfo); - } - - /** - * @dataProvider provideValues - */ - public function testValues(string $source, array $variables, array $expected): void - { - $information = $this->resolveNodeAtOffset( - LocalAssignments::fromArray($variables), - PropertyAssignments::create(), - $source, - ); - $this->assertExpectedInformation($expected, $information); - } - - /** - * These tests test the case where a class in the resolution tree was not found, however - * their usefulness is limited because we use the StringSourceLocator for these tests which - * "always" finds the source. - * - * @dataProvider provideNotResolvableClass - */ - public function testNotResolvableClass(string $source): void - { - $value = $this->resolveNodeAtOffset( - LocalAssignments::fromArray([ - 0 => Variable::fromSymbolContext( - NodeContext::for(Symbol::fromTypeNameAndPosition( - Symbol::CLASS_, - 'bar', - ByteOffsetRange::fromInts(0, 0) - ))->withType(TypeFactory::fromString('Foobar')) - ) - ]), - PropertyAssignments::create(), - $source - ); - $this->assertEquals(TypeFactory::unknown(), $value->type()); - } - - public function provideGeneral() - { - yield 'It should return none value for whitespace' => [ - ' <> ', [], - ['type' => ''], - ]; - - yield 'It should return the name of a class' => [ - <<<'EOT' - assName(); - - EOT - , [], ['type' => 'ClassName', 'symbol_type' => Symbol::CLASS_] - ]; - - yield 'It should return the fully qualified name of a class' => [ - <<<'EOT' - assName(); - - EOT - , [], ['type' => 'Foobar\Barfoo\ClassName'] - ]; - - yield 'It should return the fully qualified name of a with an imported name.' => [ - <<<'EOT' - sName(); - - EOT - , [], ['type' => 'BarBar\ClassName', 'symbol_type' => Symbol::CLASS_, 'symbol_name' => 'ClassName'] - ]; - - yield 'It should return the fully qualified name of a use definition' => [ - <<<'EOT' - sName(); - - $foo = new ClassName(); - - EOT - , [], ['type' => 'BarBar\ClassName'] - ]; - - yield 'It returns the FQN of a method parameter with a default' => [ - <<<'EOT' - barfoo = 'test') - { - } - } - - EOT - , [], ['type' => 'Foobar\Barfoo\Barfoo', 'symbol_type' => Symbol::VARIABLE, 'symbol_name' => 'barfoo'] - ]; - - yield 'It returns the type and value of a scalar method parameter' => [ - <<<'EOT' - arfoo = 'test') - { - } - } - - EOT - , [], ['type' => 'string'] - ]; - - yield 'It returns the value of a method parameter with a constant' => [ - <<<'EOT' - rfoo = 'test') - { - } - } - - EOT - , [], ['type' => 'string'] - ]; - - yield 'It returns the FQN of a method parameter in an interface' => [ - <<<'EOT' - ld); - } - - EOT - , [], ['type' => 'Foobar\Barfoo\World'] - ]; - - yield 'It returns the FQN of a method parameter in a trait' => [ - <<<'EOT' - World $world) - { - } - } - - EOT - , [], ['type' => 'Foobar\Barfoo\World', 'symbol_type' => Symbol::CLASS_, 'symbol_name' => 'World'] - ]; - - yield 'It returns the value of a method parameter' => [ - <<<'EOT' - barfoo = 'test') - { - } - } - - EOT - , [], ['type' => 'string'] - ]; - - yield 'Ignores parameter on anonymous class' => [ - <<<'EOT' - bar) {} }; - } - } - - EOT - , [], ['type' => '', 'symbol_type' => '', 'symbol_name' => 'Parameter'] - ]; - - yield 'It returns the FQN of a static call' => [ - <<<'EOT' - tory::create(); - - EOT - , [], ['type' => 'Acme\Factory', 'symbol_type' => Symbol::CLASS_] - ]; - - yield 'It returns the FQN of a method parameter' => [ - <<<'EOT' - orld $world) - { - } - } - - EOT - , [], ['type' => 'Foobar\Barfoo\World'] - ]; - - yield 'It resolves a anonymous function use' => [ - <<<'EOT' - oo) { - - } - - EOT - , [ 'foo' => TypeFactory::fromString('string') ], ['type' => 'string', 'symbol_type' => Symbol::VARIABLE, 'symbol_name' => 'foo'] - ]; - - yield 'It resolves an undeclared variable' => [ - <<<'EOT' - lah; - - EOT - , [], ['type' => '', 'symbol_type' => Symbol::VARIABLE, 'symbol_name' => 'blah'] - ]; - - yield 'It returns the FQN of variable assigned in frame' => [ - <<<'EOT' - orld; - } - } - - EOT - , [ 'world' => TypeFactory::fromString('World') ], ['type' => 'World', 'symbol_type' => Symbol::VARIABLE, 'symbol_name' => 'world'] - ]; - - yield 'It returns type for a call access expression' => [ - <<<'EOT' - foobar->type2()->type3(<>); - } - } - EOT - , [ - 'this' => TypeFactory::fromString('Foobar\Barfoo\Foobar'), - ], [ - 'type' => 'Foobar\Barfoo\Type3', - 'symbol_type' => Symbol::METHOD, - 'symbol_name' => 'type3', - 'container_type' => 'Foobar\Barfoo\Type2', - ], - ]; - - yield 'It returns type for a method which returns an interface type' => [ - <<<'EOT' - hello()->foo(<>); - } - } - EOT - , [ - 'this' => TypeFactory::fromString('Foobar'), - ], [ - 'type' => 'string', - 'symbol_type' => Symbol::METHOD, - 'symbol_name' => 'foo', - 'container_type' => 'Barfoo', - ], - ]; - - yield 'It returns class type for parent class for parent method' => [ - <<<'EOT' - type3(<>); - } - } - EOT - , [ - 'this' => TypeFactory::fromString('Foobar'), - ], [ - 'type' => 'Type3', - 'symbol_type' => Symbol::METHOD, - 'symbol_name' => 'type3', - 'container_type' => 'Foobar', - ], - ]; - - yield 'It returns type for a property access when class has method of same name' => [ - <<<'EOT' - foobar->asString(<>); - } - } - EOT - , [ - 'this' => TypeFactory::fromString('Foobar'), - ], ['type' => 'string'], - ]; - - yield 'It returns type for a new instantiation' => [ - <<<'EOT' - Bar(); - EOT - , [], ['type' => 'Bar'], - ]; - - yield 'It returns type for a new instantiation from a variable' => [ - <<<'EOT' - foobar; - EOT - , [ - 'foobar' => TypeFactory::fromString('Foobar'), - ], ['type' => 'Foobar'], - ]; - - yield 'It returns type for string literal' => [ - <<<'EOT' - '; - EOT - , [], ['type' => '"bar"', 'symbol_type' => Symbol::STRING ] - ]; - - yield 'It returns type for float' => [ - <<<'EOT' - 2; - EOT - , [], ['type' => '1.2', 'symbol_type' => Symbol::NUMBER], - ]; - - yield 'It returns type for integer' => [ - <<<'EOT' - ; - EOT - , [], ['type' => '12', 'symbol_type' => Symbol::NUMBER], - ]; - - yield 'It returns type for octal integer' => [ - <<<'EOT' - ; - EOT - , [], ['type' => '012', 'symbol_type' => Symbol::NUMBER], - ]; - - yield 'It returns type for hexadecimal integer' => [ - <<<'EOT' - ; - EOT - , [], ['type' => '0x1A', 'symbol_type' => Symbol::NUMBER], - ]; - - yield 'It returns type for binary integer' => [ - <<<'EOT' - ; - EOT - , [], ['type' => '0b11', 'symbol_type' => Symbol::NUMBER], - ]; - - yield 'It returns type for bool true' => [ - <<<'EOT' - ue; - EOT - , [], ['type' => 'true', 'symbol_type' => Symbol::BOOLEAN], - ]; - - yield 'It returns type for bool false' => [ - <<<'EOT' - false; - EOT - , [], ['type' => 'false', 'symbol_type' => Symbol::BOOLEAN], - ]; - - yield 'It returns type null' => [ - <<<'EOT' - ull; - EOT - , [], ['type' => 'null', ] ]; - - yield 'It returns type null case insensitive' => [ - <<<'EOT' - ULL; - EOT - , [], ['type' => 'null', ] ]; - - yield 'It returns type and value for an array' => [ - <<<'EOT' - 'two', 'three' => 3 <>]; - EOT - , [], ['type' => 'array{one:"two",three:3}'], - ]; - - yield 'Empty array' => [ - <<<'EOT' - ]; - EOT - , [], ['type' => 'array{}']]; - - yield 'It type for a class constant' => [ - <<<'EOT' - O; - - class Foobar - { - const HELLO = 'string'; - } - EOT - , [], ['type' => '"string"'], - ]; - - yield 'Static method access' => [ - <<<'EOT' - r(); - - class Hello - { - } - EOT - , [], ['type' => 'Hello'], - ]; - - yield 'Static constant access' => [ - <<<'EOT' - CONSTANT; - - class Foobar - { - const HELLO_CONSTANT = 'hello'; - } - EOT - , [], ['type' => '"hello"'], - ]; - - yield 'Static property access' => [ - <<<'EOT' - Property; - - class Foobar - { - /** @var string */ - public static $myProperty = 'hello'; - } - EOT - , [], [ - 'type' => 'string', - 'symbol_type' => Symbol::PROPERTY, - 'symbol_name' => 'myProperty', - 'container_type' => 'Foobar', - ], - ]; - - yield 'Static property access 2' => [ - <<<'EOT' - Property = 5; - } - } - EOT - , [], [ - 'type' => 'string', - 'symbol_type' => Symbol::PROPERTY, - 'symbol_name' => 'myProperty', - 'container_type' => 'Foobar', - ], - ]; - - yield 'Static property access instance)' => [ - <<<'EOT' - Property = 5; - EOT - , [ - 'foobar' => TypeFactory::fromString('Foobar') - ], [ - 'type' => 'string', - 'symbol_type' => Symbol::PROPERTY, - 'symbol_name' => 'myProperty', - 'container_type' => 'Foobar', - ], - ]; - - yield 'Member access with variable' => [ - <<<'EOT' - $barfoo(<>); - - class Foobar - { - } - EOT - , [], ['type' => ''], - ]; - - yield 'Member access with valued variable' => [ - <<<'EOT' - $barfoo(<>); - EOT - , [ - 'foobar' => TypeFactory::fromString('Foobar'), - 'barfoo' => NodeContext::for( - Symbol::fromTypeNameAndPosition(Symbol::STRING, 'barfoo', ByteOffsetRange::fromInts(0, 0)) - )->withType(TypeFactory::stringLiteral('hello')) - ], ['type' => 'string'], - ]; - - yield 'It returns type of property' => [ - <<<'EOT' - Class; - } - EOT - , [], ['type' => 'stdClass', 'symbol_name' => 'stdClass'], - ]; - - yield 'It returns type for parenthesised new object' => [ - <<<'EOT' - ; - EOT - , [], ['type' => 'stdClass', 'symbol_name' => 'stdClass'], - ]; - - yield 'It resolves a clone expression' => [ - <<<'EOT' - ; - EOT - , [], ['type' => 'stdClass', 'symbol_name' => 'stdClass'], - ]; - - yield 'It returns the FQN of variable assigned in frame 2' => [ - <<<'EOT' - bar instanceof Factory); - - $this->ba<>r - } - } - - EOT - , [ - 'this' => TypeFactory::class('Foobar\Barfoo\Foobar'), - 'bar' => NodeContext::for(Symbol::fromTypeNameAndPosition( - Symbol::PROPERTY, - 'bar', - ByteOffsetRange::fromInts(0, 0), - )) - ->withContainerType(TypeFactory::class('Foobar\Barfoo\Foobar')) - ->withType(TypeFactory::class('Acme\Factory')), - ], [ - 'types' => [ - TypeFactory::class('Acme\Factory'), - ], - 'symbol_type' => Symbol::PROPERTY, - 'symbol_name' => 'bar', - ] - ]; - } - - public function provideValues() - { - yield 'It returns type for self' => [ - <<<'EOT' - f:: - } - } - EOT - , [], ['type' => 'Foobar'] - ]; - - yield 'It returns type for static' => [ - <<<'EOT' - ic:: - } - } - EOT - , [], ['type' => 'Foobar'] - ]; - - yield 'It returns type for parent' => [ - <<<'EOT' - nt:: - } - } - EOT - , [], ['type' => 'ParentClass'] - ]; - - yield 'It assumes true for ternary expressions' => [ - <<<'EOT' - 'foobar' : 'barfoo'; - EOT - , [], ['type' => '"foobar"', ] - ]; - - yield 'It uses condition value if ternery "if" is empty' => [ - <<<'EOT' - new \stdClass(); - EOT - , [], ['type' => '"string"', ] - ]; - - yield 'It shows the symbol name for a method declartion' => [ - <<<'EOT' - thod() - { - } - } - EOT - , [], [ - 'symbol_type' => Symbol::METHOD, - 'symbol_name' => 'method', - 'container_type' => 'Foobar', - ] - ]; - - yield 'Class name' => [ - <<<'EOT' - obar - { - } - EOT - , [], ['type' => 'Foobar', 'symbol_type' => Symbol::CLASS_, 'symbol_name' => 'Foobar'], - ]; - - yield 'Property name' => [ - <<<'EOT' - aa = 'asd'; - } - EOT - , [], ['type' => '', 'symbol_type' => Symbol::PROPERTY, 'symbol_name' => 'aaa', 'container_type' => 'Foobar'], - ]; - - yield 'Constant name' => [ - <<<'EOT' - A = 'aaa'; - } - EOT - , [], [ - 'type' => '', - 'symbol_type' => Symbol::CONSTANT, - 'symbol_name' => 'AAA', - 'container_type' => 'Foobar' - ], - ]; - - // 8.1 only - if (defined('T_ENUM')) { - yield 'Enum case name' => [ - <<<'EOT' - A = 'aaa'; - } - EOT - , [], [ - 'type' => '', - 'symbol_type' => Symbol::CASE, - 'symbol_name' => 'AAA', - 'container_type' => 'Foobar' - ], - ]; - } - - yield 'Function name' => [ - <<<'EOT' - oobar() - { - } - EOT - , [], ['symbol_type' => Symbol::FUNCTION, 'symbol_name' => 'foobar'], - ]; - - - yield 'Function call' => [ - <<<'EOT' - lo(); - EOT - , [], ['type' => 'string', 'symbol_type' => Symbol::FUNCTION, 'symbol_name' => 'hello'], - ]; - - yield 'Trait name' => [ - <<<'EOT' - bar - { - } - EOT - , [], ['symbol_type' => 'class', 'symbol_name' => 'Barbar', 'type' => 'Barbar' ], - ]; - } - - public function provideNotResolvableClass() - { - yield 'Calling property method for non-existing class' => [ - <<<'EOT' - hello->foobar(<>); - } - } - EOT - ]; - - yield 'Class extends non-existing class' => [ - <<<'EOT' - foobar(<>); - } - } - EOT - ]; - - yield 'Method returns non-existing class' => [ - <<<'EOT' - hai()->foo(<>); - } - } - EOT - ]; - - yield 'Method returns class which extends non-existing class' => [ - <<<'EOT' - hai()->foo(<>); - } - } - - class Hai extends NonExisting - { - } - EOT - ]; - - - yield 'Static method returns non-existing class' => [ - <<<'EOT' - foo(<>); - - class Foobar - { - public static function hai(): Foo - { - } - } - EOT - ]; - } - - public function testAttachesScope(): void - { - $source = <<<'EOT' - o; - EOT - ; - $context = $this->resolveNodeAtOffset( - LocalAssignments::create(), - PropertyAssignments::create(), - $source, - ); - $this->assertCount(2, $context->scope()->nameImports()); - } - - private function resolveNodeAtOffset( - LocalAssignments $locals, - PropertyAssignments $properties, - string $source - ): NodeContext { - $frame = new Frame($locals, $properties); - - [$source, $offset] = ExtractOffset::fromSource($source); - $node = $this->parseSource($source)->getDescendantNodeAtPosition($offset); - - $reflector = $this->createReflector($source); - $nameResolver = new NodeToTypeConverter($reflector, $this->logger()); - $resolver = new NodeContextResolver( - $reflector, - new DocblockParserFactory($reflector), - $this->logger(), - new StaticCache(), - (new DefaultResolverFactory( - $reflector, - $nameResolver, - new GenericMapResolver($reflector), - new NodeContextFromMemberAccess( - new GenericMapResolver($reflector), - [] - ) - ))->createResolvers(), - ); - - return $resolver->resolveNode($frame, $node); - } - - private function assertExpectedInformation(array $expectedInformation, NodeContext $information): void - { - foreach ($expectedInformation as $name => $value) { - switch ($name) { - case 'type': - $this->assertEquals($value, (string) $information->type(), $name); - continue 2; - case 'types': - $this->assertEquals( - Type::fromTypes(...$value)->__toString(), - $information->type()->__toString(), - $name, - ); - continue 2; - case 'symbol_type': - $this->assertEquals($value, $information->symbol()->symbolType(), $name); - continue 2; - case 'symbol_name': - $this->assertEquals($value, $information->symbol()->name(), $name); - continue 2; - case 'container_type': - $this->assertEquals($value, (string) $information->containerType(), $name); - continue 2; - case 'log': - $this->assertStringContainsString($value, implode(' ', $this->logger->messages()), $name); - continue 2; - default: - throw new RuntimeException(sprintf('Do not know how to test symbol information attribute "%s"', $name)); - } - } - } -} diff --git a/src/Tests/Integration/Core/SourceCodeLocator/StubSourceLocatorTest.php b/src/Tests/Integration/Core/SourceCodeLocator/StubSourceLocatorTest.php deleted file mode 100644 index b3ae46096..000000000 --- a/src/Tests/Integration/Core/SourceCodeLocator/StubSourceLocatorTest.php +++ /dev/null @@ -1,63 +0,0 @@ -workspace()->reset(); - - $locator = new StringSourceLocator(TextDocumentBuilder::create('')->build()); - $reflector = ReflectorBuilder::create()->addLocator($locator)->build(); - $this->workspace()->mkdir('stubs')->mkdir('cache'); - - $this->sourceLocator = new StubSourceLocator( - $reflector, - $this->workspace()->path('stubs'), - $this->workspace()->path('cache') - ); - } - - public function testCanLocateClasses(): void - { - $this->workspace()->put('stubs/Stub.php', 'sourceLocator->locate(ClassName::fromString('StubOne')); - $this->assertStringContainsString('class StubOne', (string) $code); - } - - public function testCanLocateFunctions(): void - { - $this->workspace()->put('stubs/Stub.php', 'sourceLocator->locate(Name::fromString('hello_world')); - $this->assertStringContainsString('function hello_world()', (string) $code); - } - - public function testDoesNotParseNonPhpFiles(): void - { - $this->workspace()->put('stubs/Stub.xml', 'workspace()->put('stubs/Stub.php', 'sourceLocator->locate(Name::fromString('hello_world')); - $this->fail('Non PHP file parsed'); - } catch (NotFound) { - $this->addToAssertionCount(1); - return; - } - - $code = $this->sourceLocator->locate(Name::fromString('goodbye_world')); - $this->assertStringContainsString('function goodbye_world()', (string) $code); - } -} diff --git a/src/Tests/Integration/Core/SourceReflectorTest.php b/src/Tests/Integration/Core/SourceReflectorTest.php deleted file mode 100644 index d2f99d7d9..000000000 --- a/src/Tests/Integration/Core/SourceReflectorTest.php +++ /dev/null @@ -1,86 +0,0 @@ -expectException(ClassNotFound::class); - $this->expectExceptionMessage($expectedErrorMessage); - - $this->createReflector($source)->$method($class); - } - - public function provideReflectClassNotCorrectType() - { - return [ - 'Class' => [ - ' [ - ' [ - 'createReflector($source)->reflectOffset($source, 27); - $this->assertEquals('"Hello"', (string) $offset->nodeContext()->type()); - } - - /** - * @testdox It reflects the value at an offset. - */ - public function testReflectOffsetRedeclared(): void - { - $source = <<<'EOT' - ar; - EOT - ; - - [$source, $offset] = ExtractOffset::fromSource($source); - - $source = TextDocumentBuilder::fromUnknown($source); - $offset = $this->createReflector($source)->reflectOffset($source, (int)$offset); - $this->assertEquals('1234', (string) $offset->nodeContext()->type()); - } -} diff --git a/src/Tests/Integration/Core/Util/OriginalMethodResolverTest.php b/src/Tests/Integration/Core/Util/OriginalMethodResolverTest.php deleted file mode 100644 index 124c5f06b..000000000 --- a/src/Tests/Integration/Core/Util/OriginalMethodResolverTest.php +++ /dev/null @@ -1,105 +0,0 @@ -workspace()->reset(); - $this->workspace()->loadManifest(implode("\n", $manifest)); - $source = $this->workspace()->getContents('test.php'); - [$source, $offset] = ExtractOffset::fromSource($source); - - $reflector = $this->createWorkspaceReflector($source); - - $member = $reflector->reflectClassLike($containerType)->members()->byMemberType($memberType)->get($memberName); - - $member = (new OriginalMethodResolver($reflector))->resolveOriginalMember($member); - - self::assertEquals($expectedType, $member->declaringClass()->name()->__toString()); - } - - /** - * @return Generator - */ - public function provideResolve(): Generator - { - yield 'declaring container type' => [ - ["// File: test.php\n [ - [ - "// File: test.php\n [ - [ - "// File: test.php\n [ - [ - "// File: test.php\n [ - [ - "// File: test.php\n [ - [ - "// File: test.php\nlogger = new ArrayLogger(); - } - - public function createBuilder(TextDocument|string $source): ReflectorBuilder - { - return ReflectorBuilder::create() - ->addSource($source) - ->addMemberProvider(new DocblockMemberProvider()) - ->addFrameWalker(new TestAssertWalker($this)) - ->withLogger($this->logger()); - } - - public function createReflector(string $source): Reflector - { - return $this->createBuilder($source)->build(); - } - - public function createWorkspaceReflector(string $source): Reflector - { - return ReflectorBuilder::create() - ->addLocator(new StubSourceLocator( - ReflectorBuilder::create()->build(), - $this->workspace()->path('/'), - $this->workspace()->path('/') - )) - ->addMemberProvider(new DocblockMemberProvider()) - ->withLogger($this->logger())->build(); - } - - protected function logger(): ArrayLogger - { - return $this->logger; - } - - protected function workspace(): Workspace - { - return new Workspace(__DIR__ . '/../Workspace'); - } - - protected function parseSource(string $source, string $uri = null): SourceFileNode - { - $parser = new Parser(); - - return $parser->parseSourceFile($source, $uri); - } -} diff --git a/src/Tests/Smoke/smoke_test.php b/src/Tests/Smoke/smoke_test.php deleted file mode 100755 index 61a4527ad..000000000 --- a/src/Tests/Smoke/smoke_test.php +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env php - '.*\.php$', - 'offset' => 0, - 'limit' => null -], getopt('', [ - 'pattern:', - 'offset:', - 'limit:', -])); - -if (isset($argv[1])) { - $pattern = $argv[1]; -} - -$reflector = ReflectorBuilder::create() - ->enableCache() - ->addLocator(new ComposerSourceLocator($autoload)) - ->addLocator(new StubSourceLocator( - ReflectorBuilder::create()->build(), - __DIR__ . '/../../vendor/jetbrains/phpstorm-stubs', - __DIR__ . '/../Workspace/smoke-cache' - )) - ->build(); - -$files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::SELF_FIRST -); - -$files = new RegexIterator($files, '{.*' . $opts['pattern'] . '.*}'); -$exceptions = []; -$count = 0; - -echo 'Legend: N = Not found, E = Error' . PHP_EOL . PHP_EOL; - -/** @var SplFileInfo $file */ -foreach ($files as $file) { - if ($count < $opts['offset']) { - $count++; - continue; - } - - if (null !== $opts['limit'] && $count > $opts['limit']) { - break; - } - - echo $count++ . ' ' . Path::makeRelative($file->getPathname(), getcwd()) . PHP_EOL; - $message = $file->getPathname(); - try { - $source = TextDocumentBuilder::create(file_get_contents($file->getPathname()))->uri($file->getPathname())->build(); - $classes = $reflector->reflectClassesIn($source); - - /** @var ReflectionClass $class */ - foreach ($classes as $class) { - /** @var ReflectionMethod $method */ - foreach ($class->methods() as $method) { - $time = microtime(true); - $method->frame(); - - $time = microtime(true) - $time; - - if ($time > $slowThreshold) { - fwrite($logHandle, sprintf('%s#%s (%ss)', $class->name()->full(), $method->name(), number_format($time, 2)) . PHP_EOL); - echo 'S'; - } - } - } - } catch (NotFound $e) { - fwrite($logHandle, sprintf('%s %s %s: ', 'NOT FOUND', Path::makeRelative($file->getPathname(), getcwd()), $e->getMessage()). PHP_EOL); - echo 'N'; - } catch (Exception $e) { - echo 'E'; - fwrite($logHandle, sprintf('%s %s [%s] %s', 'ERROR', $message, get_class($e), $e->getMessage()).PHP_EOL); - ; - $exceptions[] = $e; - } finally { - } -} - -fclose($logHandle); diff --git a/src/Tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php b/src/Tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php deleted file mode 100644 index 237cf5b39..000000000 --- a/src/Tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php +++ /dev/null @@ -1,18 +0,0 @@ -locate(Name::fromString(ComposerSourceLocatorTest::class)); - $this->assertEquals(__FILE__, realpath($sourceCode->uri()->path())); - } -} diff --git a/src/Tests/Unit/Bridge/Phpactor/DocblockParser/DocblockParserFactoryTest.php b/src/Tests/Unit/Bridge/Phpactor/DocblockParser/DocblockParserFactoryTest.php deleted file mode 100644 index d666ce176..000000000 --- a/src/Tests/Unit/Bridge/Phpactor/DocblockParser/DocblockParserFactoryTest.php +++ /dev/null @@ -1,427 +0,0 @@ -parseDocblock($docblock); - - if (is_string($expected)) { - self::assertEquals($expected, $docblock->returnType()->__toString()); - return; - } - - self::assertInstanceOf(get_class($expected), $docblock->returnType()); - self::assertEquals($expected->__toString(), $docblock->returnType()->__toString()); - } - - /** - * @return Generator - */ - public function provideResolveType(): Generator - { - yield [ - '/** @return string */', - new StringType() - ]; - - yield [ - '/** @return int */', - new IntType() - ]; - - yield [ - '/** @return float */', - new FloatType() - ]; - - yield [ - '/** @return mixed */', - new MixedType() - ]; - - yield [ - '/** @return array */', - new ArrayType(new MissingType()) - ]; - - yield [ - '/** @return array|string */', - new UnionType(new ArrayType(new MissingType()), new StringType()) - ]; - - yield [ - '/** @return array&string */', - new IntersectionType(new ArrayType(new MissingType()), new StringType()) - ]; - - yield [ - '/** @return array */', - new ArrayType(new StringType()) - ]; - - yield [ - '/** @return bool */', - new BooleanType() - ]; - - yield [ - '/** @return null */', - new NullType() - ]; - - yield [ - '/** @return callable */', - new CallableType([], new MissingType()) - ]; - - yield [ - '/** @return callable(): string */', - new CallableType([], new StringType()) - ]; - yield [ - '/** @return callable(string,bool): string */', - new CallableType([ - new StringType(), - new BooleanType(), - ], new StringType()) - ]; - yield [ - '/** @return iterable */', - new PseudoIterableType(), - ]; - yield [ - '/** @return object */', - new ObjectType(), - ]; - yield [ - '/** @return resource */', - new ResourceType(), - ]; - - yield [ - '/** @return void */', - new VoidType(), - ]; - yield [ - '/** @return array */', - new ArrayType(new IntType(), new StringType()) - ]; - yield [ - '/** @return array> */', - new ArrayType(new IntType(), new ArrayType(new StringType(), new BooleanType())) - ]; - - yield 'nullable' => [ - '/** @return ?string */', - '?string', - ]; - - yield [ - '/** @return T */', - 'T', - ]; - - yield [ - '/** @return \IteratorAggregate */', - 'IteratorAggregate', - ]; - - yield [ - '/** @return array{} */', - 'array{}', - ]; - - yield 'arrayshape with keys' => [ - '/** @return array{foo:int,bar:string} */', - 'array{foo:int,bar:string}', - ]; - - yield 'parenthesized' => [ - '/** @return null|(callable(int):string)|string|int */', - 'null|(callable(int): string)|string|int', - ]; - - yield 'multiline array shape' => [ - <<<'EOT' - /** - * @return array{ - * foo:int, - * bar:string - * } - */ - EOT - , - 'array{foo:int,bar:string}', - ]; - - yield 'literals' => [ - '/** @return null|"foo"|123|123.3 */', - 'null|"foo"|123|123.3', - ]; - - yield 'list' => [ - '/** @return list */', - TypeFactory::list(), - ]; - - yield 'list with type' => [ - '/** @return list */', - TypeFactory::list(TypeFactory::string()), - ]; - - yield 'never' => [ - '/** @return never */', - TypeFactory::never(), - ]; - - yield 'false' => [ - '/** @return false */', - TypeFactory::false(), - ]; - yield 'union false' => [ - '/** @return false|int */', - TypeFactory::union(TypeFactory::false(), TypeFactory::int()) - ]; - - yield 'psalm prefix' => [ - '/** @psalm-return int */', - TypeFactory::int(), - ]; - - yield 'conditional type' => [ - '/** @return ($foo is true ? string : int) */', - TypeFactory::parenthesized( - new ConditionalType( - '$foo', - TypeFactory::boolLiteral(true), - TypeFactory::string(), - TypeFactory::int() - ) - ) - ]; - - yield 'class string generic' => [ - '/** @return class-string */', - TypeFactory::classString('T'), - ]; - - yield 'int range max' => [ - '/** @return int<12, max> */', - TypeFactory::intRange( - new IntLiteralType(12), - new IntMaxType(PHP_INT_MAX) - ) - ]; - - yield 'int range' => [ - '/** @return int<12, 23> */', - TypeFactory::intRange( - new IntLiteralType(12), - new IntLiteralType(23), - ) - ]; - - yield 'int positive' => [ - '/** @return positive-int */', - TypeFactory::intPositive() - ]; - - yield 'int negative' => [ - '/** @return negative-int */', - TypeFactory::intNegative() - ]; - } - - public function testClassConstant(): void - { - $source = <<<'EOT' - addSource($source)->build(); - $source = TextDocumentBuilder::fromUnknown($source); - $class = $reflector->reflectClassesIn( - $source - )->first(); - $docblock = $this->parseDocblockWithClass($reflector, $class, '/** @return self::BAR */'); - self::assertEquals('self::BAR', $docblock->returnType()->__toString()); - } - - public function testClassConstantGlob(): void - { - $source = <<<'EOT' - addSource($source)->build(); - $source = TextDocumentBuilder::fromUnknown($source); - $class = $reflector->reflectClassesIn($source)->first(); - $docblock = $this->parseDocblockWithClass($reflector, $class, '/** @return Foo::BA* */'); - self::assertEquals('Foo::BA*', $docblock->returnType()->__toString()); - } - - public function testClassConstantGlobInArrayShape(): void - { - $source = <<<'EOT' - addSource($source)->build(); - $source = TextDocumentBuilder::fromUnknown($source); - $class = $reflector->reflectClassesIn($source)->first(); - $docblock = $this->parseDocblockWithClass($reflector, $class, '/** @return array{string,Foo::*} */'); - self::assertEquals('array{string,Foo::*}', $docblock->returnType()->__toString()); - } - - public function testMethods(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @method Barfoo foobar() */'); - $methods = $docblock->methods($reflector->reflectClass('Bar\Foobar')); - - self::assertEquals('foobar', $methods->first()->name()); - self::assertEquals('Barfoo', $methods->first()->type()); - } - - public function testStaticMethods(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @method static Barfoo foobar() */'); - $methods = $docblock->methods($reflector->reflectClass('Bar\Foobar')); - - self::assertEquals('foobar', $methods->first()->name()); - self::assertEquals('Barfoo', $methods->first()->type()); - self::assertTrue($methods->first()->isStatic()); - } - - public function testMethodsWithParams(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @method Barfoo foobar(string $foobar, int $barfoo) */'); - $methods = $docblock->methods($reflector->reflectClass('Bar\Foobar')); - - self::assertEquals('foobar', $methods->first()->name()); - self::assertEquals('Barfoo', $methods->first()->type()); - self::assertEquals('foobar', $methods->first()->parameters()->first()->name()); - self::assertEquals('string', $methods->first()->parameters()->first()->type()); - self::assertEquals('barfoo', $methods->first()->parameters()->get('barfoo')->name()); - self::assertEquals('int', $methods->first()->parameters()->get('barfoo')->type()); - } - - public function testProperties(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @property Barfoo $foobar */'); - $methods = $docblock->properties($reflector->reflectClass('Bar\Foobar')); - - self::assertEquals('foobar', $methods->first()->name()); - self::assertEquals('Barfoo', $methods->first()->type()->__toString()); - } - - public function testVars(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @var Barfoo */'); - $vars = $docblock->vars(); - self::assertEquals('Barfoo', $vars->type()); - } - - public function testVarsWithName(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @var Barfoo $foo */'); - $vars = iterator_to_array($docblock->vars()); - self::assertCount(1, $vars); - self::assertEquals('Barfoo', $vars[0]->type()); - self::assertEquals('foo', $vars[0]->name()); - } - - public function testParameterType(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @param Barfoo $foobar */'); - $type = $docblock->parameterType('foobar'); - self::assertEquals('Barfoo', $type->__toString()); - } - - public function testPropertyType(): void - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, '/** @property Barfoo $foobar */'); - $type = $docblock->propertyType('foobar'); - self::assertEquals('Barfoo', $type->__toString()); - } - - private function parseDocblock(string $docblock): DocBlock - { - $reflector = $this->createReflector('parseDocblockWithReflector($reflector, $docblock); - } - - private function parseDocblockWithReflector(Reflector $reflector, string $docblock): DocBlock - { - $scope = $this->prophesize(ReflectionScope::class); - $scope->resolveFullyQualifiedName(Argument::any())->will(fn ($args) => $args[0]); - - return (new DocblockParserFactory($reflector))->create($docblock, $scope->reveal()); - } - - private function parseDocblockWithClass(Reflector $reflector, ReflectionClassLike $classLike, string $docblock): DocBlock - { - return (new DocblockParserFactory($reflector))->create($docblock, $classLike->scope()); - } -} diff --git a/src/Tests/Unit/Bridge/TolerantParser/Parser/CachedParserTest.php b/src/Tests/Unit/Bridge/TolerantParser/Parser/CachedParserTest.php deleted file mode 100644 index bace7cf54..000000000 --- a/src/Tests/Unit/Bridge/TolerantParser/Parser/CachedParserTest.php +++ /dev/null @@ -1,28 +0,0 @@ -parseSourceFile(file_get_contents(__FILE__)); - $node2 = $parser->parseSourceFile(file_get_contents(__FILE__)); - - $this->assertSame($node1, $node2); - } - - public function testReturnsDifferentResultsForDifferentSourceCodes(): void - { - $parser = new CachedParser(new TtlCache()); - $node1 = $parser->parseSourceFile(file_get_contents(__FILE__)); - $node2 = $parser->parseSourceFile('Foobar' . file_get_contents(__FILE__)); - - $this->assertNotSame($node1, $node2); - } -} diff --git a/src/Tests/Unit/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php b/src/Tests/Unit/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php deleted file mode 100644 index 12a54016f..000000000 --- a/src/Tests/Unit/Bridge/TolerantParser/Reflection/Collection/ReflectionClassCollectionTest.php +++ /dev/null @@ -1,46 +0,0 @@ -serviceLocator = $this->prophesize(ServiceLocator::class); - $this->reflection1 = $this->prophesize(ReflectionClass::class); - $this->reflection2 = $this->prophesize(ReflectionClass::class); - $this->reflection3 = $this->prophesize(ReflectionClass::class); - } - - /** - * @testdox It returns only concrete classes. - */ - public function testConcrete(): void - { - $this->reflection1->isConcrete()->willReturn(false); - $this->reflection2->isConcrete()->willReturn(true); - $this->reflection3->isConcrete()->willReturn(false); - - $collection = ReflectionClassLikeCollection::fromReflections([ - $this->reflection1->reveal(), $this->reflection2->reveal(), $this->reflection3->reveal() - ]); - - $this->assertCount(1, $collection->concrete()); - } -} diff --git a/src/Tests/Unit/Core/Cache/TtlCacheTest.php b/src/Tests/Unit/Core/Cache/TtlCacheTest.php deleted file mode 100644 index 07efd7ca8..000000000 --- a/src/Tests/Unit/Core/Cache/TtlCacheTest.php +++ /dev/null @@ -1,63 +0,0 @@ -getOrSet('foobar', function () { - return 1234; - })); - } - - public function testCachesResultOfPromise(): void - { - $cache = new TtlCache(); - $calls = 0; - $setter = function () use (&$calls) { - return call(function () use (&$calls): void { - $calls++; - }); - }; - wait($cache->getOrSet('foobar', $setter)); - wait($cache->getOrSet('foobar', $setter)); - wait($cache->getOrSet('foobar', $setter)); - self::assertEquals(1, $calls); - } - - public function testCallbackIsOnlyCalledOnce(): void - { - $cache = new TtlCache(); - $count = 0; - for ($i = 0; $i < 5; $i++) { - $cache->getOrSet('foobar', function () use (&$count) { - $count++; - return 1234; - }); - } - self::assertEquals(1, $count); - } - - public function testDiscardsEntryIfExpired(): void - { - $cache = new TtlCache(0.0001); - $count = 0; - - for ($i = 0; $i < 5; $i++) { - $cache->getOrSet('foobar', function () use (&$count) { - $count++; - return 1234; - }); - usleep(50); - } - - self::assertLessThanOrEqual(5, $count); - } -} diff --git a/src/Tests/Unit/Core/CacheForDocumentTest.php b/src/Tests/Unit/Core/CacheForDocumentTest.php deleted file mode 100644 index 6714faa79..000000000 --- a/src/Tests/Unit/Core/CacheForDocumentTest.php +++ /dev/null @@ -1,28 +0,0 @@ - new StaticCache()); - $result = $cache->getOrSet(TextDocumentUri::fromString('file:///foo'), 'bar', function () { - return 'bar'; - }); - self::assertEquals('bar', $result); - $result = $cache->getOrSet(TextDocumentUri::fromString('file:///foo'), 'bar', function () { - return 'boo'; - }); - self::assertEquals('bar', $result); - $result = $cache->getOrSet(TextDocumentUri::fromString('file:///baz'), 'bar', function () { - return 'boo'; - }); - self::assertEquals('boo', $result); - } -} diff --git a/src/Tests/Unit/Core/ClassNameTest.php b/src/Tests/Unit/Core/ClassNameTest.php deleted file mode 100644 index ca2e9110a..000000000 --- a/src/Tests/Unit/Core/ClassNameTest.php +++ /dev/null @@ -1,51 +0,0 @@ -assertSame($givenClass, $className); - } - - public function testFromUnknownString(): void - { - $className = ClassName::fromUnknown(self::CLASS_NAME); - - $this->assertEquals(ClassName::fromString(self::CLASS_NAME), $className); - } - - public function testFromUnknownInvalid(): void - { - $this->expectExceptionMessage('Do not know how to create class'); - ClassName::fromUnknown(new stdClass()); - } - - public function testFromUnknownClassName(): void - { - $className1 = ClassName::fromString('Foobar'); - $className2 = ClassName::fromUnknown($className1); - - $this->assertSame($className1, $className2); - } - - public function testPrepend(): void - { - $className1 = ClassName::fromString('Foobar'); - $className2 = ClassName::fromString('Barfoo'); - - $className3 = $className1->prepend($className2); - - $this->assertEquals('Barfoo\\Foobar', (string) $className3); - } -} diff --git a/src/Tests/Unit/Core/DefaultValueTest.php b/src/Tests/Unit/Core/DefaultValueTest.php deleted file mode 100644 index f417c32ad..000000000 --- a/src/Tests/Unit/Core/DefaultValueTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertFalse($value->isDefined()); - } - - /** - * @testdox It represents a value - */ - public function testValue(): void - { - $value = DefaultValue::fromValue(42); - $this->assertEquals(42, $value->value()); - } -} diff --git a/src/Tests/Unit/Core/DocBlock/PlainDocblockTest.php b/src/Tests/Unit/Core/DocBlock/PlainDocblockTest.php deleted file mode 100644 index 044332a46..000000000 --- a/src/Tests/Unit/Core/DocBlock/PlainDocblockTest.php +++ /dev/null @@ -1,47 +0,0 @@ -createDocblock('')->isDefined()); - self::assertTrue($this->createDocblock('foo')->isDefined()); - } - - public function testInherits(): void - { - self::assertFalse($this->createDocblock('')->inherits()); - self::assertTrue($this->createDocblock('@inheritDoc')->inherits()); - } - - public function testFormatted(): void - { - self::assertEquals("hello world\ngoodbye world", $this->createDocblock( - <<<'EOT' - /** - * hello world - * goodbye world - */ - - EOT - )->formatted()); - } - - public function testSingle(): void - { - self::assertEquals('hello world', $this->createDocblock( - '/** hello world */' - )->formatted()); - } - - private function createDocblock(string $string): DocBlock - { - return new PlainDocblock($string); - } -} diff --git a/src/Tests/Unit/Core/Inference/AssignmentstTestCase.php b/src/Tests/Unit/Core/Inference/AssignmentstTestCase.php deleted file mode 100644 index f22b07d15..000000000 --- a/src/Tests/Unit/Core/Inference/AssignmentstTestCase.php +++ /dev/null @@ -1,98 +0,0 @@ -assignments(); - $this->assertCount(0, $assignments->byName('hello')); - - $information = NodeContext::for( - Symbol::fromTypeNameAndPosition( - Symbol::VARIABLE, - 'hello', - ByteOffsetRange::fromInts(0, 0) - ) - ); - - $assignments->set(Variable::fromSymbolContext($information)); - - $this->assertEquals('hello', $assignments->byName('hello')->first()->name()); - } - - public function testLessThanEqualTo(): void - { - $assignments = $this->assignments(); - - $assignments->set($this->createVariable('hello', 0, 5)); - $assignments->set($this->createVariable('hello', 5, 10)); - $assignments->set($this->createVariable('hello', 10, 15)); - - $this->assertCount(2, $assignments->byName('hello')->lessThanOrEqualTo(5)); - } - - public function testLessThan(): void - { - $assignments = $this->assignments(); - - $assignments->set($this->createVariable('hello', 0, 5)); - $assignments->set($this->createVariable('hello', 5, 10)); - $assignments->set($this->createVariable('hello', 10, 15)); - - $this->assertCount(1, $assignments->byName('hello')->lessThan(5)); - } - - public function testGreaterThanOrEqualTo(): void - { - $assignments = $this->assignments(); - - $assignments->set($this->createVariable('hello', 0, 5)); - $assignments->set($this->createVariable('hello', 5, 10)); - $assignments->set($this->createVariable('hello', 10, 15)); - - $this->assertCount(2, $assignments->byName('hello')->greaterThanOrEqualTo(5)); - } - - public function testGreaterThan(): void - { - $assignments = $this->assignments(); - - $assignments->set($this->createVariable('hello', 0, 5)); - $assignments->set($this->createVariable('hello', 5, 10)); - $assignments->set($this->createVariable('hello', 10, 15)); - - $this->assertCount(1, $assignments->byName('hello')->greaterThan(5)); - } - - public function testThrowsExceptionIfIndexNotExist(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No variable at index "5"'); - $assignments = $this->assignments(); - - $assignments->set($this->createVariable('hello', 0, 5)); - - $this->assertCount(1, $assignments->atIndex(5)); - } - - abstract protected function assignments(): Assignments; - - private function createVariable(string $name, int $start, int $end): Variable - { - return Variable::fromSymbolContext(NodeContext::for(Symbol::fromTypeNameAndPosition( - Symbol::VARIABLE, - $name, - ByteOffsetRange::fromInts($start, $end) - ))); - } -} diff --git a/src/Tests/Unit/Core/Inference/FrameTest.php b/src/Tests/Unit/Core/Inference/FrameTest.php deleted file mode 100644 index 112b41055..000000000 --- a/src/Tests/Unit/Core/Inference/FrameTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertInstanceOf(LocalAssignments::class, $frame->locals()); - $this->assertInstanceOf(PropertyAssignments::class, $frame->properties()); - } -} diff --git a/src/Tests/Unit/Core/Inference/LocalAssignmentsTest.php b/src/Tests/Unit/Core/Inference/LocalAssignmentsTest.php deleted file mode 100644 index 83474eb3f..000000000 --- a/src/Tests/Unit/Core/Inference/LocalAssignmentsTest.php +++ /dev/null @@ -1,14 +0,0 @@ -expectException(CouldNotResolveNode::class); - $this->expectExceptionMessage('Did not know how'); - $frame = new Frame(); - $locator = $this->prophesize(ServiceLocator::class); - $nodeReflector = new NodeReflector($locator->reveal()); - - $nodeReflector->reflectNode($frame, new SourceFileNode()); - } -} diff --git a/src/Tests/Unit/Core/Inference/ProblemsTest.php b/src/Tests/Unit/Core/Inference/ProblemsTest.php deleted file mode 100644 index b6a218f55..000000000 --- a/src/Tests/Unit/Core/Inference/ProblemsTest.php +++ /dev/null @@ -1,30 +0,0 @@ -add($s1); - $p1->add($s2); - - $p2 = Problems::create(); - $p2->add($s3); - $p2->add($s4); - - $p3 = $p2->merge($p1); - - $this->assertEquals([ $s3, $s4, $s1, $s2 ], $p3->toArray()); - } -} diff --git a/src/Tests/Unit/Core/Inference/SymbolFactoryTest.php b/src/Tests/Unit/Core/Inference/SymbolFactoryTest.php deleted file mode 100644 index 6f5df505c..000000000 --- a/src/Tests/Unit/Core/Inference/SymbolFactoryTest.php +++ /dev/null @@ -1,66 +0,0 @@ -factory = new NodeContextFactory(); - $this->node = $this->prophesize(Node::class); - } - - public function testInformationInvalidKeys(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Invalid keys "asd"'); - $this->factory->create('hello', 10, 20, [ 'asd' => 'asd' ]); - } - - public function testInformation(): void - { - $information = $this->factory->create('hello', 10, 20); - $this->assertInstanceOf(NodeContext::class, $information); - $symbol = $information->symbol(); - - $this->assertEquals('hello', $symbol->name()); - $this->assertEquals(10, $symbol->position()->start()->toInt()); - $this->assertEquals(20, $symbol->position()->end()->toInt()); - } - - public function testInformationOptions(): void - { - $containerType = TypeFactory::fromString('container'); - $type = TypeFactory::fromString('type'); - - $information = $this->factory->create('hello', 10, 20, [ - 'symbol_type' => Symbol::ARRAY, - 'container_type' => $containerType, - 'type' => $type, - ]); - - $this->assertInstanceOf(NodeContext::class, $information); - $this->assertSame($information->type(), $type); - $this->assertSame($information->containerType(), $containerType); - $this->assertEquals(Symbol::ARRAY, $information->symbol()->symbolType()); - } -} diff --git a/src/Tests/Unit/Core/Inference/TypeAssertionsTest.php b/src/Tests/Unit/Core/Inference/TypeAssertionsTest.php deleted file mode 100644 index 4cbe7acc4..000000000 --- a/src/Tests/Unit/Core/Inference/TypeAssertionsTest.php +++ /dev/null @@ -1,110 +0,0 @@ -or(new TypeAssertions([$b])); - $assertion = $assertions->variables()->firstForName('foo'); - - self::assertEquals($expected->__toString(), $assertion->apply($type)->__toString()); - self::assertEquals($negated->__toString(), $assertion->negate()->apply($type)->__toString()); - } - - public function provideOr(): Generator - { - yield [ - TypeFactory::mixed(), - - // assert foo is STRING positively and NULL negatively - TypeAssertion::variable( - 'foo', - 0, - fn (Type $t) => $t->addType(TypeFactory::string()), - fn (Type $t) => $t->addType(TypeFactory::null()), - ), - - // assert foo is STRING positively and int NULL negatively - TypeAssertion::variable( - 'foo', - 0, - fn (Type $t) => $t->addType(TypeFactory::int()), - fn (Type $t) => $t->addType(TypeFactory::float()), - ), - - // it's either mixed, int or string - TypeFactory::union( - TypeFactory::mixed(), - TypeFactory::string(), - TypeFactory::int() - ), - - // it's either mixed, int or string - TypeFactory::union( - TypeFactory::mixed(), - TypeFactory::null(), - TypeFactory::float() - ), - ]; - } - - /** - * @dataProvider provideAnd - */ - public function testAnd(Type $type, TypeAssertion $a, TypeAssertion $b, Type $expected, Type $negated): void - { - $assertions = new TypeAssertions([$a]); - $assertions = $assertions->and(new TypeAssertions([$b])); - $assertion = $assertions->variables()->firstForName('foo'); - - self::assertEquals($expected->__toString(), $assertion->apply($type)->__toString()); - self::assertEquals($negated->__toString(), $assertion->negate()->apply($type)->__toString()); - } - - public function provideAnd(): Generator - { - yield [ - TypeFactory::mixed(), - - // both assertions should be applied on positive - TypeAssertion::variable( - 'foo', - 0, - fn (Type $t) => $t->addType(TypeFactory::string()), - fn (Type $t) => $t->addType(TypeFactory::null()), - ), - - // both assertions should be applied on negative - TypeAssertion::variable( - 'foo', - 0, - fn (Type $t) => $t->addType(TypeFactory::int()), - fn (Type $t) => $t->addType(TypeFactory::float()), - ), - - TypeFactory::union( - TypeFactory::mixed(), - TypeFactory::string(), - TypeFactory::int(), - ), - TypeFactory::union( - TypeFactory::mixed(), - TypeFactory::null(), - TypeFactory::float(), - ), - ]; - } -} diff --git a/src/Tests/Unit/Core/Inference/TypeCombinatorTest.php b/src/Tests/Unit/Core/Inference/TypeCombinatorTest.php deleted file mode 100644 index 7144495a7..000000000 --- a/src/Tests/Unit/Core/Inference/TypeCombinatorTest.php +++ /dev/null @@ -1,212 +0,0 @@ -__toString() - ); - } - - /** - * @return Generator - */ - public function provideNarrow(): Generator - { - yield 'cannot narrow from smaller to wider (e.g. string to mixed)' => [ - TypeFactory::union( - TypeFactory::string(), - ), - [ - TypeFactory::mixed(), - ], - '' - ]; - - yield 'mixed narrows to int' => [ - TypeFactory::union( - TypeFactory::mixed(), - ), - [ - TypeFactory::int(), - ], - 'int' - ]; - - yield 'mixed and string narrows to int' => [ - TypeFactory::union( - TypeFactory::mixed(), - TypeFactory::string(), - ), - [ - TypeFactory::int(), - ], - 'int' - ]; - - $classTypes = $this->classTypes( - ' [ - TypeFactory::union( - $classTypes[0], - $classTypes[1], - ), - [ - $classTypes[1], - ], - 'Barfoo', - ]; - - yield 'narrow abstract class to concerete with other types' => [ - TypeFactory::union(...array_merge( - [ - $classTypes[0], - $classTypes[1], - ], - [ - TypeFactory::string(), - ], - )), - [ - $classTypes[1], - ], - 'Barfoo', - ]; - - $classTypes = $this->classTypes( - ' [ - TypeFactory::union( - $classTypes[0], - $classTypes[1], - ), - [ - $classTypes[2], - ], - '(Foobar&Bar)|(Barfoo&Bar)', - ]; - $classTypes = $this->classTypes( - ' [ - TypeFactory::union( - $classTypes[0], - $classTypes[1], - $classTypes[2], - ), - [ - $classTypes[1], - ], - 'Barfoo', - ]; - - yield 'strips unknown types' => [ - TypeFactory::union( - TypeFactory::unknown(), - TypeFactory::string(), - ), - [ - TypeFactory::string(), - ], - 'string', - ]; - - $classTypes = $this->classTypes( - ' [ - TypeFactory::union( - $classTypes[0], - $classTypes[1], - ), - [ - $classTypes[1], - ], - 'Bar', - ]; - - yield 'narrow intersection to unknown type ' => [ - TypeFactory::intersection( - $classTypes[0], - TypeFactory::class('Car'), - ), - [ - $classTypes[1], - ], - 'Foo&Car&Bar', - ]; - - yield 'narrow parenthesized intersection to unknown type ' => [ - TypeFactory::parenthesized( - TypeFactory::intersection( - $classTypes[0], - TypeFactory::class('Car'), - ) - ), - [ - $classTypes[1], - ], - 'Foo&Car&Bar', - ]; - - yield 'narrow parenthesized intersection to intersection type ' => [ - TypeFactory::parenthesized( - TypeFactory::intersection( - $classTypes[0], - TypeFactory::class('Car'), - ) - ), - [ - TypeFactory::parenthesized( - TypeFactory::intersection( - $classTypes[1], - TypeFactory::class('Dar'), - ) - ), - ], - 'Foo&Car&Bar&Dar', - ]; - } - - /** - * @return Type[] - */ - private function classTypes(string $string, string ...$classNames): array - { - $reflector = ReflectorBuilder::create()->addSource($string)->build(); - return array_values(array_map(function (string $className) use ($reflector) { - return TypeFactory::reflectedClass($reflector, $className); - }, $classNames)); - } -} diff --git a/src/Tests/Unit/Core/NameImportsTest.php b/src/Tests/Unit/Core/NameImportsTest.php deleted file mode 100644 index 12f3e63a0..000000000 --- a/src/Tests/Unit/Core/NameImportsTest.php +++ /dev/null @@ -1,86 +0,0 @@ - Name::fromString('Foobar\\Barfoo'), - ]); - - $this->assertTrue($imports->hasAlias('Barfoo')); - $this->assertEquals( - Name::fromString('Foobar\\Barfoo'), - $imports->getByAlias('Barfoo') - ); - } - - public function testResolveAliasedLocalName(): void - { - $imports = NameImports::fromNames([ - 'Baz' => Name::fromString('Foobar\\Barfoo'), - ]); - - $this->assertEquals( - Name::fromString('Baz'), - $imports->resolveLocalName(Name::fromString('Foobar\\Barfoo')) - ); - } - - public function testResolveRelativeAliasedLocalName(): void - { - $imports = NameImports::fromNames([ - 'Baz' => Name::fromString('Foobar\\Barfoo'), - ]); - - $this->assertEquals( - Name::fromString('Baz\\Zoz'), - $imports->resolveLocalName( - Name::fromString('Foobar\\Barfoo\\Zoz') - ) - ); - } - - public function testResolveRelativeAliasedLocalName2(): void - { - $imports = NameImports::fromNames([ - 'Baz' => Name::fromString('Foobar\\Barfoo'), - ]); - - $this->assertEquals( - Name::fromString('Baz\\Zoz\\Foo'), - $imports->resolveLocalName( - Name::fromString('Foobar\\Barfoo\\Zoz\\Foo') - ) - ); - } - - public function testLocalNameIfNoImport(): void - { - $imports = NameImports::fromNames([ - ]); - - $this->assertEquals( - Name::fromString('Foo'), - $imports->resolveLocalName( - Name::fromString('Foobar\\Barfoo\\Zoz\\Foo') - ) - ); - } - - public function testAliasNotFound(): void - { - $this->expectException(RuntimeException::class); - - $imports = NameImports::fromNames([]); - - $imports->getByAlias('Barfoo'); - } -} diff --git a/src/Tests/Unit/Core/NameTest.php b/src/Tests/Unit/Core/NameTest.php deleted file mode 100644 index 0a89c4e5e..000000000 --- a/src/Tests/Unit/Core/NameTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertEquals('Foo', (string) $name->head()); - } - - public function testTail(): void - { - $name = Name::fromString('Foo\\Bar\\Baz'); - $this->assertEquals('Bar\\Baz', (string) $name->tail()); - } - - public function testIsFullyQualified(): void - { - $name = Name::fromString('\\Foo\\Bar\\Baz'); - $this->assertTrue($name->wasFullyQualified()); - } -} diff --git a/src/Tests/Unit/Core/PositionTest.php b/src/Tests/Unit/Core/PositionTest.php deleted file mode 100644 index 9776b7432..000000000 --- a/src/Tests/Unit/Core/PositionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertEquals(15, $position->start()->toInt()); - $this->assertEquals(35, $position->end()->toInt()); - } -} diff --git a/src/Tests/Unit/Core/Reflection/Collection/ChainReflectionMemberCollectionTest.php b/src/Tests/Unit/Core/Reflection/Collection/ChainReflectionMemberCollectionTest.php deleted file mode 100644 index e9107cf45..000000000 --- a/src/Tests/Unit/Core/Reflection/Collection/ChainReflectionMemberCollectionTest.php +++ /dev/null @@ -1,260 +0,0 @@ - - */ - private ObjectProphecy $collection3; - - /** - * @var ObjectProphecy - */ - private ObjectProphecy $collection4; - - public function setUp(): void - { - $this->collection1 = $this->prophesize(ReflectionMemberCollection::class); - $this->collection2 = $this->prophesize(ReflectionMemberCollection::class); - $this->collection3 = $this->prophesize(ReflectionMemberCollection::class); - $this->collection4 = $this->prophesize(ReflectionMemberCollection::class); - - $this->member1 = $this->prophesize(ReflectionMember::class); - } - - public function testIsIterable(): void - { - $collection = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal() - ]); - - $this->collection1->getIterator()->willReturn(new ArrayIterator([1])); - $this->collection2->getIterator()->willReturn(new ArrayIterator([2])); - - $this->assertInstanceOf(Traversable::class, $collection->getIterator()); - $iterator = $collection->getIterator(); - $this->assertEquals(1, $iterator->current()); - $iterator->next(); - $this->assertEquals(2, $iterator->current()); - } - - public function testItReturnsTheCount(): void - { - $collection = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal() - ]); - - $this->collection1->count()->willReturn(1); - $this->collection2->count()->willReturn(2); - $this->assertCount(3, $collection); - } - - public function testItMergesAnotherCollection(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal() - ]); - $collection2 = $collection1->merge($this->collection2->reveal()); - - $this->collection1->count()->willReturn(1); - $this->collection2->count()->willReturn(2); - - $this->assertCount(1, $collection1); - $this->assertCount(3, $collection2); - $this->assertNotSame($collection1, $collection2); - } - - public function testGetsItemByName(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal() - ]); - - $this->collection1->count()->willReturn(1); - $this->collection2->count()->willReturn(3); - - $this->collection1->get('foobar')->willReturn($this->member1->reveal()); - $this->collection1->has('foobar')->willReturn(true); - $this->collection1->keys()->willReturn([]); - $this->collection2->keys()->willReturn([]); - - - $item = $collection1->get('foobar'); - $this->assertSame($this->member1->reveal(), $item); - } - - public function testThrowsExceptionIfItemDoesNotExistOnGet(): void - { - $this->expectException(ItemNotFound::class); - - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal() - ]); - - $this->collection1->getIterator()->willReturn(new ArrayIterator([1])); - $this->collection2->getIterator()->willReturn(new ArrayIterator([2,3])); - - $this->collection1->has('foobar')->willReturn(false); - $this->collection2->has('foobar')->willReturn(false); - - $this->collection1->keys()->willReturn([]); - $this->collection2->keys()->willReturn([]); - - - $item = $collection1->get('foobar'); - $this->assertSame($this->member1->reveal(), $item); - } - - public function testReturnsFirstItem(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal() - ]); - - $this->collection1->first()->willReturn($this->member1->reveal()); - $this->collection1->count()->willReturn(1); - - $member = $collection1->first(); - $this->assertSame($this->member1->reveal(), $member); - } - - public function testReturnsLastItem(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal() - ]); - - $this->collection1->last()->willReturn($this->member1->reveal()); - - $member = $collection1->last(); - $this->assertSame($this->member1->reveal(), $member); - } - - public function testThrowsExceptionIfNoFirstItem(): void - { - $this->expectException(ItemNotFound::class); - $collection1 = ChainReflectionMemberCollection::fromCollections([]); - - $collection1->first(); - } - - public function testThrowsExceptionIfNoLastItem(): void - { - $this->expectException(ItemNotFound::class); - $collection1 = ChainReflectionMemberCollection::fromCollections([]); - - $collection1->last(); - } - - public function testHas(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal(), - ]); - - $this->collection1->has('foo')->willReturn(true); - $this->collection2->has('foo')->shouldNotBeCalled(); - $this->collection1->has('bar')->willReturn(false); - $this->collection2->has('bar')->willReturn(false); - - $this->assertTrue($collection1->has('foo')); - $this->assertFalse($collection1->has('bar')); - } - - public function testReturnByVisibilities(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal(), - ]); - - $visibilties = [ Visibility::protected() ]; - $this->collection1->byVisibilities($visibilties)->willReturn($this->collection3->reveal()); - $this->collection2->byVisibilities($visibilties)->willReturn($this->collection4->reveal()); - $this->collection1->count()->willReturn(1); - $this->collection2->count()->willReturn(1); - $this->collection3->count()->willReturn(1); - $this->collection4->count()->willReturn(1); - - $collection = $collection1->byVisibilities($visibilties); - $this->assertCount(2, $collection); - } - - public function testReturnBelongingTo(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal(), - ]); - - $className = ClassName::fromString('Foo'); - $this->collection1->belongingTo($className)->willReturn($this->collection3->reveal()); - $this->collection2->belongingTo($className)->willReturn($this->collection4->reveal()); - - $this->assertEquals(ChainReflectionMemberCollection::fromCollections([ - $this->collection3->reveal(), - $this->collection4->reveal() - ]), $collection1->belongingTo($className)); - } - - public function testReturnsItemsAtOffset(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal(), - ]); - - $name = 1; - $this->collection1->atOffset($name)->willReturn($this->collection3->reveal()); - $this->collection2->atOffset($name)->willReturn($this->collection4->reveal()); - - $this->assertEquals(ChainReflectionMemberCollection::fromCollections([ - $this->collection3->reveal(), - $this->collection4->reveal() - ]), $collection1->atOffset($name)); - } - - public function testReturnsMembersByName(): void - { - $collection1 = ChainReflectionMemberCollection::fromCollections([ - $this->collection1->reveal(), - $this->collection2->reveal(), - ]); - - $name = 'name'; - $this->collection1->byName($name)->willReturn($this->collection3->reveal()); - $this->collection2->byName($name)->willReturn($this->collection4->reveal()); - - $this->assertEquals(ChainReflectionMemberCollection::fromCollections([ - $this->collection3->reveal(), - $this->collection4->reveal() - ]), $collection1->byName($name)); - } -} diff --git a/src/Tests/Unit/Core/Reflection/Collection/HomogeneousReflectionMemberCollectionTest.php b/src/Tests/Unit/Core/Reflection/Collection/HomogeneousReflectionMemberCollectionTest.php deleted file mode 100644 index aa08708c1..000000000 --- a/src/Tests/Unit/Core/Reflection/Collection/HomogeneousReflectionMemberCollectionTest.php +++ /dev/null @@ -1,117 +0,0 @@ - - */ - private ObjectProphecy $member1; - - /** - * @var ObjectProphecy - */ - private ObjectProphecy $member2; - - /** - * @var ObjectProphecy - */ - private ObjectProphecy $member3; - - public function setUp(): void - { - $this->member1 = $this->prophesize(ReflectionMember::class); - $this->member2 = $this->prophesize(ReflectionMember::class); - $this->member3 = $this->prophesize(ReflectionMember::class); - } - - public function testByVisibilities(): void - { - $collection = $this->create([ - $this->member1->reveal(), - $this->member2->reveal(), - $this->member3->reveal(), - ]); - - $this->member1->visibility()->willReturn(Visibility::public()); - $this->member2->visibility()->willReturn(Visibility::private()); - $this->member3->visibility()->willReturn(Visibility::public()); - - $collection = $collection->byVisibilities([Visibility::public()]); - $this->assertCount(2, $collection); - } - - public function testBelongingTo(): void - { - $collection = $this->create([ - $this->member1->reveal(), - $this->member2->reveal(), - $this->member3->reveal(), - ]); - - $class1 = $this->prophesize(ReflectionClass::class); - $class2 = $this->prophesize(ReflectionClass::class); - $class1->name()->willReturn(ClassName::fromString('foo')); - $class2->name()->willReturn(ClassName::fromString('bar')); - - $this->member1->declaringClass()->willReturn($class1->reveal()); - $this->member2->declaringClass()->willReturn($class2->reveal()); - $this->member3->declaringClass()->willReturn($class1->reveal()); - - $collection = $collection->belongingTo(ClassName::fromString('foo')); - $this->assertCount(2, $collection); - } - - public function testAtOffset(): void - { - $collection = $this->create([ - $this->member1->reveal(), - $this->member2->reveal(), - $this->member3->reveal(), - ]); - - $this->member1->position()->willReturn(ByteOffsetRange::fromInts(0, 10)); - $this->member2->position()->willReturn(ByteOffsetRange::fromInts(11, 11)); - $this->member3->position()->willReturn(ByteOffsetRange::fromInts(13, 16)); - - $collection = $collection->atOffset(11); - $this->assertCount(1, $collection); - } - - public function testByName(): void - { - $collection = $this->create([ - 'foo' => $this->member1->reveal(), - 'bar' => $this->member2->reveal() - ]); - - $this->member1->name()->willReturn('foo'); - - $collection = $collection->byName('foo'); - $this->assertCount(1, $collection); - - $collection = $collection->byName('bar'); - $this->assertCount(0, $collection); - } - - private function create(array $members): ReflectionMemberCollection - { - return HomogeneousReflectionMemberCollection::fromReflections( - $members - ); - } -} diff --git a/src/Tests/Unit/Core/Reflector/ClassReflector/MemonizedClassReflectorTest.php b/src/Tests/Unit/Core/Reflector/ClassReflector/MemonizedClassReflectorTest.php deleted file mode 100644 index bc6cd487d..000000000 --- a/src/Tests/Unit/Core/Reflector/ClassReflector/MemonizedClassReflectorTest.php +++ /dev/null @@ -1,97 +0,0 @@ - - */ - private ObjectProphecy $innerClassReflector; - - /** - * @var MemonizedClassReflector - */ - private MemonizedReflector $reflector; - - /** - * @var ObjectProphecy - */ - private ObjectProphecy $innerFunctionReflector; - - private ClassName $className; - - /** - * @var ObjectProphecy - */ - private ObjectProphecy $innerConstantReflector; - - public function setUp(): void - { - $this->innerClassReflector = $this->prophesize(ClassReflector::class); - $this->innerFunctionReflector = $this->prophesize(FunctionReflector::class); - $this->innerConstantReflector = $this->prophesize(ConstantReflector::class); - - $this->reflector = new MemonizedReflector( - $this->innerClassReflector->reveal(), - $this->innerFunctionReflector->reveal(), - $this->innerConstantReflector->reveal(), - new TtlCache(10) - ); - $this->className = ClassName::fromString('Hello'); - } - - public function testReflectClass(): void - { - $this->innerClassReflector->reflectClass($this->className)->shouldBeCalledTimes(1); - $this->reflector->reflectClass($this->className); - $this->reflector->reflectClass($this->className); - $this->reflector->reflectClass($this->className); - } - - public function testReflectInterface(): void - { - $this->innerClassReflector->reflectInterface($this->className, [])->shouldBeCalledTimes(1); - $this->reflector->reflectInterface($this->className); - $this->reflector->reflectInterface($this->className); - $this->reflector->reflectInterface($this->className); - } - - public function testReflectTrait(): void - { - $this->innerClassReflector->reflectTrait($this->className, [])->shouldBeCalledTimes(1); - $this->reflector->reflectTrait($this->className); - $this->reflector->reflectTrait($this->className); - $this->reflector->reflectTrait($this->className); - } - - public function testReflectClassLike(): void - { - $this->innerClassReflector->reflectClassLike($this->className, [])->shouldBeCalledTimes(1); - $this->reflector->reflectClassLike($this->className); - $this->reflector->reflectClassLike($this->className); - $this->reflector->reflectClassLike($this->className); - } - - public function testReflectFunction(): void - { - $name = Name::fromString('Foo'); - $this->innerFunctionReflector->reflectFunction($name)->shouldBeCalledTimes(1); - $this->reflector->reflectFunction($name); - $this->reflector->reflectFunction($name); - $this->reflector->reflectFunction($name); - } -} diff --git a/src/Tests/Unit/Core/Reflector/SourceCode/ContextualSourceCodeReflectorTest.php b/src/Tests/Unit/Core/Reflector/SourceCode/ContextualSourceCodeReflectorTest.php deleted file mode 100644 index 46771899b..000000000 --- a/src/Tests/Unit/Core/Reflector/SourceCode/ContextualSourceCodeReflectorTest.php +++ /dev/null @@ -1,54 +0,0 @@ -locator = new TemporarySourceLocator(ReflectorBuilder::create()->build()); - - $this->reflector = new ContextualSourceCodeReflector( - ReflectorBuilder::create()->build(), - $this->locator - ); - - $this->code = TextDocumentBuilder::create(self::TEST_SOURCE_CODE)->build(); - } - - public function testReflectsClassesIn(): void - { - self::assertEquals(2, $this->reflector->reflectClassesIn(TextDocumentBuilder::fromUnknown('count()); - } - - public function testReflectOffset(): void - { - $offset = $this->reflector->reflectOffset(TextDocumentBuilder::fromUnknown(self::TEST_SOURCE_CODE), self::TEST_OFFSET); - self::assertInstanceOf(ReflectionOffset::class, $offset); - } - - public function testReflectMethodCall(): void - { - $call = $this->reflector->reflectMethodCall(TextDocumentBuilder::fromUnknown('bar();'), 59); - self::assertInstanceOf(ReflectionMethodCall::class, $call); - } -} diff --git a/src/Tests/Unit/Core/SourceCodeLocator/ChainSourceLocatorTest.php b/src/Tests/Unit/Core/SourceCodeLocator/ChainSourceLocatorTest.php deleted file mode 100644 index af9a7c98f..000000000 --- a/src/Tests/Unit/Core/SourceCodeLocator/ChainSourceLocatorTest.php +++ /dev/null @@ -1,97 +0,0 @@ -locator1 = $this->prophesize(SourceCodeLocator::class); - $this->locator2 = $this->prophesize(SourceCodeLocator::class); - } - - /** - * @testdox It throws an exception if no loaders found. - */ - public function testNoLocators(): void - { - $this->expectException(\Phpactor\WorseReflection\Core\Exception\SourceNotFound::class); - $this->locate([], ClassName::fromString('as')); - } - - /** - * @testdox It delegates to first loader. - */ - public function testDelegateToFirst(): void - { - $expectedSource = TextDocumentBuilder::create('hello')->build(); - $class = ClassName::fromString('Foobar'); - $this->locator1->locate($class)->willReturn($expectedSource); - $this->locator2->locate($class)->shouldNotBeCalled(); - - $source = $this->locate([ - $this->locator1->reveal(), - $this->locator2->reveal() - ], $class); - - $this->assertSame($expectedSource, $source); - } - - /** - * @testdox It delegates to second if first throws exception. - */ - public function testDelegateToSecond(): void - { - $expectedSource = TextDocumentBuilder::create('hello')->build(); - $class = ClassName::fromString('Foobar'); - $this->locator1->locate($class)->willThrow(new SourceNotFound('Foo')); - $this->locator2->locate($class)->willReturn($expectedSource); - - $source = $this->locate([ - $this->locator1->reveal(), - $this->locator2->reveal() - ], $class); - - $this->assertSame($expectedSource, $source); - } - - /** - * @testdox It throws an exception if all fail - */ - public function testAllFail(): void - { - $this->expectException(\Phpactor\WorseReflection\Core\Exception\SourceNotFound::class); - $this->expectExceptionMessage('Could not find source with "Foobar"'); - $expectedSource = TextDocumentBuilder::create('hello')->build(); - $class = ClassName::fromString('Foobar'); - $this->locator1->locate($class)->willThrow(new SourceNotFound('Foo')); - $this->locator2->locate($class)->willThrow(new SourceNotFound('Foo')); - - $this->locate([ - $this->locator1->reveal(), - $this->locator2->reveal() - ], $class); - } - - private function locate(array $locators, ClassName $className) - { - $locator = new ChainSourceLocator($locators); - return $locator->locate($className); - } -} diff --git a/src/Tests/Unit/Core/SourceCodeLocator/NativeReflectionFunctionSourceLocatorTest.php b/src/Tests/Unit/Core/SourceCodeLocator/NativeReflectionFunctionSourceLocatorTest.php deleted file mode 100644 index 16ba5e7ca..000000000 --- a/src/Tests/Unit/Core/SourceCodeLocator/NativeReflectionFunctionSourceLocatorTest.php +++ /dev/null @@ -1,44 +0,0 @@ -locator = new NativeReflectionFunctionSourceLocator(); - } - - public function testLocatesAFunction(): void - { - $location = $this->locator->locate(Name::fromString(__NAMESPACE__ . '\\test_function')); - $this->assertEquals(__FILE__, $location->uri()->path()); - $this->assertEquals(file_get_contents(__FILE__), $location->__toString()); - } - - public function testThrowsExceptionWhenSourceNotFound(): void - { - $this->expectException(SourceNotFound::class); - $this->locator->locate(Name::fromString(__NAMESPACE__ . '\\not_existing')); - } - - public function testDoesNotLocateInternalFunctions(): void - { - $this->expectException(SourceNotFound::class); - $this->locator->locate(Name::fromString('assert')); - } -} - -function test_function(): void -{ -} diff --git a/src/Tests/Unit/Core/SourceCodeLocator/StringSourceLocatorTest.php b/src/Tests/Unit/Core/SourceCodeLocator/StringSourceLocatorTest.php deleted file mode 100644 index 21ad5086a..000000000 --- a/src/Tests/Unit/Core/SourceCodeLocator/StringSourceLocatorTest.php +++ /dev/null @@ -1,19 +0,0 @@ -build()); - $source = $locator->locate(ClassName::fromString('Foobar')); - - $this->assertEquals('Hello', (string) $source); - } -} diff --git a/src/Tests/Unit/Core/SourceCodeLocator/TemporarySourceLocatorTest.php b/src/Tests/Unit/Core/SourceCodeLocator/TemporarySourceLocatorTest.php deleted file mode 100644 index 6c7e89331..000000000 --- a/src/Tests/Unit/Core/SourceCodeLocator/TemporarySourceLocatorTest.php +++ /dev/null @@ -1,60 +0,0 @@ -locator = new TemporarySourceLocator( - ReflectorBuilder::create()->build() - ); - } - - public function testThrowsExceptionWhenClassNotFound(): void - { - $this->expectException(SourceNotFound::class); - $this->expectExceptionMessage('Class "Foobar" not found'); - - $source = TextDocumentBuilder::create('build(); - $this->locator->pushSourceCode($source); - - $this->locator->locate(ClassName::fromString('Foobar')); - } - - public function testReturnsSourceIfClassIsInTheSource(): void - { - $code = 'locator->pushSourceCode(TextDocumentBuilder::create($code)->build()); - $source = $this->locator->locate(ClassName::fromString('Foobar')); - $this->assertEquals($code, (string) $source); - } - - public function testNewFilesOverridePreviousOnes(): void - { - $code1 = 'locator->pushSourceCode(TextDocumentBuilder::create($code1)->uri('file:///foo.php')->build()); - - $code2 = 'locator->pushSourceCode(TextDocumentBuilder::create($code2)->uri('file:///foo.php')->build()); - - $source = $this->locator->locate(ClassName::fromString('Boobar')); - $this->assertEquals($code2, (string) $source); - } -} diff --git a/src/Tests/Unit/Core/TemplateMapTest.php b/src/Tests/Unit/Core/TemplateMapTest.php deleted file mode 100644 index 20f2b7938..000000000 --- a/src/Tests/Unit/Core/TemplateMapTest.php +++ /dev/null @@ -1,18 +0,0 @@ - TypeFactory::undefined(), 'TValue' => TypeFactory::unknown()]); - $mapped = $templateMap->mapArguments([TypeFactory::string(), TypeFactory::int()]); - - self::assertEquals(new TemplateMap(['TKey' => TypeFactory::string(), 'TValue' => TypeFactory::int()]), $mapped); - } -} diff --git a/src/Tests/Unit/Core/Type/AggregateTypeTest.php b/src/Tests/Unit/Core/Type/AggregateTypeTest.php deleted file mode 100644 index 2d0de7c75..000000000 --- a/src/Tests/Unit/Core/Type/AggregateTypeTest.php +++ /dev/null @@ -1,202 +0,0 @@ -remove($remove)->__toString()); - } - - /** - * @return Generator - */ - public function provideRemove(): Generator - { - yield [ - [ - ], - new MissingType(), - '' - ]; - - yield 'do not remove existing type' => [ - [ - TypeFactory::string(), - ], - new MissingType(), - 'string' - ]; - - yield 'remove union' => [ - [ - TypeFactory::string(), - TypeFactory::int(), - TypeFactory::class('Foo'), - TypeFactory::class('Bar'), - ], - TypeFactory::union( - TypeFactory::string(), - TypeFactory::class('Bar'), - ), - 'int|Foo', - ]; - } - - /** - * @dataProvider provideClean - * @param Type[] $types - */ - public function testClean(array $types, string $expected): void - { - self::assertEquals($expected, TypeFactory::union(...$types)->clean()->__toString()); - } - - /** - * @return Generator - */ - public function provideClean(): Generator - { - yield [[], '']; - yield [[TypeFactory::undefined()], '']; - yield [[TypeFactory::string()], 'string']; - - yield [ - [ - TypeFactory::string(), - TypeFactory::string(), - TypeFactory::string(), - TypeFactory::string(), - ], - 'string' - ]; - - yield [ - [ - TypeFactory::string(), - TypeFactory::int(), - TypeFactory::string(), - TypeFactory::string(), - ], - 'string|int' - ]; - } - - public function testFilter(): void - { - $types = TypeFactory::union( - TypeFactory::int(), - TypeFactory::float(), - )->filter(fn (Type $type) => $type instanceof FloatType); - - self::assertEquals(TypeFactory::union(TypeFactory::float()), $types); - } - - /** - * @dataProvider provideReduce - * @param Type[] $types - */ - public function testReduce(array $types, string $expected): void - { - self::assertEquals($expected, TypeFactory::union(...$types)->reduce()->__toString()); - } - - /** - * @return Generator - */ - public function provideReduce(): Generator - { - yield [[], '']; - yield [[TypeFactory::undefined()], '']; - yield [[TypeFactory::string(), ], 'string']; - - yield 'strips parenthesis' => [ - [ - TypeFactory::parenthesized(TypeFactory::string()), - ], - 'string' - ]; - } - - public function testDeduplicatesTypesOnConstruct(): void - { - self::assertEquals('One|Two', TypeFactory::union( - TypeFactory::class('One'), - TypeFactory::class('Two'), - TypeFactory::class('One'), - TypeFactory::class('Two'), - )->__toString()); - } - - public function testDedupesNullOnConstruct(): void - { - self::assertEquals('null|One|Two', TypeFactory::union( - TypeFactory::nullable(TypeFactory::class('One')), - TypeFactory::class('Two'), - TypeFactory::class('One'), - TypeFactory::null(), - TypeFactory::null(), - )->__toString()); - } - - public function testRemovesPointlessParenthesisForIntersection(): void - { - self::assertEquals('null|One|Two', TypeFactory::union( - TypeFactory::null(), - TypeFactory::intersection(TypeFactory::class('One')), - TypeFactory::class('Two') - )->__toString()); - } - - public function testAddsParenthesisForIntersection(): void - { - self::assertEquals('null|(One&string)|Two', TypeFactory::union( - TypeFactory::null(), - TypeFactory::intersection(TypeFactory::class('One'), TypeFactory::string()), - TypeFactory::class('Two') - )->__toString()); - } - - public function testMergeUnions(): void - { - self::assertEquals( - TypeFactory::union( - TypeFactory::class('Two'), - TypeFactory::class('One'), - TypeFactory::string(), - TypeFactory::int() - ), - TypeFactory::union( - TypeFactory::class('Two'), - TypeFactory::class('One'), - TypeFactory::union( - TypeFactory::string(), - TypeFactory::int() - ) - ) - ); - } - - public function testToPhpString(): void - { - self::assertEquals( - 'Foobar|array', - TypeFactory::union( - TypeFactory::class('Foobar'), - TypeFactory::array(TypeFactory::string()) - )->toPhpString() - ); - } -} diff --git a/src/Tests/Unit/Core/Type/ArrayLiteralTypeTest.php b/src/Tests/Unit/Core/Type/ArrayLiteralTypeTest.php deleted file mode 100644 index 92dbce6d5..000000000 --- a/src/Tests/Unit/Core/Type/ArrayLiteralTypeTest.php +++ /dev/null @@ -1,57 +0,0 @@ -generalize()->__toString()); - } - - /** - * @return Generator - */ - public function provideGeneralize(): Generator - { - yield [ - // ['foo','bar'] - TypeFactory::arrayLiteral([ - TypeFactory::stringLiteral('foo'), - TypeFactory::stringLiteral('bar') - ]), - 'array', - ]; - yield [ - TypeFactory::arrayLiteral([ - TypeFactory::arrayLiteral([ - TypeFactory::stringLiteral('one'), - TypeFactory::stringLiteral('two'), - ]), - TypeFactory::arrayLiteral([ - TypeFactory::stringLiteral('one'), - TypeFactory::stringLiteral('two'), - ]), - ]), - 'array>', - ]; - yield [ - // ['foo','bar'] - TypeFactory::arrayLiteral([ - TypeFactory::arrayShape([ - 'foo' => TypeFactory::intLiteral(12), - 'bar' => TypeFactory::intLiteral(12), - ]) - ]), - 'array', - ]; - } -} diff --git a/src/Tests/Unit/Core/Type/ArrayShapeTypeTest.php b/src/Tests/Unit/Core/Type/ArrayShapeTypeTest.php deleted file mode 100644 index e8ab8b10f..000000000 --- a/src/Tests/Unit/Core/Type/ArrayShapeTypeTest.php +++ /dev/null @@ -1,44 +0,0 @@ -generalize()->__toString()); - } - - /** - * @return Generator - */ - public function provideGeneralize(): Generator - { - yield [ - TypeFactory::arrayShape([ - TypeFactory::stringLiteral('foo'), - TypeFactory::stringLiteral('bar') - ]), - 'array{string,string}', - ]; - - yield [ - TypeFactory::arrayShape([ - TypeFactory::stringLiteral('foo'), - TypeFactory::arrayShape([ - 'foo' => TypeFactory::intLiteral(12), - 'bar' => TypeFactory::intLiteral(12), - ]) - ]), - 'array{string,array{foo:int,bar:int}}', - ]; - } -} diff --git a/src/Tests/Unit/Core/Type/ArrayTypeTest.php b/src/Tests/Unit/Core/Type/ArrayTypeTest.php deleted file mode 100644 index 51a807a2e..000000000 --- a/src/Tests/Unit/Core/Type/ArrayTypeTest.php +++ /dev/null @@ -1,39 +0,0 @@ -__toString()); - } - - /** - * @return Generator - */ - public function provideToString(): Generator - { - yield [ - new ArrayType(new StringType()), - 'string[]', - ]; - yield [ - new ArrayType(null, new StringType()), - 'string[]', - ]; - yield [ - new ArrayType(new IntType(), new StringType()), - 'array', - ]; - } -} diff --git a/src/Tests/Unit/Core/Type/CallableTypeTest.php b/src/Tests/Unit/Core/Type/CallableTypeTest.php deleted file mode 100644 index f3f455760..000000000 --- a/src/Tests/Unit/Core/Type/CallableTypeTest.php +++ /dev/null @@ -1,35 +0,0 @@ -__toString()); - } - - public function testToStringWithReturnType(): void - { - self::assertEquals('callable(): string', (new CallableType([], TypeFactory::string()))->__toString()); - } - - public function testAllTypes(): void - { - $type = new CallableType([ - TypeFactory::string(), - TypeFactory::int(), - ], TypeFactory::string()); - - self::assertEquals(new Types([ - TypeFactory::string(), - TypeFactory::int(), - TypeFactory::string(), - ]), $type->allTypes()); - } -} diff --git a/src/Tests/Unit/Core/Type/ClosureTypeTest.php b/src/Tests/Unit/Core/Type/ClosureTypeTest.php deleted file mode 100644 index 867352b46..000000000 --- a/src/Tests/Unit/Core/Type/ClosureTypeTest.php +++ /dev/null @@ -1,29 +0,0 @@ -build(); - $type = new ClosureType($reflector, [ - TypeFactory::string(), - TypeFactory::int(), - ], TypeFactory::string()); - - self::assertEquals(new Types([ - TypeFactory::reflectedClass($reflector, ClassName::fromString('Closure')), - TypeFactory::string(), - TypeFactory::int(), - TypeFactory::string(), - ]), $type->allTypes()); - } -} diff --git a/src/Tests/Unit/Core/Type/GenericClassTypeTest.php b/src/Tests/Unit/Core/Type/GenericClassTypeTest.php deleted file mode 100644 index d5931517e..000000000 --- a/src/Tests/Unit/Core/Type/GenericClassTypeTest.php +++ /dev/null @@ -1,45 +0,0 @@ -build(); - $type = new GenericClassType($reflector, ClassName::fromString('Foo'), [ - TypeFactory::string(), - TypeFactory::int(), - ]); - - self::assertEquals(new Types([ - TypeFactory::reflectedClass($reflector, ClassName::fromString('Foo')), - TypeFactory::string(), - TypeFactory::int(), - ]), $type->allTypes()); - } - - public function testAcceptsUnion(): void - { - $reflector = ReflectorBuilder::create()->addSource('build(); - $type1 = new GenericClassType($reflector, ClassName::fromString('Foo'), [ - TypeFactory::reflectedClass($reflector, ClassName::fromString('A')) - ]); - - $type2 = new GenericClassType($reflector, ClassName::fromString('Foo'), [ - TypeFactory::union( - TypeFactory::reflectedClass($reflector, ClassName::fromString('B')), - TypeFactory::reflectedClass($reflector, ClassName::fromString('C')) - ) - ]); - - self::assertTrue($type1->accepts($type2)->isTrue()); - } -} diff --git a/src/Tests/Unit/Core/Type/IntersectionTypeTest.php b/src/Tests/Unit/Core/Type/IntersectionTypeTest.php deleted file mode 100644 index 50a7d70e3..000000000 --- a/src/Tests/Unit/Core/Type/IntersectionTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -accepts($type)); - } - - /** - * @return Generator - */ - public function provideAccepts(): Generator - { - yield 'does not accept non-class types' => [ - TypeFactory::intersection(TypeFactory::intLiteral(12), TypeFactory::string()), - TypeFactory::int(), - Trinary::false(), - ]; - - yield 'does not accept single type' => [ - TypeFactory::intersection(TypeFactory::class('Foobar'), TypeFactory::class('Barfoo')), - TypeFactory::class('Barfoo'), - Trinary::false(), - ]; - - yield 'accepts intersection' => [ - TypeFactory::intersection(TypeFactory::class('Foobar'), TypeFactory::class('Barfoo')), - TypeFactory::intersection(TypeFactory::class('Foobar'), TypeFactory::class('Barfoo')), - Trinary::true(), - ]; - - $reflector = ReflectorBuilder::create() - ->addSource('build(); - yield 'accepts class that implements intersection interface' => [ - TypeFactory::intersection(TypeFactory::class('B'), TypeFactory::class('C')), - TypeFactory::reflectedClass($reflector, 'A'), - Trinary::true(), - ]; - } -} diff --git a/src/Tests/Unit/Core/Type/NumericTypeTest.php b/src/Tests/Unit/Core/Type/NumericTypeTest.php deleted file mode 100644 index b8ff77b18..000000000 --- a/src/Tests/Unit/Core/Type/NumericTypeTest.php +++ /dev/null @@ -1,21 +0,0 @@ -divide(new IntLiteralType(0))->value()); - self::assertEquals(0, (new IntLiteralType(0))->divide(new IntLiteralType(1))->value()); - } - - public function testDivisionByNonLiteral(): void - { - self::assertEquals(1, (new IntLiteralType(1))->divide(new IntType())->value()); - } -} diff --git a/src/Tests/Unit/Core/Type/ReflectedClassTypeTest.php b/src/Tests/Unit/Core/Type/ReflectedClassTypeTest.php deleted file mode 100644 index 14dec1580..000000000 --- a/src/Tests/Unit/Core/Type/ReflectedClassTypeTest.php +++ /dev/null @@ -1,105 +0,0 @@ - [ - $this->createType( - 'accepts(TypeFactory::class('Bar'))->isTrue()); - } - ]; - yield 'accepts class which extends it' => [ - $this->createType( - 'accepts(TypeFactory::class('Foobar'))->isTrue()); - } - ]; - yield 'rejects class which implements it' => [ - $this->createType( - 'accepts(TypeFactory::class('Foobar'))->isTrue()); - } - ]; - yield 'rejects class which is not it' => [ - $this->createType( - 'accepts(TypeFactory::class('Foobar'))->isFalse()); - } - ]; - - yield 'interface accepts class which implements it' => [ - $this->createType( - 'accepts(TypeFactory::class('Foobar'))->isTrue()); - } - ]; - } - - public function testInstanceOf(): void - { - // is extends - self::assertTrinaryTrue($this->createType( - 'instanceof(TypeFactory::class('Bar'))); - - // is not instance of - self::assertTrinaryFalse($this->createType( - 'instanceof(TypeFactory::class('Baz'))); - - // is possibly instance of because we can't reflect the class - self::assertTrinaryMaybe($this->createType( - '', - 'Foobar' - )->instanceof(TypeFactory::class('Baz'))); - } - - private function createType(string $source, string $name): ReflectedClassType - { - return new ReflectedClassType( - ReflectorBuilder::create()->addSource($source)->build(), - ClassName::fromUnknown($name) - ); - } -} diff --git a/src/Tests/Unit/Core/Type/StringLiteralTypeTest.php b/src/Tests/Unit/Core/Type/StringLiteralTypeTest.php deleted file mode 100644 index 4e1e7b281..000000000 --- a/src/Tests/Unit/Core/Type/StringLiteralTypeTest.php +++ /dev/null @@ -1,23 +0,0 @@ -value(), -3) - ); - $value = str_repeat('a', 356); - self::assertEquals( - '...', - substr((new StringLiteralType($value))->value(), -3) - ); - } -} diff --git a/src/Tests/Unit/Core/Type/UnionTypeTest.php b/src/Tests/Unit/Core/Type/UnionTypeTest.php deleted file mode 100644 index c2020fd7e..000000000 --- a/src/Tests/Unit/Core/Type/UnionTypeTest.php +++ /dev/null @@ -1,63 +0,0 @@ -accepts($type)); - } - - /** - * @return Generator - */ - public function provideAccepts(): Generator - { - yield [ - TypeFactory::union(TypeFactory::int(), TypeFactory::string()), - TypeFactory::int(), - Trinary::true(), - ]; - yield [ - TypeFactory::union(TypeFactory::int(), TypeFactory::string()), - TypeFactory::class('Foobar'), - Trinary::false(), - ]; - yield 'int literal maybe accepts int' => [ - TypeFactory::union(TypeFactory::intLiteral(12), TypeFactory::string()), - TypeFactory::int(), - Trinary::maybe(), - ]; - yield 'string literal maybe string' => [ - TypeFactory::union(TypeFactory::stringLiteral('foo')), - TypeFactory::string(), - Trinary::maybe(), - ]; - yield 'bool literal maybe bool' => [ - TypeFactory::union(TypeFactory::boolLiteral(true)), - TypeFactory::bool(), - Trinary::maybe(), - ]; - yield 'float literal maybe float' => [ - TypeFactory::union(TypeFactory::floatLiteral(12.2)), - TypeFactory::float(), - Trinary::maybe(), - ]; - yield 'boolean true is not empty' => [ - TypeFactory::union(TypeFactory::unionEmpty()), - TypeFactory::boolLiteral(true), - Trinary::false(), - ]; - } -} diff --git a/src/Tests/Unit/Core/TypeFactoryTest.php b/src/Tests/Unit/Core/TypeFactoryTest.php deleted file mode 100644 index edf10cf80..000000000 --- a/src/Tests/Unit/Core/TypeFactoryTest.php +++ /dev/null @@ -1,222 +0,0 @@ -assertEquals($toString, (string) $type, '__toString()'); - $this->assertEquals($phpType, $type->toPhpString(), 'phptype'); - } - - public function provideToString() - { - $reflector = ReflectorBuilder::create()->build(); - yield [ - TypeFactory::fromString('string'), - 'string', - 'string', - ]; - - yield [ - TypeFactory::fromString('class-string'), - 'class-string', - 'string', - ]; - - yield [ - TypeFactory::fromString('float'), - 'float', - 'float', - ]; - - yield [ - TypeFactory::fromString('int'), - 'int', - 'int', - ]; - - yield [ - TypeFactory::fromString('bool'), - 'bool', - 'bool', - ]; - - yield [ - TypeFactory::fromString('array'), - 'array', - 'array', - ]; - - yield [ - TypeFactory::fromString('void'), - 'void', - 'void', - ]; - - yield [ - TypeFactory::fromString('Foobar'), - 'Foobar', - 'Foobar' - ]; - - yield [ - TypeFactory::fromString('mixed'), - 'mixed', - 'mixed' - ]; - - yield 'Collection' => [ - TypeFactory::collection($reflector, 'Foobar', TypeFactory::string()), - 'Foobar', - 'Foobar', - ]; - - yield 'Typed array' => [ - TypeFactory::array(TypeFactory::string()), - 'string[]', - 'array', - ]; - - yield 'Nullable string' => [ - TypeFactory::fromString('?string'), - '?string', - '?string', - ]; - - yield 'Nullable class' => [ - TypeFactory::fromString('?Foobar'), - '?Foobar', - '?Foobar', - ]; - - yield 'Nullable iterable class' => [ - TypeFactory::nullable(TypeFactory::collection($reflector, 'Foo', 'Bar')), - '?Foo', - '?Foo', - ]; - - yield 'callable' => [ - TypeFactory::fromString('callable'), - 'callable()', - 'callable' - ]; - - yield 'iterable' => [ - TypeFactory::fromString('iterable'), - 'iterable', - 'iterable' - ]; - - yield 'resource' => [ - TypeFactory::fromString('resource'), - 'resource', - 'resource' - ]; - - yield 'class-string' => [ - TypeFactory::fromString('class-string'), - 'class-string', - 'string', - ]; - - yield 'list' => [ - TypeFactory::fromString('class-string'), - 'class-string', - 'string', - ]; - - yield 'false' => [ - TypeFactory::fromString('false'), - 'false', - 'false', - ]; - } - - /** - * @dataProvider provideValues - */ - public function testItCanBeCreatedFromAValue($value, Type $expectedType): void - { - $type = TypeFactory::fromValue($value); - $this->assertEquals($expectedType, $type); - } - - public function provideValues() - { - yield [ - 'string', - TypeFactory::stringLiteral('string'), - ]; - - yield [ - 11, - TypeFactory::intLiteral(11), - ]; - - yield [ - 11.2, - TypeFactory::floatLiteral(11.2), - ]; - - yield [ - [], - TypeFactory::array(), - ]; - - yield [ - true, - TypeFactory::boolLiteral(true), - ]; - - yield [ - false, - TypeFactory::boolLiteral(false), - ]; - - yield [ - null, - TypeFactory::null(), - ]; - - yield [ - new stdClass(), - TypeFactory::class(ClassName::fromString('stdClass')), - ]; - - yield 'resource' => [ - \fopen(__FILE__, 'r'), - TypeFactory::resource(), - ]; - - yield 'callable' => [ - function (): void { - }, - TypeFactory::callable(), - ]; - } -} diff --git a/src/Tests/Unit/Core/Virtual/VirtualReflectionMemberTestCase.php b/src/Tests/Unit/Core/Virtual/VirtualReflectionMemberTestCase.php deleted file mode 100644 index 6f6434fa7..000000000 --- a/src/Tests/Unit/Core/Virtual/VirtualReflectionMemberTestCase.php +++ /dev/null @@ -1,99 +0,0 @@ -position = ByteOffsetRange::fromInts(0, 0); - $this->declaringClass = $this->prophesize(ReflectionClass::class); - $this->class = $this->prophesize(ReflectionClass::class); - $this->name = 'test_name'; - $this->frame = $this->prophesize(Frame::class); - $this->docblock = $this->prophesize(DocBlock::class); - $this->scope = $this->prophesize(ReflectionScope::class); - $this->visibility = Visibility::public(); - $this->type = TypeFactory::unknown(); - } - - abstract public function member(): ReflectionMember; - - public function testPosition(): void - { - $this->assertSame($this->position, $this->member()->position()); - } - - public function testDeclaringClass(): void - { - $this->assertSame($this->declaringClass->reveal(), $this->member()->declaringClass()); - } - - public function testClass(): void - { - $this->assertSame($this->class->reveal(), $this->member()->class()); - } - - public function testName(): void - { - $this->assertEquals($this->name, $this->member()->name()); - } - - public function testFrame(): void - { - $this->assertEquals($this->frame->reveal(), $this->member()->frame()); - } - - public function testDocblock(): void - { - $this->assertEquals($this->docblock->reveal(), $this->member()->docblock()); - } - - public function testScope(): void - { - $this->assertEquals($this->scope->reveal(), $this->member()->scope()); - } - - public function testVisibility(): void - { - $this->assertEquals($this->visibility, $this->member()->visibility()); - } - - public function testType(): void - { - $this->assertEquals($this->type, $this->member()->type()); - } -} diff --git a/src/Tests/Unit/Core/Virtual/VirtualReflectionMethodTest.php b/src/Tests/Unit/Core/Virtual/VirtualReflectionMethodTest.php deleted file mode 100644 index a59afcde6..000000000 --- a/src/Tests/Unit/Core/Virtual/VirtualReflectionMethodTest.php +++ /dev/null @@ -1,85 +0,0 @@ -parameters = ReflectionParameterCollection::empty(); - $this->body = NodeText::fromString('hello'); - $this->isAbstract = true; - $this->isStatic = true; - } - - /** - * @return ReflectionMethod - */ - public function member(): ReflectionMember - { - return new VirtualReflectionMethod( - $this->position, - $this->declaringClass->reveal(), - $this->class->reveal(), - $this->name, - $this->frame->reveal(), - $this->docblock->reveal(), - $this->scope->reveal(), - $this->visibility, - $this->type, - $this->type, - $this->parameters, - $this->body, - $this->isAbstract, - $this->isStatic, - new Deprecation(false) - ); - } - - public function testParameters(): void - { - $this->assertEquals($this->parameters, $this->member()->parameters()); - } - - public function testBody(): void - { - $this->assertEquals($this->body, $this->member()->body()); - } - - public function testIsAbstract(): void - { - $this->assertEquals($this->isAbstract, $this->member()->isAbstract()); - } - - public function testIsStatic(): void - { - $this->assertEquals($this->isStatic, $this->member()->isStatic()); - } - - public function testVirtual(): void - { - $this->assertTrue($this->member()->isStatic()); - } - - public function testReturnType(): void - { - $this->assertEquals(TypeFactory::unknown(), $this->member()->returnType()); - } -} diff --git a/src/Tests/Unit/Core/Virtual/VirtualReflectionParameterTest.php b/src/Tests/Unit/Core/Virtual/VirtualReflectionParameterTest.php deleted file mode 100644 index 79dfdbebb..000000000 --- a/src/Tests/Unit/Core/Virtual/VirtualReflectionParameterTest.php +++ /dev/null @@ -1,81 +0,0 @@ - */ - private ObjectProphecy $class; - - private string $name; - - /** @var ObjectProphecy */ - private ObjectProphecy $scope; - - private Type $type; - - /** @var ObjectProphecy */ - private ObjectProphecy $method; - - private DefaultValue $defaults; - - private bool $byReference; - - public function setUp(): void - { - $this->position = ByteOffsetRange::fromInts(0, 0); - $this->class = $this->prophesize(ReflectionClass::class); - $this->name = 'test_name'; - $this->scope = $this->prophesize(ReflectionScope::class); - $this->type = TypeFactory::unknown(); - $this->method = $this->prophesize(ReflectionMethod::class); - $this->defaults = DefaultValue::fromValue(1234); - $this->byReference = false; - } - - public function parameter(): ReflectionParameter - { - return new VirtualReflectionParameter( - $this->name, - $this->method->reveal(), - $this->type, - $this->type, - $this->defaults, - $this->byReference, - $this->scope->reveal(), - $this->position, - 0 - ); - } - - public function testAccess(): void - { - $parameter = $this->parameter(); - $this->assertEquals($this->name, $parameter->name()); - $this->assertEquals($this->method->reveal(), $parameter->functionLike()); - $this->assertEquals($this->type, $parameter->inferredType()); - $this->assertEquals($this->type, $parameter->type()); - $this->assertEquals($this->defaults, $parameter->default()); - $this->assertEquals($this->byReference, $parameter->byReference()); - $this->assertEquals($this->scope->reveal(), $parameter->scope()); - $this->assertEquals($this->position, $parameter->position()); - $this->assertEquals(0, $parameter->index()); - } -} diff --git a/src/Tests/Unit/ReflectorBuilderTest.php b/src/Tests/Unit/ReflectorBuilderTest.php deleted file mode 100644 index dc4500d10..000000000 --- a/src/Tests/Unit/ReflectorBuilderTest.php +++ /dev/null @@ -1,117 +0,0 @@ -build(); - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testReplacesLogger(): void - { - $logger = $this->prophesize(LoggerInterface::class); - $reflector = ReflectorBuilder::create() - ->withLogger($logger->reveal()) - ->build(); - - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testHasOneLocator(): void - { - $locator = $this->prophesize(SourceCodeLocator::class); - $reflector = ReflectorBuilder::create() - ->addLocator($locator->reveal()) - ->build(); - - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testHasManyLocators(): void - { - $locator = $this->prophesize(SourceCodeLocator::class); - $reflector = ReflectorBuilder::create() - ->addLocator($locator->reveal()) - ->addLocator($locator->reveal()) - ->build(); - - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testHighestPriorityLocatorWins(): void - { - $locator1 = $this->prophesize(SourceCodeLocator::class); - $locator2 = $this->prophesize(SourceCodeLocator::class); - $locator3 = $this->prophesize(SourceCodeLocator::class); - - $reflector = ReflectorBuilder::create() - ->addLocator($locator1->reveal(), 0) - ->addLocator($locator2->reveal(), 10) - ->addLocator($locator3->reveal(), -10) - ->build(); - - $locator1->locate(Argument::any())->shouldNotBeCalled(); - $locator2->locate(Argument::any())->willReturn(TextDocumentBuilder::create(file_get_contents(__FILE__))->build()); - $locator3->locate(Argument::any())->shouldNotBeCalled(); - - $this->assertInstanceOf(Reflector::class, $reflector); - $reflector->reflectClass(__CLASS__); - } - - public function testWithSource(): void - { - $reflector = ReflectorBuilder::create() - ->addSource('build(); - - $class = $reflector->reflectClass('Foobar'); - $this->assertEquals('Foobar', $class->name()->__toString()); - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testInternalLocatorGetsHighestPriority(): void - { - $reflector = ReflectorBuilder::create() - ->addLocator(new StringSourceLocator( - TextDocumentBuilder::create('build() - ), 100) - ->build(); - - $class = $reflector->reflectInterface('BackedEnum'); - $this->assertEquals('BackedEnum', $class->name()->__toString()); - $this->assertStringContainsString('InternalStubs', $class->sourceCode()->uri()->path()); - } - - public function testEnableCache(): void - { - $reflector = ReflectorBuilder::create() - ->enableCache() - ->build(); - - $this->assertInstanceOf(Reflector::class, $reflector); - } - - public function testEnableContextualSourceLocation(): void - { - $reflector = ReflectorBuilder::create() - ->enableContextualSourceLocation() - ->build(); - - $this->assertInstanceOf(Reflector::class, $reflector); - } -} diff --git a/src/Tests/Unit/TypeUtilTest.php b/src/Tests/Unit/TypeUtilTest.php deleted file mode 100644 index c905266e7..000000000 --- a/src/Tests/Unit/TypeUtilTest.php +++ /dev/null @@ -1,165 +0,0 @@ -addSource($source)->build(); - $class = $reflector->reflectClassLike('Foo'); - self::assertEquals( - $expected, - (string)$type->toLocalType($class->scope()) - ); - } - - public function provideToLocalType(): Generator - { - $reflector = ReflectorBuilder::create()->build(); - yield [ - '', - ]; - } - - /** - * @dataProvider provideShort - */ - public function testShort(Type $type, string $expected): void - { - self::assertEquals( - $expected, - $type->short(), - ); - } - - public function provideShort(): Generator - { - yield 'scalar' => [ - TypeFactory::string(), - 'string', - ]; - - yield 'Root class' => [ - TypeFactory::class('Foo'), - 'Foo', - ]; - yield 'Namespaced class' => [ - TypeFactory::class('\Foo\Bar'), - 'Bar', - ]; - yield 'Union' => [ - TypeFactory::union( - TypeFactory::class('\Foo\Bar'), - ), - 'Bar', - ]; - yield 'Union with two elements' => [ - TypeFactory::union( - TypeFactory::class('\Foo\Bar'), - TypeFactory::class('\Foo\Baz'), - ), - 'Bar|Baz', - ]; - } - - /** - * @dataProvider provideShortenClassTypes - */ - public function testShortenClassTypes(Type $type, string $expected): void - { - self::assertEquals( - $expected, - TypeUtil::shortenClassTypes($type)->__toString() - ); - } - - public function provideShortenClassTypes(): Generator - { - yield 'scalar' => [ - TypeFactory::string(), - 'string', - ]; - - yield 'Root class' => [ - TypeFactory::class('Foo'), - 'Foo', - ]; - yield 'Namespaced class' => [ - TypeFactory::class('\Foo\Bar'), - 'Bar', - ]; - yield 'Union' => [ - TypeFactory::union( - TypeFactory::class('\Foo\Bar'), - ), - 'Bar', - ]; - yield 'Union with two elements' => [ - TypeFactory::union( - TypeFactory::class('\Foo\Bar'), - TypeFactory::class('\Foo\Baz'), - ), - 'Bar|Baz', - ]; - yield 'Static' => [ - TypeFactory::static( - TypeFactory::class('\Foo\Bar'), - ), - 'static(Bar)', - ]; - yield 'This' => [ - TypeFactory::this( - TypeFactory::class('\Foo\Bar'), - ), - '$this(Bar)', - ]; - yield 'Nullable' => [ - TypeFactory::nullable( - TypeFactory::class('\Foo\Bar'), - ), - '?Bar', - ]; - } -} diff --git a/src/Tests/Workspace/88f1dd30b180fd8d7e17547833be97ef.map b/src/Tests/Workspace/88f1dd30b180fd8d7e17547833be97ef.map deleted file mode 100644 index 72c5ed9b1..000000000 --- a/src/Tests/Workspace/88f1dd30b180fd8d7e17547833be97ef.map +++ /dev/null @@ -1 +0,0 @@ -a:2:{s:6:"Barfoo";s:78:"/home/daniel/www/phpactor/phpactor/lib/WorseReflection/Tests/Workspace/one.php";s:6:"Foobar";s:79:"/home/daniel/www/phpactor/phpactor/lib/WorseReflection/Tests/Workspace/test.php";} \ No newline at end of file diff --git a/src/Tests/Workspace/one.php b/src/Tests/Workspace/one.php deleted file mode 100644 index 98b687c73..000000000 --- a/src/Tests/Workspace/one.php +++ /dev/null @@ -1 +0,0 @@ -locate(Name::fromString(__CLASS__)); $this->assertSame(file_get_contents(__FILE__), (string) $code); diff --git a/tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php b/tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php index a9d0cae29..5dc17f3cb 100644 --- a/tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php +++ b/tests/Integration/Bridge/Phpactor/ClassToFileSourceLocatorTest.php @@ -14,7 +14,7 @@ class ClassToFileSourceLocatorTest extends IntegrationTestCase public function setUp(): void { - $classToFile = ClassToFileConverter::fromComposerAutoloader(include(__DIR__ . '/../../../../../../vendor/autoload.php')); + $classToFile = ClassToFileConverter::fromComposerAutoloader(include(__DIR__ . '/../../../../vendor/autoload.php')); $this->locator = new ClassToFileSourceLocator($classToFile); } diff --git a/tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php b/tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php index 237cf5b39..f0a1c9639 100644 --- a/tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php +++ b/tests/Unit/Bridge/Composer/ComposerSourceLocatorTest.php @@ -10,7 +10,7 @@ class ComposerSourceLocatorTest extends TestCase { public function testLocate(): void { - $autoloader = require(__DIR__ . '/../../../../../../vendor/autoload.php'); + $autoloader = require(__DIR__ . '/../../../../vendor/autoload.php'); $locator = new ComposerSourceLocator($autoloader); $sourceCode = $locator->locate(Name::fromString(ComposerSourceLocatorTest::class)); $this->assertEquals(__FILE__, realpath($sourceCode->uri()->path()));