From d8a88bca5495673ab613b7c65e2f0da07c6a8d69 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Fri, 28 Jun 2024 14:01:03 +0300 Subject: [PATCH] wip --- src/Infer/Definition/ClassDefinition.php | 10 ++----- src/Infer/Services/ReferenceTypeResolver.php | 31 ++++++++++++++++++++ src/Support/RouteInfo.php | 21 +------------ src/Support/Type/AbstractType.php | 5 ++++ src/Support/Type/ArrayType.php | 13 ++++++++ src/Support/Type/ObjectType.php | 9 ++++++ src/Support/Type/Type.php | 2 ++ src/Support/Type/TypeHelper.php | 6 ++++ tests/Infer/AnnotatedReturnTypesTest.php | 20 +++++++++++++ tests/Pest.php | 14 ++------- 10 files changed, 92 insertions(+), 39 deletions(-) create mode 100644 tests/Infer/AnnotatedReturnTypesTest.php diff --git a/src/Infer/Definition/ClassDefinition.php b/src/Infer/Definition/ClassDefinition.php index 53c1776d..fcc52e4c 100644 --- a/src/Infer/Definition/ClassDefinition.php +++ b/src/Infer/Definition/ClassDefinition.php @@ -69,13 +69,9 @@ public function getMethodDefinition(string $name, Scope $scope = new GlobalScope new FileNameResolver(new NameContext(new Throwing())), ); - if (ReferenceTypeResolver::hasResolvableReferences($returnType = $this->methods[$name]->type->getReturnType())) { - $this->methods[$name]->type->setReturnType( - (new ReferenceTypeResolver($scope->index)) - ->resolve($methodScope, $returnType) - ->mergeAttributes($returnType->attributes()) - ); - } + (new ReferenceTypeResolver($scope->index)) + ->resolveFunctionReturnReferences($scope, $this->methods[$name]->type); + foreach ($this->methods[$name]->type->exceptions as $i => $exceptionType) { if (ReferenceTypeResolver::hasResolvableReferences($exceptionType)) { $this->methods[$name]->type->exceptions[$i] = (new ReferenceTypeResolver($scope->index)) diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index aa9eeb8c..d6422132 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -12,6 +12,7 @@ use Dedoc\Scramble\Infer\Scope\Index; use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\CallableStringType; +use Dedoc\Scramble\Support\Type\FunctionLikeType; use Dedoc\Scramble\Support\Type\FunctionType; use Dedoc\Scramble\Support\Type\Generic; use Dedoc\Scramble\Support\Type\ObjectType; @@ -49,6 +50,36 @@ public static function getInstance(): static return app(static::class); } + public function resolveFunctionReturnReferences(Scope $scope, FunctionType $functionType): void + { + if (static::hasResolvableReferences($returnType = $functionType->getReturnType())) { + $resolvedReference = $this->resolve($scope, $returnType); + $functionType->setReturnType($resolvedReference); + } + + if ($annotatedReturnType = $functionType->getAttribute('returnTypeAnnotation')) { +// $functionType->setReturnType( +// $this->addAnnotatedReturnType($functionType->getReturnType(), $annotatedReturnType) +// ); + } + } + + private function addAnnotatedReturnType(Type $inferredReturnType, Type $annotatedReturnType): Type + { + $types = $inferredReturnType instanceof Union + ? $inferredReturnType->types + : [$inferredReturnType]; + + $annotatedTypeCanAcceptAnyInferredType = collect($types) + ->some(fn (Type $t) => $annotatedReturnType->accepts($t)); + + if (! $annotatedTypeCanAcceptAnyInferredType) { + $types = [$annotatedReturnType]; + } + + return Union::wrap($types)->mergeAttributes($inferredReturnType->attributes()); + } + public static function hasResolvableReferences(Type $type): bool { return (bool) (new TypeWalker)->first( diff --git a/src/Support/RouteInfo.php b/src/Support/RouteInfo.php index 44353831..e88503a8 100644 --- a/src/Support/RouteInfo.php +++ b/src/Support/RouteInfo.php @@ -20,7 +20,6 @@ use Dedoc\Scramble\Support\Type\Type; use Dedoc\Scramble\Support\Type\TypeTraverser; use Dedoc\Scramble\Support\Type\TypeWalker; -use Dedoc\Scramble\Support\Type\Union; use Dedoc\Scramble\Support\Type\UnknownType; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Resources\Json\JsonResource; @@ -209,26 +208,8 @@ public function getCodeReturnType() return null; } - $inferredType = (new ObjectType($this->reflectionMethod()->getDeclaringClass()->getName())) + return (new ObjectType($this->reflectionMethod()->getDeclaringClass()->getName())) ->getMethodReturnType($this->methodName()); - - $inferredTypes = $inferredType instanceof Union - ? $inferredType->types - : [$inferredType]; - - /* - * Despite us respecting manually annotated type, there may be an inferred type that contains more information - * than annotated type. In fact, any inferred type will contain more information than annotated type. Hence, - * we want to make sure that we omit annotated type. - */ - if ( - ($annotationType = $this->getMethodType()->getAttribute('returnTypeAnnotation')) - && ! $this->inferredTypesContainMoreConcreteAnnotatedType($inferredTypes, $annotationType) - ) { - $inferredTypes = [$annotationType, ...$inferredTypes]; - } - - return Union::wrap($inferredTypes); } private function inferredTypesContainMoreConcreteAnnotatedType(array $inferredTypes, Type $annotationType): bool diff --git a/src/Support/Type/AbstractType.php b/src/Support/Type/AbstractType.php index 02c2137b..93d1eda7 100644 --- a/src/Support/Type/AbstractType.php +++ b/src/Support/Type/AbstractType.php @@ -20,6 +20,11 @@ public function isInstanceOf(string $className) return false; } + public function accepts(Type $otherType): bool + { + return is_a($this::class, $otherType::class, true); + } + public function getPropertyType(string $propertyName, Scope $scope): Type { $className = $this::class; diff --git a/src/Support/Type/ArrayType.php b/src/Support/Type/ArrayType.php index 88630a88..bf4ba008 100644 --- a/src/Support/Type/ArrayType.php +++ b/src/Support/Type/ArrayType.php @@ -14,6 +14,19 @@ public function nodes(): array return ['value', 'key']; } + public function accepts(Type $otherType): bool + { + if (parent::accepts($otherType)) { + return true; + } + + if ($otherType instanceof KeyedArrayType) { + return true; + } + + return false; + } + public function isSame(Type $type) { return false; diff --git a/src/Support/Type/ObjectType.php b/src/Support/Type/ObjectType.php index c606d0f2..e898295f 100644 --- a/src/Support/Type/ObjectType.php +++ b/src/Support/Type/ObjectType.php @@ -74,6 +74,15 @@ public function getMethodReturnType(string $methodName, array $arguments = [], S : $returnType; } + public function accepts(Type $otherType): bool + { + if (! $otherType instanceof ObjectType) { + return false; + } + + return is_a($this->name, $otherType->name, true); + } + public function toString(): string { return $this->name; diff --git a/src/Support/Type/Type.php b/src/Support/Type/Type.php index 184c111b..447f0c4a 100644 --- a/src/Support/Type/Type.php +++ b/src/Support/Type/Type.php @@ -16,6 +16,8 @@ public function getAttribute(string $key); public function isInstanceOf(string $className); + public function accepts(Type $otherType): bool; + public function nodes(): array; public function getPropertyType(string $propertyName, Scope $scope): Type; diff --git a/src/Support/Type/TypeHelper.php b/src/Support/Type/TypeHelper.php index ad49183b..6bfa9da0 100644 --- a/src/Support/Type/TypeHelper.php +++ b/src/Support/Type/TypeHelper.php @@ -58,6 +58,12 @@ public static function createTypeFromTypeNode(Node $typeNode) return new FloatType(); } + if ($typeNode->name === 'array') { + return new ArrayType( + value: new MixedType(), + ); + } + return new ObjectType($typeNode->toString()); } diff --git a/tests/Infer/AnnotatedReturnTypesTest.php b/tests/Infer/AnnotatedReturnTypesTest.php new file mode 100644 index 00000000..73e72fd4 --- /dev/null +++ b/tests/Infer/AnnotatedReturnTypesTest.php @@ -0,0 +1,20 @@ +getFunctionDefinition('foo'); + + expect($type->type->returnType->toString())->toBe($inferredReturnTypeString); +})->with([ + ['Foo_AnnotatedReturnTypesTest', 'new Foo_AnnotatedReturnTypesTest(42)', 'Foo_AnnotatedReturnTypesTest'], + ['int', 'new Foo_AnnotatedReturnTypesTest(42)', 'int'], + ['Foo_AnnotatedReturnTypesTest', '42', 'Foo_AnnotatedReturnTypesTest'], +]); + +class Foo_AnnotatedReturnTypesTest { + public function __construct(private int $wow){} +} diff --git a/tests/Pest.php b/tests/Pest.php index 37c44059..9d6cc173 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -73,16 +73,6 @@ function analyzeClass(string $className, array $extensions = []): AnalysisResult function resolveReferences(Index $index, ReferenceTypeResolver $referenceResolver) { - $resolveReferencesInFunctionReturn = function ($scope, $functionType) use ($referenceResolver) { - if (! ReferenceTypeResolver::hasResolvableReferences($returnType = $functionType->getReturnType())) { - return; - } - - $resolvedReference = $referenceResolver->resolve($scope, $returnType); - - $functionType->setReturnType($resolvedReference); - }; - foreach ($index->functionsDefinitions as $functionDefinition) { $fnScope = new Scope( $index, @@ -90,7 +80,7 @@ function resolveReferences(Index $index, ReferenceTypeResolver $referenceResolve new ScopeContext(functionDefinition: $functionDefinition), new FileNameResolver(new NameContext(new Throwing())), ); - $resolveReferencesInFunctionReturn($fnScope, $functionDefinition->type); + $referenceResolver->resolveFunctionReturnReferences($fnScope, $functionDefinition->type); } foreach ($index->classesDefinitions as $classDefinition) { @@ -101,7 +91,7 @@ function resolveReferences(Index $index, ReferenceTypeResolver $referenceResolve new ScopeContext($classDefinition, $methodDefinition), new FileNameResolver(new NameContext(new Throwing())), ); - $resolveReferencesInFunctionReturn($methodScope, $methodDefinition->type); + $referenceResolver->resolveFunctionReturnReferences($methodScope, $methodDefinition->type); } } }