diff --git a/README.md b/README.md index c3d4141..804e00a 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ Here are some of the features supported: - 🔴 Support for `strict_types` configurations - 🔴 Conditional types - 🔴 Type aliases -- 🔴 Extensions reflection (SPL, Zip, DS, DOM, etc.) - 🔴 Template type inference for functions ### 🐞 How reliable is this? diff --git a/phpstan.neon b/phpstan.neon index b913a7b..3a0c9b4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,6 +12,9 @@ parameters: reportUnmatchedIgnoredErrors: false ignoreErrors: + # They do have the type specified. I don't know what PHPStan is on about here. + - '#Method GoodPhp\\Reflection\\Reflection\\Methods\\MergedInheritanceMethodReflection::invoke\(\) has parameter \$args with no type specified.#' + - '#Method GoodPhp\\Reflection\\Reflection\\Methods\\MergedInheritanceMethodReflection::invokeLax\(\) has parameter \$args with no type specified.#' # PHPStan doesn't understand that Collection::values() produces a list - '#Method .*::.* should return list<(.*)> but returns array<(int, )?\1>.#i' - '#Parameter [\$|\#].* of .* expects list<(.*)>(|.*)?, (array|non-empty-array)<(int, )?\1> given.#i' diff --git a/src/NativePHPDoc/Definition/BuiltIns/BuiltInCoreDefinitionProvider.php b/src/NativePHPDoc/Definition/BuiltIns/BuiltInCoreDefinitionProvider.php index 3c018bc..31d160a 100644 --- a/src/NativePHPDoc/Definition/BuiltIns/BuiltInCoreDefinitionProvider.php +++ b/src/NativePHPDoc/Definition/BuiltIns/BuiltInCoreDefinitionProvider.php @@ -13,6 +13,7 @@ use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\MethodDefinition; use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\TypeParameterDefinition; use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\UsedTraitsDefinition; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\Combinatorial\ExpandedType; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\PrimitiveType; @@ -21,6 +22,7 @@ use GoodPhp\Reflection\Type\Template\TemplateType; use GoodPhp\Reflection\Type\Template\TemplateTypeVariance; use GoodPhp\Reflection\Util\Lazy\Lazy; +use IteratorAggregate; use Traversable; use function GoodPhp\Reflection\Util\Lazy\lazy; @@ -45,6 +47,7 @@ public function __construct() typeParameters: [], parameters: [], returnType: PrimitiveType::integer(), + returnTypeSource: TypeSource::PHP_DOC, ), ] )), @@ -77,10 +80,12 @@ public function __construct() type: new TemplateType( name: 'TKey', ), + typeSource: TypeSource::PHP_DOC, hasDefaultValue: false, ), ], returnType: PrimitiveType::boolean(), + returnTypeSource: TypeSource::PHP_DOC, ), new MethodDefinition( name: 'offsetGet', @@ -91,6 +96,7 @@ public function __construct() type: new TemplateType( name: 'TKey', ), + typeSource: TypeSource::PHP_DOC, hasDefaultValue: false, ), ], @@ -99,6 +105,7 @@ public function __construct() name: 'TValue', ) ), + returnTypeSource: TypeSource::PHP_DOC, ), new MethodDefinition( name: 'offsetSet', @@ -111,6 +118,7 @@ public function __construct() name: 'TKey', ) ), + typeSource: TypeSource::PHP_DOC, hasDefaultValue: false, ), new FunctionParameterDefinition( @@ -118,10 +126,12 @@ public function __construct() type: new TemplateType( name: 'TValue', ), + typeSource: TypeSource::PHP_DOC, hasDefaultValue: false, ), ], returnType: VoidType::get(), + returnTypeSource: TypeSource::PHP_DOC, ), new MethodDefinition( name: 'offsetUnset', @@ -132,10 +142,12 @@ public function __construct() type: new TemplateType( name: 'TKey', ), + typeSource: TypeSource::PHP_DOC, hasDefaultValue: false, ), ], returnType: VoidType::get(), + returnTypeSource: TypeSource::PHP_DOC, ), ] )), @@ -148,7 +160,7 @@ public function __construct() name: 'TKey', variadic: false, upperBound: null, - variance: TemplateTypeVariance::COVARIANT, + variance: TemplateTypeVariance::INVARIANT, ), new TypeParameterDefinition( name: 'TValue', @@ -169,6 +181,51 @@ public function __construct() ], methods: [] )), + IteratorAggregate::class => lazy(fn () => new InterfaceTypeDefinition( + qualifiedName: IteratorAggregate::class, + fileName: null, + builtIn: true, + typeParameters: [ + new TypeParameterDefinition( + name: 'TKey', + variadic: false, + upperBound: null, + variance: TemplateTypeVariance::INVARIANT, + ), + new TypeParameterDefinition( + name: 'TValue', + variadic: false, + upperBound: null, + variance: TemplateTypeVariance::COVARIANT, + ), + ], + extends: [ + new NamedType(Traversable::class, [ + new TemplateType( + name: 'TKey', + ), + new TemplateType( + name: 'TValue', + ), + ]), + ], + methods: [ + new MethodDefinition( + name: 'getIterator', + typeParameters: [], + parameters: [], + returnType: new NamedType(Traversable::class, [ + new TemplateType( + name: 'TKey', + ), + new TemplateType( + name: 'TValue', + ), + ]), + returnTypeSource: TypeSource::PHP_DOC, + ), + ] + )), Closure::class => lazy(fn () => new ClassTypeDefinition( qualifiedName: Closure::class, fileName: null, diff --git a/src/NativePHPDoc/Definition/NativePHPDoc/NativePHPDocDefinitionProvider.php b/src/NativePHPDoc/Definition/NativePHPDoc/NativePHPDocDefinitionProvider.php index d6c80d4..bb405bc 100644 --- a/src/NativePHPDoc/Definition/NativePHPDoc/NativePHPDocDefinitionProvider.php +++ b/src/NativePHPDoc/Definition/NativePHPDoc/NativePHPDocDefinitionProvider.php @@ -25,6 +25,7 @@ use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\UsedTraitAliasDefinition; use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\UsedTraitDefinition; use GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition\UsedTraitsDefinition; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\Template\TemplateTypeVariance; use GoodPhp\Reflection\Type\Type; @@ -70,18 +71,29 @@ class_exists($type), interface_exists($type), trait_exists($type) => $this->forC }; } + /** + * @return array{ Type|null, TypeSource|null } + */ public function map( ReflectionType|string|null $nativeType, ?TypeNode $phpDocType, TypeContext $context - ): ?Type { - if (!$nativeType && !$phpDocType) { - return null; + ): array { + if ($phpDocType) { + return [ + $this->phpDocTypeMapper->map($phpDocType, $context), + TypeSource::PHP_DOC, + ]; } - return $phpDocType ? - $this->phpDocTypeMapper->map($phpDocType, $context) : - $this->nativeTypeMapper->map($nativeType, $context); + if ($nativeType) { + return [ + $this->nativeTypeMapper->map($nativeType, $context), + TypeSource::NATIVE, + ]; + } + + return [null, null]; } /** @@ -217,13 +229,16 @@ private function properties(ReflectionClass $reflection, TypeContext $context): $phpDocType = $constructorPhpDoc->firstParamTagValue($property->getName())?->type; } + [$type, $typeSource] = $this->map( + $property->getType(), + $phpDocType, + $context + ); + return new PropertyDefinition( name: $property->getName(), - type: $this->map( - $property->getType(), - $phpDocType, - $context - ), + type: $type, + typeSource: $typeSource, hasDefaultValue: $property->hasDefaultValue(), isPromoted: $property->isPromoted(), ); @@ -251,15 +266,18 @@ private function methods(ReflectionClass $reflection, TypeContext $context): arr $this->lazyTypeParameters($phpDoc, $context) ); + [$returnType, $returnTypeSource] = $this->map( + $method->getReturnType(), + $phpDocType, + $context, + ); + return new MethodDefinition( name: $method->getName(), typeParameters: $this->typeParameters($phpDoc, $context), parameters: $this->functionParameters($method, $phpDoc, $context), - returnType: $this->map( - $method->getReturnType(), - $phpDocType, - $context, - ) + returnType: $returnType, + returnTypeSource: $returnTypeSource, ); }) ->values() @@ -329,13 +347,16 @@ private function functionParameters(ReflectionMethod $reflection, ParsedPhpDoc $ ->map(function (ReflectionParameter $parameter) use ($context, $phpDoc) { $paramTag = $phpDoc->firstParamTagValue($parameter->getName()); + [$type, $typeSource] = $this->map( + $parameter->getType(), + $paramTag?->type, + $context + ); + return new FunctionParameterDefinition( name: $parameter->getName(), - type: $this->map( - $parameter->getType(), - $paramTag?->type, - $context - ), + type: $type, + typeSource: $typeSource, hasDefaultValue: $parameter->isDefaultValueAvailable(), ); }) @@ -357,7 +378,7 @@ private function parent(ReflectionClass $reflection, ParsedPhpDoc $phpDoc, TypeC fn (ExtendsTagValueNode $node) => $parentClass === $this->typeAliasResolver->resolve($node->type->type->name, $context->fileClassLikeContext) ); - $type = $this->map( + [$type] = $this->map( $parentClass, $tagValue?->type, $context @@ -389,7 +410,7 @@ private function interfaces(ReflectionClass $reflection, ParsedPhpDoc $phpDoc, T fn (ExtendsTagValueNode $node) => $className === $this->typeAliasResolver->resolve($node->type->type->name, $context->fileClassLikeContext) ); - $type = $this->map( + [$type] = $this->map( $className, $tagValue?->type, $context @@ -435,7 +456,7 @@ private function traits(ReflectionClass $reflection, TypeContext $context): Used fn (UsesTagValueNode $node) => $qualifiedName === $this->typeAliasResolver->resolve($node->type->type->name, $context->fileClassLikeContext) ); - $type = $this->map( + [$type] = $this->map( $qualifiedName, $tagValue?->type, $context diff --git a/src/NativePHPDoc/Definition/TypeDefinition/FunctionParameterDefinition.php b/src/NativePHPDoc/Definition/TypeDefinition/FunctionParameterDefinition.php index f4c0f67..d073bb9 100644 --- a/src/NativePHPDoc/Definition/TypeDefinition/FunctionParameterDefinition.php +++ b/src/NativePHPDoc/Definition/TypeDefinition/FunctionParameterDefinition.php @@ -2,6 +2,7 @@ namespace GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\Type; final class FunctionParameterDefinition @@ -9,6 +10,7 @@ final class FunctionParameterDefinition public function __construct( public readonly string $name, public readonly ?Type $type, + public readonly ?TypeSource $typeSource, public readonly bool $hasDefaultValue, ) {} } diff --git a/src/NativePHPDoc/Definition/TypeDefinition/MethodDefinition.php b/src/NativePHPDoc/Definition/TypeDefinition/MethodDefinition.php index 51571d4..7038576 100644 --- a/src/NativePHPDoc/Definition/TypeDefinition/MethodDefinition.php +++ b/src/NativePHPDoc/Definition/TypeDefinition/MethodDefinition.php @@ -2,6 +2,7 @@ namespace GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\Type; final class MethodDefinition @@ -15,5 +16,6 @@ public function __construct( public readonly array $typeParameters, public readonly array $parameters, public readonly ?Type $returnType, + public readonly ?TypeSource $returnTypeSource, ) {} } diff --git a/src/NativePHPDoc/Definition/TypeDefinition/PropertyDefinition.php b/src/NativePHPDoc/Definition/TypeDefinition/PropertyDefinition.php index 90e9d38..2ad1e26 100644 --- a/src/NativePHPDoc/Definition/TypeDefinition/PropertyDefinition.php +++ b/src/NativePHPDoc/Definition/TypeDefinition/PropertyDefinition.php @@ -2,6 +2,7 @@ namespace GoodPhp\Reflection\NativePHPDoc\Definition\TypeDefinition; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\Type; final class PropertyDefinition @@ -9,6 +10,7 @@ final class PropertyDefinition public function __construct( public readonly string $name, public readonly ?Type $type, + public readonly ?TypeSource $typeSource, public readonly bool $hasDefaultValue, public readonly bool $isPromoted, ) {} diff --git a/src/NativePHPDoc/DefinitionProviderReflector.php b/src/NativePHPDoc/DefinitionProviderReflector.php index 3ebaf98..eca9ebb 100644 --- a/src/NativePHPDoc/DefinitionProviderReflector.php +++ b/src/NativePHPDoc/DefinitionProviderReflector.php @@ -13,6 +13,7 @@ use GoodPhp\Reflection\NativePHPDoc\Reflection\NpdInterfaceReflection; use GoodPhp\Reflection\NativePHPDoc\Reflection\NpdSpecialTypeReflection; use GoodPhp\Reflection\NativePHPDoc\Reflection\NpdTraitReflection; +use GoodPhp\Reflection\Reflection\ClassMemberInheritanceResolver; use GoodPhp\Reflection\Reflection\TypeReflection; use GoodPhp\Reflection\Reflector; use GoodPhp\Reflection\Type\NamedType; @@ -26,10 +27,13 @@ final class DefinitionProviderReflector implements Reflector { private readonly TypeComparator $typeComparator; + private readonly ClassMemberInheritanceResolver $classMemberInheritanceResolver; + public function __construct( private readonly DefinitionProvider $definitionProvider, ) { $this->typeComparator = new TypeComparator($this); + $this->classMemberInheritanceResolver = new ClassMemberInheritanceResolver(); } public function typeComparator(): TypeComparator @@ -54,10 +58,10 @@ public function forNamedType(NamedType $type): TypeReflection }; return match (true) { - $definition instanceof ClassTypeDefinition => new NpdClassReflection($definition, $resolvedTypeParameterMap, $this), - $definition instanceof InterfaceTypeDefinition => new NpdInterfaceReflection($definition, $resolvedTypeParameterMap, $this), - $definition instanceof TraitTypeDefinition => new NpdTraitReflection($definition, $resolvedTypeParameterMap, $this), - $definition instanceof EnumTypeDefinition => new NpdEnumReflection($definition, $this), + $definition instanceof ClassTypeDefinition => new NpdClassReflection($definition, $resolvedTypeParameterMap, $this, $this->classMemberInheritanceResolver), + $definition instanceof InterfaceTypeDefinition => new NpdInterfaceReflection($definition, $resolvedTypeParameterMap, $this, $this->classMemberInheritanceResolver), + $definition instanceof TraitTypeDefinition => new NpdTraitReflection($definition, $resolvedTypeParameterMap, $this, $this->classMemberInheritanceResolver), + $definition instanceof EnumTypeDefinition => new NpdEnumReflection($definition, $this, $this->classMemberInheritanceResolver), $definition instanceof SpecialTypeDefinition => new NpdSpecialTypeReflection($definition, $resolvedTypeParameterMap), default => throw new InvalidArgumentException('Unsupported definition of type ' . $definition::class . ' given.') }; diff --git a/src/NativePHPDoc/Reflection/NpdClassReflection.php b/src/NativePHPDoc/Reflection/NpdClassReflection.php index 07e0aed..0e1c70c 100644 --- a/src/NativePHPDoc/Reflection/NpdClassReflection.php +++ b/src/NativePHPDoc/Reflection/NpdClassReflection.php @@ -10,8 +10,8 @@ use GoodPhp\Reflection\NativePHPDoc\Reflection\Traits\NpdUsedTraitsReflection; use GoodPhp\Reflection\NativePHPDoc\Reflection\TypeParameters\NpdTypeParameterReflection; use GoodPhp\Reflection\Reflection\Attributes\Attributes; +use GoodPhp\Reflection\Reflection\ClassMemberInheritanceResolver; use GoodPhp\Reflection\Reflection\ClassReflection; -use GoodPhp\Reflection\Reflection\InheritsClassMembers; use GoodPhp\Reflection\Reflection\MethodReflection; use GoodPhp\Reflection\Reflection\Methods\HasMethodsDefaults; use GoodPhp\Reflection\Reflection\Properties\HasPropertiesDefaults; @@ -40,9 +40,6 @@ final class NpdClassReflection extends NpdTypeReflection implements ClassReflect use HasTypeParametersDefaults; - /** @use InheritsClassMembers */ - use InheritsClassMembers; - private readonly NamedType $type; private NamedType $staticType; @@ -81,6 +78,7 @@ public function __construct( private readonly ClassTypeDefinition $definition, private readonly TypeParameterMap $resolvedTypeParameterMap, private readonly Reflector $reflector, + private readonly ClassMemberInheritanceResolver $classMemberInheritanceResolver, ) { $this->type = new NamedType($this->qualifiedName(), $this->resolvedTypeParameterMap->toArguments($this->definition->typeParameters)); $this->staticType = $this->type; @@ -185,14 +183,14 @@ public function declaredProperties(): array */ public function properties(): array { - return $this->properties ??= collect([ - ...$this->propertiesFromTraits($this->uses(), $this->staticType, $this->reflector), - ...($this->extends() ? $this->propertiesFromTypes($this->extends(), $this->staticType, $this->reflector) : []), - ...$this->declaredProperties(), - ]) - ->keyBy(fn (PropertyReflection $property) => $property->name()) - ->values() - ->all(); + return $this->properties ??= $this->classMemberInheritanceResolver->properties( + reflector: $this->reflector, + staticType: $this->staticType, + declaredProperties: $this->declaredProperties(), + extends: $this->extends(), + implements: $this->implements(), + usedTraits: $this->uses(), + ); } /** @@ -211,15 +209,14 @@ public function declaredMethods(): array */ public function methods(): array { - return $this->methods ??= collect([ - ...$this->methodsFromTypes($this->implements(), $this->staticType, $this->reflector), - ...$this->methodsFromTraits($this->uses(), $this->staticType, $this->reflector), - ...($this->extends() ? $this->methodsFromTypes($this->extends(), $this->staticType, $this->reflector) : []), - ...$this->declaredMethods(), - ]) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() - ->all(); + return $this->methods ??= $this->classMemberInheritanceResolver->methods( + reflector: $this->reflector, + staticType: $this->staticType, + declaredMethods: $this->declaredMethods(), + extends: $this->extends(), + implements: $this->implements(), + usedTraits: $this->uses(), + ); } /** diff --git a/src/NativePHPDoc/Reflection/NpdEnumReflection.php b/src/NativePHPDoc/Reflection/NpdEnumReflection.php index 014992f..3e626db 100644 --- a/src/NativePHPDoc/Reflection/NpdEnumReflection.php +++ b/src/NativePHPDoc/Reflection/NpdEnumReflection.php @@ -7,8 +7,8 @@ use GoodPhp\Reflection\NativePHPDoc\Reflection\Attributes\NativeAttributes; use GoodPhp\Reflection\NativePHPDoc\Reflection\Traits\NpdUsedTraitsReflection; use GoodPhp\Reflection\Reflection\Attributes\Attributes; +use GoodPhp\Reflection\Reflection\ClassMemberInheritanceResolver; use GoodPhp\Reflection\Reflection\EnumReflection; -use GoodPhp\Reflection\Reflection\InheritsClassMembers; use GoodPhp\Reflection\Reflection\MethodReflection; use GoodPhp\Reflection\Reflection\Methods\HasMethodsDefaults; use GoodPhp\Reflection\Reflection\Traits\UsedTraitsReflection; @@ -27,9 +27,6 @@ final class NpdEnumReflection extends NpdTypeReflection implements EnumReflectio /** @use HasMethodsDefaults */ use HasMethodsDefaults; - /** @use InheritsClassMembers */ - use InheritsClassMembers; - private readonly NamedType $type; private NamedType $staticType; @@ -52,7 +49,8 @@ final class NpdEnumReflection extends NpdTypeReflection implements EnumReflectio */ public function __construct( private readonly EnumTypeDefinition $definition, - private readonly Reflector $reflector + private readonly Reflector $reflector, + private readonly ClassMemberInheritanceResolver $classMemberInheritanceResolver, ) { $this->type = new NamedType($this->qualifiedName()); $this->staticType = $this->type; @@ -122,14 +120,13 @@ public function declaredMethods(): array */ public function methods(): array { - return $this->methods ??= collect([ - ...$this->methodsFromTypes($this->implements(), $this->staticType, $this->reflector), - ...$this->methodsFromTraits($this->uses(), $this->staticType, $this->reflector), - ...$this->declaredMethods(), - ]) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() - ->all(); + return $this->methods ??= $this->classMemberInheritanceResolver->methods( + reflector: $this->reflector, + staticType: $this->staticType, + declaredMethods: $this->declaredMethods(), + implements: $this->implements(), + usedTraits: $this->uses(), + ); } public function isBuiltIn(): bool diff --git a/src/NativePHPDoc/Reflection/NpdFunctionParameterReflection.php b/src/NativePHPDoc/Reflection/NpdFunctionParameterReflection.php index 932c21f..5c446ea 100644 --- a/src/NativePHPDoc/Reflection/NpdFunctionParameterReflection.php +++ b/src/NativePHPDoc/Reflection/NpdFunctionParameterReflection.php @@ -7,6 +7,7 @@ use GoodPhp\Reflection\Reflection\Attributes\Attributes; use GoodPhp\Reflection\Reflection\FunctionParameterReflection; use GoodPhp\Reflection\Reflection\MethodReflection; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\Template\TypeParameterMap; use GoodPhp\Reflection\Type\Type; @@ -54,6 +55,11 @@ public function type(): ?Type ); } + public function typeSource(): ?TypeSource + { + return $this->definition->typeSource; + } + public function hasDefaultValue(): bool { return $this->definition->hasDefaultValue; diff --git a/src/NativePHPDoc/Reflection/NpdInterfaceReflection.php b/src/NativePHPDoc/Reflection/NpdInterfaceReflection.php index d902818..55e1f4e 100644 --- a/src/NativePHPDoc/Reflection/NpdInterfaceReflection.php +++ b/src/NativePHPDoc/Reflection/NpdInterfaceReflection.php @@ -8,7 +8,7 @@ use GoodPhp\Reflection\NativePHPDoc\Reflection\Attributes\NativeAttributes; use GoodPhp\Reflection\NativePHPDoc\Reflection\TypeParameters\NpdTypeParameterReflection; use GoodPhp\Reflection\Reflection\Attributes\Attributes; -use GoodPhp\Reflection\Reflection\InheritsClassMembers; +use GoodPhp\Reflection\Reflection\ClassMemberInheritanceResolver; use GoodPhp\Reflection\Reflection\InterfaceReflection; use GoodPhp\Reflection\Reflection\MethodReflection; use GoodPhp\Reflection\Reflection\Methods\HasMethodsDefaults; @@ -32,9 +32,6 @@ final class NpdInterfaceReflection extends NpdTypeReflection implements Interfac use HasTypeParametersDefaults; - /** @use InheritsClassMembers */ - use InheritsClassMembers; - private readonly NamedType $type; private NamedType $staticType; @@ -63,6 +60,7 @@ public function __construct( private readonly InterfaceTypeDefinition $definition, private readonly TypeParameterMap $resolvedTypeParameterMap, private readonly Reflector $reflector, + private readonly ClassMemberInheritanceResolver $classMemberInheritanceResolver, ) { $this->type = new NamedType($this->qualifiedName(), $this->resolvedTypeParameterMap->toArguments($this->definition->typeParameters)); $this->staticType = $this->type; @@ -142,13 +140,12 @@ public function declaredMethods(): array */ public function methods(): array { - return $this->methods ??= collect([ - ...$this->methodsFromTypes($this->extends(), $this->staticType, $this->reflector), - ...$this->declaredMethods(), - ]) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() - ->all(); + return $this->methods ??= $this->classMemberInheritanceResolver->methods( + reflector: $this->reflector, + staticType: $this->staticType, + declaredMethods: $this->declaredMethods(), + implements: $this->extends(), + ); } public function isBuiltIn(): bool diff --git a/src/NativePHPDoc/Reflection/NpdMethodReflection.php b/src/NativePHPDoc/Reflection/NpdMethodReflection.php index a3dbeee..7b93249 100644 --- a/src/NativePHPDoc/Reflection/NpdMethodReflection.php +++ b/src/NativePHPDoc/Reflection/NpdMethodReflection.php @@ -14,6 +14,7 @@ use GoodPhp\Reflection\Reflection\Methods\HasMethods; use GoodPhp\Reflection\Reflection\TypeParameters\HasTypeParametersDefaults; use GoodPhp\Reflection\Reflection\TypeParameters\TypeParameterReflection; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\Template\TypeParameterMap; use GoodPhp\Reflection\Type\Type; @@ -117,6 +118,11 @@ public function returnType(): ?Type ); } + public function returnTypeSource(): ?TypeSource + { + return $this->definition->returnTypeSource; + } + public function invoke(object $receiver, mixed ...$args): mixed { $methodName = $this->name(); diff --git a/src/NativePHPDoc/Reflection/NpdPropertyReflection.php b/src/NativePHPDoc/Reflection/NpdPropertyReflection.php index b1035ac..bb2766e 100644 --- a/src/NativePHPDoc/Reflection/NpdPropertyReflection.php +++ b/src/NativePHPDoc/Reflection/NpdPropertyReflection.php @@ -9,11 +9,11 @@ use GoodPhp\Reflection\Reflection\FunctionParameterReflection; use GoodPhp\Reflection\Reflection\Properties\HasProperties; use GoodPhp\Reflection\Reflection\PropertyReflection; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\Template\TypeParameterMap; use GoodPhp\Reflection\Type\Type; use GoodPhp\Reflection\Type\TypeProjector; -use Illuminate\Support\Arr; use ReflectionProperty; use Webmozart\Assert\Assert; @@ -77,6 +77,11 @@ public function type(): ?Type ); } + public function typeSource(): ?TypeSource + { + return $this->definition->typeSource; + } + public function hasDefaultValue(): bool { return $this->definition->hasDefaultValue; @@ -102,7 +107,7 @@ public function isPromoted(): bool */ public function promotedParameter(): ?FunctionParameterReflection { - if (isset($this->promotedParameter)) { + if (isset($this->promotedParameter) || (new ReflectionProperty(self::class, 'promotedParameter'))->isInitialized($this)) { return $this->promotedParameter; } @@ -114,10 +119,7 @@ public function promotedParameter(): ?FunctionParameterReflection Assert::notNull($constructor); - return $this->promotedParameter ??= Arr::first( - $constructor->parameters(), - fn (FunctionParameterReflection $parameter) => $this->definition->name === $parameter->name() - ); + return $this->promotedParameter ??= $constructor->parameter($this->definition->name); } public function attributes(): Attributes diff --git a/src/NativePHPDoc/Reflection/NpdTraitReflection.php b/src/NativePHPDoc/Reflection/NpdTraitReflection.php index dc93178..f8744ab 100644 --- a/src/NativePHPDoc/Reflection/NpdTraitReflection.php +++ b/src/NativePHPDoc/Reflection/NpdTraitReflection.php @@ -10,7 +10,7 @@ use GoodPhp\Reflection\NativePHPDoc\Reflection\Traits\NpdUsedTraitsReflection; use GoodPhp\Reflection\NativePHPDoc\Reflection\TypeParameters\NpdTypeParameterReflection; use GoodPhp\Reflection\Reflection\Attributes\Attributes; -use GoodPhp\Reflection\Reflection\InheritsClassMembers; +use GoodPhp\Reflection\Reflection\ClassMemberInheritanceResolver; use GoodPhp\Reflection\Reflection\MethodReflection; use GoodPhp\Reflection\Reflection\Methods\HasMethodsDefaults; use GoodPhp\Reflection\Reflection\Properties\HasPropertiesDefaults; @@ -39,9 +39,6 @@ final class NpdTraitReflection extends NpdTypeReflection implements TraitReflect use HasTypeParametersDefaults; - /** @use InheritsClassMembers */ - use InheritsClassMembers; - private readonly NamedType $type; private NamedType $staticType; @@ -75,6 +72,7 @@ public function __construct( private readonly TraitTypeDefinition $definition, private readonly TypeParameterMap $resolvedTypeParameterMap, private readonly Reflector $reflector, + private readonly ClassMemberInheritanceResolver $classMemberInheritanceResolver, ) { $this->type = new NamedType($this->qualifiedName(), $this->resolvedTypeParameterMap->toArguments($this->definition->typeParameters)); $this->staticType = $this->type; @@ -147,13 +145,12 @@ public function declaredProperties(): array */ public function properties(): array { - return $this->properties ??= collect([ - ...$this->propertiesFromTraits($this->uses(), $this->staticType, $this->reflector), - ...$this->declaredProperties(), - ]) - ->keyBy(fn (PropertyReflection $property) => $property->name()) - ->values() - ->all(); + return $this->properties ??= $this->classMemberInheritanceResolver->properties( + reflector: $this->reflector, + staticType: $this->staticType, + declaredProperties: $this->declaredProperties(), + usedTraits: $this->uses(), + ); } /** @@ -172,13 +169,12 @@ public function declaredMethods(): array */ public function methods(): array { - return $this->methods ??= collect([ - ...$this->methodsFromTraits($this->uses(), $this->staticType, $this->reflector), - ...$this->declaredMethods(), - ]) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() - ->all(); + return $this->methods ??= $this->classMemberInheritanceResolver->methods( + reflector: $this->reflector, + staticType: $this->staticType, + declaredMethods: $this->declaredMethods(), + usedTraits: $this->uses(), + ); } public function isBuiltIn(): bool diff --git a/src/Reflection/InheritsClassMembers.php b/src/Reflection/ClassMemberInheritanceResolver.php similarity index 61% rename from src/Reflection/InheritsClassMembers.php rename to src/Reflection/ClassMemberInheritanceResolver.php index 1a30701..516daed 100644 --- a/src/Reflection/InheritsClassMembers.php +++ b/src/Reflection/ClassMemberInheritanceResolver.php @@ -3,28 +3,83 @@ namespace GoodPhp\Reflection\Reflection; use GoodPhp\Reflection\Reflection\Methods\HasMethods; +use GoodPhp\Reflection\Reflection\Methods\MergedInheritanceMethodReflection; use GoodPhp\Reflection\Reflection\Properties\HasProperties; +use GoodPhp\Reflection\Reflection\Properties\MergedInheritancePropertyReflection; use GoodPhp\Reflection\Reflection\Traits\TraitAliasesMethodReflection; use GoodPhp\Reflection\Reflection\Traits\UsedTraitAliasReflection; use GoodPhp\Reflection\Reflection\Traits\UsedTraitReflection; use GoodPhp\Reflection\Reflection\Traits\UsedTraitsReflection; use GoodPhp\Reflection\Reflector; use GoodPhp\Reflection\Type\NamedType; +use Illuminate\Support\Collection; use Webmozart\Assert\Assert; -/** - * @template ReflectableType of object - */ -trait InheritsClassMembers +final class ClassMemberInheritanceResolver { /** - * @param list|NamedType $types + * @template ReflectableType of object + * + * @param list> $declaredProperties + * @param list $implements * * @return list> */ + public function properties( + Reflector $reflector, + NamedType $staticType, + array $declaredProperties, + ?NamedType $extends = null, + array $implements = [], + ?UsedTraitsReflection $usedTraits = null, + ): array { + return collect([ + ...$declaredProperties, + ...($extends ? $this->propertiesFromTypes($extends, $staticType, $reflector) : []), + ...($usedTraits ? $this->propertiesFromTraits($usedTraits, $staticType, $reflector) : []), + ...$this->propertiesFromTypes($implements, $staticType, $reflector), + ]) + ->groupBy(fn (PropertyReflection $property) => $property->name()) + ->map(fn (Collection $sameProperties) => MergedInheritancePropertyReflection::merge($sameProperties->all())) + ->values() + ->all(); + } + + /** + * @template ReflectableType of object + * + * @param list> $declaredMethods + * @param list $implements + * + * @return list> + */ + public function methods( + Reflector $reflector, + NamedType $staticType, + array $declaredMethods, + ?NamedType $extends = null, + array $implements = [], + ?UsedTraitsReflection $usedTraits = null, + ): array { + return collect([ + ...$declaredMethods, + ...($extends ? $this->methodsFromTypes($extends, $staticType, $reflector) : []), + ...($usedTraits ? $this->methodsFromTraits($usedTraits, $staticType, $reflector) : []), + ...$this->methodsFromTypes($implements, $staticType, $reflector), + ]) + ->groupBy(fn (MethodReflection $method) => $method->name()) + ->map(fn (Collection $sameMethods) => MergedInheritanceMethodReflection::merge($sameMethods->all())) + ->values() + ->all(); + } + + /** + * @param list|NamedType $types + * + * @return list> + */ protected function propertiesFromTypes(array|NamedType $types, NamedType $staticType, Reflector $reflector): array { - /** @var list> */ return collect(is_array($types) ? $types : [$types]) ->flatMap(function (NamedType $type) use ($staticType, $reflector) { $reflection = $reflector->forNamedType($type); @@ -33,18 +88,15 @@ protected function propertiesFromTypes(array|NamedType $types, NamedType $static return []; } - /** @var list> */ return $reflection ->withStaticType($staticType) ->properties(); }) - ->keyBy(fn (PropertyReflection $property) => $property->name()) - ->values() ->all(); } /** - * @return list> + * @return list> */ protected function propertiesFromTraits(UsedTraitsReflection $usedTraits, NamedType $staticType, Reflector $reflector): array { @@ -59,11 +111,10 @@ protected function propertiesFromTraits(UsedTraitsReflection $usedTraits, NamedT /** * @param list|NamedType $types * - * @return list> + * @return list> */ protected function methodsFromTypes(array|NamedType $types, NamedType $staticType, Reflector $reflector): array { - /** @var list> */ return collect(is_array($types) ? $types : [$types]) ->flatMap(function (NamedType $type) use ($staticType, $reflector) { $reflection = $reflector->forNamedType($type); @@ -72,18 +123,15 @@ protected function methodsFromTypes(array|NamedType $types, NamedType $staticTyp return []; } - /** @var list> */ return $reflection ->withStaticType($staticType) ->methods(); }) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() ->all(); } /** - * @return list> + * @return list> */ protected function methodsFromTraits(UsedTraitsReflection $usedTraits, NamedType $staticType, Reflector $reflector): array { @@ -100,12 +148,12 @@ protected function methodsFromTraits(UsedTraitsReflection $usedTraits, NamedType ->reject(fn (MethodReflection $method) => in_array($method->name(), $traitExcludedMethods, true)) ->flatMap(fn (MethodReflection $method) => $this->aliasMethod($method, $usedTrait->aliases())); }) - ->keyBy(fn (MethodReflection $method) => $method->name()) - ->values() ->all(); } /** + * @template ReflectableType of object + * * @param MethodReflection $method * @param list $aliases * diff --git a/src/Reflection/FunctionParameterReflection.php b/src/Reflection/FunctionParameterReflection.php index 0981d7b..ebe375b 100644 --- a/src/Reflection/FunctionParameterReflection.php +++ b/src/Reflection/FunctionParameterReflection.php @@ -12,6 +12,8 @@ public function name(): string; public function type(): ?Type; + public function typeSource(): ?TypeSource; + public function hasDefaultValue(): bool; public function defaultValue(): mixed; diff --git a/src/Reflection/Functions/MergedInheritanceFunctionParameterReflection.php b/src/Reflection/Functions/MergedInheritanceFunctionParameterReflection.php new file mode 100644 index 0000000..6aea87d --- /dev/null +++ b/src/Reflection/Functions/MergedInheritanceFunctionParameterReflection.php @@ -0,0 +1,97 @@ + $reflections + * @param MethodReflection<*> $declaringMethod + */ + private function __construct( + private readonly array $reflections, + private readonly MethodReflection $declaringMethod, + ) {} + + /** + * @param list $reflections + * @param MethodReflection<*> $declaringMethod + */ + public static function merge(array $reflections, MethodReflection $declaringMethod): FunctionParameterReflection + { + Assert::notEmpty($reflections); + + if (count($reflections) === 1) { + return $reflections[0]; + } + + return new self($reflections, $declaringMethod); + } + + public function attributes(): Attributes + { + return $this->reflections[0]->attributes(); + } + + public function name(): string + { + return $this->reflections[0]->name(); + } + + public function type(): ?Type + { + return $this->typeFromReflection()->type(); + } + + public function typeSource(): ?TypeSource + { + return $this->typeFromReflection()->typeSource(); + } + + public function hasDefaultValue(): bool + { + return $this->reflections[0]->hasDefaultValue(); + } + + public function defaultValue(): mixed + { + return $this->reflections[0]->defaultValue(); + } + + public function location(): string + { + return $this->reflections[0]->location(); + } + + public function declaringMethod(): MethodReflection + { + return $this->declaringMethod; + } + + private function typeFromReflection(): FunctionParameterReflection + { + if (isset($this->typeFromReflection)) { + return $this->typeFromReflection; + } + + // First @param in the inheritance tree - overwrites the native typehint + $firstMethodWithPhpDocParam = Arr::first($this->reflections, fn (FunctionParameterReflection $reflection) => $reflection->typeSource() === TypeSource::PHP_DOC); + + return $this->typeFromReflection = $firstMethodWithPhpDocParam ?? $this->reflections[0]; + } + + public function __toString(): string + { + return (string) $this->reflections[0]; + } +} diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index 3b820a1..04126d6 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -27,6 +27,8 @@ public function parameter(string|int $nameOrIndex): ?FunctionParameterReflection public function returnType(): ?Type; + public function returnTypeSource(): ?TypeSource; + /** * Call a method with strict_types=1. * diff --git a/src/Reflection/Methods/MergedInheritanceMethodReflection.php b/src/Reflection/Methods/MergedInheritanceMethodReflection.php new file mode 100644 index 0000000..31bd4d6 --- /dev/null +++ b/src/Reflection/Methods/MergedInheritanceMethodReflection.php @@ -0,0 +1,166 @@ + + */ +final class MergedInheritanceMethodReflection implements MethodReflection +{ + use HasTypeParametersDefaults; + + use MethodReflectionDefaults; + + /** @var list */ + private array $typeParameters; + + /** @var list */ + private array $parameters; + + /** @var MethodReflection */ + private MethodReflection $returnTypeFromReflection; + + /** + * @param list> $reflections + */ + private function __construct( + private readonly array $reflections, + ) {} + + /** + * @template ReflectableTypeScoped of object + * + * @param list> $reflections + * + * @return MethodReflection + */ + public static function merge(array $reflections): MethodReflection + { + Assert::notEmpty($reflections); + + if (count($reflections) === 1) { + return $reflections[0]; + } + + return new self($reflections); + } + + public function attributes(): Attributes + { + return $this->reflections[0]->attributes(); + } + + public function typeParameters(): array + { + if (isset($this->typeParameters)) { + return $this->typeParameters; + } + + // First PHPDoc (top to bottom) with any @template tag - wins. The rest are ignored. + $firstMethodWithTypeParameters = Arr::first($this->reflections, fn (MethodReflection $reflection) => (bool) $reflection->typeParameters()); + + return $this->typeParameters = $firstMethodWithTypeParameters?->typeParameters() ?? []; + } + + public function withStaticType(NamedType $staticType): static + { + return new self( + array_map(fn (MethodReflection $reflection) => $reflection->withStaticType($staticType), $this->reflections), + ); + } + + public function name(): string + { + return $this->reflections[0]->name(); + } + + public function parameters(): array + { + if (isset($this->parameters)) { + return $this->parameters; + } + + return $this->parameters ??= array_map(fn (int $index) => MergedInheritanceFunctionParameterReflection::merge( + array_values( + array_filter( + array_map(fn (MethodReflection $method) => $method->parameter($index), $this->reflections) + ), + ), + $this, + ), array_keys($this->reflections[0]->parameters())); + } + + public function returnType(): ?Type + { + return $this->returnTypeFromReflection()->returnType(); + } + + public function returnTypeSource(): ?TypeSource + { + return $this->returnTypeFromReflection()->returnTypeSource(); + } + + public function invoke(object $receiver, ...$args): mixed + { + return $this->reflections[0]->invoke($receiver, ...$args); + } + + public function invokeLax(object $receiver, ...$args): mixed + { + return $this->reflections[0]->invokeLax($receiver, ...$args); + } + + public function location(): string + { + return $this->reflections[0]->location(); + } + + /** + * @return HasMethods + */ + public function declaringType(): HasMethods + { + return $this->reflections[0]->declaringType(); + } + + /** + * @return MethodReflection + */ + private function returnTypeFromReflection(): MethodReflection + { + if (isset($this->returnTypeFromReflection)) { + return $this->returnTypeFromReflection; + } + + // First @return in the inheritance tree - overwrites the native typehint + $firstMethodWithPhpDocReturn = Arr::first($this->reflections, fn (MethodReflection $reflection) => $reflection->returnTypeSource() === TypeSource::PHP_DOC); + + return $this->returnTypeFromReflection = $firstMethodWithPhpDocReturn ?? $this->reflections[0]; + } + + public function __toString(): string + { + return (string) $this->reflections[0]; + } +} diff --git a/src/Reflection/Properties/MergedInheritancePropertyReflection.php b/src/Reflection/Properties/MergedInheritancePropertyReflection.php new file mode 100644 index 0000000..42605ae --- /dev/null +++ b/src/Reflection/Properties/MergedInheritancePropertyReflection.php @@ -0,0 +1,149 @@ + + */ +final class MergedInheritancePropertyReflection implements PropertyReflection +{ + /** @var PropertyReflection */ + private PropertyReflection $typeFromReflection; + + /** + * @param list> $reflections + */ + private function __construct( + private readonly array $reflections, + ) {} + + /** + * @template ReflectableTypeScoped of object + * + * @param list> $reflections + * + * @return PropertyReflection + */ + public static function merge(array $reflections): PropertyReflection + { + Assert::notEmpty($reflections); + + if (count($reflections) === 1) { + return $reflections[0]; + } + + return new self($reflections); + } + + public function attributes(): Attributes + { + return $this->reflections[0]->attributes(); + } + + public function withStaticType(NamedType $staticType): static + { + return new self( + array_map(fn (PropertyReflection $reflection) => $reflection->withStaticType($staticType), $this->reflections), + ); + } + + public function name(): string + { + return $this->reflections[0]->name(); + } + + public function type(): ?Type + { + return $this->typeFromReflection()->type(); + } + + public function typeSource(): ?TypeSource + { + return $this->typeFromReflection()->typeSource(); + } + + public function hasDefaultValue(): bool + { + return $this->reflections[0]->hasDefaultValue(); + } + + public function defaultValue(): mixed + { + return $this->reflections[0]->defaultValue(); + } + + public function isPromoted(): bool + { + return $this->reflections[0]->isPromoted(); + } + + public function promotedParameter(): ?FunctionParameterReflection + { + return $this->reflections[0]->promotedParameter(); + } + + public function get(object $receiver): mixed + { + return $this->reflections[0]->get($receiver); + } + + public function set(object $receiver, mixed $value): void + { + $this->reflections[0]->set($receiver, $value); + } + + public function setLax(object $receiver, mixed $value): void + { + $this->reflections[0]->setLax($receiver, $value); + } + + public function location(): string + { + return $this->reflections[0]->location(); + } + + /** + * @return HasProperties + */ + public function declaringType(): HasProperties + { + return $this->reflections[0]->declaringType(); + } + + /** + * @return PropertyReflection + */ + private function typeFromReflection(): PropertyReflection + { + if (isset($this->typeFromReflection)) { + return $this->typeFromReflection; + } + + // First @var in the inheritance tree - overwrites the native typehint + $firstPropertyWithPhpDocVar = Arr::first($this->reflections, fn (PropertyReflection $reflection) => $reflection->typeSource() === TypeSource::PHP_DOC); + + return $this->typeFromReflection = $firstPropertyWithPhpDocVar ?? $this->reflections[0]; + } + + public function __toString(): string + { + return (string) $this->reflections[0]; + } +} diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index 3e5fc97..ae97206 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -19,6 +19,8 @@ public function name(): string; public function type(): ?Type; + public function typeSource(): ?TypeSource; + public function hasDefaultValue(): bool; public function defaultValue(): mixed; diff --git a/src/Reflection/Traits/TraitAliasesMethodReflection.php b/src/Reflection/Traits/TraitAliasesMethodReflection.php index 9681101..dd5f0ee 100644 --- a/src/Reflection/Traits/TraitAliasesMethodReflection.php +++ b/src/Reflection/Traits/TraitAliasesMethodReflection.php @@ -7,6 +7,7 @@ use GoodPhp\Reflection\Reflection\MethodReflection; use GoodPhp\Reflection\Reflection\Methods\HasMethods; use GoodPhp\Reflection\Reflection\TypeParameters\TypeParameterReflection; +use GoodPhp\Reflection\Reflection\TypeSource; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\Type; @@ -70,6 +71,11 @@ public function returnType(): ?Type return $this->method->returnType(); } + public function returnTypeSource(): ?TypeSource + { + return $this->method->returnTypeSource(); + } + public function invoke(object $receiver, mixed ...$args): mixed { return $this->method->invoke($receiver, ...$args); diff --git a/src/Reflection/TypeSource.php b/src/Reflection/TypeSource.php new file mode 100644 index 0000000..eb61780 --- /dev/null +++ b/src/Reflection/TypeSource.php @@ -0,0 +1,9 @@ +' - attributes: - asString: '#[]' - all: { } - type: T - hasDefaultValue: true - defaultValue: N; - isPromoted: false - promotedParameter: null - asString: $factories name: factories @@ -168,6 +142,30 @@ properties: type: T hasDefaultValue: false defaultValue: null + - asString: $parentProperty + name: parentProperty + location: 'Tests\Stubs\Classes\ParentClassStub::$parentProperty' + declaringType: 'Tests\Stubs\Classes\ParentClassStub' + attributes: + asString: '#[]' + all: { } + type: T + hasDefaultValue: true + defaultValue: N; + isPromoted: false + promotedParameter: null + - asString: $prop + name: prop + location: 'Tests\Stubs\Traits\ParentTraitStub::$prop' + declaringType: 'Tests\Stubs\Traits\ParentTraitStub' + attributes: + asString: '#[]' + all: { } + type: int + hasDefaultValue: false + defaultValue: null + isPromoted: false + promotedParameter: null declaredMethods: - asString: __construct() @@ -265,69 +263,6 @@ declaredMethods: defaultValue: null returnType: static methods: - - - asString: test() - name: test - location: 'Tests\Stubs\Classes\ParentClassStub::test()' - declaringType: 'Tests\Stubs\Classes\ParentClassStub' - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: - - - asString: 'arg $str' - name: str - location: 'Tests\Stubs\Classes\ParentClassStub::test() arg $str' - declaringMethod: test() - type: '?string' - hasDefaultValue: true - defaultValue: N; - returnType: static - - - asString: otherFunction() - name: otherFunction - location: 'Tests\Stubs\Traits\TraitWithoutProperties::otherFunction()' - declaringType: Tests\Stubs\Traits\TraitWithoutProperties - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: Generator - - - asString: traitMethod() - name: traitMethod - location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' - declaringType: Tests\Stubs\Traits\ParentTraitStub - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: void - - - asString: traitMethodTwo() - name: traitMethodTwo - location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' - declaringType: 'Tests\Stubs\Traits\ParentTraitStub' - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: void - - - asString: parentMethod() - name: parentMethod - location: 'Tests\Stubs\Classes\ParentClassStub::parentMethod()' - declaringType: 'Tests\Stubs\Classes\ParentClassStub' - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: Tests\Stubs\Classes\SomeStub - asString: __construct() name: __construct @@ -423,3 +358,62 @@ methods: hasDefaultValue: false defaultValue: null returnType: static + - + asString: parentMethod() + name: parentMethod + location: 'Tests\Stubs\Classes\ParentClassStub::parentMethod()' + declaringType: 'Tests\Stubs\Classes\ParentClassStub' + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: Tests\Stubs\Classes\SomeStub + - asString: test() + name: test + location: 'Tests\Stubs\Classes\ParentClassStub::test()' + declaringType: 'Tests\Stubs\Classes\ParentClassStub' + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - asString: 'arg $str' + name: str + location: 'Tests\Stubs\Classes\ParentClassStub::test() arg $str' + declaringMethod: test() + type: '?string' + hasDefaultValue: true + defaultValue: N; + returnType: static + - asString: traitMethod() + name: traitMethod + location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' + declaringType: 'Tests\Stubs\Traits\ParentTraitStub' + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: void + - asString: traitMethodTwo() + name: traitMethodTwo + location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' + declaringType: 'Tests\Stubs\Traits\ParentTraitStub' + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: void + - + asString: otherFunction() + name: otherFunction + location: 'Tests\Stubs\Traits\TraitWithoutProperties::otherFunction()' + declaringType: Tests\Stubs\Traits\TraitWithoutProperties + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: Generator diff --git a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Classes.CollectionStub.yaml b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Classes.CollectionStub.yaml index 2c48672..e23d2a7 100644 --- a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Classes.CollectionStub.yaml +++ b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Classes.CollectionStub.yaml @@ -13,7 +13,8 @@ attributes: typeParameters: { } extends: null implements: - - IteratorAggregate + - 'IteratorAggregate' + - 'ArrayAccess' uses: traits: { } excludedTraitMethods: { } @@ -31,6 +32,90 @@ declaredMethods: typeParameters: { } parameters: { } returnType: Traversable + - + asString: offsetExists() + name: offsetExists + location: 'Tests\Stubs\Classes\CollectionStub::offsetExists()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetExists() arg $offset' + declaringMethod: offsetExists() + type: mixed + hasDefaultValue: false + defaultValue: null + returnType: bool + - + asString: offsetGet() + name: offsetGet + location: 'Tests\Stubs\Classes\CollectionStub::offsetGet()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetGet() arg $offset' + declaringMethod: offsetGet() + type: mixed + hasDefaultValue: false + defaultValue: null + returnType: mixed + - + asString: offsetSet() + name: offsetSet + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet() arg $offset' + declaringMethod: offsetSet() + type: mixed + hasDefaultValue: false + defaultValue: null + - + asString: 'arg $value' + name: value + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet() arg $value' + declaringMethod: offsetSet() + type: mixed + hasDefaultValue: false + defaultValue: null + returnType: void + - + asString: offsetUnset() + name: offsetUnset + location: 'Tests\Stubs\Classes\CollectionStub::offsetUnset()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetUnset() arg $offset' + declaringMethod: offsetUnset() + type: mixed + hasDefaultValue: false + defaultValue: null + returnType: void methods: - asString: getIterator() @@ -42,4 +127,88 @@ methods: all: { } typeParameters: { } parameters: { } - returnType: Traversable + returnType: 'Traversable' + - + asString: offsetExists() + name: offsetExists + location: 'Tests\Stubs\Classes\CollectionStub::offsetExists()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetExists() arg $offset' + declaringMethod: offsetExists() + type: string + hasDefaultValue: false + defaultValue: null + returnType: bool + - + asString: offsetGet() + name: offsetGet + location: 'Tests\Stubs\Classes\CollectionStub::offsetGet()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetGet() arg $offset' + declaringMethod: offsetGet() + type: string + hasDefaultValue: false + defaultValue: null + returnType: '?Tests\Stubs\Classes\SomeStub' + - + asString: offsetSet() + name: offsetSet + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet() arg $offset' + declaringMethod: offsetSet() + type: '?string' + hasDefaultValue: false + defaultValue: null + - + asString: 'arg $value' + name: value + location: 'Tests\Stubs\Classes\CollectionStub::offsetSet() arg $value' + declaringMethod: offsetSet() + type: Tests\Stubs\Classes\SomeStub + hasDefaultValue: false + defaultValue: null + returnType: void + - + asString: offsetUnset() + name: offsetUnset + location: 'Tests\Stubs\Classes\CollectionStub::offsetUnset()' + declaringType: Tests\Stubs\Classes\CollectionStub + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: + - + asString: 'arg $offset' + name: offset + location: 'Tests\Stubs\Classes\CollectionStub::offsetUnset() arg $offset' + declaringMethod: offsetUnset() + type: string + hasDefaultValue: false + defaultValue: null + returnType: void diff --git a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.BackedEnum.yaml b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.BackedEnum.yaml index 1e97c5e..105e3fc 100644 --- a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.BackedEnum.yaml +++ b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.BackedEnum.yaml @@ -15,17 +15,6 @@ uses: excludedTraitMethods: { } declaredMethods: { } methods: - - - asString: cases() - name: cases - location: 'BackedEnum::cases()' - declaringType: BackedEnum - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: array - asString: from() name: from @@ -64,3 +53,14 @@ methods: hasDefaultValue: false defaultValue: null returnType: '?static' + - + asString: cases() + name: cases + location: 'BackedEnum::cases()' + declaringType: BackedEnum + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: array diff --git a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.UnitEnum.yaml b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.UnitEnum.yaml index 7921c0b..e24de48 100644 --- a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.UnitEnum.yaml +++ b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Enums.UnitEnum.yaml @@ -63,17 +63,6 @@ methods: hasDefaultValue: false defaultValue: null returnType: mixed - - - asString: cases() - name: cases - location: 'UnitEnum::cases()' - declaringType: UnitEnum - attributes: - asString: '#[]' - all: { } - typeParameters: { } - parameters: { } - returnType: array - asString: otherFunction() name: otherFunction @@ -96,3 +85,14 @@ methods: typeParameters: { } parameters: { } returnType: Generator + - + asString: cases() + name: cases + location: 'UnitEnum::cases()' + declaringType: UnitEnum + attributes: + asString: '#[]' + all: { } + typeParameters: { } + parameters: { } + returnType: array diff --git a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Traits.ParentTraitStub.yaml b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Traits.ParentTraitStub.yaml index bf9afca..83606d1 100644 --- a/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Traits.ParentTraitStub.yaml +++ b/tests/Integration/Reflection/ReflectionSnapshots/Tests.Stubs.Traits.ParentTraitStub.yaml @@ -68,24 +68,24 @@ declaredMethods: returnType: void methods: - - asString: otherFunction() - name: otherFunction - location: 'Tests\Stubs\Traits\TraitWithoutProperties::otherFunction()' - declaringType: Tests\Stubs\Traits\TraitWithoutProperties + asString: traitMethod() + name: traitMethod + location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' + declaringType: Tests\Stubs\Traits\ParentTraitStub attributes: asString: '#[]' all: { } typeParameters: { } parameters: { } - returnType: Generator + returnType: void - - asString: traitMethod() - name: traitMethod - location: 'Tests\Stubs\Traits\ParentTraitStub::traitMethod()' - declaringType: Tests\Stubs\Traits\ParentTraitStub + asString: otherFunction() + name: otherFunction + location: 'Tests\Stubs\Traits\TraitWithoutProperties::otherFunction()' + declaringType: Tests\Stubs\Traits\TraitWithoutProperties attributes: asString: '#[]' all: { } typeParameters: { } parameters: { } - returnType: void + returnType: Generator diff --git a/tests/Integration/Reflection/ReflectionSnapshotsTest.php b/tests/Integration/Reflection/ReflectionSnapshotsTest.php index 8725262..ded8dad 100644 --- a/tests/Integration/Reflection/ReflectionSnapshotsTest.php +++ b/tests/Integration/Reflection/ReflectionSnapshotsTest.php @@ -133,12 +133,12 @@ public function testReflectsTypes(string $className, string $snapshotFilename): ]; } - $expectedString = Yaml::dump($expected, inline: 10, flags: Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $toYaml = fn (mixed $value) => Yaml::dump($value, inline: 10, flags: Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); if (file_exists($snapshotFilename)) { - self::assertStringEqualsFile($snapshotFilename, $expectedString); + self::assertSame($toYaml(Yaml::parseFile($snapshotFilename)), $toYaml($expected)); } else { - \Safe\file_put_contents($snapshotFilename, $expectedString); + \Safe\file_put_contents($snapshotFilename, $toYaml($expected)); } } diff --git a/tests/Integration/Reflection/ReflectionTest.php b/tests/Integration/Reflection/ReflectionTest.php index 362d68a..c6c4038 100644 --- a/tests/Integration/Reflection/ReflectionTest.php +++ b/tests/Integration/Reflection/ReflectionTest.php @@ -2,6 +2,7 @@ namespace Tests\Integration\Reflection; +use ArrayAccess; use DateTime; use Generator; use GoodPhp\Reflection\Reflection\ClassReflection; @@ -121,59 +122,59 @@ function (ClassReflection $reflection) { self::assertContainsOnlyInstancesOf(PropertyReflection::class, $properties); self::assertSame('factories', $properties[0]->name()); - self::assertSame($reflection->properties()[2], $properties[0]); + self::assertSame($reflection->properties()[0], $properties[0]); self::assertSame('generic', $properties[1]->name()); - self::assertSame($reflection->properties()[3], $properties[1]); + self::assertSame($reflection->properties()[1], $properties[1]); self::assertSame('promoted', $properties[2]->name()); - self::assertSame($reflection->properties()[4], $properties[2]); + self::assertSame($reflection->properties()[2], $properties[2]); }); with($reflection->properties(), function (array $properties) use ($reflection) { self::assertCount(5, $properties); self::assertContainsOnlyInstancesOf(PropertyReflection::class, $properties); - self::assertSame('prop', $properties[0]->name()); - self::assertEquals(PrimitiveType::integer(), $properties[0]->type()); + self::assertSame('factories', $properties[0]->name()); + self::assertEquals(PrimitiveType::array(SomeStub::class), $properties[0]->type()); self::assertFalse($properties[0]->hasDefaultValue()); self::assertFalse($properties[0]->isPromoted()); self::assertNull($properties[0]->promotedParameter()); - self::assertEmpty($properties[0]->attributes()->all()); - self::assertSame($properties[0], $reflection->property('prop')); + self::assertEquals([new AttributeStub('4')], $properties[0]->attributes()->all()); + self::assertSame($properties[0], $reflection->property('factories')); - self::assertSame('parentProperty', $properties[1]->name()); - self::assertEquals(new NamedType(stdClass::class), $properties[1]->type()); - self::assertTrue($properties[1]->hasDefaultValue()); - self::assertNull($properties[1]->defaultValue()); + self::assertSame('generic', $properties[1]->name()); + self::assertEquals(NamedType::wrap(DoubleTemplateType::class, [DateTime::class, stdClass::class]), $properties[1]->type()); + self::assertFalse($properties[1]->hasDefaultValue()); self::assertFalse($properties[1]->isPromoted()); self::assertNull($properties[1]->promotedParameter()); self::assertEmpty($properties[1]->attributes()->all()); - self::assertSame($properties[1], $reflection->property('parentProperty')); + self::assertSame($properties[1], $reflection->property('generic')); - self::assertSame('factories', $properties[2]->name()); - self::assertEquals(PrimitiveType::array(SomeStub::class), $properties[2]->type()); + self::assertSame('promoted', $properties[2]->name()); + self::assertEquals(NamedType::wrap(stdClass::class), $properties[2]->type()); self::assertFalse($properties[2]->hasDefaultValue()); - self::assertFalse($properties[2]->isPromoted()); - self::assertNull($properties[2]->promotedParameter()); - self::assertEquals([new AttributeStub('4')], $properties[2]->attributes()->all()); - self::assertSame($properties[2], $reflection->property('factories')); - - self::assertSame('generic', $properties[3]->name()); - self::assertEquals(NamedType::wrap(DoubleTemplateType::class, [DateTime::class, stdClass::class]), $properties[3]->type()); - self::assertFalse($properties[3]->hasDefaultValue()); + self::assertTrue($properties[2]->isPromoted()); + self::assertSame($reflection->constructor()->parameters()[0], $properties[2]->promotedParameter()); + self::assertEquals([new AttributeStub('6')], $properties[2]->attributes()->all()); + self::assertSame($properties[2], $reflection->property('promoted')); + + self::assertSame('parentProperty', $properties[3]->name()); + self::assertEquals(new NamedType(stdClass::class), $properties[3]->type()); + self::assertTrue($properties[3]->hasDefaultValue()); + self::assertNull($properties[3]->defaultValue()); self::assertFalse($properties[3]->isPromoted()); self::assertNull($properties[3]->promotedParameter()); self::assertEmpty($properties[3]->attributes()->all()); - self::assertSame($properties[3], $reflection->property('generic')); + self::assertSame($properties[3], $reflection->property('parentProperty')); - self::assertSame('promoted', $properties[4]->name()); - self::assertEquals(NamedType::wrap(stdClass::class), $properties[4]->type()); + self::assertSame('prop', $properties[4]->name()); + self::assertEquals(PrimitiveType::integer(), $properties[4]->type()); self::assertFalse($properties[4]->hasDefaultValue()); - self::assertTrue($properties[4]->isPromoted()); - self::assertSame($reflection->constructor()->parameters()[0], $properties[4]->promotedParameter()); - self::assertEquals([new AttributeStub('6')], $properties[4]->attributes()->all()); - self::assertSame($properties[4], $reflection->property('promoted')); + self::assertFalse($properties[4]->isPromoted()); + self::assertNull($properties[4]->promotedParameter()); + self::assertEmpty($properties[4]->attributes()->all()); + self::assertSame($properties[4], $reflection->property('prop')); }); with($reflection->declaredMethods(), function (array $methods) use ($reflection) { @@ -197,81 +198,29 @@ function (ClassReflection $reflection) { self::assertCount(9, $methods); self::assertContainsOnlyInstancesOf(MethodReflection::class, $methods); - self::assertSame('test', $methods[0]->name()); + self::assertSame('__construct', $methods[0]->name()); self::assertEmpty($methods[0]->attributes()->all()); self::assertEmpty($methods[0]->typeParameters()); with($methods[0]->parameters(), function (array $parameters) use ($methods) { self::assertCount(1, $parameters); self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); - self::assertEquals('str', $parameters[0]->name()); - self::assertEquals(new NullableType(PrimitiveType::string()), $parameters[0]->type()); - self::assertTrue($parameters[0]->hasDefaultValue()); - self::assertNull($parameters[0]->defaultValue()); - self::assertEmpty($parameters[0]->attributes()->all()); - self::assertSame('arg $str', (string) $parameters[0]); - self::assertSame($parameters[0], $methods[0]->parameter('str')); - self::assertSame($parameters[0], $methods[0]->parameter(0)); - }); - self::assertEquals(new StaticType(NamedType::wrap(ClassStub::class, [stdClass::class])), $methods[0]->returnType()); - self::assertSame('test()', (string) $methods[0]); - self::assertSame($methods[0], $reflection->method('test')); - - self::assertSame('otherFunction', $methods[1]->name()); - self::assertEmpty($methods[1]->attributes()->all()); - self::assertEmpty($methods[1]->typeParameters()); - self::assertEmpty($methods[1]->parameters()); - self::assertEquals(new NamedType(Generator::class), $methods[1]->returnType()); - self::assertSame('otherFunction()', (string) $methods[1]); - self::assertSame($methods[1], $reflection->method('otherFunction')); - - self::assertSame('traitMethod', $methods[2]->name()); - self::assertEmpty($methods[2]->attributes()->all()); - self::assertEmpty($methods[2]->typeParameters()); - self::assertEmpty($methods[2]->parameters()); - self::assertEquals(VoidType::get(), $methods[2]->returnType()); - self::assertSame('traitMethod()', (string) $methods[2]); - self::assertSame($methods[2], $reflection->method('traitMethod')); - - self::assertSame('traitMethodTwo', $methods[3]->name()); - self::assertEmpty($methods[3]->attributes()->all()); - self::assertEmpty($methods[3]->typeParameters()); - self::assertEmpty($methods[3]->parameters()); - self::assertEquals(VoidType::get(), $methods[3]->returnType()); - self::assertSame('traitMethodTwo()', (string) $methods[3]); - self::assertSame($methods[3], $reflection->method('traitMethodTwo')); - - self::assertSame('parentMethod', $methods[4]->name()); - self::assertEmpty($methods[4]->attributes()->all()); - self::assertEmpty($methods[4]->typeParameters()); - self::assertEmpty($methods[4]->parameters()); - self::assertEquals(new NamedType(SomeStub::class), $methods[4]->returnType()); - self::assertSame('parentMethod()', (string) $methods[4]); - self::assertSame($methods[4], $reflection->method('parentMethod')); - - self::assertSame('__construct', $methods[5]->name()); - self::assertEmpty($methods[5]->attributes()->all()); - self::assertEmpty($methods[5]->typeParameters()); - with($methods[5]->parameters(), function (array $parameters) use ($methods) { - self::assertCount(1, $parameters); - self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); - self::assertEquals('promoted', $parameters[0]->name()); self::assertEquals(new NamedType(stdClass::class), $parameters[0]->type()); self::assertFalse($parameters[0]->hasDefaultValue()); self::assertEquals([new AttributeStub('6')], $parameters[0]->attributes()->all()); self::assertSame('arg $promoted', (string) $parameters[0]); - self::assertSame($parameters[0], $methods[5]->parameter('promoted')); - self::assertSame($parameters[0], $methods[5]->parameter(0)); + self::assertSame($parameters[0], $methods[0]->parameter('promoted')); + self::assertSame($parameters[0], $methods[0]->parameter(0)); }); - self::assertNull($methods[5]->returnType()); - self::assertSame('__construct()', (string) $methods[5]); - self::assertSame($methods[5], $reflection->method('__construct')); - self::assertSame($methods[5], $reflection->constructor()); - - self::assertSame('method', $methods[6]->name()); - self::assertEquals([new AttributeStub('5')], $methods[6]->attributes()->all()); - with($methods[6]->typeParameters(), function (array $parameters) use ($methods) { + self::assertNull($methods[0]->returnType()); + self::assertSame('__construct()', (string) $methods[0]); + self::assertSame($methods[0], $reflection->method('__construct')); + self::assertSame($methods[0], $reflection->constructor()); + + self::assertSame('method', $methods[1]->name()); + self::assertEquals([new AttributeStub('5')], $methods[1]->attributes()->all()); + with($methods[1]->typeParameters(), function (array $parameters) use ($methods) { self::assertCount(1, $parameters); self::assertContainsOnlyInstancesOf(TypeParameterReflection::class, $parameters); @@ -279,10 +228,10 @@ function (ClassReflection $reflection) { self::assertFalse($parameters[0]->variadic()); self::assertSame(MixedType::get(), $parameters[0]->upperBound()); self::assertSame(TemplateTypeVariance::INVARIANT, $parameters[0]->variance()); - self::assertSame($parameters[0], $methods[6]->typeParameter('G')); - self::assertSame($parameters[0], $methods[6]->typeParameter(0)); + self::assertSame($parameters[0], $methods[1]->typeParameter('G')); + self::assertSame($parameters[0], $methods[1]->typeParameter(0)); }); - with($methods[6]->parameters(), function (array $parameters) use ($methods) { + with($methods[1]->parameters(), function (array $parameters) use ($methods) { self::assertCount(1, $parameters); self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); @@ -291,16 +240,16 @@ function (ClassReflection $reflection) { self::assertFalse($parameters[0]->hasDefaultValue()); self::assertEquals([new AttributeStub('6')], $parameters[0]->attributes()->all()); self::assertSame('arg $param', (string) $parameters[0]); - self::assertSame($parameters[0], $methods[6]->parameter('param')); - self::assertSame($parameters[0], $methods[6]->parameter(0)); + self::assertSame($parameters[0], $methods[1]->parameter('param')); + self::assertSame($parameters[0], $methods[1]->parameter(0)); }); - self::assertEquals(NamedType::wrap(Collection::class, [new TemplateType('S'), new TemplateType('G')]), $methods[6]->returnType()); - self::assertSame('method()', (string) $methods[6]); - self::assertSame($methods[6], $reflection->method('method')); + self::assertEquals(NamedType::wrap(Collection::class, [new TemplateType('S'), new TemplateType('G')]), $methods[1]->returnType()); + self::assertSame('method()', (string) $methods[1]); + self::assertSame($methods[1], $reflection->method('method')); - self::assertSame('methodTwo', $methods[7]->name()); - self::assertEmpty($methods[7]->attributes()->all()); - with($methods[7]->typeParameters(), function (array $parameters) use ($methods) { + self::assertSame('methodTwo', $methods[2]->name()); + self::assertEmpty($methods[2]->attributes()->all()); + with($methods[2]->typeParameters(), function (array $parameters) use ($methods) { self::assertCount(2, $parameters); self::assertContainsOnlyInstancesOf(TypeParameterReflection::class, $parameters); @@ -308,17 +257,17 @@ function (ClassReflection $reflection) { self::assertFalse($parameters[0]->variadic()); self::assertSame(MixedType::get(), $parameters[0]->upperBound()); self::assertSame(TemplateTypeVariance::INVARIANT, $parameters[0]->variance()); - self::assertSame($parameters[0], $methods[7]->typeParameter('KValue')); - self::assertSame($parameters[0], $methods[7]->typeParameter(0)); + self::assertSame($parameters[0], $methods[2]->typeParameter('KValue')); + self::assertSame($parameters[0], $methods[2]->typeParameter(0)); self::assertEquals('K', $parameters[1]->name()); self::assertFalse($parameters[1]->variadic()); self::assertEquals(NamedType::wrap(SingleTemplateType::class, [new TemplateType('KValue')]), $parameters[1]->upperBound()); self::assertSame(TemplateTypeVariance::INVARIANT, $parameters[1]->variance()); - self::assertSame($parameters[1], $methods[7]->typeParameter('K')); - self::assertSame($parameters[1], $methods[7]->typeParameter(1)); + self::assertSame($parameters[1], $methods[2]->typeParameter('K')); + self::assertSame($parameters[1], $methods[2]->typeParameter(1)); }); - with($methods[7]->parameters(), function (array $parameters) use ($methods) { + with($methods[2]->parameters(), function (array $parameters) use ($methods) { self::assertCount(1, $parameters); self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); @@ -327,17 +276,17 @@ function (ClassReflection $reflection) { self::assertFalse($parameters[0]->hasDefaultValue()); self::assertEmpty($parameters[0]->attributes()->all()); self::assertSame('arg $param', (string) $parameters[0]); - self::assertSame($parameters[0], $methods[7]->parameter('param')); - self::assertSame($parameters[0], $methods[7]->parameter(0)); + self::assertSame($parameters[0], $methods[2]->parameter('param')); + self::assertSame($parameters[0], $methods[2]->parameter(0)); }); - self::assertEquals(new TemplateType('KValue'), $methods[7]->returnType()); - self::assertSame('methodTwo()', (string) $methods[7]); - self::assertSame($methods[7], $reflection->method('methodTwo')); + self::assertEquals(new TemplateType('KValue'), $methods[2]->returnType()); + self::assertSame('methodTwo()', (string) $methods[2]); + self::assertSame($methods[2], $reflection->method('methodTwo')); - self::assertSame('self', $methods[8]->name()); - self::assertEmpty($methods[8]->attributes()->all()); - self::assertEmpty($methods[8]->typeParameters()); - with($methods[8]->parameters(), function (array $parameters) use ($methods) { + self::assertSame('self', $methods[3]->name()); + self::assertEmpty($methods[3]->attributes()->all()); + self::assertEmpty($methods[3]->typeParameters()); + with($methods[3]->parameters(), function (array $parameters) use ($methods) { self::assertCount(1, $parameters); self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); @@ -346,12 +295,64 @@ function (ClassReflection $reflection) { self::assertFalse($parameters[0]->hasDefaultValue()); self::assertEmpty($parameters[0]->attributes()->all()); self::assertSame('arg $parent', (string) $parameters[0]); - self::assertSame($parameters[0], $methods[8]->parameter('parent')); - self::assertSame($parameters[0], $methods[8]->parameter(0)); + self::assertSame($parameters[0], $methods[3]->parameter('parent')); + self::assertSame($parameters[0], $methods[3]->parameter(0)); }); - self::assertEquals(new StaticType(NamedType::wrap(ClassStub::class, [stdClass::class])), $methods[8]->returnType()); - self::assertSame('self()', (string) $methods[8]); - self::assertSame($methods[8], $reflection->method('self')); + self::assertEquals(new StaticType(NamedType::wrap(ClassStub::class, [stdClass::class])), $methods[3]->returnType()); + self::assertSame('self()', (string) $methods[3]); + self::assertSame($methods[3], $reflection->method('self')); + + self::assertSame('parentMethod', $methods[4]->name()); + self::assertEmpty($methods[4]->attributes()->all()); + self::assertEmpty($methods[4]->typeParameters()); + self::assertEmpty($methods[4]->parameters()); + self::assertEquals(new NamedType(SomeStub::class), $methods[4]->returnType()); + self::assertSame('parentMethod()', (string) $methods[4]); + self::assertSame($methods[4], $reflection->method('parentMethod')); + + self::assertSame('test', $methods[5]->name()); + self::assertEmpty($methods[5]->attributes()->all()); + self::assertEmpty($methods[5]->typeParameters()); + with($methods[5]->parameters(), function (array $parameters) use ($methods) { + self::assertCount(1, $parameters); + self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); + + self::assertEquals('str', $parameters[0]->name()); + self::assertEquals(new NullableType(PrimitiveType::string()), $parameters[0]->type()); + self::assertTrue($parameters[0]->hasDefaultValue()); + self::assertNull($parameters[0]->defaultValue()); + self::assertEmpty($parameters[0]->attributes()->all()); + self::assertSame('arg $str', (string) $parameters[0]); + self::assertSame($parameters[0], $methods[5]->parameter('str')); + self::assertSame($parameters[0], $methods[5]->parameter(0)); + }); + self::assertEquals(new StaticType(NamedType::wrap(ClassStub::class, [stdClass::class])), $methods[5]->returnType()); + self::assertSame('test()', (string) $methods[5]); + self::assertSame($methods[5], $reflection->method('test')); + + self::assertSame('traitMethod', $methods[6]->name()); + self::assertEmpty($methods[6]->attributes()->all()); + self::assertEmpty($methods[6]->typeParameters()); + self::assertEmpty($methods[6]->parameters()); + self::assertEquals(VoidType::get(), $methods[6]->returnType()); + self::assertSame('traitMethod()', (string) $methods[6]); + self::assertSame($methods[6], $reflection->method('traitMethod')); + + self::assertSame('traitMethodTwo', $methods[7]->name()); + self::assertEmpty($methods[7]->attributes()->all()); + self::assertEmpty($methods[7]->typeParameters()); + self::assertEmpty($methods[7]->parameters()); + self::assertEquals(VoidType::get(), $methods[7]->returnType()); + self::assertSame('traitMethodTwo()', (string) $methods[7]); + self::assertSame($methods[7], $reflection->method('traitMethodTwo')); + + self::assertSame('otherFunction', $methods[8]->name()); + self::assertEmpty($methods[8]->attributes()->all()); + self::assertEmpty($methods[8]->typeParameters()); + self::assertEmpty($methods[8]->parameters()); + self::assertEquals(new NamedType(Generator::class), $methods[8]->returnType()); + self::assertSame('otherFunction()', (string) $methods[8]); + self::assertSame($methods[8], $reflection->method('otherFunction')); }); }, ]; @@ -376,34 +377,137 @@ function (ClassReflection $reflection) { self::assertEmpty($reflection->typeParameters()); self::assertNull($reflection->extends()); - self::assertCount(1, $reflection->implements()); + self::assertCount(2, $reflection->implements()); self::assertEquals( - NamedType::wrap(IteratorAggregate::class), + NamedType::wrap(IteratorAggregate::class, [ + PrimitiveType::string(), + new NamedType(SomeStub::class), + ]), $reflection->implements()[0] ); + self::assertEquals( + NamedType::wrap(ArrayAccess::class, [ + PrimitiveType::string(), + new NamedType(SomeStub::class), + ]), + $reflection->implements()[1] + ); self::assertEmpty($reflection->uses()->traits()); self::assertEmpty($reflection->declaredProperties()); self::assertEmpty($reflection->properties()); with($reflection->declaredMethods(), function (array $methods) use ($reflection) { - self::assertCount(1, $methods); + self::assertCount(5, $methods); self::assertContainsOnlyInstancesOf(MethodReflection::class, $methods); self::assertSame('getIterator', $methods[0]->name()); + self::assertSame('offsetExists', $methods[1]->name()); + self::assertSame('offsetGet', $methods[2]->name()); + self::assertSame('offsetSet', $methods[3]->name()); + self::assertSame('offsetUnset', $methods[4]->name()); }); with($reflection->methods(), function (array $methods) use ($reflection) { - self::assertCount(1, $methods); + self::assertCount(5, $methods); self::assertContainsOnlyInstancesOf(MethodReflection::class, $methods); self::assertSame('getIterator', $methods[0]->name()); self::assertEmpty($methods[0]->attributes()->all()); self::assertEmpty($methods[0]->typeParameters()); self::assertEmpty($methods[0]->parameters()); - self::assertEquals(new NamedType(Traversable::class), $methods[0]->returnType()); + self::assertEquals(NamedType::wrap(Traversable::class, [ + PrimitiveType::string(), + SomeStub::class, + ]), $methods[0]->returnType()); self::assertSame('getIterator()', (string) $methods[0]); self::assertSame($methods[0], $reflection->method('getIterator')); + + self::assertSame('offsetExists', $methods[1]->name()); + self::assertEmpty($methods[1]->attributes()->all()); + self::assertEmpty($methods[1]->typeParameters()); + with($methods[1]->parameters(), function (array $parameters) use ($methods) { + self::assertCount(1, $parameters); + self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); + + self::assertEquals('offset', $parameters[0]->name()); + self::assertEquals(PrimitiveType::string(), $parameters[0]->type()); + self::assertFalse($parameters[0]->hasDefaultValue()); + self::assertEmpty($parameters[0]->attributes()->all()); + self::assertSame('arg $offset', (string) $parameters[0]); + self::assertSame($parameters[0], $methods[1]->parameter('offset')); + self::assertSame($parameters[0], $methods[1]->parameter(0)); + }); + self::assertEquals(PrimitiveType::boolean(), $methods[1]->returnType()); + self::assertSame('offsetExists()', (string) $methods[1]); + self::assertSame($methods[1], $reflection->method('offsetExists')); + + self::assertSame('offsetGet', $methods[2]->name()); + self::assertEmpty($methods[2]->attributes()->all()); + self::assertEmpty($methods[2]->typeParameters()); + with($methods[2]->parameters(), function (array $parameters) use ($methods) { + self::assertCount(1, $parameters); + self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); + + self::assertEquals('offset', $parameters[0]->name()); + self::assertEquals(PrimitiveType::string(), $parameters[0]->type()); + self::assertFalse($parameters[0]->hasDefaultValue()); + self::assertEmpty($parameters[0]->attributes()->all()); + self::assertSame('arg $offset', (string) $parameters[0]); + self::assertSame($parameters[0], $methods[2]->parameter('offset')); + self::assertSame($parameters[0], $methods[2]->parameter(0)); + }); + self::assertEquals(new NullableType( + new NamedType(SomeStub::class) + ), $methods[2]->returnType()); + self::assertSame('offsetGet()', (string) $methods[2]); + self::assertSame($methods[2], $reflection->method('offsetGet')); + + self::assertSame('offsetSet', $methods[3]->name()); + self::assertEmpty($methods[3]->attributes()->all()); + self::assertEmpty($methods[3]->typeParameters()); + with($methods[3]->parameters(), function (array $parameters) use ($methods) { + self::assertCount(2, $parameters); + self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); + + self::assertEquals('offset', $parameters[0]->name()); + self::assertEquals(new NullableType(PrimitiveType::string()), $parameters[0]->type()); + self::assertFalse($parameters[0]->hasDefaultValue()); + self::assertEmpty($parameters[0]->attributes()->all()); + self::assertSame('arg $offset', (string) $parameters[0]); + self::assertSame($parameters[0], $methods[3]->parameter('offset')); + self::assertSame($parameters[0], $methods[3]->parameter(0)); + + self::assertEquals('value', $parameters[1]->name()); + self::assertEquals(new NamedType(SomeStub::class), $parameters[1]->type()); + self::assertFalse($parameters[1]->hasDefaultValue()); + self::assertEmpty($parameters[1]->attributes()->all()); + self::assertSame('arg $value', (string) $parameters[1]); + self::assertSame($parameters[1], $methods[3]->parameter('value')); + self::assertSame($parameters[1], $methods[3]->parameter(1)); + }); + self::assertEquals(VoidType::get(), $methods[3]->returnType()); + self::assertSame('offsetSet()', (string) $methods[3]); + self::assertSame($methods[3], $reflection->method('offsetSet')); + + self::assertSame('offsetUnset', $methods[4]->name()); + self::assertEmpty($methods[4]->attributes()->all()); + self::assertEmpty($methods[4]->typeParameters()); + with($methods[4]->parameters(), function (array $parameters) use ($methods) { + self::assertCount(1, $parameters); + self::assertContainsOnlyInstancesOf(FunctionParameterReflection::class, $parameters); + + self::assertEquals('offset', $parameters[0]->name()); + self::assertEquals(PrimitiveType::string(), $parameters[0]->type()); + self::assertFalse($parameters[0]->hasDefaultValue()); + self::assertEmpty($parameters[0]->attributes()->all()); + self::assertSame('arg $offset', (string) $parameters[0]); + self::assertSame($parameters[0], $methods[4]->parameter('offset')); + self::assertSame($parameters[0], $methods[4]->parameter(0)); + }); + self::assertEquals(VoidType::get(), $methods[4]->returnType()); + self::assertSame('offsetUnset()', (string) $methods[4]); + self::assertSame($methods[4], $reflection->method('offsetUnset')); }); }, ]; diff --git a/tests/Stubs/Classes/CollectionStub.php b/tests/Stubs/Classes/CollectionStub.php index 3462bcb..1a58c12 100644 --- a/tests/Stubs/Classes/CollectionStub.php +++ b/tests/Stubs/Classes/CollectionStub.php @@ -2,13 +2,34 @@ namespace Tests\Stubs\Classes; +use ArrayAccess; use IteratorAggregate; use Traversable; -class CollectionStub implements IteratorAggregate +/** + * @implements IteratorAggregate + * @implements ArrayAccess + */ +class CollectionStub implements IteratorAggregate, ArrayAccess { public function getIterator(): Traversable { yield from []; } + + public function offsetExists(mixed $offset): bool + { + } + + public function offsetGet(mixed $offset): mixed + { + } + + public function offsetSet(mixed $offset, mixed $value): void + { + } + + public function offsetUnset(mixed $offset): void + { + } }